diff --git a/internal/provider/user_resource.go b/internal/provider/user_resource.go index 9bc8018..907e203 100644 --- a/internal/provider/user_resource.go +++ b/internal/provider/user_resource.go @@ -41,13 +41,14 @@ type UserResource struct { type UserResourceModel struct { ID types.String `tfsdk:"id"` - Username types.String `tfsdk:"username"` - Name types.String `tfsdk:"name"` - Email types.String `tfsdk:"email"` - Roles types.Set `tfsdk:"roles"` // owner, template-admin, user-admin, auditor (member is implicit) - LoginType types.String `tfsdk:"login_type"` // none, password, github, oidc - Password types.String `tfsdk:"password"` // only when login_type is password - Suspended types.Bool `tfsdk:"suspended"` + Username types.String `tfsdk:"username"` + Name types.String `tfsdk:"name"` + Email types.String `tfsdk:"email"` + Roles types.Set `tfsdk:"roles"` // owner, template-admin, user-admin, auditor (member is implicit) + LoginType types.String `tfsdk:"login_type"` // none, password, github, oidc + Password types.String `tfsdk:"password"` // only when login_type is password + Suspended types.Bool `tfsdk:"suspended"` + CascadeDelete types.Bool `tfsdk:"cascade_delete"` } func (r *UserResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -79,8 +80,11 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r // Defaulted in Create }, "email": schema.StringAttribute{ - MarkdownDescription: "Email address of the user.", + MarkdownDescription: "Email address of the user. Modifying this field will trigger a resource replacement.", Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "roles": schema.SetAttribute{ MarkdownDescription: "Roles assigned to the user. Valid roles are 'owner', 'template-admin', 'user-admin', and 'auditor'.", @@ -118,6 +122,13 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r Optional: true, Default: booldefault.StaticBool(false), }, + "cascade_delete": schema.BoolAttribute{ + Computed: true, + MarkdownDescription: "Whether to delete owned workspaces when this resource is deleted or replaced.", + Required: false, + Optional: true, + Default: booldefault.StaticBool(false), + }, }, } } @@ -363,6 +374,29 @@ func (r *UserResource) Delete(ctx context.Context, req resource.DeleteRequest, r resp.Diagnostics.AddError("Data Error", fmt.Sprintf("Unable to parse user ID, got error: %s", err)) return } + + if data.CascadeDelete.ValueBool() { + tflog.Trace(ctx, "deleting user workspaces") + workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + Owner: data.Username.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get user workspaces, got error: %s", err)) + return + } + for _, workspace := range workspaces.Workspaces { + _, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{ + Transition: codersdk.WorkspaceTransitionDelete, + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete user workspace, got error: %s", err)) + return + } + } + //TODO: Wait for builds to finish + tflog.Trace(ctx, "successfully deleted user workspaces") + } + tflog.Trace(ctx, "deleting user") err = client.DeleteUser(ctx, id) if err != nil { diff --git a/internal/provider/user_resource_test.go b/internal/provider/user_resource_test.go index f955310..40a91ae 100644 --- a/internal/provider/user_resource_test.go +++ b/internal/provider/user_resource_test.go @@ -10,6 +10,7 @@ import ( "github.com/coder/terraform-provider-coderd/integration" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stretchr/testify/require" ) @@ -35,6 +36,10 @@ func TestAccUserResource(t *testing.T) { cfg2.Username = PtrTo("exampleNew") cfg2.Name = PtrTo("Example User New") + cfg3 := cfg1 + cfg3.Email = PtrTo("example2@coder.com") + + var userId string resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -68,6 +73,15 @@ func TestAccUserResource(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("coderd_user.test", "username", "exampleNew"), resource.TestCheckResourceAttr("coderd_user.test", "name", "Example User New"), + testAccIdChanged("coderd_user.test", &userId), + ), + }, + // Replace testing + { + Config: cfg3.String(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("coderd_user.test", "email", "example2@coder.com"), + testAccIdChanged("coderd_user.test", &userId), ), }, // Delete testing automatically occurs in TestCase @@ -151,3 +165,24 @@ resource "coderd_user" "test" { return buf.String() } + +// Check if the id has changed since the last time this check function was run. +func testAccIdChanged(resourceName string, id *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Resource %s not found", resourceName) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + if *id == "" { + *id = rs.Primary.ID + return nil + } + if rs.Primary.ID == *id { + return fmt.Errorf("ID did not change from %s", rs.Primary.ID) + } + return nil + } +} 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