Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: addorg_sync_idp_groups attribute tocoderd_organization resource#182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
aslilac merged 8 commits intomainfromlilac/org-sync-mapping
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes fromall 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
29 changes: 29 additions & 0 deletionsdocs/resources/organization.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,7 +15,35 @@ An organization on the Coder deployment.
~> **Warning**
This resource is only compatible with Coder version [2.16.0](https://github.com/coder/coder/releases/tag/v2.16.0) and later.

## Example Usage

```terraform
resource "coderd_organization" "blueberry" {
name = "blueberry"
display_name = "Blueberry"
description = "The organization for blueberries"
icon = "/emojis/1fad0.png"

org_sync_idp_groups = [
"wibble",
"wobble",
]

group_sync {
field = "coder_groups"
mapping = {
toast = [coderd_group.bread.id]
}
}

role_sync {
field = "coder_roles"
mapping = {
manager = ["organization-user-admin"]
}
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema
Expand All@@ -30,6 +58,7 @@ This resource is only compatible with Coder version [2.16.0](https://github.com/
- `display_name` (String) Display name of the organization. Defaults to name.
- `group_sync` (Block, Optional) Group sync settings to sync groups from an IdP. (see [below for nested schema](#nestedblock--group_sync))
- `icon` (String)
- `org_sync_idp_groups` (Set of String) Claims from the IdP provider that will give users access to this organization.
- `role_sync` (Block, Optional) Role sync settings to sync organization roles from an IdP. (see [below for nested schema](#nestedblock--role_sync))

### Read-Only
Expand Down
25 changes: 25 additions & 0 deletionsexamples/resources/coderd_organization/resource.tf
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
resource "coderd_organization" "blueberry" {
name = "blueberry"
display_name = "Blueberry"
description = "The organization for blueberries"
icon = "/emojis/1fad0.png"

org_sync_idp_groups = [
"wibble",
"wobble",
]

group_sync {
field = "coder_groups"
mapping = {
toast = [coderd_group.bread.id]
}
}

role_sync {
field = "coder_roles"
mapping = {
manager = ["organization-user-admin"]
}
}
}
127 changes: 112 additions & 15 deletionsinternal/provider/organization_resource.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import (
"fmt"
"regexp"

"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/terraform-provider-coderd/internal/codersdkvalidator"
"github.com/google/uuid"
Expand DownExpand Up@@ -40,8 +41,9 @@ type OrganizationResourceModel struct {
Description types.String `tfsdk:"description"`
Icon types.String `tfsdk:"icon"`

GroupSync types.Object `tfsdk:"group_sync"`
RoleSync types.Object `tfsdk:"role_sync"`
OrgSyncIdpGroups types.Set `tfsdk:"org_sync_idp_groups"`
GroupSync types.Object `tfsdk:"group_sync"`
RoleSync types.Object `tfsdk:"role_sync"`
}

type GroupSyncModel struct {
Expand DownExpand Up@@ -134,6 +136,12 @@ This resource is only compatible with Coder version [2.16.0](https://github.com/
Computed: true,
Default: stringdefault.StaticString(""),
},

"org_sync_idp_groups": schema.SetAttribute{
ElementType: types.StringType,
Optional: true,
MarkdownDescription: "Claims from the IdP provider that will give users access to this organization.",
},
},

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

// Now apply group and role sync settings, if specified
orgID := data.ID.ValueUUID()
tflog.Trace(ctx, "updating group sync", map[string]any{
"orgID": orgID,
})

// Apply org sync patches, if specified
if !data.OrgSyncIdpGroups.IsNull() {
tflog.Trace(ctx, "updating org sync", map[string]any{
"orgID": orgID,
})

var claims []string
resp.Diagnostics.Append(data.OrgSyncIdpGroups.ElementsAs(ctx, &claims, false)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(r.patchOrgSyncMapping(ctx, orgID, []string{}, claims)...)
}

// Apply group and role sync settings, if specified
if !data.GroupSync.IsNull() {
tflog.Trace(ctx, "updating group sync", map[string]any{
"orgID": orgID,
})

resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...)
if resp.Diagnostics.HasError() {
return
}
}
tflog.Trace(ctx, "updating role sync", map[string]any{
"orgID": orgID,
})
if !data.RoleSync.IsNull() {
tflog.Trace(ctx, "updating role sync", map[string]any{
"orgID": orgID,
})
resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...)
if resp.Diagnostics.HasError() {
return
Expand DownExpand Up@@ -423,19 +448,42 @@ func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRe
"icon": org.Icon,
})

tflog.Trace(ctx, "updating group sync", map[string]any{
"orgID": orgID,
})
// Apply org sync patches, if specified
if !data.OrgSyncIdpGroups.IsNull() {
tflog.Trace(ctx, "updating org sync mappings", map[string]any{
"orgID": orgID,
})

var state OrganizationResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
var currentClaims []string
resp.Diagnostics.Append(state.OrgSyncIdpGroups.ElementsAs(ctx, &currentClaims, false)...)

var plannedClaims []string
resp.Diagnostics.Append(data.OrgSyncIdpGroups.ElementsAs(ctx, &plannedClaims, false)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(r.patchOrgSyncMapping(ctx, orgID, currentClaims, plannedClaims)...)
if resp.Diagnostics.HasError() {
return
}
}

if !data.GroupSync.IsNull() {
tflog.Trace(ctx, "updating group sync", map[string]any{
"orgID": orgID,
})
resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...)
if resp.Diagnostics.HasError() {
return
}
}
tflog.Trace(ctx, "updating role sync", map[string]any{
"orgID": orgID,
})
if !data.RoleSync.IsNull() {
tflog.Trace(ctx, "updating role sync", map[string]any{
"orgID": orgID,
})
resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...)
if resp.Diagnostics.HasError() {
return
Expand All@@ -456,6 +504,21 @@ func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRe

orgID := data.ID.ValueUUID()

// Remove org sync mappings, if we were managing them
if !data.OrgSyncIdpGroups.IsNull() {
tflog.Trace(ctx, "deleting org sync mappings", map[string]any{
"orgID": orgID,
})

var claims []string
resp.Diagnostics.Append(data.OrgSyncIdpGroups.ElementsAs(ctx, &claims, false)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(r.patchOrgSyncMapping(ctx, orgID, claims, []string{})...)
}

tflog.Trace(ctx, "deleting organization", map[string]any{
"id": orgID,
"name": data.Name.ValueString(),
Expand DownExpand Up@@ -554,3 +617,37 @@ func (r *OrganizationResource) patchRoleSync(

return diags
}

func (r *OrganizationResource) patchOrgSyncMapping(
ctx context.Context,
orgID uuid.UUID,
currentClaims, plannedClaims []string,
) diag.Diagnostics {
var diags diag.Diagnostics

add, remove := slice.SymmetricDifference(currentClaims, plannedClaims)
var addMappings []codersdk.IDPSyncMapping[uuid.UUID]
for _, claim := range add {
addMappings = append(addMappings, codersdk.IDPSyncMapping[uuid.UUID]{
Given: claim,
Gets: orgID,
})
}
var removeMappings []codersdk.IDPSyncMapping[uuid.UUID]
for _, claim := range remove {
removeMappings = append(removeMappings, codersdk.IDPSyncMapping[uuid.UUID]{
Given: claim,
Gets: orgID,
})
}

_, err := r.Client.PatchOrganizationIDPSyncMapping(ctx, codersdk.PatchOrganizationIDPSyncMappingRequest{
Add: addMappings,
Remove: removeMappings,
})
if err != nil {
diags.AddError("Org Sync Update error", err.Error())
}

return diags
}
41 changes: 36 additions & 5 deletionsinternal/provider/organization_resource_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,13 +42,19 @@ func TestAccOrganizationResource(t *testing.T) {
cfg2.DisplayName = ptr.Ref("Example Organization New")

cfg3 := cfg2
cfg3.GroupSync = ptr.Ref(codersdk.GroupSyncSettings{
cfg3.OrgSyncIdpGroups = []string{"wibble", "wobble"}

cfg4 := cfg3
cfg4.OrgSyncIdpGroups = []string{"wibbley", "wobbley"}

cfg5 := cfg4
cfg5.GroupSync = ptr.Ref(codersdk.GroupSyncSettings{
Field: "wibble",
Mapping: map[string][]uuid.UUID{
"wibble": {uuid.MustParse("6e57187f-6543-46ab-a62c-a10065dd4314")},
},
})
cfg3.RoleSync = ptr.Ref(codersdk.RoleSyncSettings{
cfg5.RoleSync = ptr.Ref(codersdk.RoleSyncSettings{
Field: "wobble",
Mapping: map[string][]string{
"wobble": {"wobbly"},
Expand DownExpand Up@@ -86,9 +92,25 @@ func TestAccOrganizationResource(t *testing.T) {
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("display_name"), knownvalue.StringExact("Example Organization New")),
},
},
// Addgroup and role sync
// Addorg sync
{
Config: cfg3.String(t),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(0), knownvalue.StringExact("wibble")),
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(1), knownvalue.StringExact("wobble")),
},
},
// Patch org sync
{
Config: cfg4.String(t),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(0), knownvalue.StringExact("wibbley")),
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("org_sync_idp_groups").AtSliceIndex(1), knownvalue.StringExact("wobbley")),
},
},
// Add group and role sync
{
Config: cfg5.String(t),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("group_sync").AtMapKey("field"), knownvalue.StringExact("wibble")),
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("group_sync").AtMapKey("mapping").AtMapKey("wibble").AtSliceIndex(0), knownvalue.StringExact("6e57187f-6543-46ab-a62c-a10065dd4314")),
Expand All@@ -110,8 +132,9 @@ type testAccOrganizationResourceConfig struct {
Description *string
Icon *string

GroupSync *codersdk.GroupSyncSettings
RoleSync *codersdk.RoleSyncSettings
OrgSyncIdpGroups []string
GroupSync *codersdk.GroupSyncSettings
RoleSync *codersdk.RoleSyncSettings
}

func (c testAccOrganizationResourceConfig) String(t *testing.T) string {
Expand All@@ -128,6 +151,14 @@ resource "coderd_organization" "test" {
description = {{orNull .Description}}
icon = {{orNull .Icon}}

{{- if .OrgSyncIdpGroups}}
org_sync_idp_groups = [
{{- range $name := .OrgSyncIdpGroups }}
"{{$name}}",
{{- end}}
]
{{- end}}

{{- if .GroupSync}}
group_sync {
field = "{{.GroupSync.Field}}"
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -244,7 +244,7 @@ func (r *OrganizationSyncSettingsResource) Delete(ctx context.Context, req resou
tflog.Trace(ctx,"deleting organization sync",map[string]any{})
_,err:=r.Client.PatchOrganizationIDPSyncConfig(ctx, codersdk.PatchOrganizationIDPSyncConfigRequest{
// This disables organization sync without causing state conflicts for
// organization resources that might still specify `sync_mapping`.
// organization resources that might still specify `org_sync_idp_groups`.
Field:"",
})
iferr!=nil {
Expand Down
6 changes: 4 additions & 2 deletionsinternal/provider/util.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,8 +83,10 @@ func computeDirectoryHash(directory string) (string, error) {
returnhex.EncodeToString(hash.Sum(nil)),nil
}

// memberDiff returns the members to add and remove from the group, given the current members and the planned members.
// plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set.
// memberDiff returns the members to add and remove from the group, given the
// current members and the planned members. plannedMembers is deliberately our
// custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a
// set.
funcmemberDiff(currentMembers []uuid.UUID,plannedMembers []UUID) (add,remove []string) {
curSet:=make(map[uuid.UUID]struct{},len(currentMembers))
planSet:=make(map[uuid.UUID]struct{},len(plannedMembers))
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp