Skip to content

populate-owners: resolve owner aliases #2572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 18, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci-operator/populate-owners.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# This script runs /tools/populate-owners

REPO_ROOT="$(git rev-parse --show-toplevel)" &&
exec go run "${REPO_ROOT}/tools/populate-owners/main.go"
exec go run "${REPO_ROOT}/tools/populate-owners/main.go" $1
174 changes: 75 additions & 99 deletions tools/populate-owners/main.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package main

import (
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"

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

if response.StatusCode != 200 {
return data, response.StatusCode, fmt.Errorf("failed to fetch %s: %s", uri, response.StatusCode, response.Status)
return data, response.StatusCode, fmt.Errorf("failed to fetch %s: %v %s", uri, response.StatusCode, response.Status)
}

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

// namespaceAliases collects a set of aliases including all upstream
// aliases. If multiple upstreams define the same alias with different
// member sets, namespaceAliases renames the colliding aliases in both
// the input 'orgRepos' and the output 'collected' to use
// unique-to-each-upstream alias names.
func namespaceAliases(orgRepos []*orgRepo) (collected *aliases, err error) {
consumerMap := map[string][]*orgRepo{}
for _, orgRepo := range orgRepos {
if orgRepo.Aliases == nil {
continue
}

for alias := range orgRepo.Aliases.Aliases {
consumerMap[alias] = append(consumerMap[alias], orgRepo)
}
}

if len(consumerMap) == 0 {
return nil, nil
}

collected = &aliases{
Aliases: map[string][]string{},
}

for alias, consumers := range consumerMap {
namespace := false
members := consumers[0].Aliases.Aliases[alias]
for _, consumer := range consumers[1:] {
otherMembers := consumer.Aliases.Aliases[alias]
if !reflect.DeepEqual(members, otherMembers) {
namespace = true
break
}
}

for i, consumer := range consumers {
newAlias := alias
if namespace {
newAlias = fmt.Sprintf("%s-%s-%s", consumer.Organization, consumer.Repository, alias)
consumer.Aliases.Aliases[newAlias] = consumer.Aliases.Aliases[alias]
delete(consumer.Aliases.Aliases, alias)
}
fmt.Fprintf(
os.Stderr,
"injecting alias %q from https://github.com/%s/%s/blob/%s/OWNERS_ALIASES\n",
alias,
consumer.Organization,
consumer.Repository,
consumer.Commit,
)
if i == 0 || namespace {
_, ok := collected.Aliases[newAlias]
if ok {
return nil, fmt.Errorf("namespaced alias collision: %q", newAlias)
}
collected.Aliases[newAlias] = consumer.Aliases.Aliases[newAlias]
}
}
}

return collected, nil
}

func writeYAML(path string, data interface{}, prefix []string) (err error) {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
Expand All @@ -363,6 +300,52 @@ func writeYAML(path string, data interface{}, prefix []string) (err error) {
return encoder.Encode(data)
}

// insertStringSlice inserts a string slice into a given index
// in another string slice. Returns a new slice with the insert
// slice replacing the elements between begin and end index.
func insertStringSlice(insert []string, intoSlice []string,
begin int, end int) []string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is end used? To overwrite items?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct, it will overwrite anything between begin and end in the existing slice. In this case it should always just be the single element which is the alias name.

if begin > end || begin < 0 || end > len(intoSlice) {
fmt.Printf("invalid begin: %v, or end: %v \n", begin, end)
return intoSlice
}
firstPart := intoSlice[:begin]
secondPart := append(insert, intoSlice[end:]...)
return append(firstPart, secondPart...)
}

// resolveAliases resolves names in the list of owners that
// match one of the given aliases. Returns a list of owners
// with each alias replaced by the list of owners it represents.
func resolveAliases(aliases *aliases, owners []string) []string {
offset := 0 // Keeps track of how many new names we've inserted
for i, owner := range owners {
if aliasOwners, ok := aliases.Aliases[owner]; ok {
index := i + offset
owners = insertStringSlice(aliasOwners, owners, index, (index + 1))
offset += len(aliasOwners) - 1
}
}
return owners
}

// resolveOwnerAliases checks whether the orgRepo includes any
// owner aliases, and attempts to resolve them to the appropriate
// set of owners. Returns an owners which replaces any
// matching aliases with the set of owner names belonging to that alias.
func (orgRepo *orgRepo) resolveOwnerAliases() *owners {
if orgRepo.Aliases == nil || len(orgRepo.Aliases.Aliases) == 0 {
return orgRepo.Owners
}

return &owners{
resolveAliases(orgRepo.Aliases, orgRepo.Owners.Approvers),
resolveAliases(orgRepo.Aliases, orgRepo.Owners.Reviewers),
orgRepo.Owners.RequiredReviewers,
orgRepo.Owners.Labels,
}
}

func (orgRepo *orgRepo) writeOwners() (err error) {
for _, directory := range orgRepo.Directories {
path := filepath.Join(directory, "OWNERS")
Expand All @@ -374,7 +357,7 @@ func (orgRepo *orgRepo) writeOwners() (err error) {
continue
}

err = writeYAML(path, orgRepo.Owners, []string{
err = writeYAML(path, orgRepo.resolveOwnerAliases(), []string{
doNotEdit,
fmt.Sprintf(
"# from https://github.com/%s/%s/blob/%s/OWNERS\n",
Expand All @@ -393,24 +376,7 @@ func (orgRepo *orgRepo) writeOwners() (err error) {
return nil
}

func writeOwnerAliases(repoRoot string, aliases *aliases) (err error) {
path := filepath.Join(repoRoot, "OWNERS_ALIASES")
if aliases == nil || len(aliases.Aliases) == 0 {
err = os.Remove(path)
if err != nil && !os.IsNotExist(err) {
return err
}
return nil
}

return writeYAML(path, aliases, []string{
doNotEdit,
ownersAliasesComment,
"\n",
})
}

func pullOwners(directory string) (err error) {
func pullOwners(directory string, pattern string) (err error) {
repoRoot, err := getRepoRoot(directory)
if err != nil {
return err
Expand All @@ -425,6 +391,10 @@ func pullOwners(directory string) (err error) {
config := filepath.Join(operatorRoot, "config")
templates := filepath.Join(operatorRoot, "templates")
for _, orgRepo := range orgRepos {
matched, _ := regexp.MatchString(pattern, orgRepo.Repository)
if !matched {
continue
}
err = orgRepo.getDirectories(config, templates)
if err != nil && !os.IsNotExist(err) {
return err
Expand All @@ -434,31 +404,37 @@ func pullOwners(directory string) (err error) {
if err != nil && !os.IsNotExist(err) {
return err
}
fmt.Fprintf(os.Stderr, "got owners for %s\n", orgRepo.String())
}

aliases, err := namespaceAliases(orgRepos)
if err != nil {
return err
}

err = writeOwnerAliases(repoRoot, aliases)
if err != nil {
return err
}

for _, orgRepo := range orgRepos {
err = orgRepo.writeOwners()
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "updated owners for %s\n", orgRepo.String())
}

return nil
}

const (
usage = `Update the OWNERS files from remote repositories.

Usage:
%s [repo-name-regex]

Args:
[repo-name-regex] A go regex which which matches the repos to update

`
)

func main() {
err := pullOwners(".")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), usage, "populate-owners")
}
flag.Parse()
repoPattern := flag.Arg(0)

err := pullOwners(".", repoPattern)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
Expand Down
Loading