From 235ef30b9ea56fb5bb7a2082c8ae78ced4e8f2b6 Mon Sep 17 00:00:00 2001 From: Enhao Cui Date: Wed, 6 Apr 2022 15:29:28 -0700 Subject: [PATCH] Add External IDs Support for Group Signed-off-by: Enhao Cui --- nsxt/resource_nsxt_policy_group.go | 81 +++++++++++++++++++ nsxt/resource_nsxt_policy_group_test.go | 99 +++++++++++++++++++++++ website/docs/r/policy_group.html.markdown | 14 ++++ 3 files changed, 194 insertions(+) diff --git a/nsxt/resource_nsxt_policy_group.go b/nsxt/resource_nsxt_policy_group.go index c69c1fcca..1810144d1 100644 --- a/nsxt/resource_nsxt_policy_group.go +++ b/nsxt/resource_nsxt_policy_group.go @@ -44,6 +44,13 @@ var conjunctionOperatorValues = []string{ model.ConjunctionOperator_CONJUNCTION_OPERATOR_AND, } +var externalMemberTypeValues = []string{ + model.ExternalIDExpression_MEMBER_TYPE_VIRTUALMACHINE, + model.ExternalIDExpression_MEMBER_TYPE_VIRTUALNETWORKINTERFACE, + model.ExternalIDExpression_MEMBER_TYPE_CLOUDNATIVESERVICEINSTANCE, + model.ExternalIDExpression_MEMBER_TYPE_PHYSICALSERVER, +} + func resourceNsxtPolicyGroup() *schema.Resource { return &schema.Resource{ Create: resourceNsxtPolicyGroupCreate, @@ -117,6 +124,27 @@ func getMACAddressExpressionSchema() *schema.Resource { } } +func getExternalIDExpressionSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "external_ids": { + Type: schema.TypeSet, + Required: true, + Description: "List of external ids", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "member_type": { + Type: schema.TypeString, + Optional: true, + Description: "External ID member type, default to virtual machine if not specified", + ValidateFunc: validation.StringInSlice(externalMemberTypeValues, false), + }, + }, + } +} + func getPathExpressionSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -226,6 +254,12 @@ func getCriteriaSetSchema() *schema.Resource { Optional: true, MaxItems: 1, }, + "external_id_expression": { + Type: schema.TypeList, + Description: "External ID expression specifying additional members in the Group", + Elem: getExternalIDExpressionSchema(), + Optional: true, + }, }, } } @@ -423,6 +457,32 @@ func buildGroupMacAddressData(ipaddr interface{}) (*data.StructValue, error) { return dataValue.(*data.StructValue), nil } +func buildGroupExternalIDExpressionData(externalId interface{}) (*data.StructValue, error) { + idMap := externalId.(map[string]interface{}) + memberType := idMap["member_type"].(string) + if memberType == "" { + memberType = model.ExternalIDExpression_MEMBER_TYPE_VIRTUALMACHINE + } + var extIds []string + + for _, extId := range idMap["external_ids"].(*schema.Set).List() { + extIds = append(extIds, extId.(string)) + } + extIdStruct := model.ExternalIDExpression{ + MemberType: &memberType, + ExternalIds: extIds, + ResourceType: model.ExternalIDExpression__TYPE_IDENTIFIER, + } + + converter := bindings.NewTypeConverter() + converter.SetMode(bindings.REST) + dataValue, errors := converter.ConvertToVapi(extIdStruct, model.ExternalIDExpressionBindingType()) + if errors != nil { + return nil, errors[0] + } + return dataValue.(*data.StructValue), nil +} + func buildGroupMemberPathData(paths interface{}) (*data.StructValue, error) { pathMap := paths.(map[string]interface{}) var pathList []string @@ -496,6 +556,12 @@ func buildGroupExpressionDataFromType(expressionType string, datum interface{}) return nil, err } return data, nil + } else if expressionType == "external_id_expression" { + data, err := buildGroupExternalIDExpressionData(datum) + if err != nil { + return nil, err + } + return data, nil } return nil, fmt.Errorf("Unknown expression type: %v", expressionType) } @@ -643,6 +709,21 @@ func fromGroupExpressionData(expressions []*data.StructValue) ([]map[string]inte addrList = append(addrList, addrMap) macMap["macaddress_expression"] = addrList parsedCriteria = append(parsedCriteria, macMap) + } else if expStruct.ResourceType == model.ExternalIDExpression__TYPE_IDENTIFIER { + log.Printf("[DEBUG] Parsing external id expression") + extIdData, errors := converter.ConvertToGolang(expression, model.ExternalIDExpressionBindingType()) + if len(errors) > 0 { + return nil, nil, errors[0] + } + extIdStruct := extIdData.(model.ExternalIDExpression) + var idList []map[string]interface{} + var extIdMap = make(map[string]interface{}) + extIdMap["member_type"] = extIdStruct.MemberType + extIdMap["external_ids"] = extIdStruct.ExternalIds + var idMap = make(map[string]interface{}) + idList = append(idList, extIdMap) + idMap["external_id_expression"] = idList + parsedCriteria = append(parsedCriteria, idMap) } else if expStruct.ResourceType == model.Condition__TYPE_IDENTIFIER { log.Printf("[DEBUG] Parsing condition") condMap, err := groupConditionDataToMap(expression) diff --git a/nsxt/resource_nsxt_policy_group_test.go b/nsxt/resource_nsxt_policy_group_test.go index ef43df942..2b8ee3df0 100644 --- a/nsxt/resource_nsxt_policy_group_test.go +++ b/nsxt/resource_nsxt_policy_group_test.go @@ -131,6 +131,52 @@ func TestAccResourceNsxtGlobalPolicyGroup_singleIPAddressCriteria(t *testing.T) }) } +func TestAccResourceNsxtGlobalPolicyGroup_externalIDCriteria(t *testing.T) { + name := getAccTestResourceName() + updatedName := getAccTestResourceName() + testResourceName := "nsxt_policy_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyGroupCheckDestroy(state, updatedName, defaultDomain) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyGroupExternalIDCreateTemplate(name, defaultDomain), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGroupExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", name), + resource.TestCheckResourceAttr(testResourceName, "description", "Acceptance Test"), + resource.TestCheckResourceAttr(testResourceName, "domain", defaultDomain), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckNoResourceAttr(testResourceName, "conjunction"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "2"), + resource.TestCheckResourceAttr(testResourceName, "criteria.#", "1"), + ), + }, + { + Config: testAccNsxtPolicyGroupExternalIDUpdateTemplate(updatedName, defaultDomain), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGroupExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(testResourceName, "description", "Acceptance Test"), + resource.TestCheckResourceAttr(testResourceName, "domain", defaultDomain), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "conjunction.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "criteria.#", "2"), + ), + }, + }, + }) +} + func TestAccResourceNsxtGlobalPolicyGroup_withDomain(t *testing.T) { name := "test-nsx-global-policy-group-domain" testResourceName := "nsxt_policy_group.test" @@ -778,6 +824,59 @@ resource "nsxt_policy_group" "test" { `, name) } +func testAccNsxtPolicyGroupExternalIDCreateTemplate(name string, siteName string) string { + return fmt.Sprintf(` +resource "nsxt_policy_group" "test" { + display_name = "%s" + description = "Acceptance Test" + + criteria { + external_id_expression { + member_type = "VirtualMachine" + external_ids = ["520ba7b0-d9f8-87b1-6f44-15bbeb7935c7", "52748a9e-d61d-e29b-d54b-07f169ff0ee8-4000"] + } + } + + tag { + scope = "scope1" + tag = "tag1" + } + + tag { + scope = "scope2" + tag = "tag2" + } +} +`, name) +} + +func testAccNsxtPolicyGroupExternalIDUpdateTemplate(name string, siteName string) string { + return fmt.Sprintf(` +resource "nsxt_policy_group" "test" { + display_name = "%s" + description = "Acceptance Test" + + criteria { + external_id_expression { + member_type = "VirtualMachine" + external_ids = ["520ba7b0-d9f8-87b1-6f44-15bbeb7935c7", "52748a9e-d61d-e29b-d54b-07f169ff0ee8-4000"] + } + } + + conjunction { + operator = "OR" + } + + criteria { + external_id_expression { + member_type = "VirtualNetworkInterface" + external_ids = ["520ba7b0-d9f8-87b1-6f44-15bbeb7935c7", "52748a9e-d61d-e29b-d54b-07f169ff0ee8-4000"] + } + } +} +`, name) +} + func testAccNsxtPolicyGroupPathsPrerequisites() string { var preRequisites string if testAccIsGlobalManager() { diff --git a/website/docs/r/policy_group.html.markdown b/website/docs/r/policy_group.html.markdown index eabde49f2..c5d7788c3 100644 --- a/website/docs/r/policy_group.html.markdown +++ b/website/docs/r/policy_group.html.markdown @@ -53,6 +53,17 @@ resource "nsxt_policy_group" "group1" { } } + conjunction { + operator = "OR" + } + + criteria { + external_id_expression { + member_type = "VirtualMachine" + external_ids = ["520ba7b0-d9f8-87b1-6f44-15bbeb7935c7", "52748a9e-d61d-e29b-d54b-07f169ff0ee8-4000"] + } + } + extended_criteria { identity_group { distinguished_name = "cn=u1,ou=users,dc=example,dc=local" @@ -158,6 +169,9 @@ The following arguments are supported: * `mac_addresses` - (Required) List of MAC addresses. * `path_expression` - (Optional) An expression block to specify direct group members by policy path. * `member_paths` - (Required) List of policy paths for direct members for this Group (such as Segments, Segment ports, Groups etc). + * `external_id_expression` - (Optional) An expression block to specify external IDs for the specified member type for this Group. + * `member_type` - (Required) External ID member type. Must be one of: `VirtualMachine`, `VirtualNetworkInterface`, `CloudNativeServiceInstance`, or `PhysicalServer`. + * `external_ids ` - (Required) List of external IDs for the specified member type. * `condition` (Optional) A repeatable condition block to select this Group's members. When multiple `condition` blocks are used in a single `criteria` they form a nested expression that's implicitly ANDed together and each nested condition must used the same `member_type`. * `key` (Required) Specifies the attribute to query. Must be one of: `Tag`, `ComputerName`, `OSName` or `Name`. For a `member_type` other than `VirtualMachine`, only the `Tag` key is supported. * `member_type` (Required) Specifies the type of resource to query. Must be one of: `IPSet`, `LogicalPort`, `LogicalSwitch`, `Segment`, `SegmentPort` or `VirtualMachine`.