Skip to content

Commit bfb2475

Browse files
Merge pull request openshift#2572 from pgier/resolve-owner-aliases
populate-owners: resolve owner aliases
2 parents 12d689c + a9f4c3a commit bfb2475

File tree

4 files changed

+126
-276
lines changed

4 files changed

+126
-276
lines changed

ci-operator/populate-owners.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
# This script runs /tools/populate-owners
44

55
REPO_ROOT="$(git rev-parse --show-toplevel)" &&
6-
exec go run "${REPO_ROOT}/tools/populate-owners/main.go"
6+
exec go run "${REPO_ROOT}/tools/populate-owners/main.go" $1

tools/populate-owners/README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
# Populating `OWNERS` and `OWNERS_ALIASES`
22

3-
This utility pulls `OWNERS` and `OWNERS_ALIASES` from upstream OpenShift repositories.
3+
This utility updates the OWNERS files from remote Openshift repositories.
4+
45
Usage:
6+
populate-owners [repo-name-regex]
7+
8+
Args:
9+
[repo-name-regex] A go regex which which matches the repos to update, by default all repos are selected
510

611
```console
7-
$ go run main.go
12+
$ go run main.go [repo-name-regex]
813
```
914

1015
Or, equivalently, execute [`populate-owners.sh`](../../ci-operator/populate-owners.sh) from anywhere in this repository.
@@ -15,13 +20,9 @@ For example, the presence of [`ci-operator/jobs/openshift/origin`](../../ci-oper
1520
The `HEAD` branch for each upstream repository is pulled to extract its `OWNERS` and `OWNERS_ALIASES`.
1621
If `OWNERS` is missing, the utility will ignore `OWNERS_ALIASES`, even if it is present upstream.
1722

18-
Once all the upstream content has been fetched, the utility namespaces any colliding upstream aliases.
19-
Collisions only occur if multiple upstreams define the same alias with different member sets.
20-
When that happens, the utility replaces the upstream alias with a `{organization}-{repository}-{upstream-alias}`.
21-
For example, if [openshift/origin][] and [openshift/installer][] both defined an alias for `security` with different member sets, the utility would rename them to `openshift-origin-security` and `openshift-installer-security` respectively.
22-
23-
After namespacing aliases, the utility writes `OWNERS_ALIASES` to the root of this repository.
24-
If no upstreams define aliases, then the utility removes `OWNER_ALIASES` from the root of this repository.
23+
Any aliases present in the upstream `OWNERS` file will be resolved to the set of usernames they represent in the associated
24+
`OWNERS_ALIASES` file. The local `OWNERS` files will therefore not contain any alias names. This avoids any conflicts between
25+
upstream alias names coming from different repos.
2526

2627
The utility also iterates through the `ci-operator/{type}/{organization}/{repository}` for `{type}` in `config`, `jobs`, and `templates`, writing `OWNERS` to reflect the upstream configuration.
2728
If the upstream did not have an `OWNERS` file, the utility removes the associated `ci-operator/*/{organization}/{repository}/OWNERS`.

tools/populate-owners/main.go

Lines changed: 78 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package main
22

33
import (
4+
"flag"
45
"fmt"
56
"io/ioutil"
67
"net/http"
78
"os"
89
"os/exec"
910
"path/filepath"
10-
"reflect"
11+
"regexp"
1112
"sort"
1213
"strings"
1314

@@ -204,7 +205,7 @@ func get(uri, accept string) (data []byte, status int, err error) {
204205
defer response.Body.Close()
205206

206207
if response.StatusCode != 200 {
207-
return data, response.StatusCode, fmt.Errorf("failed to fetch %s: %s", uri, response.StatusCode, response.Status)
208+
return data, response.StatusCode, fmt.Errorf("failed to fetch %s: %v %s", uri, response.StatusCode, response.Status)
208209
}
209210

210211
data, err = ioutil.ReadAll(response.Body)
@@ -281,70 +282,6 @@ func (orgRepo *orgRepo) extractOwners(repoRoot string) (err error) {
281282
return nil
282283
}
283284

284-
// namespaceAliases collects a set of aliases including all upstream
285-
// aliases. If multiple upstreams define the same alias with different
286-
// member sets, namespaceAliases renames the colliding aliases in both
287-
// the input 'orgRepos' and the output 'collected' to use
288-
// unique-to-each-upstream alias names.
289-
func namespaceAliases(orgRepos []*orgRepo) (collected *aliases, err error) {
290-
consumerMap := map[string][]*orgRepo{}
291-
for _, orgRepo := range orgRepos {
292-
if orgRepo.Aliases == nil {
293-
continue
294-
}
295-
296-
for alias := range orgRepo.Aliases.Aliases {
297-
consumerMap[alias] = append(consumerMap[alias], orgRepo)
298-
}
299-
}
300-
301-
if len(consumerMap) == 0 {
302-
return nil, nil
303-
}
304-
305-
collected = &aliases{
306-
Aliases: map[string][]string{},
307-
}
308-
309-
for alias, consumers := range consumerMap {
310-
namespace := false
311-
members := consumers[0].Aliases.Aliases[alias]
312-
for _, consumer := range consumers[1:] {
313-
otherMembers := consumer.Aliases.Aliases[alias]
314-
if !reflect.DeepEqual(members, otherMembers) {
315-
namespace = true
316-
break
317-
}
318-
}
319-
320-
for i, consumer := range consumers {
321-
newAlias := alias
322-
if namespace {
323-
newAlias = fmt.Sprintf("%s-%s-%s", consumer.Organization, consumer.Repository, alias)
324-
consumer.Aliases.Aliases[newAlias] = consumer.Aliases.Aliases[alias]
325-
delete(consumer.Aliases.Aliases, alias)
326-
}
327-
fmt.Fprintf(
328-
os.Stderr,
329-
"injecting alias %q from https://github.com/%s/%s/blob/%s/OWNERS_ALIASES\n",
330-
alias,
331-
consumer.Organization,
332-
consumer.Repository,
333-
consumer.Commit,
334-
)
335-
if i == 0 || namespace {
336-
_, ok := collected.Aliases[newAlias]
337-
if ok {
338-
return nil, fmt.Errorf("namespaced alias collision: %q", newAlias)
339-
}
340-
collected.Aliases[newAlias] = consumer.Aliases.Aliases[newAlias]
341-
}
342-
}
343-
}
344-
345-
return collected, nil
346-
}
347-
348285
func writeYAML(path string, data interface{}, prefix []string) (err error) {
349286
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
350287
if err != nil {
@@ -363,6 +300,55 @@ func writeYAML(path string, data interface{}, prefix []string) (err error) {
363300
return encoder.Encode(data)
364301
}
365302

303+
// insertStringSlice inserts a string slice into another string slice
304+
// replacing the elements starting with the begin index up to the end
305+
// index. The element at end index in the original slice will remain
306+
// in the resulting slice. Returns a new slice with the elements
307+
// replaced. If the begin index is larger than the end, or either of the
308+
// indexes are out of range of the slice, the original slice is returned
309+
// unmodified.
310+
func insertStringSlice(insert []string, intoSlice []string,
311+
begin int, end int) []string {
312+
if begin > end || begin < 0 || end > len(intoSlice) {
313+
return intoSlice
314+
}
315+
firstPart := intoSlice[:begin]
316+
secondPart := append(insert, intoSlice[end:]...)
317+
return append(firstPart, secondPart...)
318+
}
319+
320+
// resolveAliases resolves names in the list of owners that
321+
// match one of the given aliases. Returns a list of owners
322+
// with each alias replaced by the list of owners it represents.
323+
func resolveAliases(aliases *aliases, owners []string) []string {
324+
offset := 0 // Keeps track of how many new names we've inserted
325+
for i, owner := range owners {
326+
if aliasOwners, ok := aliases.Aliases[owner]; ok {
327+
index := i + offset
328+
owners = insertStringSlice(aliasOwners, owners, index, (index + 1))
329+
offset += len(aliasOwners) - 1
330+
}
331+
}
332+
return owners
333+
}
334+
335+
// resolveOwnerAliases checks whether the orgRepo includes any
336+
// owner aliases, and attempts to resolve them to the appropriate
337+
// set of owners. Returns an owners which replaces any
338+
// matching aliases with the set of owner names belonging to that alias.
339+
func (orgRepo *orgRepo) resolveOwnerAliases() *owners {
340+
if orgRepo.Aliases == nil || len(orgRepo.Aliases.Aliases) == 0 {
341+
return orgRepo.Owners
342+
}
343+
344+
return &owners{
345+
resolveAliases(orgRepo.Aliases, orgRepo.Owners.Approvers),
346+
resolveAliases(orgRepo.Aliases, orgRepo.Owners.Reviewers),
347+
orgRepo.Owners.RequiredReviewers,
348+
orgRepo.Owners.Labels,
349+
}
350+
}
351+
366352
func (orgRepo *orgRepo) writeOwners() (err error) {
367353
for _, directory := range orgRepo.Directories {
368354
path := filepath.Join(directory, "OWNERS")
@@ -374,7 +360,7 @@ func (orgRepo *orgRepo) writeOwners() (err error) {
374360
continue
375361
}
376362

377-
err = writeYAML(path, orgRepo.Owners, []string{
363+
err = writeYAML(path, orgRepo.resolveOwnerAliases(), []string{
378364
doNotEdit,
379365
fmt.Sprintf(
380366
"# from https://github.com/%s/%s/blob/%s/OWNERS\n",
@@ -393,24 +379,7 @@ func (orgRepo *orgRepo) writeOwners() (err error) {
393379
return nil
394380
}
395381

396-
func writeOwnerAliases(repoRoot string, aliases *aliases) (err error) {
397-
path := filepath.Join(repoRoot, "OWNERS_ALIASES")
398-
if aliases == nil || len(aliases.Aliases) == 0 {
399-
err = os.Remove(path)
400-
if err != nil && !os.IsNotExist(err) {
401-
return err
402-
}
403-
return nil
404-
}
405-
406-
return writeYAML(path, aliases, []string{
407-
doNotEdit,
408-
ownersAliasesComment,
409-
"\n",
410-
})
411-
}
412-
413-
func pullOwners(directory string) (err error) {
382+
func pullOwners(directory string, pattern string) (err error) {
414383
repoRoot, err := getRepoRoot(directory)
415384
if err != nil {
416385
return err
@@ -425,6 +394,10 @@ func pullOwners(directory string) (err error) {
425394
config := filepath.Join(operatorRoot, "config")
426395
templates := filepath.Join(operatorRoot, "templates")
427396
for _, orgRepo := range orgRepos {
397+
matched, _ := regexp.MatchString(pattern, orgRepo.Repository)
398+
if !matched {
399+
continue
400+
}
428401
err = orgRepo.getDirectories(config, templates)
429402
if err != nil && !os.IsNotExist(err) {
430403
return err
@@ -434,31 +407,37 @@ func pullOwners(directory string) (err error) {
434407
if err != nil && !os.IsNotExist(err) {
435408
return err
436409
}
437-
fmt.Fprintf(os.Stderr, "got owners for %s\n", orgRepo.String())
438-
}
439-
440-
aliases, err := namespaceAliases(orgRepos)
441-
if err != nil {
442-
return err
443-
}
444-
445-
err = writeOwnerAliases(repoRoot, aliases)
446-
if err != nil {
447-
return err
448-
}
449410

450-
for _, orgRepo := range orgRepos {
451411
err = orgRepo.writeOwners()
452412
if err != nil {
453413
return err
454414
}
415+
fmt.Fprintf(os.Stderr, "updated owners for %s\n", orgRepo.String())
455416
}
456417

457418
return nil
458419
}
459420

421+
const (
422+
usage = `Update the OWNERS files from remote repositories.
423+
424+
Usage:
425+
%s [repo-name-regex]
426+
427+
Args:
428+
[repo-name-regex] A go regex which which matches the repos to update, by default all repos are selected
429+
430+
`
431+
)
432+
460433
func main() {
461-
err := pullOwners(".")
434+
flag.Usage = func() {
435+
fmt.Fprintf(flag.CommandLine.Output(), usage, "populate-owners")
436+
}
437+
flag.Parse()
438+
repoPattern := flag.Arg(0)
439+
440+
err := pullOwners(".", repoPattern)
462441
if err != nil {
463442
fmt.Fprintln(os.Stderr, err.Error())
464443
os.Exit(1)

0 commit comments

Comments
 (0)