Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions docs/resources/organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ resource "coderd_organization" "blueberry" {

### Optional

- `default_org_member_roles` (List of String) Built-in organization role names that are unioned into every member's effective roles. Changes propagate to members on their next request. Setting any value other than the deployment defaults requires the `minimum-implicit-member` experiment to be enabled on the Coder Deployment.
- `description` (String)
- `display_name` (String) Display name of the organization. Defaults to name.
- `group_sync` (Block, Optional, Deprecated) Group sync settings to sync groups from an IdP.
Expand Down
82 changes: 78 additions & 4 deletions internal/provider/organization_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type OrganizationResourceModel struct {
Icon types.String `tfsdk:"icon"`
WorkspaceSharing types.String `tfsdk:"workspace_sharing"`

DefaultOrgMemberRoles types.List `tfsdk:"default_org_member_roles"`

OrgSyncIdpGroups types.Set `tfsdk:"org_sync_idp_groups"`
GroupSync types.Object `tfsdk:"group_sync"`
RoleSync types.Object `tfsdk:"role_sync"`
Expand Down Expand Up @@ -149,6 +151,15 @@ This resource is only compatible with Coder version [2.16.0](https://github.com/
},
},

"default_org_member_roles": schema.ListAttribute{
ElementType: types.StringType,
MarkdownDescription: "Built-in organization role names that are unioned into every member's effective roles. " +
"Changes propagate to members on their next request. Setting any value other than the deployment defaults " +
"requires the `minimum-implicit-member` experiment to be enabled on the Coder Deployment.",
Optional: true,
Computed: true,
},

"org_sync_idp_groups": schema.SetAttribute{
ElementType: types.StringType,
Optional: true,
Expand Down Expand Up @@ -352,6 +363,13 @@ func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadReques
data.Icon = types.StringValue(org.Icon)
data.WorkspaceSharing = workspaceSharing

defaultOrgMemberRoles, diags := defaultOrgMemberRolesValueFromAPI(ctx, org.DefaultOrgMemberRoles)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.DefaultOrgMemberRoles = defaultOrgMemberRoles

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Expand Down Expand Up @@ -456,6 +474,26 @@ func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRe
}
}

// Apply default_org_member_roles if the user specified them.
if !data.DefaultOrgMemberRoles.IsNull() && !data.DefaultOrgMemberRoles.IsUnknown() {
tflog.Trace(ctx, "updating default org member roles", map[string]any{
"orgID": orgID,
})

var roles []string
resp.Diagnostics.Append(data.DefaultOrgMemberRoles.ElementsAs(ctx, &roles, false)...)
if resp.Diagnostics.HasError() {
return
}
org, err = r.Client.UpdateOrganization(ctx, orgID.String(), codersdk.UpdateOrganizationRequest{
DefaultOrgMemberRoles: &roles,
})
if err != nil {
resp.Diagnostics.AddError("Default Org Member Roles Update error", err.Error())
return
}
}

// This is computed, we need to write a known value to the state
// in any case.
workspaceSharing, err := fetchWorkspaceSharingValue(ctx, r.Client, orgID.String())
Expand All @@ -466,6 +504,13 @@ func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRe
}
data.WorkspaceSharing = workspaceSharing

defaultOrgMemberRoles, diags := defaultOrgMemberRolesValueFromAPI(ctx, org.DefaultOrgMemberRoles)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.DefaultOrgMemberRoles = defaultOrgMemberRoles

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Expand All @@ -488,11 +533,23 @@ func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRe
"new_description": data.Description.ValueString(),
"new_icon": data.Icon.ValueString(),
})

var defaultRolesPtr *[]string
if !data.DefaultOrgMemberRoles.IsNull() && !data.DefaultOrgMemberRoles.IsUnknown() {
var roles []string
resp.Diagnostics.Append(data.DefaultOrgMemberRoles.ElementsAs(ctx, &roles, false)...)
if resp.Diagnostics.HasError() {
return
}
defaultRolesPtr = &roles
}

