Skip to content

Commit

Permalink
Merge pull request vmware#1212 from vmware/ignore-tag-scope
Browse files Browse the repository at this point in the history
Add ignore_tags feature for segments
  • Loading branch information
annakhm authored Jun 4, 2024
2 parents 09cd1f2 + 03d3e23 commit 673b1dc
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 1 deletion.
37 changes: 37 additions & 0 deletions nsxt/policy_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,43 @@ func getPolicySecurityPolicySchema(isIds, withContext, withRule bool) map[string
return result
}

func getIgnoreTagsSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"scopes": {
Type: schema.TypeList,
Description: "List of scopes to ignore",
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"detected": {
Type: schema.TypeSet,
Description: "Tags matching scopes to ignore",
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"scope": {
Type: schema.TypeString,
Computed: true,
},
"tag": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
},
}
}

func setPolicyRulesInSchema(d *schema.ResourceData, rules []model.Rule) error {
var rulesList []map[string]interface{}
for _, rule := range rules {
Expand Down
64 changes: 63 additions & 1 deletion nsxt/policy_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,24 @@ func initPolicyTagsSet(tags []model.Tag) []map[string]interface{} {
return tagList
}

func getIgnoredTagsFromSchema(d *schema.ResourceData) []model.Tag {
tags, defined := d.GetOk("ignore_tags")
if !defined {
return nil
}

tagMaps := tags.([]interface{})
if len(tagMaps) == 0 {
return nil
}
tagMap := tagMaps[0].(map[string]interface{})
discoveredTags := tagMap["detected"].(*schema.Set)
return getPolicyTagsFromSet(discoveredTags)
}

func getCustomizedPolicyTagsFromSchema(d *schema.ResourceData, schemaName string) []model.Tag {
tags := d.Get(schemaName).(*schema.Set).List()
ignoredTags := getIgnoredTagsFromSchema(d)
tagList := make([]model.Tag, 0)
for _, tag := range tags {
data := tag.(map[string]interface{})
Expand All @@ -112,21 +128,67 @@ func getCustomizedPolicyTagsFromSchema(d *schema.ResourceData, schemaName string

tagList = append(tagList, elem)
}
if len(ignoredTags) > 0 {
tagList = append(tagList, ignoredTags...)
}
return tagList
}

func setIgnoredTagsInSchema(d *schema.ResourceData, scopesToIgnore []string, tags []map[string]interface{}) {

elem := make(map[string]interface{})
elem["scopes"] = scopesToIgnore
elem["detected"] = tags

d.Set("ignore_tags", []map[string]interface{}{elem})
}

func getTagScopesToIgnore(d *schema.ResourceData) []string {

tags, defined := d.GetOk("ignore_tags")
if !defined {
return nil
}
tagMaps := tags.([]interface{})
if len(tagMaps) == 0 {
return nil
}
tagMap := tagMaps[0].(map[string]interface{})
return interface2StringList(tagMap["scopes"].([]interface{}))
}

// TODO - replace with slices.Contains when go is upgraded everywhere
func shouldIgnoreScope(scope string, scopesToIgnore []string) bool {
for _, scopeToIgnore := range scopesToIgnore {
if scope == scopeToIgnore {
return true
}
}
return false
}

func setCustomizedPolicyTagsInSchema(d *schema.ResourceData, tags []model.Tag, schemaName string) {
var tagList []map[string]interface{}
var ignoredTagList []map[string]interface{}
scopesToIgnore := getTagScopesToIgnore(d)
for _, tag := range tags {
elem := make(map[string]interface{})
elem["scope"] = tag.Scope
elem["tag"] = tag.Tag
tagList = append(tagList, elem)
if tag.Scope != nil && shouldIgnoreScope(*tag.Scope, scopesToIgnore) {
ignoredTagList = append(ignoredTagList, elem)
} else {
tagList = append(tagList, elem)
}
}
err := d.Set(schemaName, tagList)
if err != nil {
log.Printf("[WARNING] Failed to set tag in schema: %v", err)
}

if len(scopesToIgnore) > 0 {
setIgnoredTagsInSchema(d, scopesToIgnore, ignoredTagList)
}
}

func getPolicyTagsFromSchema(d *schema.ResourceData) []model.Tag {
Expand Down
200 changes: 200 additions & 0 deletions nsxt/resource_nsxt_policy_segment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,84 @@ func TestAccResourceNsxtPolicySegment_withDhcp(t *testing.T) {
})
}

func TestAccResourceNsxtPolicySegment_withIgnoreTags(t *testing.T) {
name := getAccTestResourceName()
testResourceName := "nsxt_policy_segment.test"
tzName := getOverlayTransportZoneName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: func(state *terraform.State) error {
return testAccNsxtPolicySegmentCheckDestroy(state, name)
},
Steps: []resource.TestStep{
{
Config: testAccNsxtPolicySegmentIgnoreTagsCreateTemplate(tzName, name),
Check: resource.ComposeTestCheckFunc(
testAccNsxtPolicySegmentExists(testResourceName),
resource.TestCheckResourceAttr(testResourceName, "display_name", name),
resource.TestCheckResourceAttr(testResourceName, "tag.#", "3"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.#", "0"),
),
},
{
Config: testAccNsxtPolicySegmentIgnoreTagsUpdate1Template(tzName, name),
// This is an "pretend" update of ignored tag on backend
// Where backend changes are simulated with terraform update
// The fake backend changes are still present in intent at this point,
// Thus terraform will detect non-empty plan
// Next step will delete those tags from intent, and we expect them to
// remain in the ignored_tags section
ExpectNonEmptyPlan: true,
Check: resource.ComposeTestCheckFunc(
testAccNsxtPolicySegmentExists(testResourceName),
resource.TestCheckResourceAttr(testResourceName, "display_name", name),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.#", "1"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.scopes.#", "2"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.#", "2"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.0.scope", "color"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.0.tag", "orange"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.1.scope", "size"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.1.tag", "small"),
),
},
{
Config: testAccNsxtPolicySegmentIgnoreTagsUpdate2Template(tzName, name),
Check: resource.ComposeTestCheckFunc(
testAccNsxtPolicySegmentExists(testResourceName),
resource.TestCheckResourceAttr(testResourceName, "display_name", name),
resource.TestCheckResourceAttr(testResourceName, "tag.#", "2"),
resource.TestCheckResourceAttr(testResourceName, "tag.0.scope", "rack"),
resource.TestCheckResourceAttr(testResourceName, "tag.0.tag", "5"),
resource.TestCheckResourceAttr(testResourceName, "tag.1.scope", "shape"),
resource.TestCheckResourceAttr(testResourceName, "tag.1.tag", "triangle"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.#", "1"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.scopes.#", "2"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.#", "2"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.0.scope", "color"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.0.tag", "orange"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.1.scope", "size"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.0.detected.1.tag", "small"),
),
},
{
Config: testAccNsxtPolicySegmentIgnoreTagsUpdate3Template(tzName, name),
Check: resource.ComposeTestCheckFunc(
testAccNsxtPolicySegmentExists(testResourceName),
resource.TestCheckResourceAttr(testResourceName, "display_name", name),
resource.TestCheckResourceAttr(testResourceName, "tag.#", "2"),
resource.TestCheckResourceAttr(testResourceName, "tag.0.scope", "color"),
resource.TestCheckResourceAttr(testResourceName, "tag.0.tag", "blue"),
resource.TestCheckResourceAttr(testResourceName, "tag.1.scope", "rack"),
resource.TestCheckResourceAttr(testResourceName, "tag.1.tag", "7"),
resource.TestCheckResourceAttr(testResourceName, "ignore_tags.#", "0"),
),
},
},
})
}

// TODO: add tests for l2_extension; requires L2 VPN Session

func testAccNsxtPolicySegmentExists(resourceName string) resource.TestCheckFunc {
Expand Down Expand Up @@ -561,6 +639,128 @@ resource "nsxt_policy_segment" "test" {
`, name)
}

func testAccNsxtPolicySegmentIgnoreTagsCreateTemplate(tzName string, name string) string {
return testAccNsxtPolicySegmentDeps(tzName, false) + fmt.Sprintf(`
resource "nsxt_policy_segment" "test" {
display_name = "%s"
overlay_id = 1011
transport_zone_path = data.nsxt_policy_transport_zone.test.path
connectivity_path = nsxt_policy_tier1_gateway.tier1ForSegments.path
subnet {
cidr = "12.12.2.1/24"
}
tag {
scope = "color"
tag = "orange"
}
tag {
scope = "shape"
tag = "triangle"
}
tag {
scope = "size"
tag = "small"
}
}
`, name)
}

func testAccNsxtPolicySegmentIgnoreTagsUpdate1Template(tzName string, name string) string {
return testAccNsxtPolicySegmentDeps(tzName, false) + fmt.Sprintf(`
resource "nsxt_policy_segment" "test" {
display_name = "%s"
overlay_id = 1011
transport_zone_path = data.nsxt_policy_transport_zone.test.path
connectivity_path = nsxt_policy_tier1_gateway.tier1ForSegments.path
subnet {
cidr = "12.12.2.1/24"
}
tag {
scope = "color"
tag = "orange"
}
tag {
scope = "shape"
tag = "triangle"
}
tag {
scope = "size"
tag = "small"
}
ignore_tags {
scopes = ["color", "size"]
}
}
`, name)
}

func testAccNsxtPolicySegmentIgnoreTagsUpdate2Template(tzName string, name string) string {
return testAccNsxtPolicySegmentDeps(tzName, false) + fmt.Sprintf(`
resource "nsxt_policy_segment" "test" {
display_name = "%s"
overlay_id = 1011
transport_zone_path = data.nsxt_policy_transport_zone.test.path
connectivity_path = nsxt_policy_tier1_gateway.tier1ForSegments.path
subnet {
cidr = "12.12.2.1/24"
}
tag {
scope = "rack"
tag = "5"
}
tag {
scope = "shape"
tag = "triangle"
}
ignore_tags {
scopes = ["color", "size"]
}
}
`, name)
}

func testAccNsxtPolicySegmentIgnoreTagsUpdate3Template(tzName string, name string) string {
return testAccNsxtPolicySegmentDeps(tzName, false) + fmt.Sprintf(`
resource "nsxt_policy_segment" "test" {
display_name = "%s"
overlay_id = 1011
transport_zone_path = data.nsxt_policy_transport_zone.test.path
connectivity_path = nsxt_policy_tier1_gateway.tier1ForSegments.path
subnet {
cidr = "12.12.2.1/24"
}
tag {
scope = "color"
tag = "blue"
}
tag {
scope = "rack"
tag = "7"
}
}
`, name)
}

func testAccNsxtPolicySegmentBasicUpdateTemplate(tzName string, name string) string {
return testAccNsxtPolicySegmentDeps(tzName, false) + fmt.Sprintf(`
Expand Down
1 change: 1 addition & 0 deletions nsxt/segment_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ func getPolicyCommonSegmentSchema(vlanRequired bool, isFixed bool) map[string]*s
"description": getDescriptionSchema(),
"revision": getRevisionSchema(),
"tag": getTagsSchema(),
"ignore_tags": getIgnoreTagsSchema(),
"context": getContextSchema(false, false),
"advanced_config": {
Type: schema.TypeList,
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/policy_fixed_segment.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The following arguments are supported:
* `display_name` - (Required) Display name of the resource.
* `description` - (Optional) Description of the resource.
* `tag` - (Optional) A list of scope + tag pairs to associate with this policy.
* `ignore_tags` - (Optional) A list of tag scopes that provider should ignore, more specifically, it should not detect drift when tags with such scope are present on NSX, and it should not overwrite them when applying its own tags. This feature is useful for external network with VCD scenario.
* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource.
* `connectivity_path` - (Required) Policy path to the connecting Tier-0 or Tier-1.
* `context` - (Optional) The context which the object belongs to
Expand Down
Loading

0 comments on commit 673b1dc

Please sign in to comment.