From 3be751f47388d1537c604ef5990cdc14ccb5b6fb Mon Sep 17 00:00:00 2001 From: Anna Khmelnitsky Date: Tue, 25 Mar 2025 12:39:40 -0700 Subject: [PATCH] Support constraint resource Signed-off-by: Anna Khmelnitsky --- api/infra/constraint.go | 137 ++++++++ docs/resources/policy_constraint.md | 154 +++++++++ nsxt/data_source_nsxt_vpc_test.go | 4 +- nsxt/provider.go | 1 + nsxt/resource_nsxt_policy_constraint.go | 315 ++++++++++++++++++ nsxt/resource_nsxt_policy_constraint_test.go | 266 +++++++++++++++ ...urce_nsxt_vpc_connectivity_profile_test.go | 4 +- nsxt/resource_nsxt_vpc_test.go | 4 +- nsxt/utils_test.go | 9 +- 9 files changed, 884 insertions(+), 10 deletions(-) create mode 100644 api/infra/constraint.go create mode 100644 docs/resources/policy_constraint.md create mode 100644 nsxt/resource_nsxt_policy_constraint.go create mode 100644 nsxt/resource_nsxt_policy_constraint_test.go diff --git a/api/infra/constraint.go b/api/infra/constraint.go new file mode 100644 index 000000000..c14e7eaa4 --- /dev/null +++ b/api/infra/constraint.go @@ -0,0 +1,137 @@ +//nolint:revive +package infra + +// The following file has been autogenerated. Please avoid any changes! +import ( + "errors" + + vapiProtocolClient_ "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + client0 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra" + model0 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + client1 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/infra" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" +) + +type ConstraintClientContext utl.ClientContext + +func NewConstraintsClient(sessionContext utl.SessionContext, connector vapiProtocolClient_.Connector) *ConstraintClientContext { + var client interface{} + + switch sessionContext.ClientType { + + case utl.Local: + client = client0.NewConstraintsClient(connector) + + case utl.Multitenancy: + client = client1.NewConstraintsClient(connector) + + default: + return nil + } + return &ConstraintClientContext{Client: client, ClientType: sessionContext.ClientType, ProjectID: sessionContext.ProjectID, VPCID: sessionContext.VPCID} +} + +func (c ConstraintClientContext) Get(constraintIdParam string) (model0.Constraint, error) { + var obj model0.Constraint + var err error + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.ConstraintsClient) + obj, err = client.Get(constraintIdParam) + if err != nil { + return obj, err + } + + case utl.Multitenancy: + client := c.Client.(client1.ConstraintsClient) + obj, err = client.Get(utl.DefaultOrgID, c.ProjectID, constraintIdParam) + if err != nil { + return obj, err + } + + default: + return obj, errors.New("invalid infrastructure for model") + } + return obj, err +} + +func (c ConstraintClientContext) Patch(constraintIdParam string, constraintParam model0.Constraint) error { + var err error + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.ConstraintsClient) + err = client.Patch(constraintIdParam, constraintParam) + + case utl.Multitenancy: + client := c.Client.(client1.ConstraintsClient) + err = client.Patch(utl.DefaultOrgID, c.ProjectID, constraintIdParam, constraintParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return err +} + +func (c ConstraintClientContext) Update(constraintIdParam string, constraintParam model0.Constraint) (model0.Constraint, error) { + var err error + var obj model0.Constraint + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.ConstraintsClient) + obj, err = client.Update(constraintIdParam, constraintParam) + + case utl.Multitenancy: + client := c.Client.(client1.ConstraintsClient) + obj, err = client.Update(utl.DefaultOrgID, c.ProjectID, constraintIdParam, constraintParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return obj, err +} + +func (c ConstraintClientContext) Delete(constraintIdParam string) error { + var err error + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.ConstraintsClient) + err = client.Delete(constraintIdParam) + + case utl.Multitenancy: + client := c.Client.(client1.ConstraintsClient) + err = client.Delete(utl.DefaultOrgID, c.ProjectID, constraintIdParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return err +} + +func (c ConstraintClientContext) List(cursorParam *string, includeMarkForDeleteObjectsParam *bool, includedFieldsParam *string, pageSizeParam *int64, sortAscendingParam *bool, sortByParam *string) (model0.ConstraintListResult, error) { + var err error + var obj model0.ConstraintListResult + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.ConstraintsClient) + obj, err = client.List(cursorParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam) + + case utl.Multitenancy: + client := c.Client.(client1.ConstraintsClient) + obj, err = client.List(utl.DefaultOrgID, c.ProjectID, cursorParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return obj, err +} diff --git a/docs/resources/policy_constraint.md b/docs/resources/policy_constraint.md new file mode 100644 index 000000000..f3b350b76 --- /dev/null +++ b/docs/resources/policy_constraint.md @@ -0,0 +1,154 @@ +--- +subcategory: "Beta" +page_title: "NSXT: nsxt_policy_constraint" +description: A resource to configure a Constraint (Quota). +--- + +# nsxt_policy_constraint + +This resource provides a method for the management of a Constraint. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_policy_constraint" "test" { + display_name = "demo-quota" + description = "Terraform provisioned Constraint" + message = "too many objects mate" + + target { + path_prefix = "/orgs/default/projects/demo" + } + + instance_count { + count = 4 + target_resource_type = "StaticRoutes" + } + + instance_count { + count = 1 + target_resource_type = "Infra.Tier1.PolicyDnsForwarder" + } + + instance_count { + count = 20 + target_resource_type = "Infra.Domain.Group" + } +} +``` + +## Example Usage - Multi-Tenancy + +```hcl +resource "nsxt_policy_constraint" "test" { + context { + project_id = "demo" + } + + display_name = "demo1-quota" + + target { + path_prefix = "/orgs/default/projects/demo/vpcs/demo1" + } + + instance_count { + count = 4 + target_resource_type = "Org.Project.Vpc.PolicyNat.PolicyVpcNatRule" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `context` - (Optional) The context which the object belongs to +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `message` - (Optional) User friendly message to be shown to users upon violation. +* `target` - (Optional) Targets for the constraints to be enforced + * `path_prefix` - (Optional) Prefix match to the path +* `instance_count` - (Optional) Constraint details + * `target_resource_type` - (Required) Type of the resource that should be limited in instance count (refer to the table below) + * `operator` - (Optional) Either `<=` or `<`. Default is `<=` + * `count` - (Required) Limit of instances +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + + +## Target resource types + +|Object|project + VPC|project only|VPC only| +|------|-------------|------------|--------| +|Group|Group|Infra.Domain.Group|Org.Project.Vpc.Group| +|Service||Infra.Service|| +|Service Entry||Infra.Service.ServiceEntry|| +|TLS Certificate||Infra.TlsCertificate|| +|TLS CRL||Infra.TlsCrl|| +|All Firewall Rules|Rule||| +|Security Policy|SecurityPolicy|Infra.Domain.SecurityPolicy|Org.Project.Vpc.SecurityPolicy| +|Security Policy Rule|SecurityPolicy.Rule|Infra.Domain.SecurityPolicy.Rule|Org.Project.Vpc.SecurityPolicy.Rule| +|Gateway Policy|SecurityPolicy|Infra.Domain.SecurityPolicy|Org.Project.Vpc.SecurityPolicy| +|Gateway Policy Rule|GatewayPolicy.Rule|Infra.Domain.GatewayPolicy.Rule|Org.Project.Vpc.GatewayPolicy.Rule| +|IDS Security Policy||Infra.Domain.IdsPolicy|| +|IDS Security Policy Rule||Infra.Domain.IdsPolicy.Rule|| +|Session Timer Profile||Infra.PolicyFirewallSessionTimerProfile|| +|Flood Protection Profile||Infra.FloodProtectionProfile|| +|DNS Security Profile||Infra.DnsSecurityProfile|| +|Context Profile||Infra.PolicyContextProfile|| +|l7 Access Profile||Infra.L7AccessProfile|| +|Tier1 Gateway||Infra.Tier1|| +|Segment||Infra.Segment|| +|Segment Port||Infra.Segment.SegmentPort|| +|Subnet|||Org.Project.Vpc.Subnet| +|Subnet Port|||Org.Project.Vpc.Subnet.SubnetPort| +|Segment Security Profile||Infra.SegmentSecurityProfile|| +|Segment QoS Profile||Infra.QosProfile|| +|Segment IP Discovery Profile||Infra.IpDiscoveryProfile|| +|Segment MAC Discovery Profile||Infra.MacDiscoveryProfile|| +|Segment Spoof Guard Profile||Infra.SpoofGuardProfile|| +|IPv6 NDRA Profile||Infra.Ipv6NdraProfile|| +|IPv6 DAD Profile||Infra.Ipv6DadProfile|| +|Gateway QoS Profile||Infra.GatewayQosProfile|| +|Static Routes|StaticRoutes|Infra.Tier1.StaticRoutes|Org.Project.Vpc.StaticRoutes| +|NAT Rule|NatRule|Infra.Tier1.PolicyNat.PolicyNatRule|Org.Project.Vpc.PolicyNat.PolicyNatRule| +|DNS Forwarder Zone||Infra.PolicyDnsForwarderZone|| +|DNS Forwarder||Infra.Tier1.PolicyDnsForwarder|| +|IP Address Block||Infra.IpAddressBlock|| +|IP Address Pool||Infra.IpAddressPool|| +|IP Address Pool Subnet||Infra.IpAddressPool.IpAddressPoolSubnet|| +|IP Address Allocation||Infra.IpAddressPool.IpAddressAllocation|| +|DHCP Server Config||Infra.DhcpServerConfig|| +|IPSec VPN Service||Infra.Tier1.IPSecVpnService|| +|IPSec VPN Session||Infra.Tier1.IPSecVpnService.IPSecVpnSession|| +|IPSec VPN Local Endpoint||Infra.Tier1.IPSecVpnService.IPSecVpnLocalEndpoint|| +|IPSec VPN Tunnel Profile||Infra.IPSecVpnTunnelProfile|| +|IPSec VPN IKE Profile||Infra.IPSecVpnIkeProfile|| +|IPSec VPN DPD Profile||Infra.IPSecVpnDpdProfile|| +|L2 VPN Service||Infra.Tier1.L2VpnService|| +|L2 VPN Session||Infra.Tier1.L2VpnService.L2VpnSession|| +|VPC||Org.Project.Vpc|| + + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_constraint.test PATH +``` + +The above command imports Constraint named `test` with the NSX path `PATH`. diff --git a/nsxt/data_source_nsxt_vpc_test.go b/nsxt/data_source_nsxt_vpc_test.go index 80ca3cd71..d565b7ce0 100644 --- a/nsxt/data_source_nsxt_vpc_test.go +++ b/nsxt/data_source_nsxt_vpc_test.go @@ -56,7 +56,7 @@ func testAccDataSourceNsxtVPCCreate(name string) error { } ipBlockID := newUUID() - err = testAccDataSourceNsxtPolicyIPBlockCreate(testAccGetProjectContext(), name, ipBlockID, "192.168.240.0/24", true) + err = testAccDataSourceNsxtPolicyIPBlockCreate(testAccGetMultitenancyContext(), name, ipBlockID, "192.168.240.0/24", true) if err != nil { return err } @@ -108,7 +108,7 @@ func testAccDataSourceNsxtVPCDeleteByName(name string) error { if err != nil { return handleDeleteError("VPC", *objInList.Id, err) } - return testAccDataSourceNsxtPolicyIPBlockDeleteByName(testAccGetProjectContext(), name) + return testAccDataSourceNsxtPolicyIPBlockDeleteByName(testAccGetMultitenancyContext(), name) } } return fmt.Errorf("error while deleting VPC '%s': resource not found", name) diff --git a/nsxt/provider.go b/nsxt/provider.go index dcf3a520f..593b75f46 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -546,6 +546,7 @@ func Provider() *schema.Provider { "nsxt_policy_edge_high_availability_profile": resourceNsxtPolicyEdgeHighAvailabilityProfile(), "nsxt_policy_edge_cluster": resourceNsxtPolicyEdgeCluster(), "nsxt_policy_ip_block_quota": resourceNsxtPolicyIpBlockQuota(), + "nsxt_policy_constraint": resourceNsxtPolicyConstraint(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_policy_constraint.go b/nsxt/resource_nsxt_policy_constraint.go new file mode 100644 index 000000000..642627eb4 --- /dev/null +++ b/nsxt/resource_nsxt_policy_constraint.go @@ -0,0 +1,315 @@ +// © Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// SPDX-License-Identifier: MPL-2.0 + +package nsxt + +import ( + "fmt" + "log" + "reflect" + "strings" + + "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/terraform-provider-nsxt/api/infra" + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var constraintTargetOwnerTypeValues = []string{ + model.Constraint_TARGET_OWNER_TYPE_GM, + model.Constraint_TARGET_OWNER_TYPE_LM, + model.Constraint_TARGET_OWNER_TYPE_ALL, +} + +var constraintPathExample = getMultitenancyPathExample("/infra/constraints/[constraint]") + +var constraintSchema = 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, false)), + "instance_count": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "count": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "Count", + }, + }, + "operator": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "<=", + ValidateFunc: validation.StringInSlice([]string{"<=", "<"}, false), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Operator", + }, + }, + "target_resource_type": { + Schema: schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "TargetResourceType", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "ConstraintExpressions", + PolymorphicType: metadata.PolymorphicTypeFlatten, + ReflectType: reflect.TypeOf(model.EntityInstanceCountConstraintExpression{}), + BindingType: model.EntityInstanceCountConstraintExpressionBindingType(), + TypeIdentifier: metadata.ResourceTypeTypeIdentifier, + ResourceType: model.ConstraintExpression_RESOURCE_TYPE_ENTITYINSTANCECOUNTCONSTRAINTEXPRESSION, + }, + }, + "target_owner_type": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(constraintTargetOwnerTypeValues, false), + Default: model.Constraint_TARGET_OWNER_TYPE_ALL, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "TargetOwnerType", + }, + }, + "target": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "path_prefix": { + Schema: schema.Schema{ + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: pathPreffixDiffSupress, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "PathPrefix", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "Targets", + ReflectType: reflect.TypeOf(model.ConstraintTarget{}), + }, + }, + "message": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Message", + }, + }, +} + +func resourceNsxtPolicyConstraint() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyConstraintCreate, + Read: resourceNsxtPolicyConstraintRead, + Update: resourceNsxtPolicyConstraintUpdate, + Delete: resourceNsxtPolicyConstraintDelete, + Importer: &schema.ResourceImporter{ + State: getPolicyPathOrIDResourceImporter(constraintPathExample), + }, + Schema: metadata.GetSchemaFromExtendedSchema(constraintSchema), + } +} + +func addTrailingSlash(s string) string { + if strings.HasSuffix(s, "/") { + return s + } + + return s + "/" +} + +func pathPreffixDiffSupress(k, oldVal, newVal string, d *schema.ResourceData) bool { + return addTrailingSlash(oldVal) == addTrailingSlash(newVal) +} + +func resourceNsxtPolicyConstraintExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + + client := clientLayer.NewConstraintsClient(sessionContext, connector) + _, err = client.Get(id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func polishConstraintTargets(constraint *model.Constraint) { + // To improve user experience, add trailing slash to PathPrefix if needed + if len(constraint.Targets) == 0 { + return + } + + for i, target := range constraint.Targets { + if target.PathPrefix != nil { + withSlash := addTrailingSlash(*target.PathPrefix) + constraint.Targets[i].PathPrefix = &withSlash + } + } + +} + +func resourceNsxtPolicyConstraintCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtPolicyConstraintExists) + if err != nil { + return err + } + + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.Constraint{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, constraintSchema, "", nil); err != nil { + return err + } + + polishConstraintTargets(&obj) + + log.Printf("[INFO] Creating Constraint with ID %s", id) + + client := clientLayer.NewConstraintsClient(getSessionContext(d, m), connector) + err = client.Patch(id, obj) + if err != nil { + return handleCreateError("Constraint", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyConstraintRead(d, m) +} + +func resourceNsxtPolicyConstraintRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Constraint ID") + } + + client := clientLayer.NewConstraintsClient(getSessionContext(d, m), connector) + + obj, err := client.Get(id) + if err != nil { + return handleReadError(d, "Constraint", 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, constraintSchema, "", nil) +} + +func resourceNsxtPolicyConstraintUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Constraint ID") + } + + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.Constraint{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, constraintSchema, "", nil); err != nil { + return err + } + + polishConstraintTargets(&obj) + + client := clientLayer.NewConstraintsClient(getSessionContext(d, m), connector) + _, err := client.Update(id, obj) + if err != nil { + return handleUpdateError("Constraint", id, err) + } + + return resourceNsxtPolicyConstraintRead(d, m) +} + +func resourceNsxtPolicyConstraintDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Constraint ID") + } + + connector := getPolicyConnector(m) + + client := clientLayer.NewConstraintsClient(getSessionContext(d, m), connector) + err := client.Delete(id) + + if err != nil { + return handleDeleteError("Constraint", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_constraint_test.go b/nsxt/resource_nsxt_policy_constraint_test.go new file mode 100644 index 000000000..7b93254ae --- /dev/null +++ b/nsxt/resource_nsxt_policy_constraint_test.go @@ -0,0 +1,266 @@ +// © Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// SPDX-License-Identifier: MPL-2.0 + +package nsxt + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestPolicyConstraintCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "target_owner_type": "GM", + "message": "test-create", + "target_resource_type": "StaticRoutes", + "count": "10", +} + +var accTestPolicyConstraintUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "target_owner_type": "LM", + "message": "test-update", + "target_resource_type": "Infra.TlsCertificate", + "count": "100", +} + +func TestAccResourceNsxtPolicyConstraint_basic(t *testing.T) { + testResourceName := "nsxt_policy_constraint.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccNSXVersion(t, "9.0.0"); testAccOnlyLocalManager(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyConstraintCheckDestroy(state, accTestPolicyConstraintUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyConstraintTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyConstraintExists(accTestPolicyConstraintCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyConstraintCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyConstraintCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "message", accTestPolicyConstraintCreateAttributes["message"]), + resource.TestCheckResourceAttr(testResourceName, "target.#", "1"), + resource.TestCheckResourceAttrSet(testResourceName, "target.0.path_prefix"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.0.target_resource_type", accTestPolicyConstraintCreateAttributes["target_resource_type"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + ), + }, + { + Config: testAccNsxtPolicyConstraintTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyConstraintExists(accTestPolicyConstraintUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyConstraintUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyConstraintUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "message", accTestPolicyConstraintUpdateAttributes["message"]), + resource.TestCheckResourceAttr(testResourceName, "target.#", "1"), + resource.TestCheckResourceAttrSet(testResourceName, "target.0.path_prefix"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.0.target_resource_type", accTestPolicyConstraintUpdateAttributes["target_resource_type"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + ), + }, + { + Config: testAccNsxtPolicyConstraintMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyConstraintExists(accTestPolicyConstraintCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyConstraint_vpc(t *testing.T) { + testResourceName := "nsxt_policy_constraint.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccNSXVersion(t, "9.0.0"); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyConstraintCheckDestroy(state, accTestPolicyConstraintUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyConstraintVpcTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyConstraintExists(accTestPolicyConstraintCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyConstraintCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyConstraintCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "message", accTestPolicyConstraintCreateAttributes["message"]), + resource.TestCheckResourceAttr(testResourceName, "target.#", "1"), + resource.TestCheckResourceAttrSet(testResourceName, "target.0.path_prefix"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.0.target_resource_type", accTestPolicyConstraintCreateAttributes["target_resource_type"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + ), + }, + { + Config: testAccNsxtPolicyConstraintVpcTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyConstraintExists(accTestPolicyConstraintUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyConstraintUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyConstraintUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "message", accTestPolicyConstraintUpdateAttributes["message"]), + resource.TestCheckResourceAttr(testResourceName, "target.#", "1"), + resource.TestCheckResourceAttrSet(testResourceName, "target.0.path_prefix"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "instance_count.0.target_resource_type", accTestPolicyConstraintUpdateAttributes["target_resource_type"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyConstraint_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_constraint.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccNSXVersion(t, "9.0.0") }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyConstraintCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyConstraintMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNsxtPolicyConstraintExists(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 Constraint resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy Constraint resource ID not set in resources") + } + + exists, err := resourceNsxtPolicyConstraintExists(testAccGetSessionProjectContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy Constraint %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtPolicyConstraintCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_constraint" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtPolicyConstraintExists(testAccGetSessionProjectContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy Constraint %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyConstraintTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestPolicyConstraintCreateAttributes + } else { + attrMap = accTestPolicyConstraintUpdateAttributes + } + return testAccNsxtPolicyProjectTemplate900(true, true, false, false) + fmt.Sprintf(` +resource "nsxt_policy_constraint" "test" { + display_name = "%s" + description = "%s" + message = "%s" + + target { + path_prefix = nsxt_policy_project.test.path + } + + instance_count { + count = "%s" + target_resource_type = "%s" + } +}`, attrMap["display_name"], attrMap["description"], attrMap["message"], attrMap["count"], attrMap["target_resource_type"]) +} + +func testAccNsxtPolicyConstraintVpcTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestPolicyConstraintCreateAttributes + } else { + attrMap = accTestPolicyConstraintUpdateAttributes + } + return fmt.Sprintf(` +data "nsxt_vpc" "test" { + %s + id = "%s" +} + +resource "nsxt_policy_constraint" "test" { + %s + display_name = "%s" + description = "%s" + message = "%s" + + target { + path_prefix = "${data.nsxt_vpc.test.path}/" + } + + instance_count { + count = "%s" + target_resource_type = "%s" + } +}`, testAccNsxtProjectContext(), os.Getenv("NSXT_VPC_ID"), testAccNsxtProjectContext(), attrMap["display_name"], attrMap["description"], attrMap["message"], attrMap["count"], attrMap["target_resource_type"]) +} + +func testAccNsxtPolicyConstraintMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_policy_constraint" "test" { + display_name = "%s" +}`, accTestPolicyConstraintUpdateAttributes["display_name"]) +} diff --git a/nsxt/resource_nsxt_vpc_connectivity_profile_test.go b/nsxt/resource_nsxt_vpc_connectivity_profile_test.go index 1ed561289..9801cf8d8 100644 --- a/nsxt/resource_nsxt_vpc_connectivity_profile_test.go +++ b/nsxt/resource_nsxt_vpc_connectivity_profile_test.go @@ -140,7 +140,7 @@ func testAccNsxtVpcConnectivityProfileExists(resourceName string) resource.TestC return fmt.Errorf("Policy VpcConnectivityProfile resource ID not set in resources") } - exists, err := resourceNsxtVpcConnectivityProfileExists(testAccGetProjectContext(), resourceID, connector) + exists, err := resourceNsxtVpcConnectivityProfileExists(testAccGetMultitenancyContext(), resourceID, connector) if err != nil { return err } @@ -161,7 +161,7 @@ func testAccNsxtVpcConnectivityProfileCheckDestroy(state *terraform.State, displ } resourceID := rs.Primary.Attributes["id"] - exists, err := resourceNsxtVpcConnectivityProfileExists(testAccGetProjectContext(), resourceID, connector) + exists, err := resourceNsxtVpcConnectivityProfileExists(testAccGetMultitenancyContext(), resourceID, connector) if err == nil { return err } diff --git a/nsxt/resource_nsxt_vpc_test.go b/nsxt/resource_nsxt_vpc_test.go index 78f401fcd..dcf37b7d9 100644 --- a/nsxt/resource_nsxt_vpc_test.go +++ b/nsxt/resource_nsxt_vpc_test.go @@ -178,7 +178,7 @@ func testAccNsxtVpcExists(displayName string, resourceName string) resource.Test return fmt.Errorf("Policy Vpc resource ID not set in resources") } - exists, err := resourceNsxtVpcExists(testAccGetProjectContext(), resourceID, connector) + exists, err := resourceNsxtVpcExists(testAccGetMultitenancyContext(), resourceID, connector) if err != nil { return err } @@ -199,7 +199,7 @@ func testAccNsxtVpcCheckDestroy(state *terraform.State, displayName string) erro } resourceID := rs.Primary.Attributes["id"] - exists, err := resourceNsxtVpcExists(testAccGetProjectContext(), resourceID, connector) + exists, err := resourceNsxtVpcExists(testAccGetMultitenancyContext(), resourceID, connector) if err == nil { return err } diff --git a/nsxt/utils_test.go b/nsxt/utils_test.go index eaede4182..ac8815188 100644 --- a/nsxt/utils_test.go +++ b/nsxt/utils_test.go @@ -267,13 +267,14 @@ func testAccGetSessionContext() tf_api.SessionContext { } func testAccGetSessionProjectContext() tf_api.SessionContext { - clientType := testAccIsGlobalManager2() projectID := os.Getenv("NSXT_PROJECT_ID") - vpcID := "" - return tf_api.SessionContext{ProjectID: projectID, ClientType: clientType, VPCID: vpcID} + if projectID != "" { + return tf_api.SessionContext{ProjectID: projectID, ClientType: tf_api.Multitenancy} + } + return tf_api.SessionContext{ClientType: tf_api.Local} } -func testAccGetProjectContext() tf_api.SessionContext { +func testAccGetMultitenancyContext() tf_api.SessionContext { projectID := os.Getenv("NSXT_PROJECT_ID") return tf_api.SessionContext{ProjectID: projectID, ClientType: tf_api.Multitenancy} }