diff --git a/examples/tenant_policies_endpoint_mac_tag_policy/main.tf b/examples/tenant_policies_endpoint_mac_tag_policy/main.tf new file mode 100644 index 00000000..5a0e3757 --- /dev/null +++ b/examples/tenant_policies_endpoint_mac_tag_policy/main.tf @@ -0,0 +1,102 @@ +terraform { + required_providers { + mso = { + source = "CiscoDevNet/mso" + } + } +} + +provider "mso" { + username = "" # + password = "" # + url = "" # + insecure = true +} + +resource "mso_tenant" "tf_tenant" { + name = "tf_tenant" + display_name = "tf_tenant" +} + +resource "mso_schema" "tf_schema" { + name = "tf_schema" + template { + name = "tf_template" + display_name = "tf_template" + tenant_id = mso_tenant.tf_tenant.id + } +} + +resource "mso_schema_template_vrf" "tf_vrf" { + name = "tf_vrf" + display_name = "tf_vrf" + schema_id = mso_schema.tf_schema.id + template = one(mso_schema.tf_schema.template).name +} + +resource "mso_schema_template_bd" "tf_bd" { + schema_id = mso_schema.tf_schema.id + template_name = one(mso_schema.tf_schema.template).name + name = "tf_bd" + display_name = "tf_bd" + layer2_unknown_unicast = "proxy" + vrf_name = mso_schema_template_vrf.tf_vrf.name +} + +resource "mso_template" "tf_tenant_template" { + template_name = "tf_tenant_template" + template_type = "tenant" + tenant_id = mso_tenant.tf_tenant.id +} + +resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_tag_bd" { + template_id = mso_template.tf_tenant_template.id + mac = "AA:BB:A1:B2:C3:D4" + bd_uuid = mso_schema_template_bd.tf_bd.uuid + + tag_annotations { + key = "annotation_key_1" + value = "annotation_value_1" + } + + tag_annotations { + key = "annotation_key_2" + value = "annotation_value_2" + } + + policy_tags { + key = "policy_key_1" + value = "policy_value_1" + } + + policy_tags { + key = "policy_key_2" + value = "policy_value_2" + } +} + +resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_tag_vrf" { + template_id = mso_template.tf_tenant_template.id + mac = "AA:BB:A1:B2:C3:D4" + vrf_uuid = mso_schema_template_vrf.tf_vrf.uuid + + tag_annotations { + key = "annotation_key_1" + value = "annotation_value_1" + } + + tag_annotations { + key = "annotation_key_2" + value = "annotation_value_2" + } + + policy_tags { + key = "policy_key_1" + value = "policy_value_1" + } + + policy_tags { + key = "policy_key_2" + value = "policy_value_2" + } +} diff --git a/mso/datasource_mso_tenant_policies_endpoint_mac_tag_policy.go b/mso/datasource_mso_tenant_policies_endpoint_mac_tag_policy.go new file mode 100644 index 00000000..bbc1bb8b --- /dev/null +++ b/mso/datasource_mso_tenant_policies_endpoint_mac_tag_policy.go @@ -0,0 +1,115 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func datasourceMSOEndpointMACTagPolicy() *schema.Resource { + return &schema.Resource{ + Read: dataSourceMSOEndpointMACTagPolicyRead, + + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + }, + "mac": { + Type: schema.TypeString, + Required: true, + }, + "bd_uuid": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"vrf_uuid"}, + }, + "vrf_uuid": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"bd_uuid"}, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "tag_annotations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "policy_tags": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceMSOEndpointMACTagPolicyRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Data Source - Beginning Read") + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + mac := d.Get("mac").(string) + bdUUID := d.Get("bd_uuid").(string) + vrfUUID := d.Get("vrf_uuid").(string) + + // Either 'bd_uuid' or 'vrf_uuid' must be specified to format the data source ID. + if bdUUID == "" && vrfUUID == "" { + return fmt.Errorf("Either 'bd_uuid' or 'vrf_uuid' must be specified to use Endpoint MAC Tag Policy Data Source") + } + + name, dataSourceId, err := setEndpointMACTagPolicyId(m, templateId, "", mac, bdUUID, vrfUUID) + if err != nil { + return err + } + d.SetId(dataSourceId) + + response, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + policy, err := GetPolicyByName(response, name, "tenantPolicyTemplate", "template", "endpointMacTagPolicies") + if err != nil { + return err + } + + err = setEndpointMACTagPolicyData(d, policy, templateId, m) + if err != nil { + return err + } + + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Data Source - Read Complete : %v", d.Id()) + return nil +} diff --git a/mso/datasource_mso_tenant_policies_endpoint_mac_tag_policy_test.go b/mso/datasource_mso_tenant_policies_endpoint_mac_tag_policy_test.go new file mode 100644 index 00000000..32df3380 --- /dev/null +++ b/mso/datasource_mso_tenant_policies_endpoint_mac_tag_policy_test.go @@ -0,0 +1,96 @@ +package mso + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesEndpointMACTagPolicyDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: Error when neither bd_uuid nor vrf_uuid is provided Data Source") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigErrorMissingScopeDataSource(), + ExpectError: regexp.MustCompile(`Either 'bd_uuid' or 'vrf_uuid' must be specified to use Endpoint MAC Tag Policy Data Source`), + }, + { + PreConfig: func() { fmt.Println("Test: Error when both bd_uuid and vrf_uuid are provided Data Source") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigErrorConflictingScopeDataSource(), + ExpectError: regexp.MustCompile(`conflicts with`), + }, + { + PreConfig: func() { + fmt.Println("Test: Endpoint MAC Tag Policy (BD scope) with multiple annotations and tags Data Source") + }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigCreateBDWithMultipleTagsDataSource(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "mac", "AA:BB:A1:B2:C3:D4"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "name", fmt.Sprintf("AA:BB:A1:B2:C3:D4-[%s]", msoSchemaTemplateBdName)), + resource.TestCheckResourceAttrSet("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "uuid"), + resource.TestCheckResourceAttrSet("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "bd_uuid"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations.#", "2"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags.#", "2"), + CustomTestCheckTypeSetElemAttrs("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations", + map[string]string{ + "key": "annotation_key_1", + "value": "annotation_value_1", + }, + ), + CustomTestCheckTypeSetElemAttrs("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations", + map[string]string{ + "key": "annotation_key_2", + "value": "annotation_value_2", + }, + ), + CustomTestCheckTypeSetElemAttrs("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags", + map[string]string{ + "key": "policy_key_1", + "value": "policy_value_1", + }, + ), + CustomTestCheckTypeSetElemAttrs("data.mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags", + map[string]string{ + "key": "policy_key_2", + "value": "policy_value_2", + }, + ), + ), + }, + }, + }) +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigErrorMissingScopeDataSource() string { + return ` + data "mso_tenant_policies_endpoint_mac_tag_policy" "error" { + template_id = "mso_template.error.id" + mac = "AA:BB:A1:B2:C3:D4" + }` +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigErrorConflictingScopeDataSource() string { + return ` + data "mso_tenant_policies_endpoint_mac_tag_policy" "error" { + template_id = "mso_template.error.id" + mac = "AA:BB:A1:B2:C3:D4" + bd_uuid = "mso_schema_template_bd.error.uuid" + vrf_uuid = "mso_schema_template_vrf.error.uuid" + }` +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigCreateBDWithMultipleTagsDataSource() string { + return fmt.Sprintf(`%[1]s + data "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd" { + template_id = mso_template.%[2]s.id + mac = "AA:BB:A1:B2:C3:D4" + bd_uuid = mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd.bd_uuid + }`, + testAccMSOTenantPoliciesEndpointMACTagPolicyConfigCreateBDWithMultipleTags(), + msoTenantPolicyTemplateName, + ) +} diff --git a/mso/provider.go b/mso/provider.go index 18d1c7ab..5a4907fe 100644 --- a/mso/provider.go +++ b/mso/provider.go @@ -142,6 +142,7 @@ func Provider() terraform.ResourceProvider { "mso_tenant_policies_ipsla_track_list": resourceMSOIPSLATrackList(), "mso_tenant_policies_l3out_interface_routing_policy": resourceMSOL3OutInterfaceRoutingPolicy(), "mso_fabric_policies_interface_setting": resourceMSOInterfaceSetting(), + "mso_tenant_policies_endpoint_mac_tag_policy": resourceMSOEndpointMACTagPolicy(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -217,6 +218,7 @@ func Provider() terraform.ResourceProvider { "mso_tenant_policies_ipsla_track_list": datasourceMSOIPSLATrackList(), "mso_tenant_policies_l3out_interface_routing_policy": datasourceMSOL3OutInterfaceRoutingPolicy(), "mso_fabric_policies_interface_setting": datasourceMSOInterfaceSetting(), + "mso_tenant_policies_endpoint_mac_tag_policy": datasourceMSOEndpointMACTagPolicy(), }, ConfigureFunc: configureClient, diff --git a/mso/resource_mso_tenant_policies_endpoint_mac_tag_policy.go b/mso/resource_mso_tenant_policies_endpoint_mac_tag_policy.go new file mode 100644 index 00000000..5aa97361 --- /dev/null +++ b/mso/resource_mso_tenant_policies_endpoint_mac_tag_policy.go @@ -0,0 +1,385 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/ciscoecosystem/mso-go-client/container" + "github.com/ciscoecosystem/mso-go-client/models" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceMSOEndpointMACTagPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceMSOEndpointMACTagPolicyCreate, + Read: resourceMSOEndpointMACTagPolicyRead, + Update: resourceMSOEndpointMACTagPolicyUpdate, + Delete: resourceMSOEndpointMACTagPolicyDelete, + Importer: &schema.ResourceImporter{ + State: resourceMSOEndpointMACTagPolicyImport, + }, + + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "mac": { + Type: schema.TypeString, + Required: true, + }, + "bd_uuid": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"vrf_uuid"}, + }, + "vrf_uuid": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"bd_uuid"}, + }, + "tag_annotations": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "policy_tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func setEndpointMACTagPolicyId(m interface{}, templateId, name, mac, bdUUID, vrfUUID string) (string, string, error) { + if name == "" { + msoClient := m.(*client.Client) + if bdUUID != "" { + bridgeDomainObject, err := GetTemplateObjectByUUID(msoClient, "bd", bdUUID) + if err != nil { + return "", "", err + } + name = fmt.Sprintf("%s-[%s]", mac, models.StripQuotes(bridgeDomainObject.S("name").String())) + } else if vrfUUID != "" { + name = fmt.Sprintf("%s-[*]", mac) + } + } + return name, fmt.Sprintf("templateId/%s/EndpointMACTagPolicy/%s", templateId, name), nil +} + +func getTagAnnotationsPayload(tagAnnotationsSet *schema.Set) []map[string]interface{} { + if tagAnnotationsSet == nil || tagAnnotationsSet.Len() == 0 { + return []map[string]interface{}{} + } + list := tagAnnotationsSet.List() + payload := make([]map[string]interface{}, 0, len(list)) + for _, item := range list { + m := item.(map[string]interface{}) + payload = append(payload, map[string]interface{}{ + "tagKey": m["key"].(string), + "tagValue": m["value"].(string), + }) + } + return payload +} + +func getPolicyTagsPayload(policyTagsSet *schema.Set) []map[string]interface{} { + if policyTagsSet == nil || policyTagsSet.Len() == 0 { + return []map[string]interface{}{} + } + list := policyTagsSet.List() + payload := make([]map[string]interface{}, 0, len(list)) + for _, item := range list { + m := item.(map[string]interface{}) + payload = append(payload, map[string]interface{}{ + "key": m["key"].(string), + "value": m["value"].(string), + }) + } + return payload +} + +func setEndpointMACTagPolicyData(d *schema.ResourceData, response *container.Container, templateId string, m interface{}) error { + d.Set("template_id", templateId) + d.Set("mac", models.StripQuotes(response.S("mac").String())) + d.Set("name", models.StripQuotes(response.S("name").String())) + d.Set("uuid", models.StripQuotes(response.S("uuid").String())) + + if response.Exists("bdRef") { + d.Set("bd_uuid", models.StripQuotes(response.S("bdRef").String())) + } + if response.Exists("vrfRef") { + d.Set("vrf_uuid", models.StripQuotes(response.S("vrfRef").String())) + } + + tagAnnotationsList := make([]map[string]interface{}, 0) + if count, err := response.ArrayCount("tagAnnotations"); err == nil { + for i := range count { + c, err := response.ArrayElement(i, "tagAnnotations") + if err != nil { + return err + } + tagAnnotationsList = append(tagAnnotationsList, map[string]interface{}{ + "key": models.StripQuotes(c.S("tagKey").String()), + "value": models.StripQuotes(c.S("tagValue").String()), + }) + } + } + d.Set("tag_annotations", tagAnnotationsList) + + policyTagsList := make([]map[string]interface{}, 0) + if count, err := response.ArrayCount("policyTags"); err == nil { + for i := range count { + c, err := response.ArrayElement(i, "policyTags") + if err != nil { + return err + } + policyTagsList = append(policyTagsList, map[string]interface{}{ + "key": models.StripQuotes(c.S("key").String()), + "value": models.StripQuotes(c.S("value").String()), + }) + } + } + d.Set("policy_tags", policyTagsList) + + return nil +} + +func resourceMSOEndpointMACTagPolicyImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Beginning Import: %v", d.Id()) + err := resourceMSOEndpointMACTagPolicyRead(d, m) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Import Complete: %v", d.Id()) + return []*schema.ResourceData{d}, nil +} + +func resourceMSOEndpointMACTagPolicyCreate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Beginning Create: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + + // Ignoring the input validation since the API returns a clear error when bd_uuid and vrf_uuid are not provided. + bdUUID := d.Get("bd_uuid").(string) + vrfUUID := d.Get("vrf_uuid").(string) + + payload := map[string]interface{}{ + "mac": d.Get("mac").(string), + "templateId": templateId, + } + + if bdUUID != "" { + payload["bdRef"] = bdUUID + } else if vrfUUID != "" { + payload["vrfRef"] = vrfUUID + } + + if tagAnnotations, ok := d.GetOk("tag_annotations"); ok { + payload["tagAnnotations"] = getTagAnnotationsPayload(tagAnnotations.(*schema.Set)) + } + + if policyTags, ok := d.GetOk("policy_tags"); ok { + payload["policyTags"] = getPolicyTagsPayload(policyTags.(*schema.Set)) + } + + payloadModel := models.GetPatchPayload("add", "/tenantPolicyTemplate/template/endpointMacTagPolicies/-", payload) + + _, err := msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + _, resourceId, err := setEndpointMACTagPolicyId(m, templateId, "", d.Get("mac").(string), d.Get("bd_uuid").(string), d.Get("vrf_uuid").(string)) + if err != nil { + return err + } + + d.SetId(resourceId) + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Create Complete: %v", d.Id()) + return resourceMSOEndpointMACTagPolicyRead(d, m) +} + +func resourceMSOEndpointMACTagPolicyRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Beginning Read: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId, err := GetTemplateIdFromResourceId(d.Id()) + if err != nil { + return err + } + + response, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + policyName, err := GetPolicyNameFromResourceId(d.Id(), "EndpointMACTagPolicy") + if err != nil { + return err + } + + policy, err := GetPolicyByName(response, policyName, "tenantPolicyTemplate", "template", "endpointMacTagPolicies") + if err != nil { + return err + } + + err = setEndpointMACTagPolicyData(d, policy, templateId, m) + if err != nil { + return err + } + + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Read Complete: %v", d.Id()) + return nil +} + +func resourceMSOEndpointMACTagPolicyUpdate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Beginning Update: %v", d.Id()) + msoClient := m.(*client.Client) + templateId := d.Get("template_id").(string) + + templateContainer, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + policyIndex, err := GetPolicyIndexByKeyAndValue(templateContainer, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "endpointMacTagPolicies") + if err != nil { + return err + } + + updatePath := fmt.Sprintf("/tenantPolicyTemplate/template/endpointMacTagPolicies/%d", policyIndex) + + payloadCont := container.New() + payloadCont.Array() + + if d.HasChange("mac") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/mac", updatePath), d.Get("mac").(string)) + if err != nil { + return err + } + } + + if d.HasChange("bd_uuid") { + if bdUUID, ok := d.GetOk("bd_uuid"); ok && bdUUID.(string) != "" { + // Remove vrfRef if bd_uuid is being set, since both cannot coexist and NDO does not remove it automatically when bdRef is added + err := addPatchPayloadToContainer(payloadCont, "remove", fmt.Sprintf("%s/vrfRef", updatePath), nil) + if err != nil { + return err + } + + err = addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/bdRef", updatePath), bdUUID.(string)) + if err != nil { + return err + } + d.Set("vrf_uuid", "") + } + } + + if d.HasChange("vrf_uuid") { + if vrfUUID, ok := d.GetOk("vrf_uuid"); ok && vrfUUID.(string) != "" { + // Remove bdRef if vrf_uuid is being set, since both cannot coexist and NDO does not remove it automatically when vrfRef is added + err := addPatchPayloadToContainer(payloadCont, "remove", fmt.Sprintf("%s/bdRef", updatePath), nil) + if err != nil { + return err + } + + err = addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/vrfRef", updatePath), vrfUUID.(string)) + if err != nil { + return err + } + d.Set("bd_uuid", "") + } + } + + if d.HasChange("tag_annotations") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/tagAnnotations", updatePath), getTagAnnotationsPayload(d.Get("tag_annotations").(*schema.Set))) + if err != nil { + return err + } + } + + if d.HasChange("policy_tags") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/policyTags", updatePath), getPolicyTagsPayload(d.Get("policy_tags").(*schema.Set))) + if err != nil { + return err + } + } + + err = doPatchRequest(msoClient, fmt.Sprintf("api/v1/templates/%s", templateId), payloadCont) + if err != nil { + return err + } + + _, resourceId, err := setEndpointMACTagPolicyId(m, templateId, "", d.Get("mac").(string), d.Get("bd_uuid").(string), d.Get("vrf_uuid").(string)) + if err != nil { + return err + } + + d.SetId(resourceId) + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Update Complete: %v", d.Id()) + return resourceMSOEndpointMACTagPolicyRead(d, m) +} + +func resourceMSOEndpointMACTagPolicyDelete(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Beginning Delete: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + + templateContainer, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + policyIndex, err := GetPolicyIndexByKeyAndValue(templateContainer, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "endpointMacTagPolicies") + if err != nil { + return err + } + + payloadModel := models.GetRemovePatchPayload(fmt.Sprintf("/tenantPolicyTemplate/template/endpointMacTagPolicies/%d", policyIndex)) + + _, err = msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId("") + log.Printf("[DEBUG] MSO Endpoint MAC Tag Policy Resource - Delete Complete: %v", d.Id()) + return nil +} diff --git a/mso/resource_mso_tenant_policies_endpoint_mac_tag_policy_test.go b/mso/resource_mso_tenant_policies_endpoint_mac_tag_policy_test.go new file mode 100644 index 00000000..58639dca --- /dev/null +++ b/mso/resource_mso_tenant_policies_endpoint_mac_tag_policy_test.go @@ -0,0 +1,287 @@ +package mso + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesEndpointMACTagPolicyResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: Error when neither bd_uuid nor vrf_uuid is provided") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigErrorMissingScope(), + ExpectError: regexp.MustCompile(`BdRef and VrfRef cannot both be empty in Endpoint Mac Tag Policy AA:BB:A1:B2:C3:D4. Either BdRef or VrfRef must be populated.`), + }, + { + PreConfig: func() { + fmt.Println("Test: Create Endpoint MAC Tag Policy (BD scope) with multiple annotations and tags") + }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigCreateBDWithMultipleTags(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "mac", "AA:BB:A1:B2:C3:D4"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "name", fmt.Sprintf("AA:BB:A1:B2:C3:D4-[%s]", msoSchemaTemplateBdName)), + resource.TestCheckResourceAttrSet("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "uuid"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "bd_uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations.#", "2"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags.#", "2"), + CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations", + map[string]string{ + "key": "annotation_key_1", + "value": "annotation_value_1", + }, + ), + CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations", + map[string]string{ + "key": "annotation_key_2", + "value": "annotation_value_2", + }, + ), + CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags", + map[string]string{ + "key": "policy_key_1", + "value": "policy_value_1", + }, + ), + CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags", + map[string]string{ + "key": "policy_key_2", + "value": "policy_value_2", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Endpoint MAC Tag Policy from BD scope to VRF scope") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateBDToVRF(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "mac", "AA:BB:A1:B2:C3:D4"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "name", "AA:BB:A1:B2:C3:D4-[*]"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "vrf_uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations.#", "2"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags.#", "2"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Revert Endpoint MAC Tag Policy from VRF scope back to BD scope") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateVRFToBD(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "mac", "AA:BB:A1:B2:C3:D4"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "name", fmt.Sprintf("AA:BB:A1:B2:C3:D4-[%s]", msoSchemaTemplateBdName)), + resource.TestCheckResourceAttrSet("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "bd_uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations.#", "2"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags.#", "2"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Endpoint MAC Tag Policy removing one annotation and one tag") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateRemoveOneTagAndAnnotation(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "mac", "AA:BB:A1:B2:C3:D4"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "name", fmt.Sprintf("AA:BB:A1:B2:C3:D4-[%s]", msoSchemaTemplateBdName)), + resource.TestCheckResourceAttrSet("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "bd_uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations.#", "1"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags.#", "1"), + CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations", + map[string]string{ + "key": "annotation_key_2", + "value": "annotation_value_2", + }, + ), + CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags", + map[string]string{ + "key": "policy_key_2", + "value": "policy_value_2", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Endpoint MAC Tag Policy removing all annotations and tags") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateRemoveAllTagsAndAnnotations(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "mac", "AA:BB:A1:B2:C3:D4"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "name", fmt.Sprintf("AA:BB:A1:B2:C3:D4-[%s]", msoSchemaTemplateBdName)), + resource.TestCheckResourceAttrSet("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "bd_uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "tag_annotations.#", "0"), + resource.TestCheckResourceAttr("mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", "policy_tags.#", "0"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Import Endpoint MAC Tag Policy") }, + ResourceName: "mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_bd", + ImportState: true, + ImportStateVerify: true, + }, + { + PreConfig: func() { fmt.Println("Test: Create Endpoint MAC Tag Policy with duplicate object") }, + Config: testAccMSOTenantPoliciesEndpointMACTagPolicyConfigWithDuplicateObject(), + ExpectError: regexp.MustCompile(regexp.QuoteMeta(fmt.Sprintf("Multiple endpointMacTag policies are using the name: AA:BB:A1:B2:C3:D4-[%s]", msoSchemaTemplateBdName))), + }, + }, + CheckDestroy: testCheckResourceDestroyPolicyWithPathAttributesAndArguments("mso_tenant_policies_endpoint_mac_tag_policy", "tenantPolicyTemplate", "template", "endpointMacTagPolicies"), + }) +} + +var endpointMACTagPolicyPreConfig = testSiteConfigAnsibleTest() + testTenantConfig() + testSchemaConfig() + testSchemaTemplateVrfConfig() + testSchemaTemplateBdConfig() + testTenantPolicyTemplateConfig() + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigErrorMissingScope() string { + return fmt.Sprintf(`%[1]s + resource "mso_tenant_policies_endpoint_mac_tag_policy" "error" { + template_id = mso_template.%[2]s.id + mac = "AA:BB:A1:B2:C3:D4" + }`, endpointMACTagPolicyPreConfig, msoTenantPolicyTemplateName) +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigCreateBDWithMultipleTags() string { + return fmt.Sprintf(`%[1]s + resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd" { + template_id = mso_template.%[2]s.id + mac = "AA:BB:A1:B2:C3:D4" + bd_uuid = mso_schema_template_bd.%[3]s.uuid + + tag_annotations { + key = "annotation_key_1" + value = "annotation_value_1" + } + + tag_annotations { + key = "annotation_key_2" + value = "annotation_value_2" + } + + policy_tags { + key = "policy_key_1" + value = "policy_value_1" + } + + policy_tags { + key = "policy_key_2" + value = "policy_value_2" + } + }`, + endpointMACTagPolicyPreConfig, + msoTenantPolicyTemplateName, + msoSchemaTemplateBdName, + ) +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateBDToVRF() string { + return fmt.Sprintf(`%[1]s + resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd" { + template_id = mso_template.%[2]s.id + mac = "AA:BB:A1:B2:C3:D4" + vrf_uuid = mso_schema_template_vrf.%[3]s.uuid + + tag_annotations { + key = "annotation_key_1" + value = "annotation_value_1" + } + + tag_annotations { + key = "annotation_key_2" + value = "annotation_value_2" + } + + policy_tags { + key = "policy_key_1" + value = "policy_value_1" + } + + policy_tags { + key = "policy_key_2" + value = "policy_value_2" + } + }`, + endpointMACTagPolicyPreConfig, + msoTenantPolicyTemplateName, + msoSchemaTemplateVrfName, + ) +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateVRFToBD() string { + return fmt.Sprintf(`%[1]s + resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd" { + template_id = mso_template.%[2]s.id + mac = "AA:BB:A1:B2:C3:D4" + bd_uuid = mso_schema_template_bd.%[3]s.uuid + + tag_annotations { + key = "annotation_key_1" + value = "annotation_value_1" + } + + tag_annotations { + key = "annotation_key_2" + value = "annotation_value_2" + } + + policy_tags { + key = "policy_key_1" + value = "policy_value_1" + } + + policy_tags { + key = "policy_key_2" + value = "policy_value_2" + } + }`, + endpointMACTagPolicyPreConfig, + msoTenantPolicyTemplateName, + msoSchemaTemplateBdName, + ) +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateRemoveOneTagAndAnnotation() string { + return fmt.Sprintf(`%[1]s + resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd" { + template_id = mso_template.%[2]s.id + bd_uuid = mso_schema_template_bd.%[3]s.uuid + mac = "AA:BB:A1:B2:C3:D4" + + tag_annotations { + key = "annotation_key_2" + value = "annotation_value_2" + } + + policy_tags { + key = "policy_key_2" + value = "policy_value_2" + } + }`, + endpointMACTagPolicyPreConfig, + msoTenantPolicyTemplateName, + msoSchemaTemplateBdName, + ) +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigUpdateRemoveAllTagsAndAnnotations() string { + return fmt.Sprintf(`%[1]s + resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd" { + template_id = mso_template.%[2]s.id + bd_uuid = mso_schema_template_bd.%[3]s.uuid + mac = "AA:BB:A1:B2:C3:D4" + }`, + endpointMACTagPolicyPreConfig, + msoTenantPolicyTemplateName, + msoSchemaTemplateBdName, + ) +} + +func testAccMSOTenantPoliciesEndpointMACTagPolicyConfigWithDuplicateObject() string { + return fmt.Sprintf(`%[1]s + resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd_duplicate" { + template_id = mso_template.%[2]s.id + bd_uuid = mso_schema_template_bd.%[3]s.uuid + mac = "AA:BB:A1:B2:C3:D4" + }`, + endpointMACTagPolicyPreConfig, + msoTenantPolicyTemplateName, + msoSchemaTemplateBdName, + ) +} diff --git a/mso/utils.go b/mso/utils.go index 7fc0678c..7bfd2bed 100644 --- a/mso/utils.go +++ b/mso/utils.go @@ -540,3 +540,12 @@ func GetDeployedSiteIdsForApplicationTemplate(msoClient *client.Client, schemaId } return siteIds, nil } + +func GetTemplateObjectByUUID(msoClient *client.Client, objectType, uuid string) (*container.Container, error) { + path := fmt.Sprintf("api/v1/templates/objects?type=%s&uuid=%s", objectType, uuid) + cont, err := msoClient.GetViaURL(path) + if err != nil { + return nil, err + } + return cont, nil +} diff --git a/website/docs/d/tenant_policies_endpoint_mac_tag_policy.html.markdown b/website/docs/d/tenant_policies_endpoint_mac_tag_policy.html.markdown new file mode 100644 index 00000000..2ff18f5c --- /dev/null +++ b/website/docs/d/tenant_policies_endpoint_mac_tag_policy.html.markdown @@ -0,0 +1,52 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_endpoint_mac_tag_policy" +sidebar_current: "docs-mso-data-source-tenant_policies_endpoint_mac_tag_policy" +description: |- + Data source for Endpoint MAC Tag Policy on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_tenant_policies_endpoint_mac_tag_policy # + +Data source for Endpoint MAC Tag Policy on Cisco Nexus Dashboard Orchestrator (NDO). This data source is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> Endpoint MAC Tag Policy + +## Example Usage ## + +```hcl +data "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_bd" { + template_id = mso_template.tf_tenant_template.id + mac = "AA:BB:A1:B2:C3:D4" + bd_uuid = mso_schema_template_bd.tf_bd.uuid +} +``` + +```hcl +data "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_vrf" { + template_id = mso_template.tf_tenant_template.id + mac = "AA:BB:A1:B2:C3:D5" + vrf_uuid = mso_schema_template_vrf.tf_vrf.uuid +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the tenant policy template. +* `mac` - (Required) The MAC address of the Endpoint MAC Tag Policy. +* `bd_uuid` - (Optional) The UUID of the Bridge Domain (BD) associated with the Endpoint MAC Tag Policy. Mutually exclusive with `vrf_uuid`. Either `bd_uuid` or `vrf_uuid` must be specified. +* `vrf_uuid` - (Optional) The UUID of the Virtual Routing and Forwarding (VRF) associated with the Endpoint MAC Tag Policy. Mutually exclusive with `bd_uuid`. Either `bd_uuid` or `vrf_uuid` must be specified. + +## Attribute Reference ## + +* `id` - (Read-Only) The unique Terraform identifier of the Endpoint MAC Tag Policy. +* `uuid` - (Read-Only) The NDO UUID of the Endpoint MAC Tag Policy. +* `name` - (Read-Only) The name of the Endpoint MAC Tag Policy as assigned by NDO. +* `tag_annotations` - (Read-Only) A list of annotation key-value pairs for the Endpoint MAC Tag Policy. + * `key` - (Read-Only) The annotation key. + * `value` - (Read-Only) The annotation value. +* `policy_tags` - (Read-Only) A list of policy tag key-value pairs for the Endpoint MAC Tag Policy. + * `key` - (Read-Only) The policy tag key. + * `value` - (Read-Only) The policy tag value. diff --git a/website/docs/r/tenant_policies_endpoint_mac_tag_policy.html.markdown b/website/docs/r/tenant_policies_endpoint_mac_tag_policy.html.markdown new file mode 100644 index 00000000..f9eaa7ce --- /dev/null +++ b/website/docs/r/tenant_policies_endpoint_mac_tag_policy.html.markdown @@ -0,0 +1,84 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_endpoint_mac_tag_policy" +sidebar_current: "docs-mso-resource-tenant_policies_endpoint_mac_tag_policy" +description: |- + Manages Endpoint MAC Tag Policy on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_tenant_policies_endpoint_mac_tag_policy # + +Manages Endpoint MAC Tag Policy on Cisco Nexus Dashboard Orchestrator (NDO). This resource is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> Endpoint MAC Tag Policy + +## Example Usage ## + +**Endpoint MAC Tag Policy with a Bridge Domain scope:** + +```hcl +resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_tag_bd" { + template_id = mso_template.tf_tenant_template.id + mac = "AA:BB:A1:B2:C3:D4" + bd_uuid = mso_schema_template_bd.tf_bd.uuid + + tag_annotations { + key = "annotation_key_1" + value = "annotation_value_1" + } + + policy_tags { + key = "policy_key_1" + value = "policy_value_1" + } +} +``` + +**Endpoint MAC Tag Policy with a VRF scope:** + +```hcl +resource "mso_tenant_policies_endpoint_mac_tag_policy" "endpoint_mac_tag_vrf" { + template_id = mso_template.tf_tenant_template.id + mac = "AA:BB:A1:B2:C3:D5" + vrf_uuid = mso_schema_template_vrf.tf_vrf.uuid + + tag_annotations { + key = "annotation_key_1" + value = "annotation_value_1" + } + + policy_tags { + key = "policy_key_1" + value = "policy_value_1" + } +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the tenant policy template. +* `mac` - (Required) The MAC address of the Endpoint MAC Tag Policy. +* `bd_uuid` - (Optional) The UUID of the Bridge Domain (BD) to associate with Endpoint MAC Tag Policy. Mutually exclusive with `vrf_uuid`. Either `bd_uuid` or `vrf_uuid` must be specified. +* `vrf_uuid` - (Optional) The UUID of the Virtual Routing and Forwarding (VRF) to associate with Endpoint MAC Tag Policy. Mutually exclusive with `bd_uuid`. Either `bd_uuid` or `vrf_uuid` must be specified. +* `tag_annotations` - (Optional) A list of annotation key-value pairs for the Endpoint MAC Tag Policy. + * `key` - (Required) The annotation key. + * `value` - (Required) The annotation value. +* `policy_tags` - (Optional) A list of policy tag key-value pairs for the Endpoint MAC Tag Policy. + * `key` - (Required) The policy tag key. + * `value` - (Required) The policy tag value. + +## Attribute Reference ## + +* `id` - (Read-Only) The unique Terraform identifier of the Endpoint MAC Tag Policy. +* `uuid` - (Read-Only) The NDO UUID of the Endpoint MAC Tag Policy. +* `name` - (Read-Only) The name of the Endpoint MAC Tag Policy. + +## Importing ## + +An existing MSO Endpoint MAC Tag Policy can be [imported][docs-import] into this resource via its ID/path, via the following command: [docs-import]: https://www.terraform.io/docs/import/index.html + +```bash +terraform import mso_tenant_policies_endpoint_mac_tag_policy.endpoint_mac_tag_bd templateId/{template_id}/EndpointMACTagPolicy/{name} +```