From ee6c46efb1e0475e6e086eb4a4351ecf6f67fcd4 Mon Sep 17 00:00:00 2001 From: wuzhuanhong Date: Thu, 20 Nov 2025 17:53:56 +0800 Subject: [PATCH] feat(workspace/app): the APP server group supports to modify tags --- docs/resources/workspace_app_server_group.md | 5 +- ...eicloud_workspace_app_server_group_test.go | 140 ++++++++++++++++++ ..._huaweicloud_workspace_app_server_group.go | 71 ++++++++- 3 files changed, 210 insertions(+), 6 deletions(-) diff --git a/docs/resources/workspace_app_server_group.md b/docs/resources/workspace_app_server_group.md index 258d2245838..cc418afb888 100644 --- a/docs/resources/workspace_app_server_group.md +++ b/docs/resources/workspace_app_server_group.md @@ -106,9 +106,8 @@ The following arguments are supported: * `description` - (Optional, String) Specifies the description of the server group. -* `tags` - (Optional, Map, ForceNew) Specifies the key/value pairs to associate with the server group. - Supports up to 20 tags. - Changing this creates a new resource. +* `tags` - (Optional, Map) Specifies the key/value pairs to associate with the server group. + Supports up to `20` tags. * `enterprise_project_id` - (Optional, String, ForceNew) Specifies the ID of the enterprise project to which the server group belong. diff --git a/huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_app_server_group_test.go b/huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_app_server_group_test.go index a71d49eb115..3465af0e694 100644 --- a/huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_app_server_group_test.go +++ b/huaweicloud/services/acceptance/workspace/resource_huaweicloud_workspace_app_server_group_test.go @@ -230,3 +230,143 @@ resource "huaweicloud_workspace_app_server_group" "test" { acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_ID, acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_PRODUCT_ID) } + +func TestAccResourceAppServerGroup_singleSession(t *testing.T) { + var ( + name = acceptance.RandomAccResourceName() + updateName = acceptance.RandomAccResourceName() + + obj interface{} + resourceName = "huaweicloud_workspace_app_server_group.test" + rc = acceptance.InitResourceCheck(resourceName, &obj, getResourceAppServerGroupFunc) + ) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acceptance.TestAccPreCheck(t) + acceptance.TestAccPreCheckWorkspaceAppServerGroup(t) + acceptance.TestAccPreCheckEpsID(t) + }, + ProviderFactories: acceptance.TestAccProviderFactories, + CheckDestroy: rc.CheckResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccResourceAppServerGroup_singleSession_step1(name), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "os_type", "Windows"), + resource.TestCheckResourceAttr(resourceName, "flavor_id", acceptance.HW_WORKSPACE_APP_SERVER_GROUP_FLAVOR_ID), + resource.TestCheckResourceAttrPair(resourceName, "vpc_id", + "data.huaweicloud_workspace_service.test", "vpc_id"), + resource.TestCheckResourceAttrPair(resourceName, "subnet_id", + "data.huaweicloud_workspace_service.test", "network_ids.0"), + resource.TestCheckResourceAttr(resourceName, "system_disk_type", "SAS"), + resource.TestCheckResourceAttr(resourceName, "system_disk_size", "90"), + resource.TestCheckResourceAttr(resourceName, "image_id", acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_ID), + resource.TestCheckResourceAttr(resourceName, "image_type", "gold"), + resource.TestCheckResourceAttr(resourceName, "image_product_id", + acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_PRODUCT_ID), + resource.TestCheckResourceAttr(resourceName, "is_vdi", "true"), + resource.TestCheckResourceAttr(resourceName, "description", "Created by script"), + resource.TestCheckResourceAttr(resourceName, "app_type", "SESSION_DESKTOP_APP"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"), + resource.TestCheckResourceAttr(resourceName, "enterprise_project_id", acceptance.HW_ENTERPRISE_PROJECT_ID_TEST), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "storage_mount_policy", "USER"), + ), + }, + { + Config: testAccResourceAppServerGroup_singleSession_step2(updateName), + Check: resource.ComposeTestCheckFunc( + rc.CheckResourceExists(), + resource.TestCheckResourceAttr(resourceName, "name", updateName), + resource.TestCheckResourceAttr(resourceName, "system_disk_type", "SAS"), + resource.TestCheckResourceAttr(resourceName, "system_disk_size", "80"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "app_type", "SESSION_DESKTOP_APP"), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "storage_mount_policy", "SHARE"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar_update"), + resource.TestCheckResourceAttr(resourceName, "tags.owner", "terraform"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "vpc_id", "image_type", "image_product_id", "availability_zone", + }, + }, + }, + }) +} + +func testAccResourceAppServerGroup_singleSession_step1(name string) string { + return fmt.Sprintf(` +data "huaweicloud_workspace_service" "test" {} + +resource "huaweicloud_workspace_app_server_group" "test" { + name = "%[1]s" + os_type = "Windows" + flavor_id = "%[2]s" + vpc_id = data.huaweicloud_workspace_service.test.vpc_id + subnet_id = data.huaweicloud_workspace_service.test.network_ids[0] + system_disk_type = "SAS" + system_disk_size = 90 + app_type = "SESSION_DESKTOP_APP" + is_vdi = true + image_id = "%[3]s" + image_type = "gold" + image_product_id = "%[4]s" + description = "Created by script" + storage_mount_policy = "USER" + enterprise_project_id = "%[5]s" + + tags = { + foo = "bar" + } +} +`, name, + acceptance.HW_WORKSPACE_APP_SERVER_GROUP_FLAVOR_ID, + acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_ID, + acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_PRODUCT_ID, + acceptance.HW_ENTERPRISE_PROJECT_ID_TEST, + ) +} + +func testAccResourceAppServerGroup_singleSession_step2(name string) string { + return fmt.Sprintf(` +data "huaweicloud_workspace_service" "test" {} + +resource "huaweicloud_workspace_app_server_group" "test" { + name = "%[1]s" + os_type = "Windows" + flavor_id = "%[2]s" + vpc_id = data.huaweicloud_workspace_service.test.vpc_id + subnet_id = data.huaweicloud_workspace_service.test.network_ids[0] + system_disk_type = "SAS" + system_disk_size = 80 + app_type = "SESSION_DESKTOP_APP" + is_vdi = true + image_id = "%[3]s" + image_type = "gold" + image_product_id = "%[4]s" + storage_mount_policy = "SHARE" + enabled = false + enterprise_project_id = "%[5]s" + + tags = { + foo = "bar_update" + owner = "terraform" + } +} +`, name, + acceptance.HW_WORKSPACE_APP_SERVER_GROUP_FLAVOR_ID, + acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_ID, + acceptance.HW_WORKSPACE_APP_SERVER_GROUP_IMAGE_PRODUCT_ID, + acceptance.HW_ENTERPRISE_PROJECT_ID_TEST, + ) +} diff --git a/huaweicloud/services/workspace/resource_huaweicloud_workspace_app_server_group.go b/huaweicloud/services/workspace/resource_huaweicloud_workspace_app_server_group.go index 94117e153d7..67ede122767 100644 --- a/huaweicloud/services/workspace/resource_huaweicloud_workspace_app_server_group.go +++ b/huaweicloud/services/workspace/resource_huaweicloud_workspace_app_server_group.go @@ -20,6 +20,8 @@ import ( // @API Workspace GET /v1/{project_id}/app-server-group // @API Workspace PATCH /v1/{project_id}/app-server-groups/{server_group_id} // @API Workspace POST /v1/{project_id}/app-server-groups/{server_group_id} +// @API Workspace POST /v1/{project_id}/server-group/tags/batch-create +// @API Workspace DELETE /v1/{project_id}/server-group/tags/batch-delete func ResourceAppServerGroup() *schema.Resource { return &schema.Resource{ CreateContext: resourceAppServerGroupCreate, @@ -118,7 +120,7 @@ func ResourceAppServerGroup() *schema.Resource { Optional: true, Description: `The description of the server group.`, }, - "tags": common.TagsForceNewSchema("The key/value pairs to associate with the server group."), + "tags": common.TagsSchema("The key/value pairs to associate with the server group."), "enterprise_project_id": { Type: schema.TypeString, Optional: true, @@ -452,7 +454,10 @@ func flattenAppServerGroupFlavor(flavors []interface{}) []map[string]interface{} } func resourceAppServerGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - cfg := meta.(*config.Config) + var ( + cfg = meta.(*config.Config) + serverGroupId = d.Id() + ) client, err := cfg.NewServiceClient("appstream", cfg.GetRegion(d)) if err != nil { return diag.Errorf("error creating Workspace APP client: %s", err) @@ -461,12 +466,18 @@ func resourceAppServerGroupUpdate(ctx context.Context, d *schema.ResourceData, m if d.HasChanges("name", "system_disk_type", "system_disk_size", "image_id", "image_type", "image_product_id", "description", "app_type", "route_policy", "ou_name", "enabled", "storage_mount_policy") { updateOpt := buildUpdateServerGroupBodyParams(d) - serverGroupId := d.Id() if err := updateAppServerGroup(client, serverGroupId, updateOpt); err != nil { return diag.Errorf("error updating server group (%s): %s", serverGroupId, err) } } + if d.HasChanges("tags") { + oRaw, nRaw := d.GetChange("tags") + if err := updateServerGroupTags(client, serverGroupId, oRaw, nRaw); err != nil { + return diag.FromErr(err) + } + } + return resourceAppServerGroupRead(ctx, d, meta) } @@ -505,6 +516,60 @@ func buildUpdateServerGroupBodyParams(d *schema.ResourceData) map[string]interfa } } +func updateServerGroupTags(client *golangsdk.ServiceClient, serverGroupId string, oRaw, nRaw interface{}) error { + removeTags := oRaw.(map[string]interface{}) + addTags := nRaw.(map[string]interface{}) + if len(removeTags) > 0 { + if err := removeServerGroupTags(client, serverGroupId, removeTags); err != nil { + return fmt.Errorf("error removing tags of server group (%s): %s", serverGroupId, err) + } + } + + if len(addTags) > 0 { + if err := addServerGroupTags(client, serverGroupId, addTags); err != nil { + return fmt.Errorf("error adding tags of server group (%s): %s", serverGroupId, err) + } + } + + return nil +} + +func buildUpdateServerGroupTagsBodyParams(serverGroupId string, tags map[string]interface{}) map[string]interface{} { + return map[string]interface{}{ + "items": []map[string]interface{}{ + { + "server_group_id": serverGroupId, + "tags": utils.ExpandResourceTags(tags), + }, + }, + } +} + +func addServerGroupTags(client *golangsdk.ServiceClient, serverGroupId string, tags map[string]interface{}) error { + httpUrl := "v1/{project_id}/server-group/tags/batch-create" + path := client.Endpoint + httpUrl + path = strings.ReplaceAll(path, "{project_id}", client.ProjectID) + opt := golangsdk.RequestOpts{ + KeepResponseBody: true, + JSONBody: buildUpdateServerGroupTagsBodyParams(serverGroupId, tags), + OkCodes: []int{204}, + } + _, err := client.Request("POST", path, &opt) + return err +} + +func removeServerGroupTags(client *golangsdk.ServiceClient, serverGroupId string, tags map[string]interface{}) error { + httpUrl := "v1/{project_id}/server-group/tags/batch-delete" + path := client.Endpoint + httpUrl + path = strings.ReplaceAll(path, "{project_id}", client.ProjectID) + opt := golangsdk.RequestOpts{ + KeepResponseBody: true, + JSONBody: buildUpdateServerGroupTagsBodyParams(serverGroupId, tags), + } + _, err := client.Request("DELETE", path, &opt) + return err +} + func resourceAppServerGroupDelete(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { cfg := meta.(*config.Config) httpUrl := "v1/{project_id}/app-server-groups/{server_group_id}"