Skip to content
Merged
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
65 changes: 63 additions & 2 deletions pkg/connector/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const (
repoPermissionAdmin = "admin"
)

const readConst = "read"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Suggestion: readConst encodes the Go type in the name rather than describing the domain concept. The other constants in this file use the repoPermission* prefix. Consider a name like apiRoleRead or roleNameRead to clarify this is the GitHub API's role vocabulary (as opposed to the entitlement permission vocabulary).

Suggested change
const readConst = "read"
const apiRoleRead = "read"


var repoAccessLevels = []string{
repoPermissionPull,
repoPermissionTriage,
Expand All @@ -39,6 +41,24 @@ var repoAccessLevels = []string{
repoPermissionAdmin,
}

// roleNameToRepoPermission maps a role returned by the "get repository
// permissions for a user" API (read/triage/write/maintain/admin) to the
// permission vocabulary used by repository entitlements
// (pull/triage/push/maintain/admin). Returns "" for custom repository
// roles it does not recognize.
func roleNameToRepoPermission(roleName string) string {
switch roleName {
case readConst:
return repoPermissionPull
case "write":
return repoPermissionPush
case repoPermissionTriage, repoPermissionMaintain, repoPermissionAdmin:
return roleName
default:
return ""
}
}

// repositoryResource returns a new connector resource for a GitHub repository.
func repositoryResource(ctx context.Context, repo *github.Repository, parentResourceID *v2.ResourceId) (*v2.Resource, error) {
ret, err := resourceSdk.NewResource(
Expand Down Expand Up @@ -389,6 +409,43 @@ func (o *repositoryResourceType) Grant(ctx context.Context, principal *v2.Resour
return nil, wrapGitHubError(err, resp, "github-connector: failed to get user")
}

collaborator, resp, err := o.client.Repositories.IsCollaborator(ctx, repo.GetOwner().GetLogin(), repo.GetName(), user.GetLogin())
if err != nil {
return nil, wrapGitHubError(err, resp, "github-connector: failed to check if user is a collaborator")
}

var replacedGrantID string
if collaborator {
permLevel, resp, err := o.client.Repositories.GetPermissionLevel(ctx, repo.GetOwner().GetLogin(), repo.GetName(), user.GetLogin())
if err != nil {
return nil, wrapGitHubError(err, resp, "github-connector: failed to get user's repository permission")
}

prevPermission := roleNameToRepoPermission(permLevel.GetRoleName())
if prevPermission == "" {
// Custom repository role: fall back to the coarse permission (read/write/admin).
prevPermission = roleNameToRepoPermission(permLevel.GetPermission())
}

switch prevPermission {
case permission:
return annotations.New(&v2.GrantAlreadyExists{}), nil
case "":
l.Warn(
"github-connectorv2: unrecognized existing repository role, granting without GrantReplaced annotation",
zap.String("role_name", permLevel.GetRoleName()),
zap.String("permission", permLevel.GetPermission()),
)
default:
// AddCollaborator overwrites the user's existing role, so report the
// old role's grant as replaced. GitHub permissions are cumulative;
// grants for other implied flags are reconciled at the next sync.
replacedGrantID = grant.NewGrantID(principal, &v2.Entitlement{
Id: entitlement.NewEntitlementID(en.Resource, prevPermission),
})
}
}

_, resp, er := o.client.Repositories.AddCollaborator(
ctx,
repo.GetOwner().GetLogin(),
Expand All @@ -400,6 +457,10 @@ func (o *repositoryResourceType) Grant(ctx context.Context, principal *v2.Resour
if er != nil {
return nil, wrapGitHubError(er, resp, "github-connector: failed to add user to repository")
}

if replacedGrantID != "" {
return annotations.New(&v2.GrantReplaced{ReplacedGrantId: replacedGrantID}), nil
}
case resourceTypeTeam.Id:
team, resp, err := o.client.Teams.GetTeamByID(ctx, org.GetID(), principalID) //nolint:staticcheck,nolintlint // TODO: migrate to GetTeamBySlug
if err != nil {
Expand Down Expand Up @@ -504,7 +565,7 @@ func (o *repositoryResourceType) getOrgBasePermission(ctx context.Context, ss se

perm := org.GetDefaultRepoPermission()
if perm == "" {
perm = "read" // GitHub default
perm = readConst // GitHub default
}

if err := session.SetJSON(ctx, ss, key, perm); err != nil {
Expand All @@ -521,7 +582,7 @@ func orgBasePermissionToRepoPermissions(basePerm string) []string {
return []string{repoPermissionPull, repoPermissionTriage, repoPermissionPush, repoPermissionMaintain, repoPermissionAdmin}
case "write":
return []string{repoPermissionPull, repoPermissionTriage, repoPermissionPush}
case "read":
case readConst:
return []string{repoPermissionPull}
default:
return nil
Expand Down
Loading