diff --git a/examples/fabric_resource_policies_virtual_port_channel_interface/main.tf b/examples/fabric_resource_policies_virtual_port_channel_interface/main.tf new file mode 100644 index 00000000..642c4fa0 --- /dev/null +++ b/examples/fabric_resource_policies_virtual_port_channel_interface/main.tf @@ -0,0 +1,51 @@ +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" "port_channel_interface" { + template_id = mso_template.fabric_policy_template.id + type = "portchannel" + name = "port_channel_interface" +} + +resource "mso_template" "fabric_resource_template" { + template_name = "fabric_resource_template" + template_type = "fabric_resource" +} + +resource "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_template.fabric_resource_template.id + name = "tf_vpc_if" + description = "Example VPC Interface" + node_1 = "101" + node_2 = "102" + node_1_interfaces = ["1/1", "1/10-11"] + node_2_interfaces = ["1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.port_channel_interface.uuid + interface_descriptions { + node = "101" + interface = "1/1" + description = "Interface Description 1/1" + } + interface_descriptions { + node = "102" + interface = "1/10" + description = "Interface Description 1/10" + } +} diff --git a/mso/datasource_mso_fabric_resource_policies_virtual_port_channel_interface.go b/mso/datasource_mso_fabric_resource_policies_virtual_port_channel_interface.go new file mode 100644 index 00000000..64fa007d --- /dev/null +++ b/mso/datasource_mso_fabric_resource_policies_virtual_port_channel_interface.go @@ -0,0 +1,78 @@ +package mso + +import ( + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func dataSourceMSOVirtualPortChannelInterface() *schema.Resource { + return &schema.Resource{ + Read: dataSourceMSOFabricResourcePoliciesVirtualPortChannelInterfaceRead, + + SchemaVersion: version, + + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "Fabric Resource template ID.", + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "Virtual Port Channel Interface name.", + }, + "uuid": {Type: schema.TypeString, Computed: true}, + "description": {Type: schema.TypeString, Computed: true}, + + "node_1": {Type: schema.TypeString, Computed: true}, + "node_2": {Type: schema.TypeString, Computed: true}, + + "node_1_interfaces": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "node_2_interfaces": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "interface_policy_group_uuid": {Type: schema.TypeString, Computed: true}, + + "interface_descriptions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "node": {Type: schema.TypeString, Computed: true}, + "interface": {Type: schema.TypeString, Computed: true}, + "description": {Type: schema.TypeString, Computed: true}, + }, + }, + }, + }, + } +} + +func dataSourceMSOFabricResourcePoliciesVirtualPortChannelInterfaceRead(d *schema.ResourceData, m any) error { + log.Printf("[DEBUG] MSO VPC Interface Data Source - Beginning Read: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + name := d.Get("name").(string) + + err := setVPCInterfaceData(d, msoClient, templateId, name) + if err != nil { + return err + } + + log.Printf("[DEBUG] MSO VPC Interface Data Source - Read Complete: %v", d.Id()) + return nil +} diff --git a/mso/datasource_mso_fabric_resource_policies_virtual_port_channel_interface_test.go b/mso/datasource_mso_fabric_resource_policies_virtual_port_channel_interface_test.go new file mode 100644 index 00000000..938f0616 --- /dev/null +++ b/mso/datasource_mso_fabric_resource_policies_virtual_port_channel_interface_test.go @@ -0,0 +1,53 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOVirtualPortChannelInterfaceDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: VPC Interface Data Source") }, + Config: testAccMSOVirtualPortChannelInterfaceDataSource(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "name", "tf_test_vpc_if"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "description", "Terraform test VPC Interface"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1", "101"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2", "102"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces.#", "2"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces.1", "1/1"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces.0", "1/10-11"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces.#", "1"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces.0", "1/2"), + resource.TestCheckResourceAttr("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_descriptions.#", "1"), + resource.TestCheckResourceAttrSet("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "uuid"), + resource.TestCheckResourceAttrSet("data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_policy_group_uuid"), + customTestCheckResourceTypeSetAttr( + "data.mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", + "interface_descriptions", + map[string]string{ + "node": "101", + "interface": "1/1", + "description": "Terraform test interface description", + }, + ), + ), + }, + }, + }) +} + +func testAccMSOVirtualPortChannelInterfaceDataSource() string { + return fmt.Sprintf(`%s + data "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if.template_id + name = "tf_test_vpc_if" + } + `, testAccMSOVirtualPortChannelInterfaceConfigCreate()) +} diff --git a/mso/provider.go b/mso/provider.go index 18d1c7ab..04771567 100644 --- a/mso/provider.go +++ b/mso/provider.go @@ -68,155 +68,157 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "mso_schema": resourceMSOSchema(), - "mso_schema_site": resourceMSOSchemaSite(), - "mso_site": resourceMSOSite(), - "mso_remote_location": resourceMSORemoteLocation(), - "mso_user": resourceMSOUser(), - "mso_label": resourceMSOLabel(), - "mso_schema_template": resourceMSOSchemaTemplate(), - "mso_tenant": resourceMSOTenant(), - "mso_schema_template_bd": resourceMSOTemplateBD(), - "mso_schema_template_vrf": resourceMSOSchemaTemplateVrf(), - "mso_schema_template_bd_subnet": resourceMSOTemplateBDSubnet(), - "mso_schema_template_anp": resourceMSOSchemaTemplateAnp(), - "mso_schema_template_anp_epg": resourceMSOSchemaTemplateAnpEpg(), - "mso_schema_template_anp_epg_contract": resourceMSOTemplateAnpEpgContract(), - "mso_schema_template_contract": resourceMSOTemplateContract(), - "mso_schema_template_anp_epg_subnet": resourceMSOSchemaTemplateAnpEpgSubnet(), - "mso_schema_template_l3out": resourceMSOTemplateL3out(), - "mso_schema_template_external_epg": resourceMSOTemplateExtenalepg(), - "mso_schema_template_contract_filter": resourceMSOTemplateContractFilter(), - "mso_schema_template_external_epg_contract": resourceMSOTemplateExternalEpgContract(), - "mso_schema_template_filter_entry": resourceMSOSchemaTemplateFilterEntry(), - "mso_schema_template_external_epg_subnet": resourceMSOTemplateExtenalepgSubnet(), - "mso_schema_site_anp_epg_static_leaf": resourceMSOSchemaSiteAnpEpgStaticleaf(), - "mso_schema_site_anp_epg_static_port": resourceMSOSchemaSiteAnpEpgStaticPort(), - "mso_schema_site_anp_epg_bulk_staticport": resourceMSOSchemaSiteAnpEpgBulkStaticPort(), - "mso_schema_site_bd": resourceMSOSchemaSiteBd(), - "mso_schema_site_anp_epg_subnet": resourceMSOSchemaSiteAnpEpgSubnet(), - "mso_schema_site_anp_epg_domain": resourceMSOSchemaSiteAnpEpgDomain(), - "mso_schema_site_bd_l3out": resourceMSOSchemaSiteBdL3out(), - "mso_schema_site_vrf": resourceMSOSchemaSiteVrf(), - "mso_schema_site_vrf_route_leak": resourceMSOSchemaSiteVrfRouteLeak(), - "mso_schema_site_vrf_region": resourceMSOSchemaSiteVrfRegion(), - "mso_schema_site_bd_subnet": resourceMSOSchemaSiteBdSubnet(), - "mso_rest": resourceMSORest(), - "mso_schema_template_deploy": resourceMSOSchemaTemplateDeploy(), - "mso_schema_template_deploy_ndo": resourceNDOSchemaTemplateDeploy(), - "mso_schema_site_vrf_region_cidr_subnet": resourceMSOSchemaSiteVrfRegionCidrSubnet(), - "mso_schema_site_vrf_region_cidr": resourceMSOSchemaSiteVrfRegionCidr(), - "mso_schema_site_anp": resourceMSOSchemaSiteAnp(), - "mso_schema_site_anp_epg": resourceMSOSchemaSiteAnpEpg(), - "mso_schema_template_anp_epg_selector": resourceMSOSchemaTemplateAnpEpgSelector(), - "mso_schema_site_external_epg": resourceMSOSchemaSiteExternalEpg(), - "mso_schema_template_external_epg_selector": resourceSchemaTemplateExternalEPGSelector(), - "mso_schema_template_anp_epg_useg_attr": resourceMSOSchemaTemplateAnpEpgUsegAttr(), - "mso_schema_site_anp_epg_selector": resourceMSOSchemaSiteAnpEpgSelector(), - "mso_schema_template_vrf_contract": resourceMSOTemplateVRFContract(), - "mso_schema_site_external_epg_selector": resourceMSOSchemaSiteExternalEpgSelector(), - "mso_schema_template_service_graph": resourceMSOSchemaTemplateServiceGraphs(), - "mso_schema_site_service_graph_node": resourceMSOSchemaSiteServiceGraphNode(), - "mso_schema_site_service_graph": resourceMSOSchemaSiteServiceGraph(), - "mso_service_node_type": resourceMSOServiceNodeType(), - "mso_schema_template_contract_service_graph": resourceMSOSchemaTemplateContractServiceGraph(), - "mso_system_config": resourceMSOSystemConfig(), - "mso_schema_site_contract_service_graph": resourceMSOSchemaSiteContractServiceGraph(), - "mso_schema_site_contract_service_graph_listener": resourceMSOSchemaSiteContractServiceGraphListener(), - "mso_template": resourceMSOTemplate(), - "mso_tenant_policies_ipsla_monitoring_policy": resourceMSOIPSLAMonitoringPolicy(), - "mso_tenant_policies_route_map_policy_multicast": resourceMSOMcastRouteMapPolicy(), - "mso_tenant_policies_dhcp_relay_policy": resourceMSOTenantPoliciesDHCPRelayPolicy(), - "mso_fabric_policies_vlan_pool": resourceMSOVlanPool(), - "mso_fabric_policies_physical_domain": resourceMSOPhysicalDomain(), - "mso_service_device_cluster": resourceMSOServiceDeviceCluster(), - "mso_fabric_policies_synce_interface_policy": resourceMSOSyncEInterfacePolicy(), - "mso_fabric_policies_macsec_policy": resourceMSOMacsecPolicy(), - "mso_schema_template_contract_service_chaining": resourceMSOSchemaTemplateContractServiceChaining(), - "mso_tenant_policies_bgp_peer_prefix_policy": resourceMSOBGPPeerPrefixPolicy(), - "mso_fabric_policies_l3_domain": resourceMSOL3Domain(), - "mso_tenant_policies_custom_qos_policy": resourceMSOCustomQoSPolicy(), - "mso_tenant_policies_dhcp_option_policy": resourceMSODHCPOptionPolicy(), - "mso_tenant_policies_mld_snooping_policy": resourceMSOMLDSnoopingPolicy(), - "mso_fabric_policies_mcp_global_policy": resourceMSOMCPGlobalPolicy(), - "mso_tenant_policies_ipsla_track_list": resourceMSOIPSLATrackList(), - "mso_tenant_policies_l3out_interface_routing_policy": resourceMSOL3OutInterfaceRoutingPolicy(), - "mso_fabric_policies_interface_setting": resourceMSOInterfaceSetting(), + "mso_schema": resourceMSOSchema(), + "mso_schema_site": resourceMSOSchemaSite(), + "mso_site": resourceMSOSite(), + "mso_remote_location": resourceMSORemoteLocation(), + "mso_user": resourceMSOUser(), + "mso_label": resourceMSOLabel(), + "mso_schema_template": resourceMSOSchemaTemplate(), + "mso_tenant": resourceMSOTenant(), + "mso_schema_template_bd": resourceMSOTemplateBD(), + "mso_schema_template_vrf": resourceMSOSchemaTemplateVrf(), + "mso_schema_template_bd_subnet": resourceMSOTemplateBDSubnet(), + "mso_schema_template_anp": resourceMSOSchemaTemplateAnp(), + "mso_schema_template_anp_epg": resourceMSOSchemaTemplateAnpEpg(), + "mso_schema_template_anp_epg_contract": resourceMSOTemplateAnpEpgContract(), + "mso_schema_template_contract": resourceMSOTemplateContract(), + "mso_schema_template_anp_epg_subnet": resourceMSOSchemaTemplateAnpEpgSubnet(), + "mso_schema_template_l3out": resourceMSOTemplateL3out(), + "mso_schema_template_external_epg": resourceMSOTemplateExtenalepg(), + "mso_schema_template_contract_filter": resourceMSOTemplateContractFilter(), + "mso_schema_template_external_epg_contract": resourceMSOTemplateExternalEpgContract(), + "mso_schema_template_filter_entry": resourceMSOSchemaTemplateFilterEntry(), + "mso_schema_template_external_epg_subnet": resourceMSOTemplateExtenalepgSubnet(), + "mso_schema_site_anp_epg_static_leaf": resourceMSOSchemaSiteAnpEpgStaticleaf(), + "mso_schema_site_anp_epg_static_port": resourceMSOSchemaSiteAnpEpgStaticPort(), + "mso_schema_site_anp_epg_bulk_staticport": resourceMSOSchemaSiteAnpEpgBulkStaticPort(), + "mso_schema_site_bd": resourceMSOSchemaSiteBd(), + "mso_schema_site_anp_epg_subnet": resourceMSOSchemaSiteAnpEpgSubnet(), + "mso_schema_site_anp_epg_domain": resourceMSOSchemaSiteAnpEpgDomain(), + "mso_schema_site_bd_l3out": resourceMSOSchemaSiteBdL3out(), + "mso_schema_site_vrf": resourceMSOSchemaSiteVrf(), + "mso_schema_site_vrf_route_leak": resourceMSOSchemaSiteVrfRouteLeak(), + "mso_schema_site_vrf_region": resourceMSOSchemaSiteVrfRegion(), + "mso_schema_site_bd_subnet": resourceMSOSchemaSiteBdSubnet(), + "mso_rest": resourceMSORest(), + "mso_schema_template_deploy": resourceMSOSchemaTemplateDeploy(), + "mso_schema_template_deploy_ndo": resourceNDOSchemaTemplateDeploy(), + "mso_schema_site_vrf_region_cidr_subnet": resourceMSOSchemaSiteVrfRegionCidrSubnet(), + "mso_schema_site_vrf_region_cidr": resourceMSOSchemaSiteVrfRegionCidr(), + "mso_schema_site_anp": resourceMSOSchemaSiteAnp(), + "mso_schema_site_anp_epg": resourceMSOSchemaSiteAnpEpg(), + "mso_schema_template_anp_epg_selector": resourceMSOSchemaTemplateAnpEpgSelector(), + "mso_schema_site_external_epg": resourceMSOSchemaSiteExternalEpg(), + "mso_schema_template_external_epg_selector": resourceSchemaTemplateExternalEPGSelector(), + "mso_schema_template_anp_epg_useg_attr": resourceMSOSchemaTemplateAnpEpgUsegAttr(), + "mso_schema_site_anp_epg_selector": resourceMSOSchemaSiteAnpEpgSelector(), + "mso_schema_template_vrf_contract": resourceMSOTemplateVRFContract(), + "mso_schema_site_external_epg_selector": resourceMSOSchemaSiteExternalEpgSelector(), + "mso_schema_template_service_graph": resourceMSOSchemaTemplateServiceGraphs(), + "mso_schema_site_service_graph_node": resourceMSOSchemaSiteServiceGraphNode(), + "mso_schema_site_service_graph": resourceMSOSchemaSiteServiceGraph(), + "mso_service_node_type": resourceMSOServiceNodeType(), + "mso_schema_template_contract_service_graph": resourceMSOSchemaTemplateContractServiceGraph(), + "mso_system_config": resourceMSOSystemConfig(), + "mso_schema_site_contract_service_graph": resourceMSOSchemaSiteContractServiceGraph(), + "mso_schema_site_contract_service_graph_listener": resourceMSOSchemaSiteContractServiceGraphListener(), + "mso_template": resourceMSOTemplate(), + "mso_tenant_policies_ipsla_monitoring_policy": resourceMSOIPSLAMonitoringPolicy(), + "mso_tenant_policies_route_map_policy_multicast": resourceMSOMcastRouteMapPolicy(), + "mso_tenant_policies_dhcp_relay_policy": resourceMSOTenantPoliciesDHCPRelayPolicy(), + "mso_fabric_policies_vlan_pool": resourceMSOVlanPool(), + "mso_fabric_policies_physical_domain": resourceMSOPhysicalDomain(), + "mso_service_device_cluster": resourceMSOServiceDeviceCluster(), + "mso_fabric_policies_synce_interface_policy": resourceMSOSyncEInterfacePolicy(), + "mso_fabric_policies_macsec_policy": resourceMSOMacsecPolicy(), + "mso_schema_template_contract_service_chaining": resourceMSOSchemaTemplateContractServiceChaining(), + "mso_tenant_policies_bgp_peer_prefix_policy": resourceMSOBGPPeerPrefixPolicy(), + "mso_fabric_policies_l3_domain": resourceMSOL3Domain(), + "mso_tenant_policies_custom_qos_policy": resourceMSOCustomQoSPolicy(), + "mso_tenant_policies_dhcp_option_policy": resourceMSODHCPOptionPolicy(), + "mso_tenant_policies_mld_snooping_policy": resourceMSOMLDSnoopingPolicy(), + "mso_fabric_policies_mcp_global_policy": resourceMSOMCPGlobalPolicy(), + "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_virtual_port_channel_interface": resourceMSOVirtualPortChannelInterface(), }, DataSourcesMap: map[string]*schema.Resource{ - "mso_schema": datasourceMSOSchema(), - "mso_schema_site": datasourceMSOSchemaSite(), - "mso_site": datasourceMSOSite(), - "mso_remote_location": datasourceMSORemoteLocation(), - "mso_role": datasourceMSORole(), - "mso_user": datasourceMSOUser(), - "mso_label": datasourceMSOLabel(), - "mso_schema_template": datasourceMSOSchemaTemplate(), - "mso_tenant": datasourceMSOTenant(), - "mso_schema_template_bd": dataSourceMSOTemplateBD(), - "mso_schema_template_vrf": datasourceMSOSchemaTemplateVrf(), - "mso_schema_template_bd_subnet": dataSourceMSOTemplateSubnetBD(), - "mso_schema_template_anp": datasourceMSOSchemaTemplateAnp(), - "mso_schema_template_anp_epg": datasourceMSOSchemaTemplateAnpEpg(), - "mso_schema_template_anp_epg_contract": dataSourceMSOTemplateAnpEpgContract(), - "mso_schema_template_contract": dataSourceMSOTemplateContract(), - "mso_schema_template_anp_epg_subnet": dataSourceMSOSchemaTemplateAnpEpgSubnet(), - "mso_schema_template_l3out": dataSourceMSOTemplateL3out(), - "mso_schema_template_external_epg": dataSourceMSOTemplateExternalepg(), - "mso_schema_template_contract_filter": dataSourceMSOTemplateContractFilter(), - "mso_schema_template_external_epg_contract": dataSourceMSOTemplateExternalEpgContract(), - "mso_schema_template_filter_entry": dataSourceMSOSchemaTemplateFilterEntry(), - "mso_schema_template_external_epg_subnet": dataSourceMSOTemplateExternalEpgSubnet(), - "mso_schema_site_anp": dataSourceMSOSchemaSiteAnp(), - "mso_schema_site_anp_epg": dataSourceMSOSchemaSiteAnpEpg(), - "mso_schema_site_anp_epg_static_leaf": dataSourceMSOSchemaSiteAnpEpgStaticleaf(), - "mso_schema_site_anp_epg_static_port": datasourceMSOSchemaSiteAnpEpgStaticPort(), - "mso_schema_site_anp_epg_bulk_staticport": datasourceMSOSchemaSiteAnpEpgBulkStaticPort(), - "mso_schema_site_bd": dataSourceMSOSchemaSiteBd(), - "mso_schema_site_anp_epg_subnet": datasourceMSOSchemaSiteAnpEpgSubnet(), - "mso_schema_site_anp_epg_domain": dataSourceMSOSchemaSiteAnpEpgDomain(), - "mso_schema_site_bd_l3out": dataSourceMSOSchemaSiteBdL3out(), - "mso_schema_site_vrf": dataSourceMSOSchemaSiteVrf(), - "mso_schema_site_vrf_region": dataSourceMSOSchemaSiteVrfRegion(), - "mso_schema_site_vrf_route_leak": dataSourceMSOSchemaSiteVrfRouteLeak(), - "mso_schema_site_bd_subnet": dataSourceMSOSchemaSiteBdSubnet(), - "mso_schema_site_vrf_region_cidr_subnet": dataSourceMSOSchemaSiteVrfRegionCidrSubnet(), - "mso_schema_site_vrf_region_cidr": dataSourceMSOSchemaSiteVrfRegionCidr(), - "mso_schema_template_anp_epg_selector": datasourceMSOSchemaTemplateAnpEpgSelector(), - "mso_schema_site_external_epg": dataSourceMSOSchemaSiteExternalEpg(), - "mso_schema_template_external_epg_selector": datasourceSchemaTemplateExternalEPGSelector(), - "mso_schema_template_anp_epg_useg_attr": dataSourceMSOSchemaTemplateAnpEpgUsegAttr(), - "mso_schema_site_anp_epg_selector": datasourceMSOSchemaSiteAnpEpgSelector(), - "mso_schema_template_vrf_contract": dataSourceMSOTemplateVRFContract(), - "mso_schema_site_external_epg_selector": datasourceMSOSchemaSiteExternalEpgSelector(), - "mso_schema_template_service_graph": dataSourceMSOSchemaTemplateServiceGraph(), - "mso_service_node_type": dataSourceMSOServiceNodeType(), - "mso_schema_site_service_graph": datasourceMSOSchemaSiteServiceGraph(), - "mso_schema_template_contract_service_graph": dataSourceMSOSchemaTemplateContractServiceGraph(), - "mso_system_config": dataSourceMSOSystemConfig(), - "mso_rest": datasourceMSORest(), - "mso_schema_site_contract_service_graph": dataSourceMSOSchemaSiteContractServiceGraph(), - "mso_schema_site_contract_service_graph_listener": dataSourceMSOSchemaSiteContractServiceGraphListener(), - "mso_template": datasourceMSOTemplate(), - "mso_tenant_policies_ipsla_monitoring_policy": datasourceMSOIPSLAMonitoringPolicy(), - "mso_tenant_policies_route_map_policy_multicast": datasourceMSOMcastRouteMapPolicy(), - "mso_tenant_policies_dhcp_relay_policy": datasourceMSOTenantPoliciesDHCPRelayPolicy(), - "mso_fabric_policies_vlan_pool": datasourceMSOVlanPool(), - "mso_fabric_policies_physical_domain": datasourceMSOPhysicalDomain(), - "mso_service_device_cluster": datasourceMSOServiceDeviceCluster(), - "mso_fabric_policies_synce_interface_policy": datasourceMSOSyncEInterfacePolicy(), - "mso_fabric_policies_macsec_policy": datasourceMacsecPolicy(), - "mso_schema_template_contract_service_chaining": datasourceMSOSchemaTemplateContractServiceChaining(), - "mso_tenant_policies_bgp_peer_prefix_policy": datasourceMSOBGPPeerPrefixPolicy(), - "mso_fabric_policies_l3_domain": datasourceMSOL3Domain(), - "mso_tenant_policies_custom_qos_policy": datasourceMSOCustomQoSPolicy(), - "mso_tenant_policies_dhcp_option_policy": datasourceMSODHCPOptionPolicy(), - "mso_tenant_policies_mld_snooping_policy": datasourceMSOMLDSnoopingPolicy(), - "mso_fabric_policies_mcp_global_policy": datasourceMSOMCPGlobalPolicy(), - "mso_tenant_policies_ipsla_track_list": datasourceMSOIPSLATrackList(), - "mso_tenant_policies_l3out_interface_routing_policy": datasourceMSOL3OutInterfaceRoutingPolicy(), - "mso_fabric_policies_interface_setting": datasourceMSOInterfaceSetting(), + "mso_schema": datasourceMSOSchema(), + "mso_schema_site": datasourceMSOSchemaSite(), + "mso_site": datasourceMSOSite(), + "mso_remote_location": datasourceMSORemoteLocation(), + "mso_role": datasourceMSORole(), + "mso_user": datasourceMSOUser(), + "mso_label": datasourceMSOLabel(), + "mso_schema_template": datasourceMSOSchemaTemplate(), + "mso_tenant": datasourceMSOTenant(), + "mso_schema_template_bd": dataSourceMSOTemplateBD(), + "mso_schema_template_vrf": datasourceMSOSchemaTemplateVrf(), + "mso_schema_template_bd_subnet": dataSourceMSOTemplateSubnetBD(), + "mso_schema_template_anp": datasourceMSOSchemaTemplateAnp(), + "mso_schema_template_anp_epg": datasourceMSOSchemaTemplateAnpEpg(), + "mso_schema_template_anp_epg_contract": dataSourceMSOTemplateAnpEpgContract(), + "mso_schema_template_contract": dataSourceMSOTemplateContract(), + "mso_schema_template_anp_epg_subnet": dataSourceMSOSchemaTemplateAnpEpgSubnet(), + "mso_schema_template_l3out": dataSourceMSOTemplateL3out(), + "mso_schema_template_external_epg": dataSourceMSOTemplateExternalepg(), + "mso_schema_template_contract_filter": dataSourceMSOTemplateContractFilter(), + "mso_schema_template_external_epg_contract": dataSourceMSOTemplateExternalEpgContract(), + "mso_schema_template_filter_entry": dataSourceMSOSchemaTemplateFilterEntry(), + "mso_schema_template_external_epg_subnet": dataSourceMSOTemplateExternalEpgSubnet(), + "mso_schema_site_anp": dataSourceMSOSchemaSiteAnp(), + "mso_schema_site_anp_epg": dataSourceMSOSchemaSiteAnpEpg(), + "mso_schema_site_anp_epg_static_leaf": dataSourceMSOSchemaSiteAnpEpgStaticleaf(), + "mso_schema_site_anp_epg_static_port": datasourceMSOSchemaSiteAnpEpgStaticPort(), + "mso_schema_site_anp_epg_bulk_staticport": datasourceMSOSchemaSiteAnpEpgBulkStaticPort(), + "mso_schema_site_bd": dataSourceMSOSchemaSiteBd(), + "mso_schema_site_anp_epg_subnet": datasourceMSOSchemaSiteAnpEpgSubnet(), + "mso_schema_site_anp_epg_domain": dataSourceMSOSchemaSiteAnpEpgDomain(), + "mso_schema_site_bd_l3out": dataSourceMSOSchemaSiteBdL3out(), + "mso_schema_site_vrf": dataSourceMSOSchemaSiteVrf(), + "mso_schema_site_vrf_region": dataSourceMSOSchemaSiteVrfRegion(), + "mso_schema_site_vrf_route_leak": dataSourceMSOSchemaSiteVrfRouteLeak(), + "mso_schema_site_bd_subnet": dataSourceMSOSchemaSiteBdSubnet(), + "mso_schema_site_vrf_region_cidr_subnet": dataSourceMSOSchemaSiteVrfRegionCidrSubnet(), + "mso_schema_site_vrf_region_cidr": dataSourceMSOSchemaSiteVrfRegionCidr(), + "mso_schema_template_anp_epg_selector": datasourceMSOSchemaTemplateAnpEpgSelector(), + "mso_schema_site_external_epg": dataSourceMSOSchemaSiteExternalEpg(), + "mso_schema_template_external_epg_selector": datasourceSchemaTemplateExternalEPGSelector(), + "mso_schema_template_anp_epg_useg_attr": dataSourceMSOSchemaTemplateAnpEpgUsegAttr(), + "mso_schema_site_anp_epg_selector": datasourceMSOSchemaSiteAnpEpgSelector(), + "mso_schema_template_vrf_contract": dataSourceMSOTemplateVRFContract(), + "mso_schema_site_external_epg_selector": datasourceMSOSchemaSiteExternalEpgSelector(), + "mso_schema_template_service_graph": dataSourceMSOSchemaTemplateServiceGraph(), + "mso_service_node_type": dataSourceMSOServiceNodeType(), + "mso_schema_site_service_graph": datasourceMSOSchemaSiteServiceGraph(), + "mso_schema_template_contract_service_graph": dataSourceMSOSchemaTemplateContractServiceGraph(), + "mso_system_config": dataSourceMSOSystemConfig(), + "mso_rest": datasourceMSORest(), + "mso_schema_site_contract_service_graph": dataSourceMSOSchemaSiteContractServiceGraph(), + "mso_schema_site_contract_service_graph_listener": dataSourceMSOSchemaSiteContractServiceGraphListener(), + "mso_template": datasourceMSOTemplate(), + "mso_tenant_policies_ipsla_monitoring_policy": datasourceMSOIPSLAMonitoringPolicy(), + "mso_tenant_policies_route_map_policy_multicast": datasourceMSOMcastRouteMapPolicy(), + "mso_tenant_policies_dhcp_relay_policy": datasourceMSOTenantPoliciesDHCPRelayPolicy(), + "mso_fabric_policies_vlan_pool": datasourceMSOVlanPool(), + "mso_fabric_policies_physical_domain": datasourceMSOPhysicalDomain(), + "mso_service_device_cluster": datasourceMSOServiceDeviceCluster(), + "mso_fabric_policies_synce_interface_policy": datasourceMSOSyncEInterfacePolicy(), + "mso_fabric_policies_macsec_policy": datasourceMacsecPolicy(), + "mso_schema_template_contract_service_chaining": datasourceMSOSchemaTemplateContractServiceChaining(), + "mso_tenant_policies_bgp_peer_prefix_policy": datasourceMSOBGPPeerPrefixPolicy(), + "mso_fabric_policies_l3_domain": datasourceMSOL3Domain(), + "mso_tenant_policies_custom_qos_policy": datasourceMSOCustomQoSPolicy(), + "mso_tenant_policies_dhcp_option_policy": datasourceMSODHCPOptionPolicy(), + "mso_tenant_policies_mld_snooping_policy": datasourceMSOMLDSnoopingPolicy(), + "mso_fabric_policies_mcp_global_policy": datasourceMSOMCPGlobalPolicy(), + "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_virtual_port_channel_interface": dataSourceMSOVirtualPortChannelInterface(), }, ConfigureFunc: configureClient, diff --git a/mso/provider_test.go b/mso/provider_test.go index 73979b1b..a6e9b88b 100644 --- a/mso/provider_test.go +++ b/mso/provider_test.go @@ -158,6 +158,24 @@ func testAccVerifyKeyValue(resourceAttrsMap *map[string]string, resourceAttrRoot } } +func testCheckTypeSetStringElemAttr(resourceName, setKey, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + prefix := setKey + "." + for k, v := range rs.Primary.Attributes { + if strings.HasPrefix(k, prefix) && !strings.HasSuffix(k, ".#") && v == value { + return nil + } + } + return fmt.Errorf("no element with value %q found in set %q", value, setKey) + } +} + +// Deprecated: This check has a bug because it matches key value pairs across different set elements instead of verifying all attributes +// belong to the same set element, leading to false positives. Use CustomTestCheckTypeSetElemAttrs instead. func customTestCheckResourceTypeSetAttr(resourceName, resourceAttrRootkey string, resourceAttrsMap map[string]string) resource.TestCheckFunc { return func(is *terraform.State) error { rootModule, err := is.RootModule().Resources[resourceName] diff --git a/mso/resource_mso_fabric_resource_policies_virtual_port_channel_interface.go b/mso/resource_mso_fabric_resource_policies_virtual_port_channel_interface.go new file mode 100644 index 00000000..35fbda7b --- /dev/null +++ b/mso/resource_mso_fabric_resource_policies_virtual_port_channel_interface.go @@ -0,0 +1,353 @@ +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 resourceMSOVirtualPortChannelInterface() *schema.Resource { + return &schema.Resource{ + Create: resourceMSOVirtualPortChannelInterfaceCreate, + Read: resourceMSOVirtualPortChannelInterfaceRead, + Update: resourceMSOVirtualPortChannelInterfaceUpdate, + Delete: resourceMSOVirtualPortChannelInterfaceDelete, + Importer: &schema.ResourceImporter{ + State: resourceMSOVirtualPortChannelInterfaceImport, + }, + + SchemaVersion: version, + + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "Fabric Resource template ID.", + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 128), + Description: "Virtual Port Channel Interface name.", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "Virtual Port Channel Interface UUID.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "Virtual Port Channel Interface description.", + }, + "node_1": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "node_2": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "node_1_interfaces": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of interface IDs (or ranges) for node 1.", + }, + "node_2_interfaces": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of interface IDs (or ranges) for node 2.", + }, + "interface_policy_group_uuid": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "UUID of the Port Channel Interface Policy Group.", + }, + "interface_descriptions": { + Type: schema.TypeSet, + Optional: true, + Description: "List of interface descriptions; provided list replaces the existing list in NDO.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "node": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "interface": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func setVPCInterfaceData(d *schema.ResourceData, msoClient *client.Client, templateId, policyName string) error { + response, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + vpcCont, err := GetPolicyByName(response, policyName, "fabricResourceTemplate", "template", "virtualPortChannels") + if err != nil { + return err + } + name := models.StripQuotes(vpcCont.S("name").String()) + + d.SetId(fmt.Sprintf("templateId/%s/virtualPortChannelInterface/%s", templateId, name)) + d.Set("template_id", templateId) + + d.Set("name", name) + d.Set("description", models.StripQuotes(vpcCont.S("description").String())) + d.Set("uuid", models.StripQuotes(vpcCont.S("uuid").String())) + + d.Set("interface_policy_group_uuid", models.StripQuotes(vpcCont.S("policy").String())) + + d.Set("node_1", models.StripQuotes(vpcCont.S("node1Details", "node").String())) + d.Set("node_2", models.StripQuotes(vpcCont.S("node2Details", "node").String())) + + d.Set("node_1_interfaces", splitCommaString(models.StripQuotes(vpcCont.S("node1Details", "memberInterfaces").String()))) + d.Set("node_2_interfaces", splitCommaString(models.StripQuotes(vpcCont.S("node2Details", "memberInterfaces").String()))) + + if vpcCont.Exists("interfaceDescriptions") { + count, _ := vpcCont.ArrayCount("interfaceDescriptions") + out := make([]any, count) + for i := 0; i < count; i++ { + descCont, err := vpcCont.ArrayElement(i, "interfaceDescriptions") + if err != nil { + return err + } + out[i] = map[string]any{ + "node": models.StripQuotes(descCont.S("nodeID").String()), + "interface": models.StripQuotes(descCont.S("interfaceID").String()), + "description": models.StripQuotes(descCont.S("description").String()), + } + } + d.Set("interface_descriptions", out) + } + + return nil +} + +func resourceMSOVirtualPortChannelInterfaceImport(d *schema.ResourceData, m any) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] MSO VPC Interface - Beginning Import: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId, err := GetTemplateIdFromResourceId(d.Id()) + if err != nil { + return nil, err + } + name, err := GetPolicyNameFromResourceId(d.Id(), "virtualPortChannelInterface") + if err != nil { + return nil, err + } + + err = setVPCInterfaceData(d, msoClient, templateId, name) + if err != nil { + return nil, err + } + + log.Printf("[DEBUG] MSO VPC Interface - Import Complete: %v", d.Id()) + return []*schema.ResourceData{d}, nil +} + +func resourceMSOVirtualPortChannelInterfaceCreate(d *schema.ResourceData, m any) error { + log.Printf("[DEBUG] MSO VPC Interface - Beginning Create: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + + interfaces1 := getListOfStringsFromSchemaSet(d, "node_1_interfaces") + interfaces2 := getListOfStringsFromSchemaSet(d, "node_2_interfaces") + + payload := map[string]any{ + "name": d.Get("name").(string), + "description": d.Get("description").(string), + "node1Details": map[string]any{ + "node": d.Get("node_1").(string), + "memberInterfaces": strings.Join(interfaces1, ","), + }, + "node2Details": map[string]any{ + "node": d.Get("node_2").(string), + "memberInterfaces": strings.Join(interfaces2, ","), + }, + "policy": d.Get("interface_policy_group_uuid").(string), + "interfaceDescriptions": buildInterfaceDescriptionsPayload(d), + } + payloadModel := models.GetPatchPayload("add", "/fabricResourceTemplate/template/virtualPortChannels/-", payload) + + _, err := msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + name := d.Get("name").(string) + d.SetId(fmt.Sprintf("templateId/%s/virtualPortChannelInterface/%s", templateId, name)) + + log.Printf("[DEBUG] MSO VPC Interface - Create Complete: %v", d.Id()) + return resourceMSOVirtualPortChannelInterfaceRead(d, m) +} + +func resourceMSOVirtualPortChannelInterfaceRead(d *schema.ResourceData, m any) error { + log.Printf("[DEBUG] MSO VPC Interface - Beginning Read: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + name := d.Get("name").(string) + + err := setVPCInterfaceData(d, msoClient, templateId, name) + if err != nil { + return err + } + + log.Printf("[DEBUG] MSO VPC Interface - Read Complete : %v", d.Id()) + return nil +} + +func resourceMSOVirtualPortChannelInterfaceUpdate(d *schema.ResourceData, m any) error { + log.Printf("[DEBUG] MSO VPC Interface - Beginning Update: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + + templateCont, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + index, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "fabricResourceTemplate", "template", "virtualPortChannels") + if err != nil { + return err + } + + updatePath := fmt.Sprintf("/fabricResourceTemplate/template/virtualPortChannels/%d", index) + + 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("node_1") { + err = addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/node1Details/node", updatePath), d.Get("node_1").(string)) + if err != nil { + return err + } + } + if d.HasChange("node_1_interfaces") { + err = addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/node1Details/memberInterfaces", updatePath), strings.Join(getListOfStringsFromSchemaSet(d, "node_1_interfaces"), ",")) + if err != nil { + return err + } + } + + if d.HasChange("node_2") { + err = addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/node2Details/node", updatePath), d.Get("node_2").(string)) + if err != nil { + return err + } + } + + if d.HasChange("node_2_interfaces") { + err = addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/node2Details/memberInterfaces", updatePath), strings.Join(getListOfStringsFromSchemaSet(d, "node_2_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("interface_descriptions") { + err = addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/interfaceDescriptions", updatePath), buildInterfaceDescriptionsPayload(d)) + 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/virtualPortChannelInterface/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO VPC Interface - Update Complete: %v", d.Id()) + return resourceMSOVirtualPortChannelInterfaceRead(d, m) +} + +func resourceMSOVirtualPortChannelInterfaceDelete(d *schema.ResourceData, m any) error { + log.Printf("[DEBUG] MSO VPC Interface - Beginning Delete: %v", d.Id()) + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + templateCont, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + index, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "fabricResourceTemplate", "template", "virtualPortChannels") + if err != nil { + return err + } + + payloadModel := models.GetRemovePatchPayload(fmt.Sprintf("/fabricResourceTemplate/template/virtualPortChannels/%d", index)) + _, err = msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId("") + log.Printf("[DEBUG] MSO VPC Interface - Delete Complete: %v", d.Id()) + return nil +} + +func buildInterfaceDescriptionsPayload(d *schema.ResourceData) []map[string]any { + interfaceDescriptions := d.Get("interface_descriptions").(*schema.Set) + interfaceDescriptionsList := interfaceDescriptions.List() + out := make([]map[string]any, len(interfaceDescriptionsList)) + for i, v := range interfaceDescriptionsList { + m := v.(map[string]any) + out[i] = map[string]any{ + "nodeID": m["node"].(string), + "interfaceID": m["interface"].(string), + "description": m["description"].(string), + } + } + return out +} diff --git a/mso/resource_mso_fabric_resource_policies_virtual_port_channel_interface_test.go b/mso/resource_mso_fabric_resource_policies_virtual_port_channel_interface_test.go new file mode 100644 index 00000000..81a8e05f --- /dev/null +++ b/mso/resource_mso_fabric_resource_policies_virtual_port_channel_interface_test.go @@ -0,0 +1,215 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOVirtualPortChannelInterfaceResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { + fmt.Println("Test: Virtual Port Channel Interface Resource - Create") + }, + Config: testAccMSOVirtualPortChannelInterfaceConfigCreate(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "name", "tf_test_vpc_if"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "description", "Terraform test VPC Interface"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1", "101"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2", "102"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces.#", "2"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces", "1/1"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces", "1/10-11"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces.#", "1"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces", "1/2"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_descriptions.#", "1"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "uuid"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_policy_group_uuid"), + CustomTestCheckTypeSetElemAttrs( + "mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", + "interface_descriptions", + map[string]string{ + "node": "101", + "interface": "1/1", + "description": "Terraform test interface description", + }, + ), + ), + }, + { + PreConfig: func() { + fmt.Println("Test: Virtual Port Channel Interface Resource - Update Unset Interface Description") + }, + Config: testAccMSOVirtualPortChannelInterfaceConfigUpdateUnsetDescription(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "name", "tf_test_vpc_if"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "description", ""), + CustomTestCheckTypeSetElemAttrs( + "mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", + "interface_descriptions", + map[string]string{ + "node": "101", + "interface": "1/1", + "description": "", + }, + ), + ), + }, + { + PreConfig: func() { + fmt.Println("Test: Virtual Port Channel Interface Resource - Update") + }, + Config: testAccMSOVirtualPortChannelInterfaceConfigUpdate(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "name", "tf_test_vpc_if_new"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "description", "Terraform test VPC Interface (updated)"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1", "103"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2", "104"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces.#", "3"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces", "1/1"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces", "1/2-5"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces", "1/7"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces.#", "3"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces", "1/2"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces", "1/3"), + testCheckTypeSetStringElemAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces", "1/5-7"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_descriptions.#", "2"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "uuid"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_policy_group_uuid"), + CustomTestCheckTypeSetElemAttrs( + "mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", + "interface_descriptions", + map[string]string{ + "node": "103", + "interface": "1/1", + "description": "Terraform test interface description 103", + }, + ), + CustomTestCheckTypeSetElemAttrs( + "mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", + "interface_descriptions", + map[string]string{ + "node": "104", + "interface": "1/3", + "description": "", + }, + ), + ), + }, + { + PreConfig: func() { + fmt.Println("Test: Virtual Port Channel Interface Resource - Removed Descriptions") + }, + Config: testAccMSOVirtualPortChannelInterfaceConfigRemoveDescriptions(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "description", "Terraform test VPC Interface (removed descriptions)"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_1_interfaces.#", "3"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "node_2_interfaces.#", "3"), + resource.TestCheckResourceAttr("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_descriptions.#", "0"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "uuid"), + resource.TestCheckResourceAttrSet("mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", "interface_policy_group_uuid"), + ), + }, + { + ResourceName: "mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if", + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testCheckResourceDestroyPolicyWithPathAttributesAndArguments( + "mso_fabric_resource_policies_virtual_port_channel_interface", + "fabricResourceTemplate", + "template", + "virtualPortChannels", + ), + }) +} + +func testAccMSOVirtualPortChannelInterfaceConfigCreate() string { + return fmt.Sprintf(`%s%s + resource "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_template.template_fabric_resource.id + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%s_portchannel.uuid + name = "tf_test_vpc_if" + description = "Terraform test VPC Interface" + node_1 = "101" + node_2 = "102" + node_1_interfaces = ["1/1", "1/10-11"] + node_2_interfaces = ["1/2"] + + interface_descriptions { + node = "101" + interface = "1/1" + description = "Terraform test interface description" + } + } + `, testAccMSOTemplateResourceFabricResourceConfig(), testAccMSOFabricPoliciesInterfaceSettingPortChannelConfigCreate(), msoFabricPolicyTemplateInterfaceSettingName) +} + +func testAccMSOVirtualPortChannelInterfaceConfigUpdateUnsetDescription() string { + return fmt.Sprintf(`%s%s + resource "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_template.template_fabric_resource.id + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%s_portchannel.uuid + name = "tf_test_vpc_if" + description = "" + node_1 = "101" + node_2 = "102" + node_1_interfaces = ["1/1", "1/10-11"] + node_2_interfaces = ["1/2"] + + interface_descriptions { + node = "101" + interface = "1/1" + description = "" + } + } + `, testAccMSOTemplateResourceFabricResourceConfig(), testAccMSOFabricPoliciesInterfaceSettingPortChannelConfigCreate(), msoFabricPolicyTemplateInterfaceSettingName) +} + +func testAccMSOVirtualPortChannelInterfaceConfigUpdate() string { + return fmt.Sprintf(`%s%s + resource "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_template.template_fabric_resource.id + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%s_portchannel.uuid + name = "tf_test_vpc_if_new" + description = "Terraform test VPC Interface (updated)" + node_1 = "103" + node_2 = "104" + node_1_interfaces = ["1/1", "1/2-5", "1/7"] + node_2_interfaces = ["1/2", "1/3", "1/5-7"] + + interface_descriptions { + node = "103" + interface = "1/1" + description = "Terraform test interface description 103" + } + + interface_descriptions { + node = "104" + interface = "1/3" + // No description on new description should be "" + } + } + `, testAccMSOTemplateResourceFabricResourceConfig(), testAccMSOFabricPoliciesInterfaceSettingPortChannelConfigCreate(), msoFabricPolicyTemplateInterfaceSettingName) +} + +func testAccMSOVirtualPortChannelInterfaceConfigRemoveDescriptions() string { + return fmt.Sprintf(`%s%s + resource "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_template.template_fabric_resource.id + interface_policy_group_uuid = mso_fabric_policies_interface_setting.%s_portchannel.uuid + name = "tf_test_vpc_if_new" + description = "Terraform test VPC Interface (removed descriptions)" + node_1 = "103" + node_2 = "104" + node_1_interfaces = ["1/1", "1/2-5", "1/7"] + node_2_interfaces = ["1/2", "1/3", "1/5-7"] + } + `, testAccMSOTemplateResourceFabricResourceConfig(), testAccMSOFabricPoliciesInterfaceSettingPortChannelConfigCreate(), msoFabricPolicyTemplateInterfaceSettingName) +} 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_virtual_port_channel_interface.html.markdown b/website/docs/d/fabric_resource_policies_virtual_port_channel_interface.html.markdown new file mode 100644 index 00000000..535b3b9c --- /dev/null +++ b/website/docs/d/fabric_resource_policies_virtual_port_channel_interface.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "mso" +page_title: "MSO: mso_fabric_resource_policies_virtual_port_channel_interface" +sidebar_current: "docs-mso-datasource-fabric_resource_policies_virtual_port_channel_interface" +description: |- + Data source for Virtual Port Channel (VPC) Interfaces on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_fabric_resource_policies_virtual_port_channel_interface # + +Data source for Virtual Port Channel (VPC) Interfaces on Cisco Nexus Dashboard Orchestrator (NDO). + +## GUI Information ## + +* `Location` - Manage -> Fabric Templates -> Fabric Resource Policies -> Virtual Port Channel Interface + +## Example Usage ## + +```hcl +data "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_template.fabric_resource_template.id + name = "tf_vpc_if" +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the Fabric Resource template. +* `name` - (Required) The name of the Virtual Port Channel Interface. + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the Virtual Port Channel Interface. +* `description` - (Read-Only) The description of the Virtual Port Channel Interface. +* `node_1` - (Read-Only) The first node ID. +* `node_2` - (Read-Only) The second node ID. +* `node_1_interfaces` - (Read-Only) List of interface IDs (or ranges) for node 1. +* `node_2_interfaces` - (Read-Only) List of interface IDs (or ranges) for node 2. +* `interface_policy_group_uuid` - (Read-Only) UUID of the Port Channel Interface Policy Group. +* `interface_descriptions` - (Read-Only) List of interface description entries. + * `node` - (Read-Only) Node ID. + * `interface` - (Read-Only) Interface ID. + * `description` - (Read-Only) Interface description. diff --git a/website/docs/r/fabric_resource_policies_virtual_port_channel_interface.html.markdown b/website/docs/r/fabric_resource_policies_virtual_port_channel_interface.html.markdown new file mode 100644 index 00000000..e4e3f140 --- /dev/null +++ b/website/docs/r/fabric_resource_policies_virtual_port_channel_interface.html.markdown @@ -0,0 +1,70 @@ +--- +layout: "mso" +page_title: "MSO: mso_fabric_resource_policies_virtual_port_channel_interface" +sidebar_current: "docs-mso-resource-fabric_resource_policies_virtual_port_channel_interface" +description: |- + Manages Virtual Port Channel (VPC) Interfaces on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_fabric_resource_policies_virtual_port_channel_interface # + +Manages Virtual Port Channel (VPC) Interfaces on Cisco Nexus Dashboard Orchestrator (NDO). This resource is supported in NDO v4.3(1) or higher. + +## GUI Information ## + +* `Location` - Manage -> Fabric Templates -> Fabric Resource Policies -> Virtual Port Channel Interface + +## Example Usage ## + +```hcl +resource "mso_fabric_resource_policies_virtual_port_channel_interface" "vpc_if" { + template_id = mso_template.fabric_resource_template.id + name = "tf_vpc_if" + description = "Example VPC Interface" + node_1 = "101" + node_2 = "102" + node_1_interfaces = ["1/1", "1/10-11"] + node_2_interfaces = ["1/2"] + interface_policy_group_uuid = mso_fabric_policies_interface_setting.port_channel_interface.uuid + + interface_descriptions { + node = "101" + interface = "1/1" + description = "Terraform example interface description" + } + + interface_descriptions { + node = "102" + interface = "1/10" + description = "Terraform example interface description" + } +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the Fabric Resource template. +* `name` - (Required) The name of the Virtual Port Channel Interface. +* `description` - (Optional) The description of the Virtual Port Channel Interface. If this argument is omitted, the value defaults to an empty string (`""`). +* `node_1` - (Required) The first node ID. +* `node_2` - (Required) The second node ID. +* `node_1_interfaces` - (Required) List of interface IDs (or ranges) for node 1. +* `node_2_interfaces` - (Required) List of interface IDs (or ranges) for node 2. +* `interface_policy_group_uuid` - (Required) UUID of the Port Channel Interface Policy Group. +* `interface_descriptions` - (Optional) List of interface description entries. The provided list replaces the existing list in NDO. + * `node` - (Required) Node ID. + * `interface` - (Required) Interface ID. + * `description` - (Optional) Interface description. If this argument is omitted, the value defaults to an empty string (`""`). + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the Virtual Port Channel Interface. +* `id` - (Read-Only) The unique Terraform identifier of the Virtual Port Channel Interface. + +## Importing ## + +An existing MSO Virtual Port Channel Interface can be [imported][docs-import] into this resource via its ID/path, via the following command: [docs-import]: + +```bash +terraform import mso_fabric_resource_policies_virtual_port_channel_interface.vpc_if templateId/{template_id}/virtualPortChannelInterface/{name} +```