@@ -18,13 +18,18 @@ import (
1818
1919func resourceGithubRepositoryCollaborators () * schema.Resource {
2020 return & schema.Resource {
21- SchemaVersion : 1 ,
21+ SchemaVersion : 2 ,
2222 StateUpgraders : []schema.StateUpgrader {
2323 {
2424 Type : resourceGithubRepositoryCollaboratorsV0 ().CoreConfigSchema ().ImpliedType (),
2525 Upgrade : resourceGithubRepositoryCollaboratorsStateUpgradeV0 ,
2626 Version : 0 ,
2727 },
28+ {
29+ Type : resourceGithubRepositoryCollaboratorsV1 ().CoreConfigSchema ().ImpliedType (),
30+ Upgrade : resourceGithubRepositoryCollaboratorsStateUpgradeV1 ,
31+ Version : 1 ,
32+ },
2833 },
2934
3035 Description : "Manage the complete set of collaborators (users and teams) for a GitHub repository." ,
@@ -89,6 +94,11 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
8994 },
9095 },
9196 },
97+ "owner_configured" : {
98+ Type : schema .TypeBool ,
99+ Computed : true ,
100+ Description : "Indicates whether the owner of a personal repository is configured as a collaborator." ,
101+ },
92102 "invitation_ids" : {
93103 Type : schema .TypeMap ,
94104 Description : "Map of usernames to invitation ID for users that haven't yet accepted their invitation to become a collaborator. This is only set on read, and is used internally to track pending invitations for users that aren't yet collaborators." ,
@@ -121,15 +131,34 @@ func resourceGithubRepositoryCollaborators() *schema.Resource {
121131}
122132
123133func resourceGithubRepositoryCollaboratorsDiff (ctx context.Context , d * schema.ResourceDiff , m any ) error {
124- tflog .Debug (ctx , "Diffing user collaborators" )
134+ tflog .Debug (ctx , "Diffing collaborators" )
135+
136+ meta , _ := m .(* Owner )
137+
125138 if d .HasChange ("user" ) {
126- users := d .Get ("user" ).(* schema.Set ).List ()
139+ users , ok := d .Get ("user" ).(* schema.Set )
140+ if ! ok {
141+ return fmt .Errorf ("error reading user config" )
142+ }
143+
127144 seen := make (map [string ]any )
145+ for _ , u := range users .List () {
146+ user , ok := u .(map [string ]any )
147+ if ! ok {
148+ return fmt .Errorf ("error reading user config" )
149+ }
128150
129- for _ , u := range users {
130- user := u .(map [string ]any )
131- username := user ["username" ].(string )
151+ usernameVal , ok := user ["username" ]
152+ if ! ok {
153+ return fmt .Errorf ("error reading user config" )
154+ }
132155
156+ username , ok := usernameVal .(string )
157+ if ! ok {
158+ return fmt .Errorf ("error reading user config" )
159+ }
160+
161+ username = strings .ToLower (username )
133162 if _ , ok := seen [username ]; ok {
134163 return fmt .Errorf ("duplicate username %s found in user collaborators" , username )
135164 }
@@ -162,7 +191,63 @@ func resourceGithubRepositoryCollaboratorsDiff(ctx context.Context, d *schema.Re
162191 }
163192 }
164193
165- if len (d .Id ()) == 0 {
194+ if meta .IsOrganization {
195+ // If the repository belongs to an organization the owner cannot be a,
196+ // collaborator, so owner_configured is always false.
197+
198+ if err := d .SetNew ("owner_configured" , false ); err != nil {
199+ return fmt .Errorf ("error setting owner_configured: %w" , err )
200+ }
201+ } else if d .NewValueKnown ("user" ) {
202+ // If the repository belongs to a user and we know the new value of user,
203+ // then we can determine the value of owner_configured by checking if
204+ // the owner is included in the list of users.
205+
206+ ownerConfigured := false
207+ owner := meta .name
208+
209+ users , ok := d .Get ("user" ).(* schema.Set )
210+ if ! ok {
211+ return fmt .Errorf ("error reading user config" )
212+ }
213+
214+ for _ , u := range users .List () {
215+ user , ok := u .(map [string ]any )
216+ if ! ok {
217+ return fmt .Errorf ("error reading user config" )
218+ }
219+
220+ usernameVal , ok := user ["username" ]
221+ if ! ok {
222+ return fmt .Errorf ("error reading user config" )
223+ }
224+
225+ username , ok := usernameVal .(string )
226+ if ! ok {
227+ return fmt .Errorf ("error reading user config" )
228+ }
229+
230+ if strings .EqualFold (username , owner ) {
231+ ownerConfigured = true
232+ break
233+ }
234+ }
235+
236+ if err := d .SetNew ("owner_configured" , ownerConfigured ); err != nil {
237+ return fmt .Errorf ("error setting owner_configured: %w" , err )
238+ }
239+ } else {
240+ // If the repository belongs to a user but we don't know the new value of user,
241+ // then we don't know if the owner is configured as a collaborator or not,
242+ // so we set owner_configured to computed to indicate that Terraform should
243+ // determine the value during apply.
244+
245+ if err := d .SetNewComputed ("owner_configured" ); err != nil {
246+ return fmt .Errorf ("error setting owner_configured to computed: %w" , err )
247+ }
248+ }
249+
250+ if d .Id () == "" {
166251 return nil
167252 }
168253
@@ -186,16 +271,36 @@ func resourceGithubRepositoryCollaboratorsCreate(ctx context.Context, d *schema.
186271 teams := d .Get ("team" ).(* schema.Set ).List ()
187272 ignoreTeams := d .Get ("ignore_team" ).(* schema.Set ).List ()
188273
189- tflog .Debug (ctx , "Creating repository collaborators" , map [string ]any {
190- "users" : users ,
191- "teams" : teams ,
192- "ignoreTeams" : ignoreTeams ,
193- })
274+ tflog .Debug (ctx , "Creating repository collaborators" , map [string ]any {"users" : users , "teams" : teams , "ignoreTeams" : ignoreTeams })
275+
194276 inUsers , err := getUserCollaborators (users )
195277 if err != nil {
196278 return diag .FromErr (err )
197279 }
198280
281+ ownerConfigured := false
282+ inIgnoreUsers := make ([]string , 0 )
283+ if ! isOrg {
284+ ownerConfigured , _ = d .Get ("owner_configured" ).(bool )
285+
286+ if ! ownerConfigured {
287+ for _ , u := range inUsers {
288+ if strings .EqualFold (u .login , owner ) {
289+ ownerConfigured = true
290+ break
291+ }
292+ }
293+
294+ if ! ownerConfigured {
295+ inIgnoreUsers = append (inIgnoreUsers , strings .ToLower (owner ))
296+ }
297+
298+ if err := d .Set ("owner_configured" , ownerConfigured ); err != nil {
299+ return diag .FromErr (err )
300+ }
301+ }
302+ }
303+
199304 inTeams , err := getTeamCollaborators (teams )
200305 if err != nil {
201306 return diag .FromErr (err )
@@ -206,7 +311,7 @@ func resourceGithubRepositoryCollaboratorsCreate(ctx context.Context, d *schema.
206311 return diag .FromErr (err )
207312 }
208313
209- invitations , err := updateUserCollaboratorsAndInvites (ctx , client , owner , repoName , inUsers )
314+ invitations , err := updateUserCollaboratorsAndInvites (ctx , client , owner , repoName , inUsers , inIgnoreUsers )
210315 if err != nil {
211316 return diag .FromErr (err )
212317 }
@@ -245,6 +350,12 @@ func resourceGithubRepositoryCollaboratorsRead(ctx context.Context, d *schema.Re
245350 repoName := d .Get ("repository" ).(string )
246351 teams := d .Get ("team" ).(* schema.Set ).List ()
247352 ignoreTeams := d .Get ("ignore_team" ).(* schema.Set ).List ()
353+ ownerConfigured , _ := d .Get ("owner_configured" ).(bool )
354+
355+ inIgnoreUsers := make ([]string , 0 )
356+ if ! isOrg && ! ownerConfigured {
357+ inIgnoreUsers = append (inIgnoreUsers , strings .ToLower (owner ))
358+ }
248359
249360 inTeams , err := getTeamCollaborators (teams )
250361 if err != nil {
@@ -256,7 +367,7 @@ func resourceGithubRepositoryCollaboratorsRead(ctx context.Context, d *schema.Re
256367 return diag .FromErr (err )
257368 }
258369
259- ghUsers , err := listUserCollaborators (ctx , client , owner , repoName )
370+ ghUsers , err := listUserCollaborators (ctx , client , owner , repoName , inIgnoreUsers )
260371 if err != nil {
261372 if err , ok := errors.AsType [* github.ErrorResponse ](err ); ok && err .Response .StatusCode == 404 {
262373 tflog .Debug (ctx , fmt .Sprintf ("Repository %s not found when listing users, removing from state." , repoName ))
@@ -316,6 +427,29 @@ func resourceGithubRepositoryCollaboratorsUpdate(ctx context.Context, d *schema.
316427 return diag .FromErr (err )
317428 }
318429
430+ ownerConfigured := false
431+ inIgnoreUsers := make ([]string , 0 )
432+ if ! isOrg {
433+ ownerConfigured , _ = d .Get ("owner_configured" ).(bool )
434+
435+ if ! ownerConfigured {
436+ for _ , u := range inUsers {
437+ if strings .EqualFold (u .login , owner ) {
438+ ownerConfigured = true
439+ break
440+ }
441+ }
442+
443+ if ! ownerConfigured {
444+ inIgnoreUsers = append (inIgnoreUsers , strings .ToLower (owner ))
445+ }
446+
447+ if err := d .Set ("owner_configured" , ownerConfigured ); err != nil {
448+ return diag .FromErr (err )
449+ }
450+ }
451+ }
452+
319453 inTeams , err := getTeamCollaborators (teams )
320454 if err != nil {
321455 return diag .FromErr (err )
@@ -326,7 +460,7 @@ func resourceGithubRepositoryCollaboratorsUpdate(ctx context.Context, d *schema.
326460 return diag .FromErr (err )
327461 }
328462
329- invitations , err := updateUserCollaboratorsAndInvites (ctx , client , owner , repoName , inUsers )
463+ invitations , err := updateUserCollaboratorsAndInvites (ctx , client , owner , repoName , inUsers , inIgnoreUsers )
330464 if err != nil {
331465 return diag .FromErr (err )
332466 }
@@ -362,14 +496,12 @@ func resourceGithubRepositoryCollaboratorsDelete(ctx context.Context, d *schema.
362496
363497 tflog .Debug (ctx , fmt .Sprintf ("Removing all collaborators from repository %s." , repoName ))
364498
365- _ , err = updateUserCollaboratorsAndInvites (ctx , client , owner , repoName , nil )
366- if err != nil {
499+ if _ , err := updateUserCollaboratorsAndInvites (ctx , client , owner , repoName , nil , nil ); err != nil {
367500 return diag .FromErr (err )
368501 }
369502
370503 if isOrg {
371- err = updateTeamCollaborators (ctx , client , meta .id , owner , repoName , nil , inIgnoreTeams )
372- if err != nil {
504+ if err := updateTeamCollaborators (ctx , client , meta .id , owner , repoName , nil , inIgnoreTeams ); err != nil {
373505 return diag .FromErr (err )
374506 }
375507 }
@@ -462,7 +594,7 @@ func getTeamCollaborators(col []any) (teamCollaborators, error) {
462594
463595 permission , ok := m ["permission" ].(string )
464596 if ! ok || len (permission ) == 0 {
465- return nil , fmt .Errorf ("team input must include ' permission' " )
597+ return nil , fmt .Errorf ("team input must include permission" )
466598 }
467599
468600 collaborators [i ] = teamCollaborator {
@@ -498,7 +630,7 @@ func getTeamIdentity(d any) (teamIdentity, error) {
498630
499631 o , ok := m ["team_id" ]
500632 if ! ok {
501- return teamIdentity {}, fmt .Errorf ("team input must include ' team_id' " )
633+ return teamIdentity {}, fmt .Errorf ("team input must include team_id" )
502634 }
503635
504636 id , ok := o .(string )
@@ -509,7 +641,7 @@ func getTeamIdentity(d any) (teamIdentity, error) {
509641 return newLegacyTeamIdentity (id ), nil
510642}
511643
512- func listUserCollaborators (ctx context.Context , client * github.Client , owner , repoName string ) (userCollaborators , error ) {
644+ func listUserCollaborators (ctx context.Context , client * github.Client , owner , repoName string , ignoreUsers [] string ) (userCollaborators , error ) {
513645 col := make ([]userCollaborator , 0 )
514646 tflog .Debug (ctx , "Listing user collaborators" , map [string ]any {
515647 "owner" : owner ,
@@ -531,6 +663,10 @@ func listUserCollaborators(ctx context.Context, client *github.Client, owner, re
531663 }
532664
533665 for _ , c := range collaborators {
666+ if slices .Contains (ignoreUsers , strings .ToLower (c .GetLogin ())) {
667+ continue
668+ }
669+
534670 col = append (col , userCollaborator {
535671 userIdentity : userIdentity {
536672 login : c .GetLogin (),
@@ -642,7 +778,7 @@ func listTeamCollaborators(ctx context.Context, client *github.Client, orgName,
642778 return col , nil
643779}
644780
645- func updateUserCollaboratorsAndInvites (ctx context.Context , client * github.Client , owner , repoName string , inUsers userCollaborators ) (userCollaborators , error ) {
781+ func updateUserCollaboratorsAndInvites (ctx context.Context , client * github.Client , owner , repoName string , inUsers userCollaborators , ignoreUsers [] string ) (userCollaborators , error ) {
646782 lookup := make (map [string ]userCollaborator )
647783 seen := make (map [string ]any )
648784 remove := make ([]string , 0 )
@@ -659,7 +795,7 @@ func updateUserCollaboratorsAndInvites(ctx context.Context, client *github.Clien
659795 "remove" : remove ,
660796 })
661797
662- ghUsers , err := listUserCollaborators (ctx , client , owner , repoName )
798+ ghUsers , err := listUserCollaborators (ctx , client , owner , repoName , ignoreUsers )
663799 if err != nil {
664800 return nil , err
665801 }
@@ -717,6 +853,7 @@ func updateUserCollaboratorsAndInvites(ctx context.Context, client *github.Clien
717853 if err != nil {
718854 return nil , err
719855 }
856+
720857 // AddCollaborator returns 204 No Content (inv == nil) when the invitee
721858 // is an organization member gaining direct access without an
722859 // invitation. In that case there is no invitation ID to record.
0 commit comments