55"fmt"
66"regexp"
77
8+ "github.com/coder/coder/v2/coderd/util/slice"
89"github.com/coder/coder/v2/codersdk"
910"github.com/coder/terraform-provider-coderd/internal/codersdkvalidator"
1011"github.com/google/uuid"
@@ -40,8 +41,9 @@ type OrganizationResourceModel struct {
4041Description types.String `tfsdk:"description"`
4142Icon types.String `tfsdk:"icon"`
4243
43- GroupSync types.Object `tfsdk:"group_sync"`
44- RoleSync types.Object `tfsdk:"role_sync"`
44+ OrgSyncIdpGroups types.Set `tfsdk:"org_sync_idp_groups"`
45+ GroupSync types.Object `tfsdk:"group_sync"`
46+ RoleSync types.Object `tfsdk:"role_sync"`
4547}
4648
4749type GroupSyncModel struct {
@@ -134,6 +136,12 @@ This resource is only compatible with Coder version [2.16.0](https://github.com/
134136Computed :true ,
135137Default :stringdefault .StaticString ("" ),
136138},
139+
140+ "org_sync_idp_groups" : schema.SetAttribute {
141+ ElementType :types .StringType ,
142+ Optional :true ,
143+ MarkdownDescription :"Claims from the IdP provider that will give users access to this organization." ,
144+ },
137145},
138146
139147Blocks :map [string ]schema.Block {
@@ -361,21 +369,38 @@ func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRe
361369// default it.
362370data .DisplayName = types .StringValue (org .DisplayName )
363371
364- // Now apply group and role sync settings, if specified
365372orgID := data .ID .ValueUUID ()
366- tflog .Trace (ctx ,"updating group sync" ,map [string ]any {
367- "orgID" :orgID ,
368- })
373+
374+ // Apply org sync patches, if specified
375+ if ! data .OrgSyncIdpGroups .IsNull () {
376+ tflog .Trace (ctx ,"updating org sync" ,map [string ]any {
377+ "orgID" :orgID ,
378+ })
379+
380+ var claims []string
381+ resp .Diagnostics .Append (data .OrgSyncIdpGroups .ElementsAs (ctx ,& claims ,false )... )
382+ if resp .Diagnostics .HasError () {
383+ return
384+ }
385+
386+ resp .Diagnostics .Append (r .patchOrgSyncMapping (ctx ,orgID , []string {},claims )... )
387+ }
388+
389+ // Apply group and role sync settings, if specified
369390if ! data .GroupSync .IsNull () {
391+ tflog .Trace (ctx ,"updating group sync" ,map [string ]any {
392+ "orgID" :orgID ,
393+ })
394+
370395resp .Diagnostics .Append (r .patchGroupSync (ctx ,orgID ,data .GroupSync )... )
371396if resp .Diagnostics .HasError () {
372397return
373398}
374399}
375- tflog .Trace (ctx ,"updating role sync" ,map [string ]any {
376- "orgID" :orgID ,
377- })
378400if ! data .RoleSync .IsNull () {
401+ tflog .Trace (ctx ,"updating role sync" ,map [string ]any {
402+ "orgID" :orgID ,
403+ })
379404resp .Diagnostics .Append (r .patchRoleSync (ctx ,orgID ,data .RoleSync )... )
380405if resp .Diagnostics .HasError () {
381406return
@@ -423,19 +448,42 @@ func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRe
423448"icon" :org .Icon ,
424449})
425450
426- tflog .Trace (ctx ,"updating group sync" ,map [string ]any {
427- "orgID" :orgID ,
428- })
451+ // Apply org sync patches, if specified
452+ if ! data .OrgSyncIdpGroups .IsNull () {
453+ tflog .Trace (ctx ,"updating org sync mappings" ,map [string ]any {
454+ "orgID" :orgID ,
455+ })
456+
457+ var state OrganizationResourceModel
458+ resp .Diagnostics .Append (req .State .Get (ctx ,& state )... )
459+ var currentClaims []string
460+ resp .Diagnostics .Append (state .OrgSyncIdpGroups .ElementsAs (ctx ,& currentClaims ,false )... )
461+
462+ var plannedClaims []string
463+ resp .Diagnostics .Append (data .OrgSyncIdpGroups .ElementsAs (ctx ,& plannedClaims ,false )... )
464+ if resp .Diagnostics .HasError () {
465+ return
466+ }
467+
468+ resp .Diagnostics .Append (r .patchOrgSyncMapping (ctx ,orgID ,currentClaims ,plannedClaims )... )
469+ if resp .Diagnostics .HasError () {
470+ return
471+ }
472+ }
473+
429474if ! data .GroupSync .IsNull () {
475+ tflog .Trace (ctx ,"updating group sync" ,map [string ]any {
476+ "orgID" :orgID ,
477+ })
430478resp .Diagnostics .Append (r .patchGroupSync (ctx ,orgID ,data .GroupSync )... )
431479if resp .Diagnostics .HasError () {
432480return
433481}
434482}
435- tflog .Trace (ctx ,"updating role sync" ,map [string ]any {
436- "orgID" :orgID ,
437- })
438483if ! data .RoleSync .IsNull () {
484+ tflog .Trace (ctx ,"updating role sync" ,map [string ]any {
485+ "orgID" :orgID ,
486+ })
439487resp .Diagnostics .Append (r .patchRoleSync (ctx ,orgID ,data .RoleSync )... )
440488if resp .Diagnostics .HasError () {
441489return
@@ -456,6 +504,21 @@ func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRe
456504
457505orgID := data .ID .ValueUUID ()
458506
507+ // Remove org sync mappings, if we were managing them
508+ if ! data .OrgSyncIdpGroups .IsNull () {
509+ tflog .Trace (ctx ,"deleting org sync mappings" ,map [string ]any {
510+ "orgID" :orgID ,
511+ })
512+
513+ var claims []string
514+ resp .Diagnostics .Append (data .OrgSyncIdpGroups .ElementsAs (ctx ,& claims ,false )... )
515+ if resp .Diagnostics .HasError () {
516+ return
517+ }
518+
519+ resp .Diagnostics .Append (r .patchOrgSyncMapping (ctx ,orgID ,claims , []string {})... )
520+ }
521+
459522tflog .Trace (ctx ,"deleting organization" ,map [string ]any {
460523"id" :orgID ,
461524"name" :data .Name .ValueString (),
@@ -554,3 +617,37 @@ func (r *OrganizationResource) patchRoleSync(
554617
555618return diags
556619}
620+
621+ func (r * OrganizationResource )patchOrgSyncMapping (
622+ ctx context.Context ,
623+ orgID uuid.UUID ,
624+ currentClaims ,plannedClaims []string ,
625+ ) diag.Diagnostics {
626+ var diags diag.Diagnostics
627+
628+ add ,remove := slice .SymmetricDifference (currentClaims ,plannedClaims )
629+ var addMappings []codersdk.IDPSyncMapping [uuid.UUID ]
630+ for _ ,claim := range add {
631+ addMappings = append (addMappings , codersdk.IDPSyncMapping [uuid.UUID ]{
632+ Given :claim ,
633+ Gets :orgID ,
634+ })
635+ }
636+ var removeMappings []codersdk.IDPSyncMapping [uuid.UUID ]
637+ for _ ,claim := range remove {
638+ removeMappings = append (removeMappings , codersdk.IDPSyncMapping [uuid.UUID ]{
639+ Given :claim ,
640+ Gets :orgID ,
641+ })
642+ }
643+
644+ _ ,err := r .Client .PatchOrganizationIDPSyncMapping (ctx , codersdk.PatchOrganizationIDPSyncMappingRequest {
645+ Add :addMappings ,
646+ Remove :removeMappings ,
647+ })
648+ if err != nil {
649+ diags .AddError ("Org Sync Update error" ,err .Error ())
650+ }
651+
652+ return diags
653+ }