Skip to content

Commit

Permalink
feat: Updated repo collaborators to support ignoring teams
Browse files Browse the repository at this point in the history
Signed-off-by: Steve Hipwell <[email protected]>
  • Loading branch information
stevehipwell committed Nov 28, 2024
1 parent 1c11053 commit 636bac2
Show file tree
Hide file tree
Showing 3 changed files with 259 additions and 74 deletions.
117 changes: 84 additions & 33 deletions github/resource_github_repository_collaborators.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
"user": {
Type: schema.TypeSet,
Optional: true,
Description: "List of users",
Description: "List of users.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"permission": {
Expand All @@ -52,7 +52,7 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
"team": {
Type: schema.TypeSet,
Optional: true,
Description: "List of teams",
Description: "List of teams.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"permission": {
Expand All @@ -76,6 +76,20 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
},
Computed: true,
},
"ignore_team": {
Type: schema.TypeSet,
Optional: true,
Description: "List of teams to ignore.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"team_id": {
Type: schema.TypeString,
Description: "ID or slug of the team to ignore.",
Required: true,
},
},
},
},
},

CustomizeDiff: customdiff.Sequence(
Expand Down Expand Up @@ -145,16 +159,16 @@ func (c teamCollaborator) Empty() bool {
return c == teamCollaborator{}
}

func flattenTeamCollaborator(obj teamCollaborator, teamIDs []int64) interface{} {
func flattenTeamCollaborator(obj teamCollaborator, teamSlugs []string) interface{} {
if obj.Empty() {
return nil
}

var teamIDString string
if slices.Contains(teamIDs, obj.teamID) {
teamIDString = strconv.FormatInt(obj.teamID, 10)
} else {
if slices.Contains(teamSlugs, obj.teamSlug) {
teamIDString = obj.teamSlug
} else {
teamIDString = strconv.FormatInt(obj.teamID, 10)
}

transformed := map[string]interface{}{
Expand All @@ -165,7 +179,7 @@ func flattenTeamCollaborator(obj teamCollaborator, teamIDs []int64) interface{}
return transformed
}

func flattenTeamCollaborators(objs []teamCollaborator, teamIDs []int64) []interface{} {
func flattenTeamCollaborators(objs []teamCollaborator, teamSlugs []string) []interface{} {
if objs == nil {
return nil
}
Expand All @@ -176,14 +190,14 @@ func flattenTeamCollaborators(objs []teamCollaborator, teamIDs []int64) []interf

items := make([]interface{}, len(objs))
for i, obj := range objs {
items[i] = flattenTeamCollaborator(obj, teamIDs)
items[i] = flattenTeamCollaborator(obj, teamSlugs)
}

return items
}

func listUserCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]userCollaborator, error) {
var userCollaborators []userCollaborator
userCollaborators := make([]userCollaborator, 0)
affiliations := []string{"direct", "outside"}
for _, affiliation := range affiliations {
opt := &github.ListCollaboratorsOptions{ListOptions: github.ListOptions{
Expand Down Expand Up @@ -217,7 +231,7 @@ func listUserCollaborators(client *github.Client, isOrg bool, ctx context.Contex
}

func listInvitations(client *github.Client, ctx context.Context, owner, repoName string) ([]invitedCollaborator, error) {
var invitedCollaborators []invitedCollaborator
invitedCollaborators := make([]invitedCollaborator, 0)

opt := &github.ListOptions{PerPage: maxPerPage}
for {
Expand All @@ -230,7 +244,8 @@ func listInvitations(client *github.Client, ctx context.Context, owner, repoName
permissionName := getPermission(i.GetPermissions())

invitedCollaborators = append(invitedCollaborators, invitedCollaborator{
userCollaborator{permissionName, i.GetInvitee().GetLogin()}, i.GetID()})
userCollaborator{permissionName, i.GetInvitee().GetLogin()}, i.GetID(),
})
}

if resp.NextPage == 0 {
Expand All @@ -241,11 +256,11 @@ func listInvitations(client *github.Client, ctx context.Context, owner, repoName
return invitedCollaborators, nil
}

func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]teamCollaborator, error) {
var teamCollaborators []teamCollaborator
func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string, ignoreTeamIds []int64) ([]teamCollaborator, error) {
allTeams := make([]teamCollaborator, 0)

if !isOrg {
return teamCollaborators, nil
return allTeams, nil
}

opt := &github.ListOptions{PerPage: maxPerPage}
Expand All @@ -256,20 +271,23 @@ func listTeams(client *github.Client, isOrg bool, ctx context.Context, owner, re
}

for _, t := range repoTeams {
permissionName := getPermission(t.GetPermission())
if slices.Contains(ignoreTeamIds, t.GetID()) {
continue
}

teamCollaborators = append(teamCollaborators, teamCollaborator{permissionName, t.GetID(), t.GetSlug()})
allTeams = append(allTeams, teamCollaborator{permission: getPermission(t.GetPermission()), teamID: t.GetID(), teamSlug: t.GetSlug()})
}

if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}
return teamCollaborators, nil

return allTeams, nil
}

func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string) ([]userCollaborator, []invitedCollaborator, []teamCollaborator, error) {
func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context, owner, repoName string, ignoreTeamIds []int64) ([]userCollaborator, []invitedCollaborator, []teamCollaborator, error) {
userCollaborators, err := listUserCollaborators(client, isOrg, ctx, owner, repoName)
if err != nil {
return nil, nil, nil, err
Expand All @@ -278,16 +296,14 @@ func listAllCollaborators(client *github.Client, isOrg bool, ctx context.Context
if err != nil {
return nil, nil, nil, err
}
teamCollaborators, err := listTeams(client, isOrg, ctx, owner, repoName)
teamCollaborators, err := listTeams(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
if err != nil {
return nil, nil, nil, err
}
return userCollaborators, invitations, teamCollaborators, err
}

func matchUserCollaboratorsAndInvites(
repoName string, want []interface{}, hasUsers []userCollaborator, hasInvites []invitedCollaborator,
meta interface{}) error {
func matchUserCollaboratorsAndInvites(repoName string, want []interface{}, hasUsers []userCollaborator, hasInvites []invitedCollaborator, meta interface{}) error {
client := meta.(*Owner).v3client

owner := meta.(*Owner).name
Expand Down Expand Up @@ -383,8 +399,7 @@ func matchUserCollaboratorsAndInvites(
return nil
}

func matchTeamCollaborators(
repoName string, want []interface{}, has []teamCollaborator, meta interface{}) error {
func matchTeamCollaborators(repoName string, want []interface{}, has []teamCollaborator, meta interface{}) error {
client := meta.(*Owner).v3client
orgID := meta.(*Owner).id
owner := meta.(*Owner).name
Expand Down Expand Up @@ -471,15 +486,15 @@ func resourceGithubRepositoryCollaboratorsCreate(d *schema.ResourceData, meta in
repoName := d.Get("repository").(string)
ctx := context.Background()

teamsMap := make(map[string]struct{})
teamsMap := make(map[string]struct{}, len(teams))
for _, team := range teams {
teamIDString := team.(map[string]interface{})["team_id"].(string)
if _, found := teamsMap[teamIDString]; found {
return fmt.Errorf("duplicate set member: %s", teamIDString)
}
teamsMap[teamIDString] = struct{}{}
}
usersMap := make(map[string]struct{})
usersMap := make(map[string]struct{}, len(users))
for _, user := range users {
username := user.(map[string]interface{})["username"].(string)
if _, found := usersMap[username]; found {
Expand All @@ -488,7 +503,12 @@ func resourceGithubRepositoryCollaboratorsCreate(d *schema.ResourceData, meta in
usersMap[username] = struct{}{}
}

userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName)
ignoreTeamIds, err := getIgnoreTeamIds(d, meta)
if err != nil {
return err
}

userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
if err != nil {
return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName)
}
Expand Down Expand Up @@ -516,7 +536,12 @@ func resourceGithubRepositoryCollaboratorsRead(d *schema.ResourceData, meta inte
repoName := d.Id()
ctx := context.WithValue(context.Background(), ctxId, d.Id())

userCollaborators, invitedCollaborators, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName)
ignoreTeamIds, err := getIgnoreTeamIds(d, meta)
if err != nil {
return err
}

userCollaborators, invitedCollaborators, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
if err != nil {
return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName)
}
Expand All @@ -526,9 +551,14 @@ func resourceGithubRepositoryCollaboratorsRead(d *schema.ResourceData, meta inte
invitationIds[i.username] = strconv.FormatInt(i.invitationID, 10)
}

teamIDs := make([]int64, len(teamCollaborators))
for i, t := range teamCollaborators {
teamIDs[i] = t.teamID
sourceTeams := d.Get("team").(*schema.Set).List()
teamSlugs := make([]string, len(sourceTeams))
for i, t := range sourceTeams {
teamIdString := t.(map[string]interface{})["team_id"].(string)
_, parseIntErr := strconv.ParseInt(teamIdString, 10, 64)
if parseIntErr != nil {
teamSlugs[i] = teamIdString
}
}

err = d.Set("repository", repoName)
Expand All @@ -539,7 +569,7 @@ func resourceGithubRepositoryCollaboratorsRead(d *schema.ResourceData, meta inte
if err != nil {
return err
}
err = d.Set("team", flattenTeamCollaborators(teamCollaborators, teamIDs))
err = d.Set("team", flattenTeamCollaborators(teamCollaborators, teamSlugs))
if err != nil {
return err
}
Expand All @@ -563,7 +593,12 @@ func resourceGithubRepositoryCollaboratorsDelete(d *schema.ResourceData, meta in
repoName := d.Get("repository").(string)
ctx := context.Background()

userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName)
ignoreTeamIds, err := getIgnoreTeamIds(d, meta)
if err != nil {
return err
}

userCollaborators, invitations, teamCollaborators, err := listAllCollaborators(client, isOrg, ctx, owner, repoName, ignoreTeamIds)
if err != nil {
return deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository collaborators (%s/%s)", owner, repoName)
}
Expand All @@ -580,3 +615,19 @@ func resourceGithubRepositoryCollaboratorsDelete(d *schema.ResourceData, meta in
err = matchTeamCollaborators(repoName, nil, teamCollaborators, meta)
return err
}

func getIgnoreTeamIds(d *schema.ResourceData, meta interface{}) ([]int64, error) {
ignoreTeams := d.Get("ignore_team").(*schema.Set).List()
ignoreTeamIds := make([]int64, len(ignoreTeams))

for i, t := range ignoreTeams {
s := t.(map[string]interface{})["team_id"].(string)
id, err := getTeamID(s, meta)
if err != nil {
return nil, err
}
ignoreTeamIds[i] = id
}

return ignoreTeamIds, nil
}
Loading

0 comments on commit 636bac2

Please sign in to comment.