diff --git a/docs/resources/gaussdb_opengauss_instance.md b/docs/resources/gaussdb_opengauss_instance.md index 83886679afa..ae2e3065567 100644 --- a/docs/resources/gaussdb_opengauss_instance.md +++ b/docs/resources/gaussdb_opengauss_instance.md @@ -169,6 +169,8 @@ The following arguments are supported: Changing this parameter will create a new resource. +* `tags` - (Optional, Map) Specifies the key/value pairs to associate with the GaussDB OpenGauss instance. + * `force_import` - (Optional, Bool) Specifies whether to import the instance with the given configuration instead of creation. If specified, try to import the instance instead of creation if the instance already existed. diff --git a/huaweicloud/services/acceptance/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance_test.go b/huaweicloud/services/acceptance/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance_test.go index 82f8a98d69b..630da90a19a 100644 --- a/huaweicloud/services/acceptance/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance_test.go +++ b/huaweicloud/services/acceptance/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance_test.go @@ -100,6 +100,8 @@ func TestAccOpenGaussInstance_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "volume.0.size", "40"), resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.start_time", "20:00-21:00"), resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.keep_days", "6"), + resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"), + resource.TestCheckResourceAttr(resourceName, "tags.key", "value"), ), }, { @@ -114,6 +116,8 @@ func TestAccOpenGaussInstance_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "volume.0.size", "80"), resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.start_time", "08:00-09:00"), resource.TestCheckResourceAttr(resourceName, "backup_strategy.0.keep_days", "8"), + resource.TestCheckResourceAttr(resourceName, "tags.foo_update", "bar"), + resource.TestCheckResourceAttr(resourceName, "tags.key", "value_update"), ), }, }, @@ -295,6 +299,11 @@ resource "huaweicloud_gaussdb_opengauss_instance" "test" { start_time = "20:00-21:00" keep_days = 6 } + + tags = { + foo = "bar" + key = "value" + } } `, testAccOpenGaussInstance_base(rName), rName, password, replicaNum, acceptance.HW_ENTERPRISE_PROJECT_ID_TEST) } @@ -346,6 +355,11 @@ resource "huaweicloud_gaussdb_opengauss_instance" "test" { start_time = "08:00-09:00" keep_days = 8 } + + tags = { + foo_update = "bar" + key = "value_update" + } } `, testAccOpenGaussInstance_base(rName), rName, password, replicaNum, acceptance.HW_ENTERPRISE_MIGRATE_PROJECT_ID_TEST) } diff --git a/huaweicloud/services/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance.go b/huaweicloud/services/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance.go index 1e6f224b89d..8ea4df5a14a 100644 --- a/huaweicloud/services/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance.go +++ b/huaweicloud/services/gaussdb/resource_huaweicloud_gaussdb_opengauss_instance.go @@ -29,11 +29,13 @@ const ( // @API GaussDB GET /v3/{project_id}/instances // @API GaussDB POST /v3/{project_id}/instances // @API GaussDB GET /v3/{project_id}/jobs +// @API GaussDB POST /v3/{project_id}/instances/{instance_id}/tags // @API GaussDB PUT /v3/{project_id}/instances/{instance_id}/name // @API GaussDB POST /v3/{project_id}/instances/{instance_id}/password // @API GaussDB POST /v3/{project_id}/instances/{instance_id}/action // @API GaussDB PUT /v3/{project_id}/instances/{instance_id}/backups/policy // @API GaussDB PUT /v3/{project_id}/instance/{instance_id}/flavor +// @API GaussDB DELETE /v3/{project_id}/instances/{instance_id}/tag // @API GaussDB DELETE /v3/{project_id}/instances/{instance_id} // @API BSS GET /v2/orders/customer-orders/details/{order_id} // @API BSS POST /v2/orders/suscriptions/resources/query @@ -246,6 +248,7 @@ func ResourceOpenGaussInstance() *schema.Resource { }, }, }, + "tags": common.TagsSchema(), "force_import": { Type: schema.TypeBool, Optional: true, @@ -423,6 +426,15 @@ func resourceOpenGaussInstanceCreate(ctx context.Context, d *schema.ResourceData return diag.Errorf("error waiting for instance (%s) to become ready: %s", d.Id(), err) } + // set tags + tagRaw := d.Get("tags").(map[string]interface{}) + if len(tagRaw) > 0 { + err = addInstanceTags(d, client, d.Get("tags").(map[string]interface{})) + if err != nil { + return diag.Errorf("error setting tags for GaussDB OpenGauss instance %s: %s", d.Id(), err) + } + } + // This is a workaround to avoid db connection issue time.Sleep(360 * time.Second) // lintignore:R018 @@ -600,6 +612,7 @@ func resourceOpenGaussInstanceRead(_ context.Context, d *schema.ResourceData, me d.Set("security_group_id", utils.PathSearch("security_group_id", instance, nil)), d.Set("db_user_name", utils.PathSearch("db_user_name", instance, nil)), d.Set("time_zone", utils.PathSearch("time_zone", instance, nil)), + d.Set("enterprise_project_id", utils.PathSearch("enterprise_project_id", instance, nil)), d.Set("flavor", utils.PathSearch("flavor_ref", instance, nil)), d.Set("port", strconv.Itoa(int(utils.PathSearch("port", instance, float64(0)).(float64)))), d.Set("switch_strategy", utils.PathSearch("switch_strategy", instance, nil)), @@ -612,6 +625,7 @@ func resourceOpenGaussInstanceRead(_ context.Context, d *schema.ResourceData, me setOpenGaussNodesAndRelatedNumbers(d, instance, &dnNum), d.Set("volume", flattenGaussDBOpenGaussResponseBodyVolume(instance, dnNum)), setOpenGaussPrivateIpsAndEndpoints(d, instance), + d.Set("tags", flattenGaussDBOpenGaussInstanceTags(instance)), ) return diag.FromErr(mErr.ErrorOrNil()) @@ -726,6 +740,22 @@ func setOpenGaussPrivateIpsAndEndpoints(d *schema.ResourceData, instance interfa return mErr } +func flattenGaussDBOpenGaussInstanceTags(resp interface{}) map[string]interface{} { + if resp == nil { + return nil + } + + curJson := utils.PathSearch("tags", resp, make([]interface{}, 0)) + curArray := curJson.([]interface{}) + rst := make(map[string]interface{}) + for _, v := range curArray { + key := utils.PathSearch("key", v, "").(string) + value := utils.PathSearch("value", v, "").(string) + rst[key] = value + } + return rst +} + func resourceOpenGaussInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { cfg := meta.(*config.Config) region := cfg.GetRegion(d) @@ -800,6 +830,13 @@ func resourceOpenGaussInstanceUpdate(ctx context.Context, d *schema.ResourceData } } + if d.HasChange("tags") { + err = updateInstanceTags(d, client) + if err != nil { + return diag.Errorf("error updating tags of GaussDB OpenGauss instance %q: %s", d.Id(), err) + } + } + return resourceOpenGaussInstanceRead(ctx, d, meta) } @@ -1136,6 +1173,96 @@ func buildUpdateInstanceFlavorBodyParams(d *schema.ResourceData) map[string]inte return bodyParams } +func addInstanceTags(d *schema.ResourceData, client *golangsdk.ServiceClient, addTags map[string]interface{}) error { + var ( + httpUrl = "v3/{project_id}/instances/{instance_id}/tags" + ) + + addPath := client.Endpoint + httpUrl + addPath = strings.ReplaceAll(addPath, "{project_id}", client.ProjectID) + addPath = strings.ReplaceAll(addPath, "{instance_id}", d.Id()) + + addOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + } + addOpt.JSONBody = utils.RemoveNil(buildAddInstanceTagsBodyParams(addTags)) + + _, err := client.Request("POST", addPath, &addOpt) + if err != nil { + return fmt.Errorf("error updating GaussDB OpenGauss instance (%s) backup strategy: %s", d.Id(), err) + } + + return nil +} + +func buildAddInstanceTagsBodyParams(addTags map[string]interface{}) map[string]interface{} { + tags := make([]interface{}, 0, len(addTags)) + for key, value := range addTags { + tags = append(tags, map[string]interface{}{ + "key": key, + "value": value, + }) + } + bodyParams := map[string]interface{}{ + "tags": tags, + } + return bodyParams +} + +func deleteInstanceTags(d *schema.ResourceData, client *golangsdk.ServiceClient, deleteTagKeys []string) error { + var ( + httpUrl = "v3/{project_id}/instances/{instance_id}/tag" + ) + + deleteBasePath := client.Endpoint + httpUrl + deleteBasePath = strings.ReplaceAll(deleteBasePath, "{project_id}", client.ProjectID) + deleteBasePath = strings.ReplaceAll(deleteBasePath, "{instance_id}", d.Id()) + + deleteOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + } + + for _, deleteTagKey := range deleteTagKeys { + deletePath := deleteBasePath + buildDeleteInstanceTagParamBodyParams(deleteTagKey) + _, err := client.Request("DELETE", deletePath, &deleteOpt) + if err != nil { + return fmt.Errorf("error deleting GaussDB OpenGauss instance (%s) tag(%s): %s", d.Id(), deleteTagKey, err) + } + } + + return nil +} + +func buildDeleteInstanceTagParamBodyParams(key string) string { + return fmt.Sprintf("?key=%s", key) +} + +func updateInstanceTags(d *schema.ResourceData, client *golangsdk.ServiceClient) error { + oRaw, nRaw := d.GetChange("tags") + oMap := oRaw.(map[string]interface{}) + nMap := nRaw.(map[string]interface{}) + + if len(oMap) > 0 { + keys := make([]string, 0, len(oMap)) + for key := range oMap { + keys = append(keys, key) + } + err := deleteInstanceTags(d, client, keys) + if err != nil { + return err + } + } + + if len(nMap) > 0 { + err := addInstanceTags(d, client, nMap) + if err != nil { + return err + } + } + + return nil +} + func resourceOpenGaussInstanceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { cfg := meta.(*config.Config) region := cfg.GetRegion(d)