From a93eeaf7e08714e872155865fa1cbd12f934fa2a Mon Sep 17 00:00:00 2001 From: Anna Khmelnitsky Date: Wed, 26 Jun 2024 03:31:46 +0000 Subject: [PATCH] Support vpc nat resource and its friends This PR offers support for: 1. vpc nat rule resource 2. vpc ip address allocation resource only addresses allocated from vpc subnet can be used as dest in nat rule 3. vpc nat data source this is helpful for specifying parent_path for nat rule Signed-off-by: Anna Khmelnitsky --- nsxt/data_source_nsxt_vpc_nat.go | 53 +++ nsxt/policy_search.go | 26 +- nsxt/policy_utils.go | 14 + nsxt/provider.go | 3 + ...resource_nsxt_vpc_ip_address_allocation.go | 222 +++++++++++++ nsxt/resource_nsxt_vpc_nat_rule.go | 294 +++++++++++++++++ nsxt/resource_nsxt_vpc_nat_rule_test.go | 308 ++++++++++++++++++ 7 files changed, 909 insertions(+), 11 deletions(-) create mode 100644 nsxt/data_source_nsxt_vpc_nat.go create mode 100644 nsxt/resource_nsxt_vpc_ip_address_allocation.go create mode 100644 nsxt/resource_nsxt_vpc_nat_rule.go create mode 100644 nsxt/resource_nsxt_vpc_nat_rule_test.go diff --git a/nsxt/data_source_nsxt_vpc_nat.go b/nsxt/data_source_nsxt_vpc_nat.go new file mode 100644 index 000000000..5ffa95999 --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_nat.go @@ -0,0 +1,53 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +var vpcNatTypes = []string{ + model.PolicyNat_NAT_TYPE_INTERNAL, + model.PolicyNat_NAT_TYPE_USER, + model.PolicyNat_NAT_TYPE_DEFAULT, + model.PolicyNat_NAT_TYPE_NAT64, +} + +func dataSourceNsxtVpcNat() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcNatRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "nat_type": { + Type: schema.TypeString, + Description: "Nat Type", + Required: true, + ValidateFunc: validation.StringInSlice(vpcNatTypes, false), + }, + "display_name": getDataSourceExtendedDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(false, false, true), + }, + } +} + +func dataSourceNsxtVpcNatRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + natType := d.Get("nat_type").(string) + query := make(map[string]string) + query["nat_type"] = natType + + _, err := policyDataSourceResourceReadWithValidation(d, connector, getSessionContext(d, m), "PolicyNat", query, false) + if err != nil { + return err + } + + return nil +} diff --git a/nsxt/policy_search.go b/nsxt/policy_search.go index 4628d3a5c..903db5e05 100644 --- a/nsxt/policy_search.go +++ b/nsxt/policy_search.go @@ -120,11 +120,11 @@ func listPolicyResourcesByNameAndType(connector client.Connector, context utl.Se return searchLMPolicyResources(connector, *buildPolicyResourcesQuery(&query, additionalQuery)) case utl.Global: return searchGMPolicyResources(connector, *buildPolicyResourcesQuery(&query, additionalQuery)) - case utl.Multitenancy: - return searchMultitenancyPolicyResources(connector, utl.DefaultOrgID, context.ProjectID, *buildPolicyResourcesQuery(&query, additionalQuery)) + case utl.Multitenancy, utl.VPC: + return searchMultitenancyResources(connector, context, *buildPolicyResourcesQuery(&query, additionalQuery)) } - return nil, errors.New("invalid ClientType %d") + return nil, errors.New("invalid ClientType") } func listInventoryResourcesByNameAndType(connector client.Connector, context utl.SessionContext, displayName string, resourceType string, additionalQuery *string) ([]*data.StructValue, error) { @@ -158,11 +158,11 @@ func listPolicyResourcesByID(connector client.Connector, context utl.SessionCont return searchLMPolicyResources(connector, *buildPolicyResourcesQuery(&query, additionalQuery)) case utl.Global: return searchGMPolicyResources(connector, *buildPolicyResourcesQuery(&query, additionalQuery)) - case utl.Multitenancy: - return searchMultitenancyPolicyResources(connector, utl.DefaultOrgID, context.ProjectID, *buildPolicyResourcesQuery(&query, additionalQuery)) + case utl.Multitenancy, utl.VPC: + return searchMultitenancyResources(connector, context, *buildPolicyResourcesQuery(&query, additionalQuery)) } - return nil, errors.New("invalid ClientType %d") + return nil, errors.New("invalid ClientType") } func listPolicyResourcesByNsxID(connector client.Connector, context utl.SessionContext, resourceID *string, additionalQuery *string) ([]*data.StructValue, error) { @@ -172,10 +172,10 @@ func listPolicyResourcesByNsxID(connector client.Connector, context utl.SessionC return searchLMPolicyResources(connector, *buildPolicyResourcesQuery(&query, additionalQuery)) case utl.Global: return searchGMPolicyResources(connector, *buildPolicyResourcesQuery(&query, additionalQuery)) - case utl.Multitenancy: - return searchMultitenancyPolicyResources(connector, utl.DefaultOrgID, context.ProjectID, *buildPolicyResourcesQuery(&query, additionalQuery)) + case utl.Multitenancy, utl.VPC: + return searchMultitenancyResources(connector, context, *buildPolicyResourcesQuery(&query, additionalQuery)) } - return nil, errors.New("invalid ClientType %d") + return nil, errors.New("invalid ClientType") } func buildPolicyResourcesQuery(query *string, additionalQuery *string) *string { @@ -241,7 +241,11 @@ func searchLMPolicyResources(connector client.Connector, query string) ([]*data. return searchLM(connector, query) } -func searchMultitenancyPolicyResources(connector client.Connector, org string, project string, query string) ([]*data.StructValue, error) { - query = query + fmt.Sprintf(" AND path:\\/orgs\\/%s\\/projects\\/%s*", org, project) +func searchMultitenancyResources(connector client.Connector, context utl.SessionContext, query string) ([]*data.StructValue, error) { + if len(context.VPCID) > 0 { + query = query + fmt.Sprintf(" AND path:\\/orgs\\/%s\\/projects\\/%s\\/vpcs\\/%s*", utl.DefaultOrgID, context.ProjectID, context.VPCID) + } else { + query = query + fmt.Sprintf(" AND path:\\/orgs\\/%s\\/projects\\/%s*", utl.DefaultOrgID, context.ProjectID) + } return searchLM(connector, query) } diff --git a/nsxt/policy_utils.go b/nsxt/policy_utils.go index 110d12f38..dc8472bee 100644 --- a/nsxt/policy_utils.go +++ b/nsxt/policy_utils.go @@ -407,6 +407,20 @@ func nsxtPolicyPathResourceImporterHelper(d *schema.ResourceData, m interface{}) return []*schema.ResourceData{d}, ErrNotAPolicyPath } +func nsxtParentPathResourceImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + importID := d.Id() + if isPolicyPath(importID) { + pathSegs := strings.Split(importID, "/") + segCount := len(pathSegs) + d.SetId(pathSegs[segCount-1]) + // to get parent path size, remove last two tokens (id and separator) plus two slashes + truncateSize := len(pathSegs[segCount-1]) + len(pathSegs[segCount-2]) + 2 + d.Set("parent_path", importID[:len(importID)-truncateSize]) + return []*schema.ResourceData{d}, nil + } + return []*schema.ResourceData{d}, ErrNotAPolicyPath +} + func isPolicyPath(policyPath string) bool { pathSegs := strings.Split(policyPath, "/") if len(pathSegs) < 4 { diff --git a/nsxt/provider.go b/nsxt/provider.go index d50f7eec1..736d9a180 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -328,6 +328,7 @@ func Provider() *schema.Provider { "nsxt_policy_gateway_flood_protection_profile": dataSourceNsxtPolicyGatewayFloodProtectionProfile(), "nsxt_manager_info": dataSourceNsxtManagerInfo(), "nsxt_policy_vpc": dataSourceNsxtPolicyVPC(), + "nsxt_vpc_nat": dataSourceNsxtVpcNat(), }, ResourcesMap: map[string]*schema.Resource{ @@ -499,6 +500,8 @@ func Provider() *schema.Provider { "nsxt_vpc_security_policy": resourceNsxtVPCSecurityPolicy(), "nsxt_vpc_group": resourceNsxtVPCGroup(), "nsxt_vpc_gateway_policy": resourceNsxtVPCGatewayPolicy(), + "nsxt_vpc_nat_rule": resourceNsxtPolicyVpcNatRule(), + "nsxt_vpc_ip_address_allocation": resourceNsxtVpcIPAddressAllocation(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_vpc_ip_address_allocation.go b/nsxt/resource_nsxt_vpc_ip_address_allocation.go new file mode 100644 index 000000000..efbeb9fdd --- /dev/null +++ b/nsxt/resource_nsxt_vpc_ip_address_allocation.go @@ -0,0 +1,222 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var vpcIPAddressAllocationIPAddressTypeValues = []string{ + model.VpcIpAddressAllocation_IP_ADDRESS_TYPE_IPV4, + model.VpcIpAddressAllocation_IP_ADDRESS_TYPE_IPV6, +} + +var vpcIPAddressAllocationIPAddressBlockVisibilityValues = []string{ + model.VpcIpAddressAllocation_IP_ADDRESS_BLOCK_VISIBILITY_EXTERNAL, + model.VpcIpAddressAllocation_IP_ADDRESS_BLOCK_VISIBILITY_PRIVATE, +} + +var vpcIPAddressAllocationSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(false, false, true)), + "ip_address_type": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcIPAddressAllocationIPAddressTypeValues, false), + Optional: true, + Default: model.VpcIpAddressAllocation_IP_ADDRESS_TYPE_IPV4, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpAddressType", + }, + }, + "allocation_ip": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "AllocationIp", + OmitIfEmpty: true, + }, + }, + "ip_address_block_visibility": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcIPAddressAllocationIPAddressBlockVisibilityValues, false), + Optional: true, + Default: model.VpcIpAddressAllocation_IP_ADDRESS_BLOCK_VISIBILITY_EXTERNAL, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpAddressBlockVisibility", + }, + }, +} + +func resourceNsxtVpcIPAddressAllocation() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcIPAddressAllocationCreate, + Read: resourceNsxtVpcIPAddressAllocationRead, + Update: resourceNsxtVpcIPAddressAllocationUpdate, + Delete: resourceNsxtVpcIPAddressAllocationDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(vpcIPAddressAllocationSchema), + } +} + +func resourceNsxtVpcIPAddressAllocationExists(sessionContext utl.SessionContext, parentPath string, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewIpAddressAllocationsClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcIPAddressAllocationCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateIDWithParent(d, m, resourceNsxtVpcIPAddressAllocationExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.VpcIpAddressAllocation{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcIPAddressAllocationSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating VpcIpAddressAllocation with ID %s", id) + + client := clientLayer.NewIpAddressAllocationsClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleCreateError("VpcIpAddressAllocation", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcIPAddressAllocationRead(d, m) +} + +func resourceNsxtVpcIPAddressAllocationRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcIpAddressAllocation ID") + } + + client := clientLayer.NewIpAddressAllocationsClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], parents[2], id) + if err != nil { + return handleReadError(d, "VpcIpAddressAllocation", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, vpcIPAddressAllocationSchema, "", nil) +} + +func resourceNsxtVpcIPAddressAllocationUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcIpAddressAllocation ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.VpcIpAddressAllocation{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcIPAddressAllocationSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewIpAddressAllocationsClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleUpdateError("VpcIpAddressAllocation", id, err) + } + + return resourceNsxtVpcIPAddressAllocationRead(d, m) +} + +func resourceNsxtVpcIPAddressAllocationDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcIpAddressAllocation ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewIpAddressAllocationsClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], id) + + if err != nil { + return handleDeleteError("VpcIpAddressAllocation", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_nat_rule.go b/nsxt/resource_nsxt_vpc_nat_rule.go new file mode 100644 index 000000000..4d975023c --- /dev/null +++ b/nsxt/resource_nsxt_vpc_nat_rule.go @@ -0,0 +1,294 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/nat" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var policyVpcNatRuleActionValues = []string{ + model.PolicyVpcNatRule_ACTION_SNAT, + model.PolicyVpcNatRule_ACTION_DNAT, + model.PolicyVpcNatRule_ACTION_REFLEXIVE, +} + +var policyVpcNatRuleFirewallMatchValues = []string{ + model.PolicyVpcNatRule_FIREWALL_MATCH_MATCH_EXTERNAL_ADDRESS, + model.PolicyVpcNatRule_FIREWALL_MATCH_MATCH_INTERNAL_ADDRESS, + model.PolicyVpcNatRule_FIREWALL_MATCH_BYPASS, +} + +var policyVpcNatRuleSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "parent_path": metadata.GetExtendedSchema(getPolicyPathSchema(true, true, "Policy path of the parent")), + "translated_network": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "TranslatedNetwork", + OmitIfEmpty: true, + }, + }, + "logging": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "Logging", + }, + }, + "destination_network": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "DestinationNetwork", + OmitIfEmpty: true, + }, + }, + "action": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(policyVpcNatRuleActionValues, false), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Action", + }, + }, + "firewall_match": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(policyVpcNatRuleFirewallMatchValues, false), + Optional: true, + Default: model.PolicyVpcNatRule_FIREWALL_MATCH_MATCH_INTERNAL_ADDRESS, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "FirewallMatch", + }, + }, + "source_network": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "SourceNetwork", + OmitIfEmpty: true, + }, + }, + "enabled": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "Enabled", + }, + }, + "sequence_number": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "SequenceNumber", + }, + }, +} + +func resourceNsxtPolicyVpcNatRule() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyVpcNatRuleCreate, + Read: resourceNsxtPolicyVpcNatRuleRead, + Update: resourceNsxtPolicyVpcNatRuleUpdate, + Delete: resourceNsxtPolicyVpcNatRuleDelete, + Importer: &schema.ResourceImporter{ + State: nsxtParentPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(policyVpcNatRuleSchema), + } +} + +func resourceNsxtPolicyVpcNatRuleExists(sessionContext utl.SessionContext, parentPath string, id string, connector client.Connector) (bool, error) { + var err error + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return false, pathErr + } + client := clientLayer.NewNatRulesClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyVpcNatRuleCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateIDWithParent(d, m, resourceNsxtPolicyVpcNatRuleExists) + if err != nil { + return err + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.PolicyVpcNatRule{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, policyVpcNatRuleSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating PolicyVpcNatRule with ID %s", id) + + client := clientLayer.NewNatRulesClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], parents[3], id, obj) + if err != nil { + return handleCreateError("PolicyVpcNatRule", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyVpcNatRuleRead(d, m) +} + +func resourceNsxtPolicyVpcNatRuleRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyVpcNatRule ID") + } + + client := clientLayer.NewNatRulesClient(connector) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + obj, err := client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err != nil { + return handleReadError(d, "PolicyVpcNatRule", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, policyVpcNatRuleSchema, "", nil) +} + +func resourceNsxtPolicyVpcNatRuleUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyVpcNatRule ID") + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.PolicyVpcNatRule{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, policyVpcNatRuleSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewNatRulesClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], parents[3], id, obj) + if err != nil { + return handleUpdateError("PolicyVpcNatRule", id, err) + } + + return resourceNsxtPolicyVpcNatRuleRead(d, m) +} + +func resourceNsxtPolicyVpcNatRuleDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyVpcNatRule ID") + } + + connector := getPolicyConnector(m) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + + client := clientLayer.NewNatRulesClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], parents[3], id) + + if err != nil { + return handleDeleteError("PolicyVpcNatRule", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_nat_rule_test.go b/nsxt/resource_nsxt_vpc_nat_rule_test.go new file mode 100644 index 000000000..eaae8b95b --- /dev/null +++ b/nsxt/resource_nsxt_vpc_nat_rule_test.go @@ -0,0 +1,308 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestVpcNatIPAllocationTestName = getAccTestResourceName() +var accTestVpcNatRuleCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "firewall_match": "MATCH_EXTERNAL_ADDRESS", + "logging": "true", + "action": "DNAT", + "source_network": "2.2.2.14", + "translated_network": "2.3.3.24", + "enabled": "false", + "sequence_number": "16", +} + +var accTestVpcNatRuleUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "firewall_match": "MATCH_INTERNAL_ADDRESS", + "logging": "false", + "action": "DNAT", + "source_network": "3.3.3.14", + "translated_network": "30.3.3.14", + "enabled": "true", + "sequence_number": "3", +} + +func TestAccResourceNsxtVpcNatRule_basic(t *testing.T) { + testResourceName := "nsxt_vpc_nat_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcNatRuleCheckDestroy(state, accTestVpcNatRuleUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcNatRuleTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(accTestVpcNatRuleCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcNatRuleCreateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcNatRuleCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "translated_network", accTestVpcNatRuleCreateAttributes["translated_network"]), + resource.TestCheckResourceAttr(testResourceName, "logging", accTestVpcNatRuleCreateAttributes["logging"]), + resource.TestCheckResourceAttrSet(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "action", accTestVpcNatRuleCreateAttributes["action"]), + resource.TestCheckResourceAttr(testResourceName, "firewall_match", accTestVpcNatRuleCreateAttributes["firewall_match"]), + resource.TestCheckResourceAttr(testResourceName, "source_network", accTestVpcNatRuleCreateAttributes["source_network"]), + resource.TestCheckResourceAttr(testResourceName, "enabled", accTestVpcNatRuleCreateAttributes["enabled"]), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", accTestVpcNatRuleCreateAttributes["sequence_number"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcNatRuleTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(accTestVpcNatRuleUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcNatRuleUpdateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcNatRuleUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "translated_network", accTestVpcNatRuleUpdateAttributes["translated_network"]), + resource.TestCheckResourceAttr(testResourceName, "logging", accTestVpcNatRuleUpdateAttributes["logging"]), + resource.TestCheckResourceAttrSet(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "action", accTestVpcNatRuleUpdateAttributes["action"]), + resource.TestCheckResourceAttr(testResourceName, "firewall_match", accTestVpcNatRuleUpdateAttributes["firewall_match"]), + resource.TestCheckResourceAttr(testResourceName, "source_network", accTestVpcNatRuleUpdateAttributes["source_network"]), + resource.TestCheckResourceAttr(testResourceName, "enabled", accTestVpcNatRuleUpdateAttributes["enabled"]), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", accTestVpcNatRuleUpdateAttributes["sequence_number"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcNatRuleMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(accTestVpcNatRuleCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcNatRule_changeTypes(t *testing.T) { + testResourceName := "nsxt_vpc_nat_rule.test" + sourceIP := "2.2.2.34" + ruleName := getAccTestResourceName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcNatRuleCheckDestroy(state, ruleName) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcNatRuleSnatTemplate(ruleName), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(ruleName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", ruleName), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "translated_network"), + resource.TestCheckResourceAttr(testResourceName, "logging", "false"), + resource.TestCheckNoResourceAttr(testResourceName, "destination_network"), + resource.TestCheckNoResourceAttr(testResourceName, "source_network"), + resource.TestCheckResourceAttr(testResourceName, "action", "SNAT"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + { + Config: testAccNsxtVpcNatRuleReflexiveTemplate(ruleName, sourceIP), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(ruleName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", ruleName), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "translated_network"), + resource.TestCheckResourceAttr(testResourceName, "logging", "false"), + resource.TestCheckNoResourceAttr(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "source_network", sourceIP), + resource.TestCheckResourceAttr(testResourceName, "action", "REFLEXIVE"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcNatRule_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_nat_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcNatRuleCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcNatRuleMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcNatRuleExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy VpcNatRule resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy VpcNatRule resource ID not set in resources") + } + + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtPolicyVpcNatRuleExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("VpcNatRule %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcNatRuleCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_nat_rule" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtPolicyVpcNatRuleExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("VpcNatRule %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtVpcNatRulePrerequisites() string { + return fmt.Sprintf(` +data "nsxt_vpc_nat" "test" { + %s + nat_type = "USER" +} + +resource "nsxt_vpc_ip_address_allocation" "test" { + %s + display_name = "%s" +} +`, testAccNsxtPolicyMultitenancyContext(), testAccNsxtPolicyMultitenancyContext(), accTestVpcNatIPAllocationTestName) +} + +func testAccNsxtVpcNatRuleTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcNatRuleCreateAttributes + } else { + attrMap = accTestVpcNatRuleUpdateAttributes + } + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + description = "%s" + translated_network = "%s" + logging = %s + destination_network = nsxt_vpc_ip_address_allocation.test.allocation_ip + action = "%s" + firewall_match = "%s" + source_network = "%s" + enabled = %s + sequence_number = %s + + tag { + scope = "scope1" + tag = "tag1" + } +}`, attrMap["display_name"], attrMap["description"], attrMap["translated_network"], attrMap["logging"], attrMap["action"], attrMap["firewall_match"], attrMap["source_network"], attrMap["enabled"], attrMap["sequence_number"]) +} + +func testAccNsxtVpcNatRuleMinimalistic() string { + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + destination_network = nsxt_vpc_ip_address_allocation.test.allocation_ip + translated_network = "%s" + action = "%s" +}`, accTestVpcNatRuleUpdateAttributes["display_name"], accTestVpcNatRuleUpdateAttributes["translated_network"], accTestVpcNatRuleUpdateAttributes["action"]) +} + +func testAccNsxtVpcNatRuleSnatTemplate(name string) string { + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + translated_network = nsxt_vpc_ip_address_allocation.test.allocation_ip + action = "SNAT" +}`, name) +} + +func testAccNsxtVpcNatRuleReflexiveTemplate(name string, sourceIP string) string { + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + source_network = "%s" + translated_network = nsxt_vpc_ip_address_allocation.test.allocation_ip + action = "REFLEXIVE" +}`, name, sourceIP) +}