-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
[chore] enforce validation of codeowners membership #24638
Changes from all commits
53d78a7
e17b4d9
8a7205d
6eada8b
963485d
10dca90
3fddc05
b452b76
8a63d71
8ee0228
5de5a64
257302a
c422b3c
727c1a8
66e1144
0d34558
71631df
8ef36b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include ../../Makefile.Common |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# githubgen | ||
|
||
This executable is used to generate the `.github/CODEOWNERS` and `.github/ALLOWLIST` files. | ||
|
||
It reads status metadata from `metadata.yaml` files located throughout the repository. | ||
|
||
It checks that codeowners are known members of the OpenTelemetry organization. | ||
|
||
## Usage | ||
|
||
``` | ||
$> make gengithub | ||
``` | ||
The equivalent of: | ||
``` | ||
$> cd cmd/githubgen && $(GOCMD) install . | ||
$> GITHUB_TOKEN=<mypattoken> githubgen --folder . [--allowlist cmd/githubgen/allowlist.txt] | ||
``` | ||
|
||
## Checking codeowners against OpenTelemetry membership via Github API | ||
|
||
To authenticate, set the environment variable `GITHUB_TOKEN` to a PAT token. | ||
|
||
For each codeowner, the script will check if the user is registered as a member of the OpenTelemetry organization. | ||
|
||
If any codeowner is missing, it will stop and print names of missing codeowners. | ||
|
||
These can be added to allowlist.txt as a workaround. | ||
|
||
If a codeowner is present in allowlist.txt and also a member of the OpenTelemetry organization, the script will error out. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
Caleb-Hurshman | ||
Doron-Bargo | ||
MaxKsyunz | ||
MitchellGale | ||
YANG-DB | ||
agoallikmaa | ||
alexvanboxel | ||
architjugran | ||
asaharn | ||
avadhut123pisal | ||
billmeyer | ||
eedorenko | ||
emreyalvac | ||
keep94 | ||
kiranmayib | ||
kkujawa-sumo | ||
leonsp-ai | ||
liqiangz | ||
oded-dd | ||
shaochengwang | ||
svrakitin | ||
thepeterstone | ||
yiyang5055 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
module github.com/open-telemetry/opentelemetry-collector-contrib/cmd/githubgen | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/google/go-github/v53 v53.2.0 | ||
go.opentelemetry.io/collector/confmap v0.83.0 | ||
) | ||
|
||
require ( | ||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect | ||
github.com/cloudflare/circl v1.3.3 // indirect | ||
github.com/golang/protobuf v1.5.2 // indirect | ||
github.com/google/go-querystring v1.1.0 // indirect | ||
github.com/knadh/koanf/maps v0.1.1 // indirect | ||
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect | ||
github.com/knadh/koanf/v2 v2.0.1 // indirect | ||
github.com/mitchellh/copystructure v1.2.0 // indirect | ||
github.com/mitchellh/mapstructure v1.5.0 // indirect | ||
github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||
go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014 // indirect | ||
go.uber.org/multierr v1.11.0 // indirect | ||
golang.org/x/crypto v0.7.0 // indirect | ||
golang.org/x/net v0.10.0 // indirect | ||
golang.org/x/oauth2 v0.8.0 // indirect | ||
golang.org/x/sys v0.8.0 // indirect | ||
google.golang.org/appengine v1.6.7 // indirect | ||
google.golang.org/protobuf v1.28.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ import ( | |
"sort" | ||
"strings" | ||
|
||
"github.com/google/go-github/v53/github" | ||
"go.opentelemetry.io/collector/confmap/provider/fileprovider" | ||
) | ||
|
||
|
@@ -79,9 +80,10 @@ const unmaintainedStatus = "unmaintained" | |
// .github/CODEOWNERS | ||
// .github/ALLOWLIST | ||
func main() { | ||
folder := flag.String("folder", ".", "folder investigated for codeowners") | ||
allowlistFilePath := flag.String("allowlist", "cmd/githubgen/allowlist.txt", "path to a file containing an allowlist of members outside the OpenTelemetry organization") | ||
flag.Parse() | ||
folder := flag.Arg(0) | ||
if err := run(folder); err != nil { | ||
if err := run(*folder, *allowlistFilePath); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
@@ -127,11 +129,27 @@ func loadMetadata(filePath string) (metadata, error) { | |
return md, nil | ||
} | ||
|
||
func run(folder string) error { | ||
func run(folder string, allowlistFilePath string) error { | ||
members, err := getGithubMembers() | ||
if err != nil { | ||
return err | ||
} | ||
allowlistData, err := os.ReadFile(allowlistFilePath) | ||
if err != nil { | ||
return err | ||
} | ||
allowlistLines := strings.Split(string(allowlistData), "\n") | ||
|
||
allowlist := make(map[string]struct{}, len(allowlistLines)) | ||
for _, line := range allowlistLines { | ||
allowlist[line] = struct{}{} | ||
} | ||
|
||
components := map[string]metadata{} | ||
foldersList := []string{} | ||
var foldersList []string | ||
maxLength := 0 | ||
err := filepath.Walk(folder, func(path string, info fs.FileInfo, err error) error { | ||
allCodeowners := map[string]struct{}{} | ||
err = filepath.Walk(folder, func(path string, info fs.FileInfo, err error) error { | ||
if info.Name() == "metadata.yaml" { | ||
m, err := loadMetadata(path) | ||
if err != nil { | ||
|
@@ -149,16 +167,43 @@ func run(folder string) error { | |
return nil | ||
} | ||
} | ||
for _, id := range m.Status.Codeowners.Active { | ||
allCodeowners[id] = struct{}{} | ||
} | ||
if len(key) > maxLength { | ||
maxLength = len(key) | ||
} | ||
} | ||
return nil | ||
}) | ||
sort.Strings(foldersList) | ||
if err != nil { | ||
return err | ||
} | ||
sort.Strings(foldersList) | ||
var missingCodeowners []string | ||
var duplicateCodeowners []string | ||
for codeowner := range allCodeowners { | ||
_, present := members[codeowner] | ||
|
||
if !present { | ||
_, allowed := allowlist[codeowner] | ||
allowed = allowed || strings.HasPrefix(codeowner, "open-telemetry/") | ||
if !allowed { | ||
missingCodeowners = append(missingCodeowners, codeowner) | ||
} | ||
} else if _, ok := allowlist[codeowner]; ok { | ||
duplicateCodeowners = append(duplicateCodeowners, codeowner) | ||
} | ||
} | ||
if len(missingCodeowners) > 0 { | ||
sort.Strings(missingCodeowners) | ||
return fmt.Errorf("codeowners are not members: %s", strings.Join(missingCodeowners, ", ")) | ||
} | ||
if len(duplicateCodeowners) > 0 { | ||
sort.Strings(duplicateCodeowners) | ||
return fmt.Errorf("codeowners members duplicate in allowlist: %s", strings.Join(duplicateCodeowners, ", ")) | ||
} | ||
|
||
codeowners := codeownersHeader | ||
deprecatedList := "## DEPRECATED components\n" | ||
unmaintainedList := "\n## UNMAINTAINED components\n" | ||
|
@@ -206,3 +251,35 @@ LOOP: | |
|
||
return nil | ||
} | ||
|
||
func getGithubMembers() (map[string]struct{}, error) { | ||
client := github.NewTokenClient(context.Background(), os.Getenv("GITHUB_TOKEN")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth passing these values in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, otherwise it fails because the github token is not set. |
||
var allUsers []*github.User | ||
pageIndex := 0 | ||
for { | ||
users, resp, err := client.Organizations.ListMembers(context.Background(), "open-telemetry", | ||
&github.ListMembersOptions{ | ||
PublicOnly: false, | ||
ListOptions: github.ListOptions{ | ||
PerPage: 50, | ||
Page: pageIndex, | ||
}, | ||
}, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
if len(users) == 0 { | ||
break | ||
} | ||
allUsers = append(allUsers, users...) | ||
pageIndex++ | ||
} | ||
|
||
usernames := make(map[string]struct{}, len(allUsers)) | ||
for _, u := range allUsers { | ||
usernames[*u.Login] = struct{}{} | ||
} | ||
return usernames, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are these names for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allowlist of github usernames who are not currently code owners and yet allowed to be kept in while we resolve #20868