Skip to content
Merged
Show file tree
Hide file tree
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
8 changes: 8 additions & 0 deletions apis/settings/v1beta1/repository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ type RepositorySpec struct {
MergeCommitTitle *string `json:"mergeCommitTitle,omitempty"`
// Can be one of: PR_BODY, PR_TITLE, BLANK
MergeCommitMessage *string `json:"mergeCommitMessage,omitempty"`

RepositoryCollaborators *RepositoryCollaborators `json:"repositoryCollaborators,omitempty"`
}

type RepositoryCollaborators struct {
PushPermission []string `json:"pushPermission,omitempty"`
PullPermission []string `json:"pullPermission,omitempty"`
AdminPermission []string `json:"adminPermission,omitempty"`
}

// RepositoryStatus defines the observed state of Repository
Expand Down
35 changes: 35 additions & 0 deletions apis/settings/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions config/crd/bases/settings.github.com_repositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ spec:
description: Either `true` to make the repository private, or `false`
to make it public.
type: boolean
repositoryCollaborators:
properties:
adminPermission:
items:
type: string
type: array
pullPermission:
items:
type: string
type: array
pushPermission:
items:
type: string
type: array
type: object
squashMergeCommitMessage:
description: 'Can be one of: PR_BODY, COMMIT_MESSAGES, BLANK'
type: string
Expand Down
2 changes: 2 additions & 0 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ patchesStrategicMerge:
# patches here are for enabling the conversion webhook for each CRD
#- patches/webhook_in_repositories.yaml
#- patches/webhook_in_teams.yaml
#- patches/webhook_in_collaborators.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch

# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- patches/cainjection_in_repositories.yaml
#- patches/cainjection_in_teams.yaml
#- patches/cainjection_in_collaborators.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch

# the following config is for teaching kustomize how to do kustomization for CRDs.
Expand Down
2 changes: 1 addition & 1 deletion config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ spec:
- /manager
args:
- --leader-elect
image: hunterthompson/github-operator:v1.0.0
image: hunterthompson/github-operator:v1.0.2
name: manager
envFrom:
- secretRef:
Expand Down
167 changes: 167 additions & 0 deletions controllers/settings/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,170 @@ func (r *RepositoryReconciler) EditRepoSettings(ctx context.Context, repo *setti
reqLogger.Info("edited repository")
return nil
}

func (r *RepositoryReconciler) EditRepoCollaboraters(ctx context.Context, repo *settingsv1beta1.Repository, reqLogger logr.Logger) error {
ghClient := gh.Login(ctx)

if repo.Spec.RepositoryCollaborators == nil {
return nil
}

opt := &github.ListMembersOptions{
ListOptions: github.ListOptions{PerPage: 10},
}

var allMembers []*github.User
for {
users, resp, err := ghClient.Organizations.ListMembers(ctx, repo.Spec.Organization, opt)
if err != nil {
return err
}
allMembers = append(allMembers, users...)
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}

for _, adminUser := range repo.Spec.RepositoryCollaborators.AdminPermission {
err := addAdminPerm(ctx, repo, ghClient, adminUser, allMembers, reqLogger)
if err != nil {
return fmt.Errorf("failed to addAdminPerm: %w", err)
}
}

for _, pullUser := range repo.Spec.RepositoryCollaborators.PullPermission {
err := addPullPerm(ctx, repo, ghClient, pullUser, allMembers, reqLogger)
if err != nil {
return fmt.Errorf("failed to addPullPerm: %w", err)
}
}

for _, pushUser := range repo.Spec.RepositoryCollaborators.PushPermission {
err := addPushPerm(ctx, repo, ghClient, pushUser, allMembers, reqLogger)
if err != nil {
return fmt.Errorf("failed to addPushPerm: %w", err)
}
}

return nil
}

