Skip to content

Commit e123f70

Browse files
committed
fix: support unmanaged roles on user resource
1 parent e5680f3 commit e123f70

File tree

2 files changed

+82
-35
lines changed

2 files changed

+82
-35
lines changed

internal/provider/user_resource.go

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1515
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
1616
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
17-
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault"
1817
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
1918
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2019
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@@ -88,7 +87,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
8887
Required: true,
8988
},
9089
"roles": schema.SetAttribute{
91-
MarkdownDescription: "Roles assigned to the user. Valid roles are `owner`, `template-admin`, `user-admin`, and `auditor`.",
90+
MarkdownDescription: "Roles assigned to the user. Valid roles are `owner`, `template-admin`, `user-admin`, and `auditor`. If `null`, roles will not be managed by Terraform. This attribute must be null if the user was created via OIDC and uses role sync.",
9291
Computed: true,
9392
Optional: true,
9493
ElementType: types.StringType,
@@ -97,7 +96,6 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
9796
stringvalidator.OneOf("owner", "template-admin", "user-admin", "auditor"),
9897
),
9998
},
100-
Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})),
10199
},
102100
"login_type": schema.StringAttribute{
103101
MarkdownDescription: "Type of login for the user. Valid types are `none`, `password`, `github`, and `oidc`.",
@@ -209,21 +207,26 @@ func (r *UserResource) Create(ctx context.Context, req resource.CreateRequest, r
209207
tflog.Info(ctx, "successfully updated user profile")
210208
data.Name = types.StringValue(user.Name)
211209

212-
var roles []string
213-
resp.Diagnostics.Append(
214-
data.Roles.ElementsAs(ctx, &roles, false)...,
215-
)
216-
tflog.Info(ctx, "updating user roles", map[string]any{
217-
"new_roles": roles,
218-
})
219-
user, err = client.UpdateUserRoles(ctx, user.ID.String(), codersdk.UpdateRoles{
220-
Roles: roles,
221-
})
222-
if err != nil {
223-
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update newly created user roles, got error: %s", err))
224-
return
210+
if !data.Roles.IsNull() {
211+
var roles []string
212+
resp.Diagnostics.Append(
213+
data.Roles.ElementsAs(ctx, &roles, false)...,
214+
)
215+
if resp.Diagnostics.HasError() {
216+
return
217+
}
218+
tflog.Info(ctx, "updating user roles", map[string]any{
219+
"new_roles": roles,
220+
})
221+
user, err = client.UpdateUserRoles(ctx, user.ID.String(), codersdk.UpdateRoles{
222+
Roles: roles,
223+
})
224+
if err != nil {
225+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update newly created user roles, got error: %s", err))
226+
return
227+
}
228+
tflog.Info(ctx, "successfully updated user roles")
225229
}
226-
tflog.Info(ctx, "successfully updated user roles")
227230

228231
if data.Suspended.ValueBool() {
229232
_, err = client.UpdateUserStatus(ctx, data.ID.ValueString(), codersdk.UserStatus("suspended"))
@@ -267,11 +270,13 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp
267270
data.Email = types.StringValue(user.Email)
268271
data.Name = types.StringValue(user.Name)
269272
data.Username = types.StringValue(user.Username)
270-
roles := make([]attr.Value, 0, len(user.Roles))
271-
for _, role := range user.Roles {
272-
roles = append(roles, types.StringValue(role.Name))
273+
if !data.Roles.IsNull() {
274+
roles := make([]attr.Value, 0, len(user.Roles))
275+
for _, role := range user.Roles {
276+
roles = append(roles, types.StringValue(role.Name))
277+
}
278+
data.Roles = types.SetValueMust(types.StringType, roles)
273279
}
274-
data.Roles = types.SetValueMust(types.StringType, roles)
275280
data.LoginType = types.StringValue(string(user.LoginType))
276281
data.Suspended = types.BoolValue(user.Status == codersdk.UserStatusSuspended)
277282

@@ -344,21 +349,26 @@ func (r *UserResource) Update(ctx context.Context, req resource.UpdateRequest, r
344349
data.Name = name
345350
tflog.Info(ctx, "successfully updated user profile")
346351

347-
var roles []string
348-
resp.Diagnostics.Append(
349-
data.Roles.ElementsAs(ctx, &roles, false)...,
350-
)
351-
tflog.Info(ctx, "updating user roles", map[string]any{
352-
"new_roles": roles,
353-
})
354-
_, err = client.UpdateUserRoles(ctx, user.ID.String(), codersdk.UpdateRoles{
355-
Roles: roles,
356-
})
357-
if err != nil {
358-
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update user roles, got error: %s", err))
359-
return
352+
if !data.Roles.IsNull() {
353+
var roles []string
354+
resp.Diagnostics.Append(
355+
data.Roles.ElementsAs(ctx, &roles, false)...,
356+
)
357+
if resp.Diagnostics.HasError() {
358+
return
359+
}
360+
tflog.Info(ctx, "updating user roles", map[string]any{
361+
"new_roles": roles,
362+
})
363+
_, err = client.UpdateUserRoles(ctx, user.ID.String(), codersdk.UpdateRoles{
364+
Roles: roles,
365+
})
366+
if err != nil {
367+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update user roles, got error: %s", err))
368+
return
369+
}
370+
tflog.Info(ctx, "successfully updated user roles")
360371
}
361-
tflog.Info(ctx, "successfully updated user roles")
362372

363373
if data.LoginType.ValueString() == string(codersdk.LoginTypePassword) && !data.Password.IsNull() {
364374
tflog.Info(ctx, "updating password")

internal/provider/user_resource_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ func TestAccUserResource(t *testing.T) {
4242
cfg4.LoginType = ptr.Ref("github")
4343
cfg4.Password = nil
4444

45+
cfg5 := cfg4
46+
cfg5.Roles = nil
47+
4548
resource.Test(t, resource.TestCase{
4649
IsUnitTest: true,
4750
PreCheck: func() { testAccPreCheck(t) },
@@ -114,8 +117,42 @@ func TestAccUserResource(t *testing.T) {
114117
// The Plan should be to create the entire resource
115118
ExpectNonEmptyPlan: true,
116119
},
120+
// Unmanaged roles
121+
{
122+
Config: cfg5.String(t),
123+
Check: resource.ComposeAggregateTestCheckFunc(
124+
resource.TestCheckNoResourceAttr("coderd_user.test", "roles"),
125+
),
126+
},
117127
},
118128
})
129+
130+
t.Run("CreateUnmanagedRolesOk", func(t *testing.T) {
131+
cfg := testAccUserResourceConfig{
132+
URL: client.URL.String(),
133+
Token: client.SessionToken(),
134+
Username: ptr.Ref("unmanaged"),
135+
Name: ptr.Ref("Unmanaged User"),
136+
Email: ptr.Ref("unmanaged@coder.com"),
137+
Roles: nil, // Start with unmanaged roles
138+
LoginType: ptr.Ref("password"),
139+
Password: ptr.Ref("SomeSecurePassword!"),
140+
}
141+
142+
resource.Test(t, resource.TestCase{
143+
IsUnitTest: true,
144+
PreCheck: func() { testAccPreCheck(t) },
145+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
146+
Steps: []resource.TestStep{
147+
{
148+
Config: cfg.String(t),
149+
Check: resource.ComposeAggregateTestCheckFunc(
150+
resource.TestCheckNoResourceAttr("coderd_user.test", "roles"),
151+
),
152+
},
153+
},
154+
})
155+
})
119156
}
120157

121158
type testAccUserResourceConfig struct {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy