Skip to content

Commit a2f1177

Browse files
committed
feat: add sync_mapping attribute to coderd_organization resource
1 parent 5fa9117 commit a2f1177

File tree

2 files changed

+116
-17
lines changed

2 files changed

+116
-17
lines changed

internal/provider/organization_resource.go

Lines changed: 112 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
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 {
4041
Description types.String `tfsdk:"description"`
4142
Icon types.String `tfsdk:"icon"`
4243

43-
GroupSync types.Object `tfsdk:"group_sync"`
44-
RoleSync types.Object `tfsdk:"role_sync"`
44+
SyncMapping types.Set `tfsdk:"sync_mapping"`
45+
GroupSync types.Object `tfsdk:"group_sync"`
46+
RoleSync types.Object `tfsdk:"role_sync"`
4547
}
4648

4749
type GroupSyncModel struct {
@@ -134,6 +136,12 @@ This resource is only compatible with Coder version [2.16.0](https://github.com/
134136
Computed: true,
135137
Default: stringdefault.StaticString(""),
136138
},
139+
140+
"sync_mapping": 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

139147
Blocks: map[string]schema.Block{
@@ -361,21 +369,38 @@ func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRe
361369
// default it.
362370
data.DisplayName = types.StringValue(org.DisplayName)
363371

364-
// Now apply group and role sync settings, if specified
365372
orgID := 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.SyncMapping.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.SyncMapping.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
369390
if !data.GroupSync.IsNull() {
391+
tflog.Trace(ctx, "updating group sync", map[string]any{
392+
"orgID": orgID,
393+
})
394+
370395
resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...)
371396
if resp.Diagnostics.HasError() {
372397
return
373398
}
374399
}
375-
tflog.Trace(ctx, "updating role sync", map[string]any{
376-
"orgID": orgID,
377-
})
378400
if !data.RoleSync.IsNull() {
401+
tflog.Trace(ctx, "updating role sync", map[string]any{
402+
"orgID": orgID,
403+
})
379404
resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...)
380405
if resp.Diagnostics.HasError() {
381406
return
@@ -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.SyncMapping.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(data.SyncMapping.ElementsAs(ctx, &currentClaims, false)...)
461+
462+
var plannedClaims []string
463+
resp.Diagnostics.Append(data.SyncMapping.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+
429474
if !data.GroupSync.IsNull() {
475+
tflog.Trace(ctx, "updating group sync", map[string]any{
476+
"orgID": orgID,
477+
})
430478
resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...)
431479
if resp.Diagnostics.HasError() {
432480
return
433481
}
434482
}
435-
tflog.Trace(ctx, "updating role sync", map[string]any{
436-
"orgID": orgID,
437-
})
438483
if !data.RoleSync.IsNull() {
484+
tflog.Trace(ctx, "updating role sync", map[string]any{
485+
"orgID": orgID,
486+
})
439487
resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...)
440488
if resp.Diagnostics.HasError() {
441489
return
@@ -456,6 +504,21 @@ func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRe
456504

457505
orgID := data.ID.ValueUUID()
458506

507+
// Remove org sync mappings, if we were managing them
508+
if !data.SyncMapping.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.SyncMapping.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+
459522
tflog.Trace(ctx, "deleting organization", map[string]any{
460523
"id": orgID,
461524
"name": data.Name.ValueString(),
@@ -554,3 +617,37 @@ func (r *OrganizationResource) patchRoleSync(
554617

555618
return 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+
addMappings = 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+
}

internal/provider/util.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ func computeDirectoryHash(directory string) (string, error) {
8383
return hex.EncodeToString(hash.Sum(nil)), nil
8484
}
8585

86-
// memberDiff returns the members to add and remove from the group, given the current members and the planned members.
87-
// plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set.
86+
// memberDiff returns the members to add and remove from the group, given the
87+
// current members and the planned members. plannedMembers is deliberately our
88+
// custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a
89+
// set.
8890
func memberDiff(currentMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) {
8991
curSet := make(map[uuid.UUID]struct{}, len(currentMembers))
9092
planSet := make(map[uuid.UUID]struct{}, len(plannedMembers))

0 commit comments

Comments
 (0)