func addAdminPerm(ctx context.Context, repo *settingsv1beta1.Repository, ghClient *github.Client, adminUser string, allMembers []*github.User, reqLogger logr.Logger) error {
permLevel, _, err := ghClient.Repositories.GetPermissionLevel(ctx, repo.Spec.Organization, repo.GetName(), adminUser)
if err != nil {
if strings.Contains(err.Error(), "is not a user []") {
reqLogger.Error(err, "not a user")
return nil
}
return fmt.Errorf("failed to get perm level for repo: %w", err)
}

if permLevel.GetPermission() == "admin" {
reqLogger.Info(adminUser + " already has admin permission")
return nil
}

add := false

for _, member := range allMembers {
// only add if user is actually a part of the org
if member.GetLogin() == adminUser {
add = true
break
}
}

if add {
_, _, err = ghClient.Repositories.AddCollaborator(ctx, repo.Spec.Organization, repo.GetName(), adminUser, &github.RepositoryAddCollaboratorOptions{
Permission: "admin",
})
if err != nil {
return fmt.Errorf("failed to add admin collaborator for repo: %w", err)
}
reqLogger.Info("gave " + adminUser + " admin permission")
} else {
reqLogger.Info(adminUser + " is not a part of the " + repo.Spec.Organization + " org")
}

return nil
}

func addPullPerm(ctx context.Context, repo *settingsv1beta1.Repository, ghClient *github.Client, pullUser string, allMembers []*github.User, reqLogger logr.Logger) error {
permLevel, _, err := ghClient.Repositories.GetPermissionLevel(ctx, repo.Spec.Organization, repo.GetName(), pullUser)
if err != nil {
if strings.Contains(err.Error(), "is not a user []") {
reqLogger.Error(err, "not a user")
return nil
}
return fmt.Errorf("failed to get perm level for repo: %w", err)
}

if permLevel.GetPermission() == "read" || permLevel.GetPermission() == "admin" || permLevel.GetPermission() == "write" {
reqLogger.Info(pullUser + " already has read permission")
return nil
}

add := false

for _, member := range allMembers {
// only add if user is actually a part of the org
if member.GetLogin() == pullUser {
add = true
break
}
}

if add {
_, _, err = ghClient.Repositories.AddCollaborator(ctx, repo.Spec.Organization, repo.GetName(), pullUser, &github.RepositoryAddCollaboratorOptions{
Permission: "pull",
})
if err != nil {
return fmt.Errorf("failed to add pull collaborator for repo: %w", err)
}
reqLogger.Info("gave " + pullUser + " pull permission")
} else {
reqLogger.Info(pullUser + " is not a part of the " + repo.Spec.Organization + " org")
}
return nil
}

func addPushPerm(ctx context.Context, repo *settingsv1beta1.Repository, ghClient *github.Client, pushUser string, allMembers []*github.User, reqLogger logr.Logger) error {
permLevel, _, err := ghClient.Repositories.GetPermissionLevel(ctx, repo.Spec.Organization, repo.GetName(), pushUser)
if err != nil {
if strings.Contains(err.Error(), "is not a user []") {
reqLogger.Error(err, "not a user")
return nil
}
return fmt.Errorf("failed to get perm level for repo: %w", err)
}

if permLevel.GetPermission() == "admin" || permLevel.GetPermission() == "write" {
reqLogger.Info(pushUser + " already has push permission")
return nil
}

add := false

for _, member := range allMembers {
// only add if user is actually a part of the org
if member.GetLogin() == pushUser {
add = true
break
}
}

if add {
_, _, err = ghClient.Repositories.AddCollaborator(ctx, repo.Spec.Organization, repo.GetName(), pushUser, &github.RepositoryAddCollaboratorOptions{
Permission: "push",
})
if err != nil {
return fmt.Errorf("failed to add push collaborator for repo: %w", err)
}
reqLogger.Info("gave " + pushUser + " push permission")
} else {
reqLogger.Info(pushUser + " is not a part of the " + repo.Spec.Organization + " org")
}

return nil
}
5 changes: 5 additions & 0 deletions controllers/settings/repository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
}

err = r.EditRepoCollaboraters(ctx, repo, reqLogger)
if err != nil {
return reconcile.Result{}, err
}

return reconcile.Result{}, nil
}

Expand Down
17 changes: 16 additions & 1 deletion docs/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ spec:
description: Either `true` to make the repository private, or `false`
to make it public.
type: boolean
repositoryCollaborators:
properties:
adminPermission:
items:
type: string
type: array
pullPermission:
items:
type: string
type: array
pushPermission:
items:
type: string
type: array
type: object
squashMergeCommitMessage:
description: 'Can be one of: PR_BODY, COMMIT_MESSAGES, BLANK'
type: string
Expand Down Expand Up @@ -441,7 +456,7 @@ spec:
envFrom:
- secretRef:
name: github-operator-secret
image: hunterthompson/github-operator:latest
image: hunterthompson/github-operator:v1.0.2
livenessProbe:
httpGet:
path: /healthz
Expand Down