From b52f5ea4706805a27c085dae3d1f9f5242c191af Mon Sep 17 00:00:00 2001 From: Lance52259 Date: Sat, 29 Nov 2025 11:20:46 +0800 Subject: [PATCH] feat(workspace): add new one-time action resource to operate user --- docs/resources/workspace_user_action.md | 47 +++++++ huaweicloud/provider.go | 1 + ..._huaweicloud_workspace_user_action_test.go | 120 ++++++++++++++++ ...ource_huaweicloud_workspace_user_action.go | 129 ++++++++++++++++++ 4 files changed, 297 insertions(+) create mode 100644 docs/resources/workspace_user_action.md create mode 100644 huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_user_action_test.go create mode 100644 huaweicloud/services/workspace/resource_huaweicloud_workspace_user_action.go diff --git a/docs/resources/workspace_user_action.md b/docs/resources/workspace_user_action.md new file mode 100644 index 00000000000..f1cbfa22d78 --- /dev/null +++ b/docs/resources/workspace_user_action.md @@ -0,0 +1,47 @@ +--- +subcategory: "Workspace" +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_workspace_user_action" +description: |- + Use this resource to operate Workspace users within HuaweiCloud. +--- + +# huaweicloud_workspace_user_action + +Use this resource to operate Workspace users within HuaweiCloud. + +-> This resource is a one-time action resource for operating user. Deleting this resource will not clear + the corresponding request record, but will only remove the resource information from the tfstate file. + +## Example Usage + +```hcl +variable "user_id" {} +variable "operation_type" {} + +resource "huaweicloud_workspace_user_action" "test" { + user_id = var.user_id + op_type = var.operation_type +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region where the user is located. + If omitted, the provider-level region will be used. Changing this parameter will create a new resource. + +* `user_id` - (Required, String, NonUpdatable) Specifies the ID of the user to be operated. + +* `op_type` - (Required, String, NonUpdatable) Specifies the operation type. + The valid values are as follows: + + **LOCK** - Lock the user. + + **UNLOCK** - Unlock the user. + + **RESET_PWD** - Reset the user password. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The resource ID. diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index 07c059067a7..589cbde7b9c 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -3756,6 +3756,7 @@ func Provider() *schema.Provider { "huaweicloud_workspace_service": workspace.ResourceService(), "huaweicloud_workspace_terminal_binding": workspace.ResourceTerminalBinding(), "huaweicloud_workspace_user": workspace.ResourceUser(), + "huaweicloud_workspace_user_action": workspace.ResourceUserAction(), "huaweicloud_workspace_user_group": workspace.ResourceUserGroup(), // Workspace APP diff --git a/huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_user_action_test.go b/huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_user_action_test.go new file mode 100644 index 00000000000..81f6f9839f5 --- /dev/null +++ b/huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_user_action_test.go @@ -0,0 +1,120 @@ +package workspace + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func TestAccUserAction_basic(t *testing.T) { + var ( + name = acceptance.RandomAccResourceName() + password = acceptance.RandomPassword() + resourceName = "huaweicloud_workspace_user_action.test" + baseConfig = testAccUserAction_base(name, password) + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acceptance.TestAccPreCheck(t) + }, + ProviderFactories: acceptance.TestAccProviderFactories, + // This resource is a one-time action resource and there is no logic in the delete method. + // lintignore:AT001 + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccUserAction_basic_step1(baseConfig), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "op_type", "LOCK"), + resource.TestCheckResourceAttrSet(resourceName, "user_id"), + ), + }, + { + Config: testAccUserAction_basic_step2(baseConfig), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "op_type", "UNLOCK"), + resource.TestCheckResourceAttrSet(resourceName, "user_id"), + ), + }, + { + Config: testAccUserAction_basic_step3(baseConfig), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "op_type", "RESET_PWD"), + resource.TestCheckResourceAttrSet(resourceName, "user_id"), + ), + }, + }, + }) +} + +func testAccUserAction_base(name, password string) string { + return fmt.Sprintf(` +resource "huaweicloud_workspace_user" "test" { + name = "%[1]s" + description = "Created by terraform script" + active_type = "ADMIN_ACTIVATE" + email = "terraform@test.com" + password = "%[2]s" + + account_expires = "0" + password_never_expires = false + enable_change_password = true + next_login_change_password = true + disabled = false +}`, name, password) +} + +func testAccUserAction_basic_step1(baseConfig string) string { + return fmt.Sprintf(` +%[1]s + +resource "huaweicloud_workspace_user_action" "test" { + depends_on = [ + huaweicloud_workspace_user.test, + ] + + user_id = huaweicloud_workspace_user.test.id + op_type = "LOCK" + + enable_force_new = "true" +} +`, baseConfig) +} + +func testAccUserAction_basic_step2(baseConfig string) string { + return fmt.Sprintf(` +%[1]s + +resource "huaweicloud_workspace_user_action" "test" { + depends_on = [ + huaweicloud_workspace_user.test, + ] + + user_id = huaweicloud_workspace_user.test.id + op_type = "UNLOCK" + + enable_force_new = "true" +} +`, baseConfig) +} + +func testAccUserAction_basic_step3(baseConfig string) string { + return fmt.Sprintf(` +%[1]s + +resource "huaweicloud_workspace_user_action" "test" { + depends_on = [ + huaweicloud_workspace_user.test, + ] + + user_id = huaweicloud_workspace_user.test.id + op_type = "RESET_PWD" + + enable_force_new = "true" +} +`, baseConfig) +} diff --git a/huaweicloud/services/workspace/resource_huaweicloud_workspace_user_action.go b/huaweicloud/services/workspace/resource_huaweicloud_workspace_user_action.go new file mode 100644 index 00000000000..11809e03472 --- /dev/null +++ b/huaweicloud/services/workspace/resource_huaweicloud_workspace_user_action.go @@ -0,0 +1,129 @@ +package workspace + +import ( + "context" + "strings" + + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/chnsz/golangsdk" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" +) + +var userActionNonUpdatableParams = []string{ + "user_id", + "op_type", +} + +// @API Workspace POST /v2/{project_id}/users/{user_id}/actions +func ResourceUserAction() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUserActionCreate, + ReadContext: resourceUserActionRead, + UpdateContext: resourceUserActionUpdate, + DeleteContext: resourceUserActionDelete, + + CustomizeDiff: config.FlexibleForceNew(userActionNonUpdatableParams), + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: `The region where the user is located.`, + }, + + // Required parameters. + "user_id": { + Type: schema.TypeString, + Required: true, + Description: `The ID of the user to be operated.`, + }, + "op_type": { + Type: schema.TypeString, + Required: true, + Description: `The operation type.`, + }, + + // Internal parameters. + "enable_force_new": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"true", "false"}, false), + Description: utils.SchemaDesc("", utils.SchemaDescInput{Internal: true}), + }, + }, + } +} + +func buildUserActionBodyParams(d *schema.ResourceData) map[string]interface{} { + return map[string]interface{}{ + "op_type": d.Get("op_type"), + } +} + +func resourceUserActionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + cfg := meta.(*config.Config) + region := cfg.GetRegion(d) + + client, err := cfg.NewServiceClient("workspace", region) + if err != nil { + return diag.Errorf("error creating Workspace client: %s", err) + } + + var ( + httpUrl = "v2/{project_id}/users/{user_id}/actions" + userId = d.Get("user_id").(string) + ) + + createPath := client.Endpoint + httpUrl + createPath = strings.ReplaceAll(createPath, "{project_id}", client.ProjectID) + createPath = strings.ReplaceAll(createPath, "{user_id}", userId) + + createOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + MoreHeaders: map[string]string{ + "Content-Type": "application/json", + }, + JSONBody: buildUserActionBodyParams(d), + OkCodes: []int{204}, + } + + _, err = client.Request("POST", createPath, &createOpt) + if err != nil { + return diag.Errorf("error operating Workspace user: %s", err) + } + + randomUUID, err := uuid.GenerateUUID() + if err != nil { + return diag.Errorf("unable to generate ID: %s", err) + } + d.SetId(randomUUID) + + return resourceUserActionRead(ctx, d, meta) +} + +func resourceUserActionRead(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil +} + +func resourceUserActionUpdate(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil +} + +func resourceUserActionDelete(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + errorMsg := `This resource is a one-time action resource for operating user. Deleting this resource will not clear +the corresponding request record, but will only remove the resource information from the tfstate file.` + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: errorMsg, + }, + } +}