diff --git a/docs/data-sources/hss_quotass.md b/docs/data-sources/hss_quotass.md new file mode 100644 index 0000000000..b991cb9177 --- /dev/null +++ b/docs/data-sources/hss_quotass.md @@ -0,0 +1,98 @@ +--- +subcategory: "Host Security Service (HSS)" +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_hss_quotas" +description: |- + Use this data source to get the list of HSS quotas within HuaweiCloud. +--- + +# huaweicloud_hss_quotas + +Use this data source to get the list of HSS quotas within HuaweiCloud. + +## Example Usage + +```hcl +variable quota_id {} + +data "huaweicloud_hss_quotas" "test" { + quota_id = var.quota_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String) Specifies the region in which to query the HSS quotas. + If omitted, the provider-level region will be used. + +* `category` - (Optional, String) Specifies the category of the quotas to be queried. + The valid values are as follows: + + **host_resource**: Host protection quota. + + **container_resource**: Container protection quota. + + If omitted, return all quotas for host resource. + If set to **container_resource**, return all quotas with version **hss.version.container.enterprise**. + +* `version` - (Optional, String) Specifies the version of the quotas to be queried. + The valid values are as follows: + + **hss.version.basic**: Basic version. + + **hss.version.advanced**: Professional version. + + **hss.version.enterprise**: Enterprise version. + + **hss.version.premium**: Ultimate version. + + **hss.version.wtp**: Web page tamper prevention version. + +* `status` - (Optional, String) Specifies the status of the quotas to be queried. + The value can be **normal**, **expired**, or **freeze**. + +* `used_status` - (Optional, String) Specifies the usage status of the quotas to be queried. + The value can be **idle** or **used**. + +* `host_name` - (Optional, String) Specifies the host name for the quota binding to be queried. + +* `quota_id` - (Optional, String) Specifies the ID of the quota to be queried. + +* `charging_mode` - (Optional, String) Specifies the charging mode of the quotas to be queried. + The valid values are as follows: + + **prePaid**: The yearly/monthly billing mode. + + **postPaid**: The pay-per-use billing mode. + +* `enterprise_project_id` - (Optional, String) Specifies the ID of the enterprise project to which the quotas belong. + For enterprise users, if omitted, will query the quotas under all enterprise projects. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The data source ID in UUID format. + +* `quotas` - All quotas that match the filter parameters. + The [quotas](#hss_quotas) structure is documented below. + + +The `quotas` block supports: + +* `id` - The ID of quota. + +* `version` - The version of quota. + +* `status` - The status of quota. + +* `used_status` - The usage status of quota. + +* `host_id` - The host ID for quota binding. + +* `host_name` - The host name for quota binding. + +* `charging_mode` - The charging mode of quota. + +* `expire_time` - The expiration time of quota, in RFC3339 format. This field is valid when the quota is a trial quota. + +* `shared_quota` - Is it a shared quota. The value can be **shared** or **unshared**. + +* `enterprise_project_id` - The enterprise project ID to which the quota belongs. + +* `enterprise_project_name` - The enterprise project name to which the quota belongs. + +* `tags` - The key/value pairs to associate with the HSS quota. diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index b6c3e04a34..9804241924 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -681,6 +681,7 @@ func Provider() *schema.Provider { "huaweicloud_hss_host_groups": hss.DataSourceHostGroups(), "huaweicloud_hss_hosts": hss.DataSourceHosts(), "huaweicloud_hss_webtamper_hosts": hss.DataSourceWebTamperHosts(), + "huaweicloud_hss_quotas": hss.DataSourceQuotas(), "huaweicloud_identity_permissions": iam.DataSourceIdentityPermissions(), "huaweicloud_identity_role": iam.DataSourceIdentityRole(), diff --git a/huaweicloud/services/acceptance/hss/data_source_huaweicloud_hss_quotas_test.go b/huaweicloud/services/acceptance/hss/data_source_huaweicloud_hss_quotas_test.go new file mode 100644 index 0000000000..eda31c0a43 --- /dev/null +++ b/huaweicloud/services/acceptance/hss/data_source_huaweicloud_hss_quotas_test.go @@ -0,0 +1,154 @@ +package hss + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func TestAccDataSourceQuotas_basic(t *testing.T) { + var ( + dataSource = "data.huaweicloud_hss_quotas.test" + dc = acceptance.InitDataSourceCheck(dataSource) + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acceptance.TestAccPreCheck(t) + }, + ProviderFactories: acceptance.TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testDataSourceQuotas_basic(), + Check: resource.ComposeTestCheckFunc( + dc.CheckResourceExists(), + resource.TestCheckResourceAttrSet(dataSource, "quotas.#"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.id"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.version"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.status"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.used_status"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.charging_mode"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.shared_quota"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.enterprise_project_id"), + resource.TestCheckResourceAttrSet(dataSource, "quotas.0.enterprise_project_name"), + resource.TestCheckResourceAttr(dataSource, "quotas.0.tags.foo", "bar"), + resource.TestCheckResourceAttr(dataSource, "quotas.0.tags.key", "value"), + + resource.TestCheckOutput("is_category_filter_useful", "true"), + resource.TestCheckOutput("is_version_filter_useful", "true"), + resource.TestCheckOutput("is_status_filter_useful", "true"), + resource.TestCheckOutput("is_used_status_filter_useful", "true"), + resource.TestCheckOutput("is_quota_id_filter_useful", "true"), + resource.TestCheckOutput("is_charging_mode_filter_useful", "true"), + resource.TestCheckOutput("not_found_validation_pass", "true"), + ), + }, + }, + }) +} + +func testDataSourceQuotas_basic() string { + return fmt.Sprintf(` +%s + +data "huaweicloud_hss_quotas" "test" { + depends_on = [huaweicloud_hss_quota.test] +} + +# Filter using category and category value is **container_resource**. +data "huaweicloud_hss_quotas" "category_filter" { + category = "container_resource" +} + +output "is_category_filter_useful" { + value = length(data.huaweicloud_hss_quotas.category_filter.quotas) == 0 +} + +# Filter using version. +locals { + version = data.huaweicloud_hss_quotas.test.quotas[0].version +} + +data "huaweicloud_hss_quotas" "version_filter" { + version = local.version +} + +output "is_version_filter_useful" { + value = length(data.huaweicloud_hss_quotas.version_filter.quotas) > 0 && alltrue( + [for v in data.huaweicloud_hss_quotas.version_filter.quotas[*].version : v == local.version] + ) +} + +# Filter using status. +locals { + status = data.huaweicloud_hss_quotas.test.quotas[0].status +} + +data "huaweicloud_hss_quotas" "status_filter" { + status = local.status +} + +output "is_status_filter_useful" { + value = length(data.huaweicloud_hss_quotas.status_filter.quotas) > 0 && alltrue( + [for v in data.huaweicloud_hss_quotas.status_filter.quotas[*].status : v == local.status] + ) +} + +# Filter using used_status. +locals { + used_status = data.huaweicloud_hss_quotas.test.quotas[0].used_status +} + +data "huaweicloud_hss_quotas" "used_status_filter" { + used_status = local.used_status +} + +output "is_used_status_filter_useful" { + value = length(data.huaweicloud_hss_quotas.used_status_filter.quotas) > 0 && alltrue( + [for v in data.huaweicloud_hss_quotas.used_status_filter.quotas[*].used_status : v == local.used_status] + ) +} + +# Filter using quota ID. +locals { + quota_id = data.huaweicloud_hss_quotas.test.quotas[0].id +} + +data "huaweicloud_hss_quotas" "quota_id_filter" { + quota_id = local.quota_id +} + +output "is_quota_id_filter_useful" { + value = length(data.huaweicloud_hss_quotas.quota_id_filter.quotas) > 0 && alltrue( + [for v in data.huaweicloud_hss_quotas.quota_id_filter.quotas[*].id : v == local.quota_id] + ) +} + +# Filter using charging mode. +locals { + charging_mode = data.huaweicloud_hss_quotas.test.quotas[0].charging_mode +} + +data "huaweicloud_hss_quotas" "charging_mode_filter" { + charging_mode = local.charging_mode +} + +output "is_charging_mode_filter_useful" { + value = length(data.huaweicloud_hss_quotas.charging_mode_filter.quotas) > 0 && alltrue( + [for v in data.huaweicloud_hss_quotas.charging_mode_filter.quotas[*].charging_mode : v == local.charging_mode] + ) +} + +# Filter using non existent quota ID. +data "huaweicloud_hss_quotas" "not_found" { + quota_id = "resource_not_found" +} + +output "not_found_validation_pass" { + value = length(data.huaweicloud_hss_quotas.not_found.quotas) == 0 +} +`, testAccQuota_basic()) +} diff --git a/huaweicloud/services/hss/data_source_huaweicloud_hss_quotas.go b/huaweicloud/services/hss/data_source_huaweicloud_hss_quotas.go new file mode 100644 index 0000000000..4c5a5fbd0b --- /dev/null +++ b/huaweicloud/services/hss/data_source_huaweicloud_hss_quotas.go @@ -0,0 +1,223 @@ +package hss + +import ( + "context" + + "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" + + hssv5model "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/hss/v5/model" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/common" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/utils" +) + +// @API HSS GET /v5/{project_id}/billing/quotas-detail +func DataSourceQuotas() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceQuotasRead, + + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "category": { + Type: schema.TypeString, + Optional: true, + }, + "version": { + Type: schema.TypeString, + Optional: true, + }, + "status": { + Type: schema.TypeString, + Optional: true, + }, + "used_status": { + Type: schema.TypeString, + Optional: true, + }, + "host_name": { + Type: schema.TypeString, + Optional: true, + }, + "quota_id": { + Type: schema.TypeString, + Optional: true, + }, + "charging_mode": { + Type: schema.TypeString, + Optional: true, + }, + "enterprise_project_id": { + Type: schema.TypeString, + Optional: true, + }, + "quotas": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "version": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "used_status": { + Type: schema.TypeString, + Computed: true, + }, + "host_id": { + Type: schema.TypeString, + Computed: true, + }, + "host_name": { + Type: schema.TypeString, + Computed: true, + }, + "charging_mode": { + Type: schema.TypeString, + Computed: true, + }, + "expire_time": { + Type: schema.TypeString, + Computed: true, + }, + "shared_quota": { + Type: schema.TypeString, + Computed: true, + }, + "enterprise_project_id": { + Type: schema.TypeString, + Computed: true, + }, + "enterprise_project_name": { + Type: schema.TypeString, + Computed: true, + }, + "tags": common.TagsComputedSchema(), + }, + }, + }, + }, + } +} + +func dataSourceQuotasRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var ( + cfg = meta.(*config.Config) + region = cfg.GetRegion(d) + epsId = cfg.DataGetEnterpriseProjectID(d) + limit = int32(20) + offset int32 + allQuotas []hssv5model.QuotaResourcesResponseInfo + ) + + client, err := cfg.HcHssV5Client(region) + if err != nil { + return diag.Errorf("error creating HSS v5 client: %s", err) + } + + for { + request := hssv5model.ListQuotasDetailRequest{ + Region: ®ion, + EnterpriseProjectId: utils.String(epsId), + Limit: utils.Int32(limit), + Offset: utils.Int32(offset), + Category: utils.StringIgnoreEmpty(d.Get("category").(string)), + Version: utils.StringIgnoreEmpty(d.Get("version").(string)), + QuotaStatus: utils.StringIgnoreEmpty(d.Get("status").(string)), + UsedStatus: utils.StringIgnoreEmpty(d.Get("used_status").(string)), + HostName: utils.StringIgnoreEmpty(d.Get("host_name").(string)), + ResourceId: utils.StringIgnoreEmpty(d.Get("quota_id").(string)), + ChargingMode: utils.StringIgnoreEmpty(convertChargingModeRequest(d.Get("charging_mode").(string))), + } + + listResp, listErr := client.ListQuotasDetail(&request) + if listErr != nil { + return diag.Errorf("error querying HSS quotas: %s", listErr) + } + + if listResp == nil || listResp.DataList == nil { + break + } + if len(*listResp.DataList) == 0 { + break + } + + allQuotas = append(allQuotas, *listResp.DataList...) + offset += limit + } + + uuId, err := uuid.GenerateUUID() + if err != nil { + return diag.Errorf("unable to generate ID: %s", err) + } + + d.SetId(uuId) + + mErr := multierror.Append(nil, + d.Set("region", region), + d.Set("quotas", flattenQuotas(allQuotas)), + ) + + return diag.FromErr(mErr.ErrorOrNil()) +} + +func flattenQuotas(quotas []hssv5model.QuotaResourcesResponseInfo) []interface{} { + if len(quotas) == 0 { + return nil + } + + rst := make([]interface{}, 0, len(quotas)) + for _, v := range quotas { + expireTime := "" + if v.ExpireTime != nil { + expireTime = utils.FormatTimeStampRFC3339(*(v.ExpireTime)/1000, false) + } + + rst = append(rst, map[string]interface{}{ + "id": v.ResourceId, + "version": v.Version, + "status": v.QuotaStatus, + "used_status": v.UsedStatus, + "host_id": v.HostId, + "host_name": v.HostName, + "charging_mode": convertChargingMode(v.ChargingMode), + "expire_time": expireTime, + "shared_quota": v.SharedQuota, + "enterprise_project_id": v.EnterpriseProjectId, + "enterprise_project_name": v.EnterpriseProjectName, + "tags": flattenTags(v.Tags), + }) + } + + return rst +} + +func flattenTags(tags *[]hssv5model.TagInfo) map[string]interface{} { + if tags == nil { + return nil + } + + rst := make(map[string]interface{}) + for _, tag := range *tags { + if tag.Key != nil { + rst[*tag.Key] = tag.Value + } + } + + return rst +}