diff --git a/examples/fabric_resource_policies_physical_interface/main.tf b/examples/fabric_resource_policies_physical_interface/main.tf new file mode 100644 index 00000000..2f779734 --- /dev/null +++ b/examples/fabric_resource_policies_physical_interface/main.tf @@ -0,0 +1,64 @@ +terraform { + required_providers { + mso = { + source = "CiscoDevNet/mso" + } + } +} + +provider "mso" { + username = "" # + password = "" # + url = "" # + insecure = true +} + +resource "mso_template" "fabric_policy_template" { + template_name = "fabric_policy_template" + template_type = "fabric_policy" +} + +resource "mso_fabric_policies_interface_setting" "physical_interface" { + template_id = mso_template.fabric_policy_template.id + type = "physical" + name = "physical_interface" +} + +resource "mso_template" "fabric_resource_template" { + template_name = "fabric_resource_template" + template_type = "fabric_resource" +} + +resource "mso_fabric_resource_policies_physical_interface" "physical" { + template_id = mso_template.fabric_resource_template.id + name = "physical_interface" + description = "Terraform test Physical Interface" + nodes = ["101", "102"] + interfaces = ["1/1", "1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.physical_interface.id + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } +} + +resource "mso_fabric_resource_policies_physical_interface" "breakout" { + template_id = mso_template.fabric_resource_template.id + name = "breakout_mode_physical_interface" + description = "Terraform test Physical Interface with breakout mode" + nodes = ["101", "102"] + interfaces = ["1/1", "1/2"] + breakout_mode = "4x100G" + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } +} \ No newline at end of file diff --git a/mso/datasource_mso_fabric_resource_policies_physical_interface.go b/mso/datasource_mso_fabric_resource_policies_physical_interface.go new file mode 100644 index 00000000..7b8bf648 --- /dev/null +++ b/mso/datasource_mso_fabric_resource_policies_physical_interface.go @@ -0,0 +1,102 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func datasourceMSOPhysicalInterface() *schema.Resource { + return &schema.Resource{ + Read: dataSourceMSOPhysicalInterfaceRead, + + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "nodes": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, + "interfaces": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, + "interface_policy_group_uuid": { + Type: schema.TypeString, + Computed: true, + }, + "breakout_mode": { + Type: schema.TypeString, + Computed: true, + }, + "interface_descriptions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "interface": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "policy_group_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceMSOPhysicalInterfaceRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Physical Interface Data Source - Beginning Read") + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + policyName := d.Get("name").(string) + + response, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + policy, err := GetPolicyByName(response, policyName, "fabricResourceTemplate", "template", "interfaceProfiles") + if err != nil { + return err + } + + err = setPhysicalInterfaceData(d, policy, templateId) + if err != nil { + return err + } + + log.Printf("[DEBUG] MSO Physical Interface Data Source - Read Complete: %v", d.Id()) + return nil +} diff --git a/mso/datasource_mso_fabric_resource_policies_physical_interface_test.go b/mso/datasource_mso_fabric_resource_policies_physical_interface_test.go new file mode 100644 index 00000000..e81b5e0f --- /dev/null +++ b/mso/datasource_mso_fabric_resource_policies_physical_interface_test.go @@ -0,0 +1,97 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOFabricResourcePhysicalInterfaceDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: Physical Interface Data Source - With Interface type physical") }, + Config: testAccMSOFabricResourcePhysicalInterfaceTypePhysicalDataSource(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "name", msoFabricResourcePhysicalInterfaceName), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "policy_group_type", "physical"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "description", "Terraform test Physical Interface updated"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "nodes.#", "2"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interfaces.#", "2"), + resource.TestCheckResourceAttrSet("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "uuid"), + resource.TestCheckResourceAttrSet("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "template_id"), + resource.TestCheckResourceAttrSet("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_policy_group_uuid"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions.#", "2"), + CustomTestCheckTypeSetElemAttrs("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions", + map[string]string{ + "interface": "1/1", + "description": "Interface Description 1/1", + }, + ), + CustomTestCheckTypeSetElemAttrs("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions", + map[string]string{ + "interface": "1/2", + "description": "Interface Description 1/2", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Physical Interface Data Source - Breakout Mode setup") }, + Config: testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingExtraInterfaceDescription(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "name", msoFabricResourcePhysicalInterfaceName+"_breakout_updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "policy_group_type", "breakout"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Physical Interface Data Source - Breakout Mode") }, + Config: testAccMSOFabricResourcePhysicalInterfaceTypeBreakoutDataSource(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "name", msoFabricResourcePhysicalInterfaceName+"_breakout_updated"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "policy_group_type", "breakout"), + resource.TestCheckResourceAttrSet("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "uuid"), + resource.TestCheckResourceAttrSet("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "template_id"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "breakout_mode", "4x100G"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "description", "Terraform test Physical Interface updated"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "nodes.#", "1"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interfaces.#", "2"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions.#", "1"), + CustomTestCheckTypeSetElemAttrs("data.mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions", + map[string]string{ + "interface": "1/2", + "description": "Interface Description 1/2", + }, + ), + ), + }, + }, + }) +} + +func testAccMSOFabricResourcePhysicalInterfaceTypePhysicalDataSource() string { + return fmt.Sprintf(`%[1]s + data "mso_fabric_resource_policies_physical_interface" "%[2]s" { + template_id = mso_template.%[3]s.id + name = mso_fabric_resource_policies_physical_interface.%[2]s.name + }`, + testAccMSOFabricResourcePhysicalInterfaceConfigUpdateAddingExtraInterfaceDescription(), + msoFabricResourcePhysicalInterfaceName, + msoFabricResourceTemplateName, + ) +} + +func testAccMSOFabricResourcePhysicalInterfaceTypeBreakoutDataSource() string { + return fmt.Sprintf(`%[1]s + data "mso_fabric_resource_policies_physical_interface" "%[2]s" { + template_id = mso_template.%[3]s.id + name = mso_fabric_resource_policies_physical_interface.%[2]s.name + }`, + testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingExtraInterfaceDescription(), + msoFabricResourcePhysicalInterfaceName+"_breakout", + msoFabricResourceTemplateName, + ) +} diff --git a/mso/provider.go b/mso/provider.go index 18d1c7ab..cb527948 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_fabric_resource_policies_physical_interface": resourceMSOPhysicalInterface(), }, 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_fabric_resource_policies_physical_interface": datasourceMSOPhysicalInterface(), }, ConfigureFunc: configureClient, diff --git a/mso/resource_mso_fabric_resource_policies_physical_interface.go b/mso/resource_mso_fabric_resource_policies_physical_interface.go new file mode 100644 index 00000000..05600dbd --- /dev/null +++ b/mso/resource_mso_fabric_resource_policies_physical_interface.go @@ -0,0 +1,362 @@ +package mso + +import ( + "fmt" + "log" + "strings" + + "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" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceMSOPhysicalInterface() *schema.Resource { + return &schema.Resource{ + Create: resourceMSOPhysicalInterfaceCreate, + Read: resourceMSOPhysicalInterfaceRead, + Update: resourceMSOPhysicalInterfaceUpdate, + Delete: resourceMSOPhysicalInterfaceDelete, + Importer: &schema.ResourceImporter{ + State: resourceMSOPhysicalInterfaceImport, + }, + + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "nodes": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "interfaces": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "interface_policy_group_uuid": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"breakout_mode"}, + }, + "breakout_mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "4x100G", + "4x25G", + "4x10G", + }, false), + ConflictsWith: []string{"interface_policy_group_uuid"}, + }, + "interface_descriptions": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "interface": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "policy_group_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func getInterfaceDescriptionsPayloadPhysical(interfaceDescriptions *schema.Set) []map[string]interface{} { + interfaceDescriptionsList := interfaceDescriptions.List() + payload := make([]map[string]interface{}, 0) + for _, interfaceDescription := range interfaceDescriptionsList { + interfaceDescriptionMap := interfaceDescription.(map[string]interface{}) + payload = append(payload, map[string]interface{}{ + "interfaceID": interfaceDescriptionMap["interface"].(string), + "description": interfaceDescriptionMap["description"].(string), + }) + } + return payload +} + +func setPhysicalInterfaceData(d *schema.ResourceData, response *container.Container, templateId string) error { + d.SetId(fmt.Sprintf("templateId/%s/PhysicalInterface/%s", templateId, models.StripQuotes(response.S("name").String()))) + d.Set("template_id", templateId) + d.Set("name", models.StripQuotes(response.S("name").String())) + d.Set("description", models.StripQuotes(response.S("description").String())) + d.Set("uuid", models.StripQuotes(response.S("uuid").String())) + d.Set("interfaces", splitCommaString(models.StripQuotes(response.S("interfaces").String()))) + d.Set("policy_group_type", models.StripQuotes(response.S("policyGroupType").String())) + + if response.Exists("nodes") { + nodeCount, err := response.ArrayCount("nodes") + if err == nil { + nodeList := make([]interface{}, 0) + for i := 0; i < nodeCount; i++ { + nodeElement, err := response.ArrayElement(i, "nodes") + if err == nil { + nodeList = append(nodeList, models.StripQuotes(nodeElement.String())) + } + } + d.Set("nodes", nodeList) + } + } + + if response.Exists("policy") { + d.Set("interface_policy_group_uuid", models.StripQuotes(response.S("policy").String())) + } + + if response.Exists("breakoutMode") { + d.Set("breakout_mode", models.StripQuotes(response.S("breakoutMode").String())) + } + + interfaceDescriptionsList := make([]map[string]interface{}, 0) + count, err := response.ArrayCount("interfaceDescriptions") + if err == nil { + for i := 0; i < count; i++ { + descriptionContainer, err := response.ArrayElement(i, "interfaceDescriptions") + if err != nil { + return err + } + interfaceDescriptionsList = append(interfaceDescriptionsList, map[string]interface{}{ + "interface": models.StripQuotes(descriptionContainer.S("interfaceID").String()), + "description": models.StripQuotes(descriptionContainer.S("description").String()), + }) + } + } + + d.Set("interface_descriptions", interfaceDescriptionsList) + + return nil +} + +func resourceMSOPhysicalInterfaceImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] MSO Physical Interface Resource - Beginning Import: %v", d.Id()) + err := resourceMSOPhysicalInterfaceRead(d, m) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] MSO Physical Interface Resource - Import Complete: %v", d.Id()) + return []*schema.ResourceData{d}, nil +} + +func resourceMSOPhysicalInterfaceCreate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Physical Interface Resource - Beginning Create: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + + payload := map[string]interface{}{ + "name": d.Get("name").(string), + "templateId": templateId, + } + + if description, ok := d.GetOk("description"); ok { + payload["description"] = description.(string) + } + + payload["nodes"] = getListOfStringsFromSchemaSet(d, "nodes") + payload["interfaces"] = strings.Join(getListOfStringsFromSchemaSet(d, "interfaces"), ",") + interfacePolicyUUID := d.Get("interface_policy_group_uuid").(string) + breakoutMode := d.Get("breakout_mode").(string) + + // API error message is not clear when both fields are empty, so adding an explicit check here to return a clearer error message + if interfacePolicyUUID == "" && breakoutMode == "" { + return fmt.Errorf("Either 'interface_policy_group_uuid' or 'breakout_mode' must be specified for creating a Physical Interface") + } + + if interfacePolicyUUID != "" { + payload["policy"] = interfacePolicyUUID + } else { + payload["breakoutMode"] = breakoutMode + payload["policyGroupType"] = "breakout" + } + + if interfaceDescriptions, ok := d.GetOk("interface_descriptions"); ok { + payload["interfaceDescriptions"] = getInterfaceDescriptionsPayloadPhysical(interfaceDescriptions.(*schema.Set)) + } + + payloadModel := models.GetPatchPayload("add", "/fabricResourceTemplate/template/interfaceProfiles/-", payload) + + _, err := msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("templateId/%s/PhysicalInterface/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO Physical Interface Resource - Create Complete: %v", d.Id()) + return resourceMSOPhysicalInterfaceRead(d, m) +} + +func resourceMSOPhysicalInterfaceRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Physical Interface 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(), "PhysicalInterface") + if err != nil { + return err + } + + policy, err := GetPolicyByName(response, policyName, "fabricResourceTemplate", "template", "interfaceProfiles") + if err != nil { + return err + } + + err = setPhysicalInterfaceData(d, policy, templateId) + if err != nil { + return err + } + + log.Printf("[DEBUG] MSO Physical Interface Resource - Read Complete: %v", d.Id()) + return nil +} + +func resourceMSOPhysicalInterfaceUpdate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Physical Interface Resource - Beginning Update: %v", d.Id()) + msoClient := m.(*client.Client) + templateId := d.Get("template_id").(string) + + // API error message is not clear when both fields are empty, so adding an explicit check here to return a clearer error message + if d.Get("interface_policy_group_uuid").(string) == "" && d.Get("breakout_mode").(string) == "" { + return fmt.Errorf("Either 'interface_policy_group_uuid' or 'breakout_mode' must be specified for creating a Physical Interface") + } + + 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), "fabricResourceTemplate", "template", "interfaceProfiles") + if err != nil { + return err + } + + updatePath := fmt.Sprintf("/fabricResourceTemplate/template/interfaceProfiles/%d", policyIndex) + + payloadCont := container.New() + payloadCont.Array() + + if d.HasChange("name") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/name", updatePath), d.Get("name").(string)) + if err != nil { + return err + } + } + + if d.HasChange("description") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/description", updatePath), d.Get("description").(string)) + if err != nil { + return err + } + } + + if d.HasChange("nodes") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/nodes", updatePath), getListOfStringsFromSchemaSet(d, "nodes")) + if err != nil { + return err + } + } + + if d.HasChange("interfaces") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/interfaces", updatePath), strings.Join(getListOfStringsFromSchemaSet(d, "interfaces"), ",")) + if err != nil { + return err + } + } + + if d.HasChange("interface_policy_group_uuid") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/policy", updatePath), d.Get("interface_policy_group_uuid").(string)) + if err != nil { + return err + } + } + + if d.HasChange("breakout_mode") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/breakoutMode", updatePath), d.Get("breakout_mode").(string)) + if err != nil { + return err + } + } + + if d.HasChange("interface_descriptions") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/interfaceDescriptions", updatePath), getInterfaceDescriptionsPayloadPhysical(d.Get("interface_descriptions").(*schema.Set))) + if err != nil { + return err + } + } + + err = doPatchRequest(msoClient, fmt.Sprintf("api/v1/templates/%s", templateId), payloadCont) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("templateId/%s/PhysicalInterface/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO Physical Interface Resource - Update Complete: %v", d.Id()) + return resourceMSOPhysicalInterfaceRead(d, m) +} + +func resourceMSOPhysicalInterfaceDelete(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO Physical Interface 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), "fabricResourceTemplate", "template", "interfaceProfiles") + if err != nil { + return err + } + + payloadModel := models.GetRemovePatchPayload(fmt.Sprintf("/fabricResourceTemplate/template/interfaceProfiles/%d", policyIndex)) + + _, err = msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId("") + log.Printf("[DEBUG] MSO Physical Interface Resource - Delete Complete: %v", d.Id()) + return nil +} diff --git a/mso/resource_mso_fabric_resource_policies_physical_interface_test.go b/mso/resource_mso_fabric_resource_policies_physical_interface_test.go new file mode 100644 index 00000000..bcf7c064 --- /dev/null +++ b/mso/resource_mso_fabric_resource_policies_physical_interface_test.go @@ -0,0 +1,369 @@ +package mso + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOFabricResourcePhysicalInterfaceResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: Create Without interface_policy_group_uuid and breakout_mode (error)") }, + Config: testAccMSOFabricResourcePhysicalInterfaceConfigErrorMissingInterfacePolicyAndBreakoutMode(), + ExpectError: regexp.MustCompile(`Either 'interface_policy_group_uuid' or 'breakout_mode' must be specified for creating a Physical Interface`), + }, + { + PreConfig: func() { fmt.Println("Test: Create Physical Interface") }, + Config: testAccMSOFabricResourcePhysicalInterfaceConfigCreate(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "name", msoFabricResourcePhysicalInterfaceName), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "policy_group_type", "physical"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "description", ""), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "nodes.#", "1"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interfaces.#", "2"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "uuid"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "template_id"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_policy_group_uuid"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Physical Interface adding interface descriptions") }, + Config: testAccMSOFabricResourcePhysicalInterfaceConfigUpdateAddingInterfaceDescriptions(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "name", msoFabricResourcePhysicalInterfaceName), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "description", "Terraform test Physical Interface updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "nodes.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interfaces.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions.#", "1"), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions", + map[string]string{ + "interface": "1/1", + "description": "Interface Description 1/1", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Physical Interface adding extra interface description") }, + Config: testAccMSOFabricResourcePhysicalInterfaceConfigUpdateAddingExtraInterfaceDescription(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "name", msoFabricResourcePhysicalInterfaceName), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "description", "Terraform test Physical Interface updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "nodes.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interfaces.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions.#", "2"), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions", + map[string]string{ + "interface": "1/1", + "description": "Interface Description 1/1", + }, + ), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions", + map[string]string{ + "interface": "1/2", + "description": "Interface Description 1/2", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Physical Interface removing extra interface description") }, + Config: testAccMSOFabricResourcePhysicalInterfaceConfigUpdateRemovingExtraInterfaceDescription(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "name", msoFabricResourcePhysicalInterfaceName+"_updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "description", ""), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "nodes.#", "1"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interfaces.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions.#", "1"), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName, "interface_descriptions", + map[string]string{ + "interface": "1/2", + "description": "", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Import Physical Interface") }, + ResourceName: "mso_fabric_resource_policies_physical_interface." + msoFabricResourcePhysicalInterfaceName, + ImportState: true, + ImportStateVerify: true, + }, + { + PreConfig: func() { fmt.Println("Test: Create Physical Interface with Breakout Mode") }, + Config: testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigCreate(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "name", msoFabricResourcePhysicalInterfaceName+"_breakout"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "policy_group_type", "breakout"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "description", "Terraform test Physical Interface"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "nodes.#", "1"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interfaces.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "breakout_mode", "4x10G"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "uuid"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "template_id"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Breakout Mode and add interface descriptions") }, + Config: testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateAddingInterfaceDescriptions(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "name", msoFabricResourcePhysicalInterfaceName+"_breakout"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "description", "Terraform test Physical Interface updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "nodes.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interfaces.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "breakout_mode", "4x25G"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions.#", "1"), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions", + map[string]string{ + "interface": "1/1", + "description": "Interface Description 1/1", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Breakout Mode and add extra interface description") }, + Config: testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateAddingExtraInterfaceDescription(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "name", msoFabricResourcePhysicalInterfaceName+"_breakout"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "description", "Terraform test Physical Interface updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "nodes.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interfaces.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "breakout_mode", "4x100G"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions.#", "2"), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions", + map[string]string{ + "interface": "1/1", + "description": "Interface Description 1/1", + }, + ), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions", + map[string]string{ + "interface": "1/2", + "description": "Interface Description 1/2", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update Breakout Mode and remove extra interface description") }, + Config: testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingExtraInterfaceDescription(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "name", msoFabricResourcePhysicalInterfaceName+"_breakout_updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "description", "Terraform test Physical Interface updated"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "nodes.#", "1"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interfaces.#", "2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "breakout_mode", "4x100G"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions.#", "1"), + CustomTestCheckTypeSetElemAttrs("mso_fabric_resource_policies_physical_interface."+msoFabricResourcePhysicalInterfaceName+"_breakout", "interface_descriptions", + map[string]string{ + "interface": "1/2", + "description": "Interface Description 1/2", + }, + ), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Import Physical Interface with Breakout Mode") }, + ResourceName: "mso_fabric_resource_policies_physical_interface." + msoFabricResourcePhysicalInterfaceName + "_breakout", + ImportState: true, + ImportStateVerify: true, + }, + { + PreConfig: func() { fmt.Println("Test: Duplicate interface descriptions (error)") }, + Config: testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingDuplicateInterfaceDescription(), + ExpectError: regexp.MustCompile(regexp.QuoteMeta(fmt.Sprintf("interface profile %s_breakout_updated have more than one description for interface 1/2", msoFabricResourcePhysicalInterfaceName))), + }, + { + PreConfig: func() { fmt.Println("Test: Invalid interface in interface descriptions (error)") }, + Config: testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingInvalidInterfaceDescription(), + ExpectError: regexp.MustCompile(regexp.QuoteMeta(fmt.Sprintf("interface profile %s_breakout_updated doesn't have interface 1/3 which is used in description", msoFabricResourcePhysicalInterfaceName))), + }, + }, + CheckDestroy: testCheckResourceDestroyPolicyWithPathAttributesAndArguments("mso_fabric_resource_policies_physical_interface", "fabricResourceTemplate", "template", "physicalInterfaces"), + }) +} + +var fabricResourcePhysicalInterfacePreConfig = testFabricResourceTemplateConfig() + testFabricPolicyTemplateConfig() + testFabricPoliciesInterfaceSettingPhysicalConfig() + +func testAccMSOFabricResourcePhysicalInterfaceConfigErrorMissingInterfacePolicyAndBreakoutMode() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s" { + template_id = mso_template.%[3]s.id + name = "%[2]s" + description = "Terraform test Physical Interface" + nodes = ["101"] + interfaces = ["1/1","1/2"] + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceConfigCreate() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s" { + template_id = mso_template.%[4]s.id + name = "%[2]s" + nodes = ["101"] + interfaces = ["1/1","1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%[3]s_physical.uuid + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricPolicyTemplateInterfaceSettingName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceConfigUpdateAddingInterfaceDescriptions() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s" { + template_id = mso_template.%[4]s.id + name = "%[2]s" + description = "Terraform test Physical Interface updated" + nodes = ["101", "102"] + interfaces = ["1/1","1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%[3]s_physical.uuid + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricPolicyTemplateInterfaceSettingName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceConfigUpdateAddingExtraInterfaceDescription() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s" { + template_id = mso_template.%[4]s.id + name = "%[2]s" + description = "Terraform test Physical Interface updated" + nodes = ["101", "102"] + interfaces = ["1/1","1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%[3]s_physical.uuid + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricPolicyTemplateInterfaceSettingName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceConfigUpdateRemovingExtraInterfaceDescription() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s" { + template_id = mso_template.%[4]s.id + name = "%[2]s_updated" + nodes = ["101"] + interfaces = ["1/1","1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%[3]s_physical.uuid + interface_descriptions { + interface = "1/2" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricPolicyTemplateInterfaceSettingName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigCreate() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s_breakout" { + template_id = mso_template.%[3]s.id + name = "%[2]s_breakout" + description = "Terraform test Physical Interface" + nodes = ["101"] + interfaces = ["1/1","1/2"] + breakout_mode = "4x10G" + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateAddingInterfaceDescriptions() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s_breakout" { + template_id = mso_template.%[3]s.id + name = "%[2]s_breakout" + description = "Terraform test Physical Interface updated" + nodes = ["101", "102"] + interfaces = ["1/1","1/2"] + breakout_mode = "4x25G" + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateAddingExtraInterfaceDescription() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s_breakout" { + template_id = mso_template.%[3]s.id + name = "%[2]s_breakout" + description = "Terraform test Physical Interface updated" + nodes = ["101", "102"] + interfaces = ["1/1","1/2"] + breakout_mode = "4x100G" + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingExtraInterfaceDescription() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s_breakout" { + template_id = mso_template.%[3]s.id + name = "%[2]s_breakout_updated" + description = "Terraform test Physical Interface updated" + nodes = ["101"] + interfaces = ["1/1","1/2"] + breakout_mode = "4x100G" + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingDuplicateInterfaceDescription() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s_breakout" { + template_id = mso_template.%[3]s.id + name = "%[2]s_breakout_updated" + description = "Terraform test Physical Interface updated" + nodes = ["101"] + interfaces = ["1/1","1/2"] + breakout_mode = "4x100G" + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2 duplicate" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricResourceTemplateName) +} + +func testAccMSOFabricResourcePhysicalInterfaceBreakoutModeConfigUpdateRemovingInvalidInterfaceDescription() string { + return fmt.Sprintf(`%[1]s + resource "mso_fabric_resource_policies_physical_interface" "%[2]s_breakout" { + template_id = mso_template.%[3]s.id + name = "%[2]s_breakout_updated" + description = "Terraform test Physical Interface updated" + nodes = ["101"] + interfaces = ["1/1","1/2"] + breakout_mode = "4x100G" + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } + interface_descriptions { + interface = "1/3" + description = "Interface Description 1/2 duplicate" + } + }`, fabricResourcePhysicalInterfacePreConfig, msoFabricResourcePhysicalInterfaceName, msoFabricResourceTemplateName) +} diff --git a/mso/resource_mso_tenant_policies_ipsla_track_list_test.go b/mso/resource_mso_tenant_policies_ipsla_track_list_test.go index 5fb85d70..608c6e82 100644 --- a/mso/resource_mso_tenant_policies_ipsla_track_list_test.go +++ b/mso/resource_mso_tenant_policies_ipsla_track_list_test.go @@ -8,7 +8,6 @@ import ( ) func TestAccMSOTenantPoliciesIPSLATrackListResource(t *testing.T) { - print(testAccMSOTenantPoliciesIPSLATrackListConfigCreate()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -25,11 +24,9 @@ func TestAccMSOTenantPoliciesIPSLATrackListResource(t *testing.T) { resource.TestCheckResourceAttr("mso_tenant_policies_ipsla_track_list."+msoTenantPolicyTemplateIPSLATrackListName, "members.#", "1"), CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_ipsla_track_list."+msoTenantPolicyTemplateIPSLATrackListName, "members", map[string]string{ - "destination_ip": "1.1.1.1", - "ipsla_monitoring_policy_uuid": fmt.Sprintf("mso_tenant_policies_ipsla_monitoring_policy.%s.uuid", msoTenantPolicyTemplateIPSLAMonitoringPolicyName), - "scope_type": "bd", - "scope_uuid": fmt.Sprintf("mso_schema_template_bd.%s.uuid", msoSchemaTemplateBdName), - "weight": "10", + "destination_ip": "1.1.1.1", + "scope_type": "bd", + "weight": "10", }, ), ), @@ -46,20 +43,16 @@ func TestAccMSOTenantPoliciesIPSLATrackListResource(t *testing.T) { resource.TestCheckResourceAttr("mso_tenant_policies_ipsla_track_list."+msoTenantPolicyTemplateIPSLATrackListName, "members.#", "2"), CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_ipsla_track_list."+msoTenantPolicyTemplateIPSLATrackListName, "members", map[string]string{ - "destination_ip": "1.1.1.3", - "ipsla_monitoring_policy_uuid": fmt.Sprintf("mso_tenant_policies_ipsla_monitoring_policy.%s.uuid", msoTenantPolicyTemplateIPSLAMonitoringPolicyName), - "scope_type": "bd", - "scope_uuid": fmt.Sprintf("mso_schema_template_bd.%s.uuid", msoSchemaTemplateBdName), - "weight": "10", + "destination_ip": "1.1.1.3", + "scope_type": "bd", + "weight": "10", }, ), CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_ipsla_track_list."+msoTenantPolicyTemplateIPSLATrackListName, "members", map[string]string{ - "destination_ip": "1.1.1.2", - "ipsla_monitoring_policy_uuid": fmt.Sprintf("mso_tenant_policies_ipsla_monitoring_policy.%s.uuid", msoTenantPolicyTemplateIPSLAMonitoringPolicyName), - "scope_type": "bd", - "scope_uuid": fmt.Sprintf("mso_schema_template_bd.%s.uuid", msoSchemaTemplateBdName), - "weight": "10", + "destination_ip": "1.1.1.2", + "scope_type": "bd", + "weight": "10", }, ), ), @@ -76,11 +69,9 @@ func TestAccMSOTenantPoliciesIPSLATrackListResource(t *testing.T) { resource.TestCheckResourceAttr("mso_tenant_policies_ipsla_track_list."+msoTenantPolicyTemplateIPSLATrackListName, "members.#", "1"), CustomTestCheckTypeSetElemAttrs("mso_tenant_policies_ipsla_track_list."+msoTenantPolicyTemplateIPSLATrackListName, "members", map[string]string{ - "destination_ip": "1.1.1.2", - "ipsla_monitoring_policy_uuid": fmt.Sprintf("mso_tenant_policies_ipsla_monitoring_policy.%s.uuid", msoTenantPolicyTemplateIPSLAMonitoringPolicyName), - "scope_type": "bd", - "scope_uuid": fmt.Sprintf("mso_schema_template_bd.%s.uuid", msoSchemaTemplateBdName), - "weight": "10", + "destination_ip": "1.1.1.2", + "scope_type": "bd", + "weight": "10", }, ), ), diff --git a/mso/test_constants.go b/mso/test_constants.go index d6ac91e7..1b842f7d 100644 --- a/mso/test_constants.go +++ b/mso/test_constants.go @@ -34,6 +34,9 @@ var msoSchemaTemplateVrfL3MulticastName = acctest.RandStringFromCharSet(10, acct const msoSchemaTemplateAnpEpgSubnetIp = "10.0.0.1/24" +var msoFabricResourceTemplateName = acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) +var msoFabricResourcePhysicalInterfaceName = acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + func testSiteConfigAnsibleTest() string { return fmt.Sprintf(` data "mso_site" "%[1]s" { @@ -292,3 +295,22 @@ resource "mso_schema_template_anp_epg_subnet" "%[1]s_subnet" { } `, msoSchemaTemplateAnpEpgName, msoSchemaName, msoSchemaTemplateName, msoSchemaTemplateAnpName, msoSchemaTemplateAnpEpgName, msoSchemaTemplateAnpEpgSubnetIp) } + +func testFabricResourceTemplateConfig() string { + return fmt.Sprintf(` +resource "mso_template" "%[1]s" { + template_name = "%[1]s" + template_type = "fabric_resource" +} + `, msoFabricResourceTemplateName) +} + +func testFabricPoliciesInterfaceSettingPhysicalConfig() string { + return fmt.Sprintf(` +resource "mso_fabric_policies_interface_setting" "%[1]s_physical" { + template_id = mso_template.%[2]s.id + type = "physical" + name = "%[1]s_physical" +} +`, msoFabricPolicyTemplateInterfaceSettingName, msoFabricPolicyTemplateName) +} diff --git a/mso/utils.go b/mso/utils.go index 7fc0678c..9d1204c3 100644 --- a/mso/utils.go +++ b/mso/utils.go @@ -301,6 +301,13 @@ func getListOfStringsFromSchemaList(d *schema.ResourceData, key string) []string return nil } +func getListOfStringsFromSchemaSet(d *schema.ResourceData, key string) []string { + if values, ok := d.GetOk(key); ok { + return convertToListOfStrings(values.(*schema.Set).List()) + } + return nil +} + func convertToListOfStrings(values []interface{}) []string { result := []string{} for _, item := range values { @@ -540,3 +547,19 @@ func GetDeployedSiteIdsForApplicationTemplate(msoClient *client.Client, schemaId } return siteIds, nil } + +func splitCommaString(s string) []string { + s = strings.TrimSpace(s) + if s == "" { + return []string{} + } + parts := strings.Split(s, ",") + out := make([]string, 0, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part != "" { + out = append(out, part) + } + } + return out +} diff --git a/mso/utils_test.go b/mso/utils_test.go new file mode 100644 index 00000000..a480f896 --- /dev/null +++ b/mso/utils_test.go @@ -0,0 +1,33 @@ +package mso + +import ( + "reflect" + "testing" +) + +func TestSplitCommaString(t *testing.T) { + cases := []struct { + name string + input string + expected []string + }{ + {"empty", "", []string{}}, + {"whitespace_only", " \t ", []string{}}, + {"single_token", "1/1", []string{"1/1"}}, + {"single_token_with_spaces", " 1/1 ", []string{"1/1"}}, + {"multiple_tokens", "1/1,1/2", []string{"1/1", "1/2"}}, + {"multiple_tokens_with_spaces", " 1/1 , 1/2 ", []string{"1/1", "1/2"}}, + {"drops_empty_tokens_leading_trailing_commas", ",1/1,1/2,", []string{"1/1", "1/2"}}, + {"drops_empty_tokens_repeated_commas", "1/1,,1/2", []string{"1/1", "1/2"}}, + {"all_empty_tokens", ",,,", []string{}}, + } + + for _, testCase := range cases { + t.Run(testCase.name, func(t *testing.T) { + got := splitCommaString(testCase.input) + if !reflect.DeepEqual(got, testCase.expected) { + t.Fatalf("splitCommaString(%q) = %#v, expected %#v", testCase.input, got, testCase.expected) + } + }) + } +} diff --git a/website/docs/d/fabric_resource_policies_physical_interface.html.markdown b/website/docs/d/fabric_resource_policies_physical_interface.html.markdown new file mode 100644 index 00000000..f874fae8 --- /dev/null +++ b/website/docs/d/fabric_resource_policies_physical_interface.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "mso" +page_title: "MSO: mso_fabric_resource_policies_physical_interface" +sidebar_current: "docs-mso-data-source-fabric_resource_policies_physical_interface" +description: |- + Data source for Fabric Resource Policies Physical Interface on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_fabric_resource_policies_physical_interface # + +Data source for Fabric Resource Policies Physical Interface on Cisco Nexus Dashboard Orchestrator (NDO). This data source is supported in NDO v4.1 or higher. + +## GUI Information ## + +* `Location`: Manage -> Fabric Resource Template -> Fabric Resource Policies -> Physical Interfaces + +## Example Usage ## + +```hcl +data "mso_fabric_resource_policies_physical_interface" "example" { + template_id = mso_template.fabric_resource_template.id + name = "physical_interface" +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The ID of the Fabric Resource template. +* `name` - (Required) The name of the Physical Interface Policy. + +## Attribute Reference ## + +* `id` - (Read-Only) The unique Terraform identifier of the Physical Interface Policy. +* `uuid` - (Read-Only) The NDO UUID of the Physical Interface Policy. +* `description` - (Read-Only) The description of the Physical Interface Policy. +* `nodes` - (Read-Only) A list of node IDs where this Physical Interface Policy will be applied. +* `interfaces` - (Read-Only) A list of interfaces where this Physical Interface Policy will be applied. +* `interface_policy_group_uuid` - (Read-Only) The UUID of the (physical) interface settings policy to associate with the Physical Interface Policy. This policy will be applied to every interface listed in the `interfaces` attribute. +* `breakout_mode` - (Read-Only) The breakout mode of the Physical Interface Policy. +* `interface_descriptions` - (Read-Only) A list of interface descriptions of the Physical Interface Policy. + * `interface` - (Read-Only) The interface ID of the member interface. + * `description` - (Read-Only) The description of the member interface. +* `policy_group_type` - (Read-Only) The policy group type of the Physical Interface. diff --git a/website/docs/r/fabric_resource_policies_physical_interface.html.markdown b/website/docs/r/fabric_resource_policies_physical_interface.html.markdown new file mode 100644 index 00000000..95cd9e3a --- /dev/null +++ b/website/docs/r/fabric_resource_policies_physical_interface.html.markdown @@ -0,0 +1,87 @@ +--- +layout: "mso" +page_title: "MSO: mso_fabric_resource_policies_physical_interface" +sidebar_current: "docs-mso-resource-fabric_resource_policies_physical_interface" +description: |- + Manages Fabric Resource Policies Physical Interface on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_fabric_resource_policies_physical_interface # + +Manages Fabric Resource Policies Physical Interface on Cisco Nexus Dashboard Orchestrator (NDO). This resource is supported in NDO v4.1 or higher. + +## GUI Information ## + +* `Location`: Manage -> Fabric Resource Template -> Fabric Resource Policies -> Physical Interfaces + +## Example Usage ## + +### Physical Interface with Interface Policy + +```hcl +resource "mso_fabric_resource_policies_physical_interface" "physical" { + template_id = mso_template.fabric_resource_template.id + name = "physical_interface" + description = "Terraform test Physical Interface" + nodes = ["101", "102"] + interfaces = ["1/1", "1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.physical_interface.id + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } +} +``` + +### Physical Interface with Breakout Mode + +```hcl +resource "mso_fabric_resource_policies_physical_interface" "breakout" { + template_id = mso_template.fabric_resource_template.id + name = "breakout_mode_physical_interface" + description = "Terraform test Physical Interface with breakout mode" + nodes = ["101", "102"] + interfaces = ["1/1", "1/2"] + breakout_mode = "4x100G" + interface_descriptions { + interface = "1/1" + description = "Interface Description 1/1" + } + interface_descriptions { + interface = "1/2" + description = "Interface Description 1/2" + } +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The ID of the Fabric Resource template. +* `name` - (Required) The name of the Physical Interface Policy. +* `description` - (Optional) The description of the Physical Interface Policy. +* `nodes` - (Required) A list of node IDs where this Physical Interface Policy will be applied. This is required when creating or updating a Physical Interface Policy. +* `interfaces` - (Required) A list of interfaces where this Physical Interface Policy will be applied. This is required when creating or updating a Physical Interface Policy. +* `interface_policy_group_uuid` - (Optional) The UUID of the (physical) interface settings policy to associate with the Physical Interface Policy. This policy will be applied to every interface listed in the `interfaces` attribute. Either `interface_policy_group_uuid` or `breakout_mode` must be specified when creating or updating a Physical Interface Policy. +* `breakout_mode` - (Optional) The breakout mode of the Physical Interface Policy. Valid values are `4x100G`, `4x25G`, and `4x10G`. Either `interface_policy_group_uuid` or `breakout_mode` must be specified when creating or updating a Physical Interface Policy. +* `interface_descriptions` - (Optional) A list of interface descriptions of the Physical Interface Policy. + * `interface` - (Required) The interface ID of the member interface. Must match an interface defined in the `interfaces` attribute. + * `description` - (Optional) The description of the member interface. + +## Attribute Reference ## + +* `id` - (Read-Only) The unique Terraform identifier of the Physical Interface Policy. +* `uuid` - (Read-Only) The NDO UUID of the Physical Interface Policy. +* `policy_group_type` - (Read-Only) The policy group type of the Physical Interface Policy. + +## Importing ## + +An existing MSO Fabric Resource Policies Physical Interface Policy can be [imported][docs-import] into this resource via its ID, using the following command: +[docs-import]: https://www.terraform.io/docs/import/index.html + +```bash +terraform import mso_fabric_resource_policies_physical_interface.example templateId/{template_id}/PhysicalInterface/{name} +``` \ No newline at end of file