From 4036f551e7abcb62d0f78b98554131e2bc0b1fb9 Mon Sep 17 00:00:00 2001 From: saf3dfsa Date: Thu, 31 Oct 2024 16:17:01 +0800 Subject: [PATCH] feat(dds): support to get recycle instances --- docs/data-sources/dds_recycle_instances.md | 66 ++++++ huaweicloud/provider.go | 1 + huaweicloud/services/acceptance/acceptance.go | 8 + ..._huaweicloud_dds_recycle_instances_test.go | 44 ++++ ...ource_huaweicloud_dds_recycle_instances.go | 202 ++++++++++++++++++ 5 files changed, 321 insertions(+) create mode 100644 docs/data-sources/dds_recycle_instances.md create mode 100644 huaweicloud/services/acceptance/dds/data_source_huaweicloud_dds_recycle_instances_test.go create mode 100644 huaweicloud/services/dds/data_source_huaweicloud_dds_recycle_instances.go diff --git a/docs/data-sources/dds_recycle_instances.md b/docs/data-sources/dds_recycle_instances.md new file mode 100644 index 0000000000..d34ef03815 --- /dev/null +++ b/docs/data-sources/dds_recycle_instances.md @@ -0,0 +1,66 @@ +--- +subcategory: "Document Database Service (DDS)" +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_dds_recycle_instances" +description: |- + Use this data source to get the list of DDS recycle instances. +--- + +# huaweicloud_dds_recycle_instances + +Use this data source to get the list of DDS recycle instances. + +## Example Usage + +```hcl +data "huaweicloud_dds_recycle_instances" "test"{} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String) Specifies the region in which to query the resource. + If omitted, the provider-level region will be used. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The data source ID. + +* `instances` - Indicates the instances. + + The [instances](#instances_struct) structure is documented below. + + +The `instances` block supports: + +* `id` - Indicates the instance ID. + +* `name` - Indicates the instance name. + +* `mode` - Indicates the instance mode. + +* `backup_id` - Indicates the backup ID. + +* `datastore` - Indicates the database information. + + The [datastore](#instances_datastore_struct) structure is documented below. + +* `charging_mode` - Indicates the charging mode. + +* `enterprise_project_id` - Indicates the enterprise project ID. + +* `created_at` - Indicates the creation time. + +* `deleted_at` - Indicates the deletion time. + +* `retained_until` - Indicates the retention end time. + + +The `datastore` block supports: + +* `version` - Indicates the database version. + +* `type` - Indicates the database type. diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index 3f8da57e65..ebe64bb3ff 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -650,6 +650,7 @@ func Provider() *schema.Provider { "huaweicloud_dds_database_users": dds.DateSourceDDSDatabaseUser(), "huaweicloud_dds_storage_types": dds.DataSourceDdsStorageTypes(), "huaweicloud_dds_restore_time_ranges": dds.DataSourceDdsRestoreTimeRanges(), + "huaweicloud_dds_recycle_instances": dds.DataSourceDdsRecycleInstances(), "huaweicloud_dds_backups": dds.DataSourceDDSBackups(), "huaweicloud_dds_database_roles": dds.DateSourceDDSDatabaseRoles(), "huaweicloud_dds_error_logs": dds.DataSourceDDSErrorLogs(), diff --git a/huaweicloud/services/acceptance/acceptance.go b/huaweicloud/services/acceptance/acceptance.go index 7f69df1079..8f7b1100ac 100644 --- a/huaweicloud/services/acceptance/acceptance.go +++ b/huaweicloud/services/acceptance/acceptance.go @@ -471,6 +471,7 @@ var ( HW_DDS_INSTANCE_ID = os.Getenv("HW_DDS_INSTANCE_ID") HW_DDS_START_TIME = os.Getenv("HW_DDS_START_TIME") HW_DDS_END_TIME = os.Getenv("HW_DDS_END_TIME") + HW_DDS_RECYCLE_INSTANCES_ENABLED = os.Getenv("HW_DDS_RECYCLE_INSTANCES_ENABLED") HW_RDS_CROSS_REGION_BACKUP_INSTANCE_ID = os.Getenv("HW_RDS_CROSS_REGION_BACKUP_INSTANCE_ID") HW_RDS_INSTANCE_ID = os.Getenv("HW_RDS_INSTANCE_ID") @@ -2319,6 +2320,13 @@ func TestAccPreCheckDDSTimeRange(t *testing.T) { } } +// lintignore:AT003 +func TestAccPreCheckDDSRecycleInstancesEnabled(t *testing.T) { + if HW_DDS_RECYCLE_INSTANCES_ENABLED == "" { + t.Skip("HW_DDS_RECYCLE_INSTANCES_ENABLED must be set for the acceptance test") + } +} + // lintignore:AT003 func TestAccPreCheckRdsCrossRegionBackupInstanceId(t *testing.T) { if HW_RDS_CROSS_REGION_BACKUP_INSTANCE_ID == "" { diff --git a/huaweicloud/services/acceptance/dds/data_source_huaweicloud_dds_recycle_instances_test.go b/huaweicloud/services/acceptance/dds/data_source_huaweicloud_dds_recycle_instances_test.go new file mode 100644 index 0000000000..c14989ece2 --- /dev/null +++ b/huaweicloud/services/acceptance/dds/data_source_huaweicloud_dds_recycle_instances_test.go @@ -0,0 +1,44 @@ +package dds + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func TestAccDataSourceDdsRecycleInstances_basic(t *testing.T) { + dataSource := "data.huaweicloud_dds_recycle_instances.test" + dc := acceptance.InitDataSourceCheck(dataSource) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acceptance.TestAccPreCheck(t) + acceptance.TestAccPreCheckDDSRecycleInstancesEnabled(t) + }, + ProviderFactories: acceptance.TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testDataSourceDdsRecycleInstances_basic, + Check: resource.ComposeTestCheckFunc( + dc.CheckResourceExists(), + resource.TestCheckResourceAttrSet(dataSource, "instances.#"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.id"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.name"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.mode"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.backup_id"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.datastore.0.version"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.datastore.0.type"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.charging_mode"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.enterprise_project_id"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.created_at"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.deleted_at"), + resource.TestCheckResourceAttrSet(dataSource, "instances.0.retained_until"), + ), + }, + }, + }) +} + +const testDataSourceDdsRecycleInstances_basic = `data "huaweicloud_dds_recycle_instances" "test" {}` diff --git a/huaweicloud/services/dds/data_source_huaweicloud_dds_recycle_instances.go b/huaweicloud/services/dds/data_source_huaweicloud_dds_recycle_instances.go new file mode 100644 index 0000000000..6ebdef9c1d --- /dev/null +++ b/huaweicloud/services/dds/data_source_huaweicloud_dds_recycle_instances.go @@ -0,0 +1,202 @@ +package dds + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/chnsz/golangsdk" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" +) + +// @API DDS GET /v3/{project_id}/recycle-instances +func DataSourceDdsRecycleInstances() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceDdsRecycleInstancesRead, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: `Specifies the region in which to query the resource. If omitted, the provider-level region will be used.`, + }, + "instances": { + Type: schema.TypeList, + Computed: true, + Description: `Indicates the instances.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the instance ID.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the instance name.`, + }, + "mode": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the instance mode.`, + }, + "backup_id": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the backup ID.`, + }, + "datastore": { + Type: schema.TypeList, + Computed: true, + Description: `Indicates the database information.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the database version.`, + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the database type.`, + }, + }, + }, + }, + "charging_mode": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the charging mode.`, + }, + "enterprise_project_id": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the enterprise project ID.`, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the creation time.`, + }, + "deleted_at": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the deletion time.`, + }, + "retained_until": { + Type: schema.TypeString, + Computed: true, + Description: `Indicates the retention end time.`, + }, + }, + }, + }, + }, + } +} + +func dataSourceDdsRecycleInstancesRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + cfg := meta.(*config.Config) + region := cfg.GetRegion(d) + client, err := cfg.NewServiceClient("dds", region) + if err != nil { + return diag.Errorf("error creating DDS client: %s", err) + } + + httpUrl := "v3/{project_id}/recycle-instances" + getPath := client.Endpoint + httpUrl + getPath = strings.ReplaceAll(getPath, "{project_id}", client.ProjectID) + getOpt := golangsdk.RequestOpts{ + KeepResponseBody: true, + MoreHeaders: map[string]string{ + "Content-Type": "application/json", + }, + } + + getPath += fmt.Sprintf("?limit=%d", pageLimit) + currentTotal := 0 + rst := make([]map[string]interface{}, 0) + for { + currentPath := getPath + fmt.Sprintf("&offset=%d", currentTotal) + getResp, err := client.Request("GET", currentPath, &getOpt) + if err != nil { + return diag.Errorf("error retrieving DDS recycle insatnces: %s", err) + } + getRespBody, err := utils.FlattenResponse(getResp) + if err != nil { + return diag.Errorf("error flattening response: %s", err) + } + + instances := utils.PathSearch("instances", getRespBody, make([]interface{}, 0)).([]interface{}) + for _, instance := range instances { + rst = append(rst, map[string]interface{}{ + "id": utils.PathSearch("id", instance, nil), + "name": utils.PathSearch("name", instance, nil), + "mode": utils.PathSearch("mode", instance, nil), + "backup_id": utils.PathSearch("backup_id", instance, nil), + "datastore": flatteRecycleInstancesResponseDatastore(instance), + "charging_mode": parseChargingMode(utils.PathSearch("pay_model", instance, "").(string)), + "enterprise_project_id": utils.PathSearch("enterprise_project_id", instance, nil), + "created_at": utils.PathSearch("create_at", instance, nil), + "deleted_at": utils.PathSearch("deleted_at", instance, nil), + "retained_until": utils.PathSearch("retained_until", instance, nil), + }) + } + + // `total_count` means the number of all `instances`, and type is float64. + currentTotal += len(instances) + totalCount := utils.PathSearch("total_count", getRespBody, float64(0)) + if int(totalCount.(float64)) == currentTotal { + break + } + } + + id, err := uuid.GenerateUUID() + if err != nil { + return diag.Errorf("unable to generate ID: %s", err) + } + d.SetId(id) + + mErr := multierror.Append(nil, + d.Set("region", region), + d.Set("instances", rst), + ) + return diag.FromErr(mErr.ErrorOrNil()) +} + +func flatteRecycleInstancesResponseDatastore(resp interface{}) []interface{} { + var rst []interface{} + curJson := utils.PathSearch("data_store", resp, nil) + if curJson == nil { + return rst + } + + rst = []interface{}{ + map[string]interface{}{ + "type": utils.PathSearch("type", curJson, nil), + "version": utils.PathSearch("version", curJson, nil), + }, + } + return rst +} + +func parseChargingMode(v string) string { + switch v { + case "0": + return "postPaid" + case "1": + return "prePaid" + default: + return v + } +}