org, err := r.Client.UpdateOrganization(ctx, orgID.String(), codersdk.UpdateOrganizationRequest{
Name: data.Name.ValueString(),
DisplayName: data.DisplayName.ValueString(),
Description: data.Description.ValueStringPointer(),
Icon: data.Icon.ValueStringPointer(),
Name: data.Name.ValueString(),
DisplayName: data.DisplayName.ValueString(),
Description: data.Description.ValueStringPointer(),
Icon: data.Icon.ValueStringPointer(),
DefaultOrgMemberRoles: defaultRolesPtr,
})
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update organization %s, got error: %s", orgID, err))
Expand Down Expand Up @@ -579,6 +636,13 @@ func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRe
}
data.WorkspaceSharing = workspaceSharing

defaultOrgMemberRoles, diags := defaultOrgMemberRolesValueFromAPI(ctx, org.DefaultOrgMemberRoles)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.DefaultOrgMemberRoles = defaultOrgMemberRoles

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Expand Down Expand Up @@ -790,3 +854,13 @@ func isWorkspaceSharingExperimentOff(err error) bool {
}
return false
}

// defaultOrgMemberRolesValueFromAPI converts the API's []string into a
// types.List[string]. A nil slice from an older server is treated as an
// empty list so the attribute always has a known value.
func defaultOrgMemberRolesValueFromAPI(ctx context.Context, roles []string) (types.List, diag.Diagnostics) {
if roles == nil {
roles = []string{}
}
return types.ListValueFrom(ctx, types.StringType, roles)
}
25 changes: 24 additions & 1 deletion internal/provider/organization_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestAccOrganizationResource(t *testing.T) {
}

ctx := t.Context()
client := integration.StartCoder(ctx, t, "organization_acc", integration.UseLicense, integration.CoderExperiments("workspace-sharing"))
client := integration.StartCoder(ctx, t, "organization_acc", integration.UseLicense, integration.CoderExperiments("workspace-sharing,minimum-implicit-member"))
_, err := client.User(ctx, codersdk.Me)
require.NoError(t, err)
runOrganizationResourceTest(t, client, true)
Expand Down Expand Up @@ -166,6 +166,9 @@ func runOrganizationResourceTest(t *testing.T, client *codersdk.Client, enableEx
cfg7 := cfg6
cfg7.WorkspaceSharing = ptr.Ref("everyone")

cfg8 := cfg7
cfg8.DefaultOrgMemberRoles = ptr.Ref([]string{"organization-template-admin", "organization-workspace-access"})

steps = append(steps,
// Disable workspace sharing for org
resource.TestStep{
Expand All @@ -181,6 +184,16 @@ func runOrganizationResourceTest(t *testing.T, client *codersdk.Client, enableEx
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("workspace_sharing"), knownvalue.StringExact("everyone")),
},
},
// Set default_org_member_roles to a non-default value
resource.TestStep{
Config: cfg8.String(t),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("default_org_member_roles"), knownvalue.ListExact([]knownvalue.Check{
knownvalue.StringExact("organization-template-admin"),
knownvalue.StringExact("organization-workspace-access"),
})),
},
},
)
}
return steps
Expand Down Expand Up @@ -222,6 +235,8 @@ type testAccOrganizationResourceConfig struct {
Icon *string
WorkspaceSharing *string

DefaultOrgMemberRoles *[]string

OrgSyncIdpGroups []string
GroupSync *codersdk.GroupSyncSettings
RoleSync *codersdk.RoleSyncSettings
Expand All @@ -242,6 +257,14 @@ resource "coderd_organization" "test" {
icon = {{orNull .Icon}}
workspace_sharing = {{orNull .WorkspaceSharing}}

{{- if .DefaultOrgMemberRoles}}
default_org_member_roles = [
{{- range $role := .DefaultOrgMemberRoles }}
"{{$role}}",
{{- end}}
]
{{- end}}

{{- if .OrgSyncIdpGroups}}
org_sync_idp_groups = [
{{- range $name := .OrgSyncIdpGroups }}
Expand Down