From f16ac1172e5c988321b229d90c52f8e70b3fdddf Mon Sep 17 00:00:00 2001 From: ruwenqiang123 Date: Fri, 19 Jan 2024 18:39:59 +0800 Subject: [PATCH] feat(nat): add a new data source to query the list of private snat rule (#4036) --- docs/data-sources/nat_private_snat_rules.md | 75 +++++ huaweicloud/provider.go | 1 + ...huaweicloud_nat_private_snat_rules_test.go | 256 ++++++++++++++++++ ...urce_huaweicloud_nat_private_snat_rules.go | 250 +++++++++++++++++ 4 files changed, 582 insertions(+) create mode 100644 docs/data-sources/nat_private_snat_rules.md create mode 100644 huaweicloud/services/acceptance/nat/data_source_huaweicloud_nat_private_snat_rules_test.go create mode 100644 huaweicloud/services/nat/data_source_huaweicloud_nat_private_snat_rules.go diff --git a/docs/data-sources/nat_private_snat_rules.md b/docs/data-sources/nat_private_snat_rules.md new file mode 100644 index 0000000000..baa052fcc8 --- /dev/null +++ b/docs/data-sources/nat_private_snat_rules.md @@ -0,0 +1,75 @@ +--- +subcategory: "NAT Gateway (NAT)" +--- + +# huaweicloud_nat_private_snat_rules + +Use this data source to get the list of private SNAT rules. + +## Example Usage + +```hcl +variable "cidr" {} + +data "huaweicloud_nat_private_snat_rules" "test" { + cidr = var.cidr +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String) Specifies the region where the private SNAT rules are located. + If omitted, the provider-level region will be used. + +* `rule_id` - (Optional, String) Specifies the ID of the private SNAT rule. + +* `gateway_id` - (Optional, String) Specifies the ID of the private NAT gateway to which the private SNAT rules + belong. + +* `cidr` - (Optional, String) Specifies the CIDR block of the private SNAT rule. + +* `subnet_id` - (Optional, String) Specifies the ID of the subnet to which the private SNAT rule belongs. + +* `transit_ip_id` - (Optional, String) Specifies the ID of the transit IP associated with the private SNAT rule. + +* `transit_ip_address` - (Optional, String) Specifies the IP address of the transit IP associated with the private + SNAT rule. + +* `enterprise_project_id` - (Optional, String) Specifies the ID of the enterprise project to which the private SNAT + rules belong. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The data source ID. + +* `rules` - The list ot the private SNAT rules. + The [rules](#snatRules) structure is documented below. + + +The `rules` block supports: + +* `id` - The ID of the private SNAT rule. + +* `gateway_id` - The ID of the private NAT gateway to which the private SNAT rule belongs. + +* `cidr` - The CIDR block of the private SNAT rule. + +* `subnet_id` - The ID of the subnet to which the private SNAT rule belongs. + +* `description` - The description of the private SNAT rule. + +* `status` - The status of the private SNAT rule. + +* `transit_ip_id` - The ID of the transit IP associated with the private SNAT rule. + +* `transit_ip_address` - The IP address of the transit IP associated with the private SNAT rule. + +* `created_at` - The creation time of the private SNAT rule. + +* `updated_at` - The latest update time of the private SNAT rule. + +* `enterprise_project_id` - The ID of the enterprise project to which the private SNAT rule belongs. diff --git a/huaweicloud/provider.go b/huaweicloud/provider.go index fa94db82f1..b2da70f608 100644 --- a/huaweicloud/provider.go +++ b/huaweicloud/provider.go @@ -571,6 +571,7 @@ func Provider() *schema.Provider { "huaweicloud_nat_gateway": nat.DataSourcePublicGateway(), "huaweicloud_nat_gateways": nat.DataSourcePublicGateways(), "huaweicloud_nat_private_gateways": nat.DataSourcePrivateGateways(), + "huaweicloud_nat_private_snat_rules": nat.DataSourcePrivateSnatRules(), "huaweicloud_nat_private_transit_ips": nat.DataSourcePrivateTransitIps(), "huaweicloud_networking_port": vpc.DataSourceNetworkingPortV2(), diff --git a/huaweicloud/services/acceptance/nat/data_source_huaweicloud_nat_private_snat_rules_test.go b/huaweicloud/services/acceptance/nat/data_source_huaweicloud_nat_private_snat_rules_test.go new file mode 100644 index 0000000000..c458108a75 --- /dev/null +++ b/huaweicloud/services/acceptance/nat/data_source_huaweicloud_nat_private_snat_rules_test.go @@ -0,0 +1,256 @@ +package nat + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance/common" +) + +func TestAccDatasourcePrivateSnatRules_basic(t *testing.T) { + var ( + name = acceptance.RandomAccResourceName() + baseConfig = testAccPrivateSnatRulesDataSource_base(name) + dataSourceName = "data.huaweicloud_nat_private_snat_rules.test" + dc = acceptance.InitDataSourceCheck(dataSourceName) + + bySnatId = "data.huaweicloud_nat_private_snat_rules.filter_by_rule_id" + dcBySnatId = acceptance.InitDataSourceCheck(bySnatId) + + byGatewayId = "data.huaweicloud_nat_private_snat_rules.filter_by_gateway_id" + dcByGatewayId = acceptance.InitDataSourceCheck(byGatewayId) + + byCidr = "data.huaweicloud_nat_private_snat_rules.filter_by_cidr" + dcByCidr = acceptance.InitDataSourceCheck(byCidr) + + bySubnetId = "data.huaweicloud_nat_private_snat_rules.filter_by_subnet_id" + dcBySubnetId = acceptance.InitDataSourceCheck(bySubnetId) + + byTransitIpId = "data.huaweicloud_nat_private_snat_rules.filter_by_transit_ip_id" + dcByTransitIpId = acceptance.InitDataSourceCheck(byTransitIpId) + + byTransitIpAddress = "data.huaweicloud_nat_private_snat_rules.filter_by_transit_ip_address" + dcByTransitIpAddress = acceptance.InitDataSourceCheck(byTransitIpAddress) + + byEps = "data.huaweicloud_nat_private_snat_rules.filter_by_eps" + dcByEps = acceptance.InitDataSourceCheck(byEps) + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t) }, + ProviderFactories: acceptance.TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDatasourcePrivateSnatRules_basic(baseConfig), + Check: resource.ComposeTestCheckFunc( + dc.CheckResourceExists(), + dcBySnatId.CheckResourceExists(), + resource.TestCheckOutput("rule_id_filter_is_useful", "true"), + + dcByGatewayId.CheckResourceExists(), + resource.TestCheckOutput("gateway_id_filter_is_useful", "true"), + + dcByCidr.CheckResourceExists(), + resource.TestCheckOutput("cidr_filter_is_useful", "true"), + + dcBySubnetId.CheckResourceExists(), + resource.TestCheckOutput("subnet_id_filter_is_useful", "true"), + + dcByTransitIpId.CheckResourceExists(), + resource.TestCheckOutput("transit_ip_id_filter_is_useful", "true"), + + dcByTransitIpAddress.CheckResourceExists(), + resource.TestCheckOutput("transit_ip_address_filter_is_useful", "true"), + + dcByEps.CheckResourceExists(), + resource.TestCheckOutput("eps_filter_is_useful", "true"), + ), + }, + }, + }) +} + +func testAccPrivateSnatRulesDataSource_base(name string) string { + return fmt.Sprintf(` +%[1]s + +resource "huaweicloud_vpc" "transit_ip_used" { + name = "%[2]s-transit" + cidr = "192.168.0.0/24" +} + +resource "huaweicloud_vpc_subnet" "transit_ip_used" { + vpc_id = huaweicloud_vpc.transit_ip_used.id + name = "%[2]s-transit" + cidr = "192.168.0.0/24" + gateway_ip = "192.168.0.1" +} + +resource "huaweicloud_nat_private_transit_ip" "test" { + subnet_id = huaweicloud_vpc_subnet.transit_ip_used.id + enterprise_project_id = "0" +} + +resource "huaweicloud_nat_private_gateway" "test" { + subnet_id = huaweicloud_vpc_subnet.test.id + name = "%[2]s" + enterprise_project_id = "0" +} + +resource "huaweicloud_nat_private_snat_rule" "test" { + gateway_id = huaweicloud_nat_private_gateway.test.id + description = "Created by acc test" + transit_ip_id = huaweicloud_nat_private_transit_ip.test.id + subnet_id = huaweicloud_vpc_subnet.test.id +} +`, common.TestBaseNetwork(name), name) +} + +func testAccDatasourcePrivateSnatRules_basic(baseConfig string) string { + return fmt.Sprintf(` +%[1]s + +data "huaweicloud_nat_private_snat_rules" "test" { + depends_on = [ + huaweicloud_nat_private_snat_rule.test + ] +} + + +locals { + rule_id = data.huaweicloud_nat_private_snat_rules.test.rules[0].id +} + +data "huaweicloud_nat_private_snat_rules" "filter_by_rule_id" { + rule_id = local.rule_id +} + +locals { + rule_id_filter_result = [ + for v in data.huaweicloud_nat_private_snat_rules.filter_by_rule_id.rules[*].id : v == local.rule_id + ] +} + +output "rule_id_filter_is_useful" { + value = alltrue(local.rule_id_filter_result) && length(local.rule_id_filter_result) > 0 +} + +locals { + gateway_id = data.huaweicloud_nat_private_snat_rules.test.rules[0].gateway_id +} + +data "huaweicloud_nat_private_snat_rules" "filter_by_gateway_id" { + gateway_id = local.gateway_id +} + +locals { + gateway_id_filter_result = [ + for v in data.huaweicloud_nat_private_snat_rules.filter_by_gateway_id.rules[*].gateway_id : + v == local.gateway_id + ] +} + +output "gateway_id_filter_is_useful" { + value = alltrue(local.gateway_id_filter_result) && length(local.gateway_id_filter_result) > 0 +} + +locals { + cidr = data.huaweicloud_nat_private_snat_rules.test.rules[0].cidr +} + +data "huaweicloud_nat_private_snat_rules" "filter_by_cidr" { + cidr = local.cidr +} + +locals { + cidr_filter_result = [ + for v in data.huaweicloud_nat_private_snat_rules.filter_by_cidr.rules[*].cidr : v == local.cidr + ] +} + +output "cidr_filter_is_useful" { + value = alltrue(local.cidr_filter_result) && length(local.cidr_filter_result) > 0 +} + +locals { + transit_ip_id = data.huaweicloud_nat_private_snat_rules.test.rules[0].transit_ip_id +} + +data "huaweicloud_nat_private_snat_rules" "filter_by_transit_ip_id" { + transit_ip_id = local.transit_ip_id +} + +locals { + transit_ip_id_filter_result = [ + for v in data.huaweicloud_nat_private_snat_rules.filter_by_transit_ip_id.rules[*].transit_ip_id : + v == local.transit_ip_id + ] +} + +output "transit_ip_id_filter_is_useful" { + value = alltrue(local.transit_ip_id_filter_result) && length(local.transit_ip_id_filter_result) > 0 +} + +locals { + transit_ip_address = data.huaweicloud_nat_private_snat_rules.test.rules[0].transit_ip_address +} + +data "huaweicloud_nat_private_snat_rules" "filter_by_transit_ip_address" { + transit_ip_address = local.transit_ip_address +} + +locals { + transit_ip_address_filter_result = [ + for v in data.huaweicloud_nat_private_snat_rules.filter_by_transit_ip_address.rules[*].transit_ip_address : + v == local.transit_ip_address + ] +} + +output "transit_ip_address_filter_is_useful" { + value = alltrue(local.transit_ip_address_filter_result) && length(local.transit_ip_address_filter_result) > 0 +} + +locals { + enterprise_project_id = data.huaweicloud_nat_private_snat_rules.test.rules[0].enterprise_project_id +} + +data "huaweicloud_nat_private_snat_rules" "filter_by_eps" { + enterprise_project_id = local.enterprise_project_id +} + +locals { + eps_filter_result = [ + for v in data.huaweicloud_nat_private_snat_rules.filter_by_eps.rules[*].enterprise_project_id : + v == local.enterprise_project_id + ] +} + +output "eps_filter_is_useful" { + value = alltrue(local.eps_filter_result) && length(local.eps_filter_result) > 0 +} + +locals { + subnet_id = [ + for v in data.huaweicloud_nat_private_snat_rules.test.rules[*].subnet_id : v if v != "" + ] +} + +data "huaweicloud_nat_private_snat_rules" "filter_by_subnet_id" { + subnet_id = local.subnet_id[0] +} + +locals { + subnet_id_filter_result = [ + for v in data.huaweicloud_nat_private_snat_rules.filter_by_subnet_id.rules[*].subnet_id : + v == local.subnet_id[0] + ] +} + +output "subnet_id_filter_is_useful" { + value = alltrue(local.subnet_id_filter_result) && length(local.subnet_id_filter_result) > 0 +} +`, baseConfig) +} diff --git a/huaweicloud/services/nat/data_source_huaweicloud_nat_private_snat_rules.go b/huaweicloud/services/nat/data_source_huaweicloud_nat_private_snat_rules.go new file mode 100644 index 0000000000..80fd7e2086 --- /dev/null +++ b/huaweicloud/services/nat/data_source_huaweicloud_nat_private_snat_rules.go @@ -0,0 +1,250 @@ +package nat + +import ( + "context" + "encoding/json" + "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/pagination" + + "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: NAT GET v3/{project_id}/private-nat/snat-rules +func DataSourcePrivateSnatRules() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourcePrivateSnatRulesRead, + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + Description: "The region where the private SNAT rules are located.", + }, + "rule_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the private SNAT rule.", + }, + "gateway_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the private NAT gateway to which the private SNAT rules belong.", + }, + "cidr": { + Type: schema.TypeString, + Optional: true, + Description: "The CIDR block of the private SNAT rule.", + }, + "subnet_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the subnet to which the private SNAT rule belongs.", + }, + "transit_ip_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the transit IP associated with the private SNAT rule.", + }, + "transit_ip_address": { + Type: schema.TypeString, + Optional: true, + Description: "The IP address of the transit IP associated with the private SNAT rule.", + }, + "enterprise_project_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the enterprise project to which the private SNAT rules belong.", + }, + "rules": { + Type: schema.TypeList, + Elem: snatRulesSchema(), + Computed: true, + Description: "The list of the private SNAT rules.", + }, + }, + } +} + +func snatRulesSchema() *schema.Resource { + sc := schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the private SNAT rule.", + }, + "gateway_id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the private NAT gateway to which the private SNAT rule belongs.", + }, + "cidr": { + Type: schema.TypeString, + Computed: true, + Description: "The CIDR block of the private SNAT rule.", + }, + "subnet_id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the subnet to which the private SNAT rule belongs.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the private SNAT rule.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "The status of the private SNAT rule.", + }, + "transit_ip_id": { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the transit IP associated with the private SNAT rule.", + }, + "transit_ip_address": { + Type: schema.TypeString, + Optional: true, + Description: "The IP address of the transit IP associated with the private SNAT rule.", + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: "The creation time of the private SNAT rule.", + }, + "updated_at": { + Type: schema.TypeString, + Computed: true, + Description: "The latest update time of the private SNAT rule.", + }, + "enterprise_project_id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the enterprise project to which the private SNAT rule belongs.", + }, + }, + } + return &sc +} + +func dataSourcePrivateSnatRulesRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + cfg := meta.(*config.Config) + region := cfg.GetRegion(d) + + // listSnatRules: Query the private SNAT rule list + var ( + listSnatRulesHttpUrl = "v3/{project_id}/private-nat/snat-rules" + listSnatRulesProduct = "nat" + ) + listSnatRulesClient, err := cfg.NewServiceClient(listSnatRulesProduct, region) + if err != nil { + return diag.Errorf("error creating NAT client: %s", err) + } + + listSnatRulesPath := listSnatRulesClient.Endpoint + listSnatRulesHttpUrl + listSnatRulesPath = strings.ReplaceAll(listSnatRulesPath, "{project_id}", listSnatRulesClient.ProjectID) + + listSnatRulesQueryParams := buildListSnatRulesQueryParams(d, cfg) + listSnatRulesPath += listSnatRulesQueryParams + + listSnatRulesResp, err := pagination.ListAllItems( + listSnatRulesClient, + "marker", + listSnatRulesPath, + &pagination.QueryOpts{MarkerField: ""}) + + if err != nil { + return common.CheckDeletedDiag(d, err, "error retrieving private SNAT rule") + } + + listSnatRulesRespJson, err := json.Marshal(listSnatRulesResp) + if err != nil { + return diag.FromErr(err) + } + var listSnatRulesRespBody interface{} + err = json.Unmarshal(listSnatRulesRespJson, &listSnatRulesRespBody) + if err != nil { + return diag.FromErr(err) + } + + uuid, err := uuid.GenerateUUID() + if err != nil { + return diag.Errorf("unable to generate ID: %s", err) + } + d.SetId(uuid) + + var mErr *multierror.Error + mErr = multierror.Append( + mErr, + d.Set("region", region), + d.Set("rules", flattenListSnatRuleResponseBody(listSnatRulesRespBody)), + ) + + return diag.FromErr(mErr.ErrorOrNil()) +} + +func flattenListSnatRuleResponseBody(resp interface{}) []interface{} { + if resp == nil { + return nil + } + + curJson := utils.PathSearch("snat_rules", resp, make([]interface{}, 0)) + curArray := curJson.([]interface{}) + rst := make([]interface{}, 0, len(curArray)) + for _, v := range curArray { + rst = append(rst, map[string]interface{}{ + "id": utils.PathSearch("id", v, nil), + "gateway_id": utils.PathSearch("gateway_id", v, nil), + "cidr": utils.PathSearch("cidr", v, nil), + "subnet_id": utils.PathSearch("virsubnet_id", v, nil), + "description": utils.PathSearch("description", v, nil), + "status": utils.PathSearch("status", v, nil), + "transit_ip_id": utils.PathSearch("transit_ip_associations[0].transit_ip_id", v, nil), + "transit_ip_address": utils.PathSearch("transit_ip_associations[0].transit_ip_address", v, nil), + "created_at": utils.PathSearch("created_at", v, nil), + "updated_at": utils.PathSearch("updated_at", v, nil), + "enterprise_project_id": utils.PathSearch("enterprise_project_id", v, nil), + }) + } + return rst +} + +func buildListSnatRulesQueryParams(d *schema.ResourceData, cfg *config.Config) string { + res := "" + epsID := cfg.GetEnterpriseProjectID(d) + + if v, ok := d.GetOk("rule_id"); ok { + res = fmt.Sprintf("%s&id=%v", res, v) + } + if v, ok := d.GetOk("gateway_id"); ok { + res = fmt.Sprintf("%s&gateway_id=%v", res, v) + } + if v, ok := d.GetOk("cidr"); ok { + res = fmt.Sprintf("%s&cidr=%v", res, v) + } + if v, ok := d.GetOk("subnet_id"); ok { + res = fmt.Sprintf("%s&virsubnet_id=%v", res, v) + } + if v, ok := d.GetOk("transit_ip_id"); ok { + res = fmt.Sprintf("%s&transit_ip_id=%v", res, v) + } + if v, ok := d.GetOk("transit_ip_address"); ok { + res = fmt.Sprintf("%s&transit_ip_address=%v", res, v) + } + if epsID != "" { + res = fmt.Sprintf("%s&enterprise_project_id=%v", res, epsID) + } + if res != "" { + res = "?" + res[1:] + } + return res +}