Skip to content
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

Test GH CI: Support outside collaborators to owners check #198

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all 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
46 changes: 43 additions & 3 deletions internal/check/valid_owner.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type ValidOwner struct {
orgName string
orgTeams []*github.Team
orgRepoName string
outsideCollaborators map[string]struct{}
ignOwners map[string]struct{}
allowUnownedPatterns bool
ownersMustBeTeams bool
Expand Down Expand Up @@ -297,6 +298,12 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid
}
}

if v.outsideCollaborators == nil { // TODO(mszostok): lazy init, make it more robust.
if err := v.initOutsideCollaboratorsList(ctx); err != nil {
return newValidateError("Cannot initialize outside collaborators list: %v", err).AsPermanent()
}
}

userName := strings.TrimPrefix(name, "@")
_, _, err := v.ghClient.Users.Get(ctx, userName)
if err != nil { // TODO(mszostok): implement retry?
Expand All @@ -314,15 +321,18 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid
}

_, isMember := (*v.orgMembers)[userName]
if !isMember {
return newValidateError("User %q is not a member of the organization", name)
_, isOutsideCollaborator := (v.outsideCollaborators)[userName]
if !(isMember || isOutsideCollaborator) {
return newValidateError("The user %q is neither a collaborator nor a member of this repository.", name)
}

return nil
}

// There is a method to check if user is a org member
// client.Organizations.IsMember(context.Background(), "org-name", "user-name")
//
// client.Organizations.IsMember(context.Background(), "org-name", "user-name")
//
// But latency is too huge for checking each single user independent
// better and faster is to ask for all members and cache them.
func (v *ValidOwner) initOrgListMembers(ctx context.Context) error {
Expand Down Expand Up @@ -351,6 +361,36 @@ func (v *ValidOwner) initOrgListMembers(ctx context.Context) error {
return nil
}

// Add all outside collaborators who are part of the repository to
//
// outsideCollaborators *map[string]struct{}
func (v *ValidOwner) initOutsideCollaboratorsList(ctx context.Context) error {
opt := &github.ListCollaboratorsOptions{
ListOptions: github.ListOptions{PerPage: 100},
Affiliation: "outside",
}

var allMembers []*github.User
for {
collaborators, resp, err := v.ghClient.Repositories.ListCollaborators(ctx, v.orgName, v.orgRepoName, opt)
if err != nil {
return err
}
allMembers = append(allMembers, collaborators...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}

v.outsideCollaborators = map[string]struct{}{}
for _, u := range allMembers {
(v.outsideCollaborators)[u.GetLogin()] = struct{}{}
}

return nil
}

// Name returns human-readable name of the validator
func (ValidOwner) Name() string {
return "Valid Owner Checker"
Expand Down