diff --git a/examples/tenant_policies_netflow_exporter/main.tf b/examples/tenant_policies_netflow_exporter/main.tf new file mode 100644 index 00000000..52f040b3 --- /dev/null +++ b/examples/tenant_policies_netflow_exporter/main.tf @@ -0,0 +1,40 @@ +terraform { + required_providers { + mso = { + source = "CiscoDevNet/mso" + } + } +} + +provider "mso" { + username = "" # + password = "" # + url = "" # + insecure = true +} + +data "mso_tenant" "example_tenant" { + name = "example_tenant" +} + +# Tenant template example + +resource "mso_template" "tenant_template" { + template_name = "tenant_template" + template_type = "tenant" + tenant_id = data.mso_tenant.example_tenant.id +} + +# NetFlow Exporter example + +resource "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.tenant_template.id + name = "netflow_exporter_1" +} + +# NetFlow Exporter data source example + +data "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.tenant_template.id + name = mso_tenant_policies_netflow_exporter.netflow_exporter.name +} \ No newline at end of file diff --git a/examples/tenant_policies_netflow_monitor/main.tf b/examples/tenant_policies_netflow_monitor/main.tf new file mode 100644 index 00000000..68b0c3d7 --- /dev/null +++ b/examples/tenant_policies_netflow_monitor/main.tf @@ -0,0 +1,57 @@ +terraform { + required_providers { + mso = { + source = "CiscoDevNet/mso" + } + } +} + +provider "mso" { + username = "" # + password = "" # + url = "" # + insecure = true +} + +data "mso_tenant" "example_tenant" { + name = "example_tenant" +} + +# Tenant template example + +resource "mso_template" "tenant_template" { + template_name = "tenant_template" + template_type = "tenant" + tenant_id = data.mso_tenant.example_tenant.id +} + +# NetFlow Exporter example + +resource "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.tenant_template.id + name = "netflow_exporter_1" +} + +# NetFlow Record example + +resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.tenant_template.id + name = "netflow_record_1" +} + +# NetFlow Monitor example + +resource "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_template.tenant_template.id + name = "netflow_monitor_1" + description = "Test NetFlow Monitor" + netflow_record_uuid = mso_tenant_policies_netflow_record.netflow_record.uuid + netflow_exporter_uuids = [mso_tenant_policies_netflow_exporter.netflow_exporter.uuid] +} + +# NetFlow Monitor data source example + +data "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_template.tenant_template.id + name = mso_tenant_policies_netflow_monitor.netflow_monitor.name +} diff --git a/examples/tenant_policies_netflow_record/main.tf b/examples/tenant_policies_netflow_record/main.tf new file mode 100644 index 00000000..3d94bad1 --- /dev/null +++ b/examples/tenant_policies_netflow_record/main.tf @@ -0,0 +1,42 @@ +terraform { + required_providers { + mso = { + source = "CiscoDevNet/mso" + } + } +} + +provider "mso" { + username = "" # + password = "" # + url = "" # + insecure = true +} + +data "mso_tenant" "example_tenant" { + name = "example_tenant" +} + +# Tenant template example + +resource "mso_template" "tenant_template" { + template_name = "tenant_template" + template_type = "tenant" + tenant_id = data.mso_tenant.example_tenant.id +} + +# NetFlow Record example + +resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.tenant_template.id + name = "netflow_record_1" + description = "My NetFlow Record" + match_parameters = ["destination_ip", "source_ip", "ip_protocol"] +} + +# NetFlow Record data source example + +data "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.tenant_template.id + name = mso_tenant_policies_netflow_record.netflow_record.name +} diff --git a/mso/constants.go b/mso/constants.go index a2b6eb46..6325c2e4 100644 --- a/mso/constants.go +++ b/mso/constants.go @@ -48,6 +48,32 @@ var targetDscpMap = map[string]string{ "voiceAdmit": "voice_admit", } +var matchParameterMap = map[string]string{ + "destination_ip": "dstIP", + "destination_ipv4": "dstIPv4", + "destination_ipv6": "dstIPv6", + "destination_mac": "dstMac", + "destination_port": "dstPort", + "ethertype": "ethertype", + "ip_protocol": "proto", + "source_ip": "srcIP", + "source_ipv4": "srcIPv4", + "source_ipv6": "srcIPv6", + "source_mac": "srcMac", + "source_port": "srcPort", + "dstIP": "destination_ip", + "dstIPv4": "destination_ipv4", + "dstIPv6": "destination_ipv6", + "dstMac": "destination_mac", + "dstPort": "destination_port", + "proto": "ip_protocol", + "srcIP": "source_ip", + "srcIPv4": "source_ipv4", + "srcIPv6": "source_ipv6", + "srcMac": "source_mac", + "srcPort": "source_port", +} + var enabledDisabledMap = map[interface{}]interface{}{ "enabled": true, "disabled": false, diff --git a/mso/datasource_mso_tenant_policies_netflow_exporter.go b/mso/datasource_mso_tenant_policies_netflow_exporter.go new file mode 100644 index 00000000..2731047a --- /dev/null +++ b/mso/datasource_mso_tenant_policies_netflow_exporter.go @@ -0,0 +1,58 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func datasourceMSONetflowExporter() *schema.Resource { + return &schema.Resource{ + Read: dataSourceMSONetflowExporterRead, + + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the tenant policy template.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the NetFlow Exporter.", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the NetFlow Exporter.", + }, + }, + } +} + +func dataSourceMSONetflowExporterRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Exporter 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, "tenantPolicyTemplate", "template", "netFlowExporters") + if err != nil { + return err + } + + err = setNetflowExporterData(d, policy, templateId) + if err != nil { + return err + } + log.Printf("[DEBUG] MSO NetFlow Exporter Data Source - Read Complete: %v", d.Id()) + return nil +} diff --git a/mso/datasource_mso_tenant_policies_netflow_exporter_test.go b/mso/datasource_mso_tenant_policies_netflow_exporter_test.go new file mode 100644 index 00000000..131abd0b --- /dev/null +++ b/mso/datasource_mso_tenant_policies_netflow_exporter_test.go @@ -0,0 +1,33 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesNetflowExporterDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: NetFlow Exporter Data Source") }, + Config: testAccMSOTenantPoliciesNetflowExporterDataSource(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_exporter.netflow_exporter", "name", "test_netflow_exporter"), + resource.TestCheckResourceAttrSet("data.mso_tenant_policies_netflow_exporter.netflow_exporter", "uuid"), + ), + }, + }, + }) +} + +func testAccMSOTenantPoliciesNetflowExporterDataSource() string { + return fmt.Sprintf(`%s + data "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_tenant_policies_netflow_exporter.netflow_exporter.template_id + name = "test_netflow_exporter" + }`, testAccMSOTenantPoliciesNetflowExporterConfigCreate()) +} diff --git a/mso/datasource_mso_tenant_policies_netflow_monitor.go b/mso/datasource_mso_tenant_policies_netflow_monitor.go new file mode 100644 index 00000000..cd186be0 --- /dev/null +++ b/mso/datasource_mso_tenant_policies_netflow_monitor.go @@ -0,0 +1,74 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func datasourceMSOTenantPoliciesNetflowMonitor() *schema.Resource { + return &schema.Resource{ + Read: dataSourceMSOTenantPoliciesNetflowMonitorRead, + + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the tenant policy template.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the NetFlow Monitor.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the NetFlow Monitor.", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the NetFlow Monitor.", + }, + "netflow_record_uuid": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the NetFlow Record.", + }, + "netflow_exporter_uuids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "The list of NetFlow Exporter UUIDs.", + }, + }, + } +} + +func dataSourceMSOTenantPoliciesNetflowMonitorRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Monitor Data Source - Beginning Read") + msoClient := m.(*client.Client) + + templateId := d.Get("template_id").(string) + monitorName := d.Get("name").(string) + + response, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", templateId)) + if err != nil { + return err + } + + monitor, err := GetPolicyByName(response, monitorName, "tenantPolicyTemplate", "template", "netFlowMonitors") + if err != nil { + return err + } + + err = setNetflowMonitorData(d, monitor, templateId) + if err != nil { + return err + } + log.Printf("[DEBUG] MSO NetFlow Monitor Data Source - Read Complete: %v", d.Id()) + return nil +} diff --git a/mso/datasource_mso_tenant_policies_netflow_monitor_test.go b/mso/datasource_mso_tenant_policies_netflow_monitor_test.go new file mode 100644 index 00000000..5ea3c85d --- /dev/null +++ b/mso/datasource_mso_tenant_policies_netflow_monitor_test.go @@ -0,0 +1,36 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesNetflowMonitorDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: NetFlow Monitor Data Source") }, + Config: testAccMSOTenantPoliciesNetflowMonitorDataSource(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_monitor.netflow_monitor", "name", "test_netflow_monitor"), + resource.TestCheckResourceAttrSet("data.mso_tenant_policies_netflow_monitor.netflow_monitor", "uuid"), + resource.TestCheckResourceAttrSet("data.mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_record_uuid"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_exporter_uuids.#", "1"), + resource.TestCheckResourceAttrSet("data.mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_exporter_uuids.0"), + ), + }, + }, + }) +} + +func testAccMSOTenantPoliciesNetflowMonitorDataSource() string { + return fmt.Sprintf(`%s + data "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_tenant_policies_netflow_monitor.netflow_monitor.template_id + name = "test_netflow_monitor" + }`, testAccMSOTenantPoliciesNetflowMonitorConfigCreate()) +} diff --git a/mso/datasource_mso_tenant_policies_netflow_record.go b/mso/datasource_mso_tenant_policies_netflow_record.go new file mode 100644 index 00000000..ce07f7c9 --- /dev/null +++ b/mso/datasource_mso_tenant_policies_netflow_record.go @@ -0,0 +1,71 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func datasourceMSONetflowRecord() *schema.Resource { + return &schema.Resource{ + Read: dataSourceMSONetflowRecordRead, + + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the tenant policy template.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the NetFlow Record.", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the NetFlow Record.", + }, + "description": { + Type: schema.TypeString, + Computed: true, + Description: "The description of the NetFlow Record.", + }, + "match_parameters": { + Type: schema.TypeList, + Computed: true, + Description: "The match parameters of the NetFlow Record.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func dataSourceMSONetflowRecordRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Record 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, "tenantPolicyTemplate", "template", "netFlowRecords") + if err != nil { + return err + } + + err = setNetflowRecordData(d, policy, templateId) + if err != nil { + return err + } + log.Printf("[DEBUG] MSO NetFlow Record Data Source - Read Complete: %v", d.Id()) + return nil +} diff --git a/mso/datasource_mso_tenant_policies_netflow_record_test.go b/mso/datasource_mso_tenant_policies_netflow_record_test.go new file mode 100644 index 00000000..ce8031a7 --- /dev/null +++ b/mso/datasource_mso_tenant_policies_netflow_record_test.go @@ -0,0 +1,37 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesNetflowRecordDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: NetFlow Record Data Source") }, + Config: testAccMSOTenantPoliciesNetflowRecordDataSource(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_record.netflow_record", "name", "test_netflow_record"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_record.netflow_record", "description", "Updated NetFlow Record"), + resource.TestCheckResourceAttrSet("data.mso_tenant_policies_netflow_record.netflow_record", "uuid"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_record.netflow_record", "match_parameters.#", "2"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_record.netflow_record", "match_parameters.0", "ethertype"), + resource.TestCheckResourceAttr("data.mso_tenant_policies_netflow_record.netflow_record", "match_parameters.1", "destination_mac"), + ), + }, + }, + }) +} + +func testAccMSOTenantPoliciesNetflowRecordDataSource() string { + return fmt.Sprintf(`%s + data "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_tenant_policies_netflow_record.netflow_record.template_id + name = "test_netflow_record" + }`, testAccMSOTenantPoliciesNetflowRecordConfigUpdateMatchParams()) +} diff --git a/mso/provider.go b/mso/provider.go index 18d1c7ab..e99247ae 100644 --- a/mso/provider.go +++ b/mso/provider.go @@ -142,6 +142,9 @@ func Provider() terraform.ResourceProvider { "mso_tenant_policies_ipsla_track_list": resourceMSOIPSLATrackList(), "mso_tenant_policies_l3out_interface_routing_policy": resourceMSOL3OutInterfaceRoutingPolicy(), "mso_fabric_policies_interface_setting": resourceMSOInterfaceSetting(), + "mso_tenant_policies_netflow_exporter": resourceMSONetflowExporter(), + "mso_tenant_policies_netflow_monitor": resourceMSOTenantPoliciesNetflowMonitor(), + "mso_tenant_policies_netflow_record": resourceMSONetflowRecord(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -217,6 +220,9 @@ func Provider() terraform.ResourceProvider { "mso_tenant_policies_ipsla_track_list": datasourceMSOIPSLATrackList(), "mso_tenant_policies_l3out_interface_routing_policy": datasourceMSOL3OutInterfaceRoutingPolicy(), "mso_fabric_policies_interface_setting": datasourceMSOInterfaceSetting(), + "mso_tenant_policies_netflow_exporter": datasourceMSONetflowExporter(), + "mso_tenant_policies_netflow_monitor": datasourceMSOTenantPoliciesNetflowMonitor(), + "mso_tenant_policies_netflow_record": datasourceMSONetflowRecord(), }, ConfigureFunc: configureClient, diff --git a/mso/provider_test.go b/mso/provider_test.go index 73979b1b..41040382 100644 --- a/mso/provider_test.go +++ b/mso/provider_test.go @@ -158,6 +158,22 @@ 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) + } +} + 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_tenant_policies_netflow_exporter.go b/mso/resource_mso_tenant_policies_netflow_exporter.go new file mode 100644 index 00000000..29096cc2 --- /dev/null +++ b/mso/resource_mso_tenant_policies_netflow_exporter.go @@ -0,0 +1,180 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/ciscoecosystem/mso-go-client/container" + "github.com/ciscoecosystem/mso-go-client/models" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceMSONetflowExporter() *schema.Resource { + return &schema.Resource{ + Create: resourceMSONetflowExporterCreate, + Read: resourceMSONetflowExporterRead, + Update: resourceMSONetflowExporterUpdate, + Delete: resourceMSONetflowExporterDelete, + Importer: &schema.ResourceImporter{ + State: resourceMSONetflowExporterImport, + }, + + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the tenant policy template.", + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 64), + Description: "The name of the NetFlow Exporter.", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the NetFlow Exporter.", + }, + }, + } +} + +func setNetflowExporterData(d *schema.ResourceData, response *container.Container, templateId string) error { + d.SetId(fmt.Sprintf("templateId/%s/NetflowExporter/%s", templateId, models.StripQuotes(response.S("name").String()))) + d.Set("template_id", templateId) + d.Set("name", models.StripQuotes(response.S("name").String())) + d.Set("uuid", models.StripQuotes(response.S("uuid").String())) + + return nil +} + +func resourceMSONetflowExporterImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - Beginning Import: %v", d.Id()) + err := resourceMSONetflowExporterRead(d, m) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - Import Complete: %v", d.Id()) + return []*schema.ResourceData{d}, nil +} + +func resourceMSONetflowExporterCreate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - Beginning Create: %v", d.Id()) + msoClient := m.(*client.Client) + + payload := map[string]interface{}{} + + payload["name"] = d.Get("name").(string) + + payloadModel := models.GetPatchPayload("add", "/tenantPolicyTemplate/template/netFlowExporters/-", payload) + templateId := d.Get("template_id").(string) + + _, err := msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("templateId/%s/NetflowExporter/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - Create Complete: %v", d.Id()) + return resourceMSONetflowExporterRead(d, m) +} + +func resourceMSONetflowExporterRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Exporter 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(), "NetflowExporter") + if err != nil { + return err + } + + policy, err := GetPolicyByName(response, policyName, "tenantPolicyTemplate", "template", "netFlowExporters") + if err != nil { + return err + } + + err = setNetflowExporterData(d, policy, templateId) + if err != nil { + return err + } + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - Read Complete: %v", d.Id()) + return nil +} + +func resourceMSONetflowExporterUpdate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - 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 + } + + policyIndex, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "netFlowExporters") + if err != nil { + return err + } + + updatePath := fmt.Sprintf("/tenantPolicyTemplate/template/netFlowExporters/%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 + } + } + + err = doPatchRequest(msoClient, fmt.Sprintf("api/v1/templates/%s", templateId), payloadCont) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("templateId/%s/NetflowExporter/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - Update Complete: %v", d.Id()) + return resourceMSONetflowExporterRead(d, m) +} + +func resourceMSONetflowExporterDelete(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - 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 + } + + policyIndex, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "netFlowExporters") + if err != nil { + return err + } + + payloadModel := models.GetRemovePatchPayload(fmt.Sprintf("/tenantPolicyTemplate/template/netFlowExporters/%d", policyIndex)) + + _, err = msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId("") + log.Printf("[DEBUG] MSO NetFlow Exporter Resource - Delete Complete: %v", d.Id()) + return nil +} diff --git a/mso/resource_mso_tenant_policies_netflow_exporter_test.go b/mso/resource_mso_tenant_policies_netflow_exporter_test.go new file mode 100644 index 00000000..dbea409c --- /dev/null +++ b/mso/resource_mso_tenant_policies_netflow_exporter_test.go @@ -0,0 +1,56 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesNetflowExporterResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: Create NetFlow Exporter") }, + Config: testAccMSOTenantPoliciesNetflowExporterConfigCreate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_exporter.netflow_exporter", "name", "test_netflow_exporter"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_exporter.netflow_exporter", "uuid"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update NetFlow Exporter Name") }, + Config: testAccMSOTenantPoliciesNetflowExporterConfigUpdateName(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_exporter.netflow_exporter", "name", "test_netflow_exporter_updated"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_exporter.netflow_exporter", "uuid"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Import NetFlow Exporter") }, + ResourceName: "mso_tenant_policies_netflow_exporter.netflow_exporter", + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testCheckResourceDestroyPolicyWithPathAttributesAndArguments("mso_tenant_policies_netflow_exporter", "tenantPolicyTemplate", "template", "netFlowExporters"), + }) +} + +func testAccMSOTenantPoliciesNetflowExporterConfigCreate() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.template_tenant.id + name = "test_netflow_exporter" + }`, testAccMSOTemplateResourceTenantConfig()) +} + +func testAccMSOTenantPoliciesNetflowExporterConfigUpdateName() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.template_tenant.id + name = "test_netflow_exporter_updated" + }`, testAccMSOTemplateResourceTenantConfig()) +} diff --git a/mso/resource_mso_tenant_policies_netflow_monitor.go b/mso/resource_mso_tenant_policies_netflow_monitor.go new file mode 100644 index 00000000..f3542515 --- /dev/null +++ b/mso/resource_mso_tenant_policies_netflow_monitor.go @@ -0,0 +1,257 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/ciscoecosystem/mso-go-client/container" + "github.com/ciscoecosystem/mso-go-client/models" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceMSOTenantPoliciesNetflowMonitor() *schema.Resource { + return &schema.Resource{ + Create: resourceMSOTenantPoliciesNetflowMonitorCreate, + Read: resourceMSOTenantPoliciesNetflowMonitorRead, + Update: resourceMSOTenantPoliciesNetflowMonitorUpdate, + Delete: resourceMSOTenantPoliciesNetflowMonitorDelete, + Importer: &schema.ResourceImporter{ + State: resourceMSOTenantPoliciesNetflowMonitorImport, + }, + + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the tenant policy template.", + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 64), + Description: "The name of the NetFlow Monitor.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "The description of the NetFlow Monitor.", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the NetFlow Monitor.", + }, + "netflow_record_uuid": { + Type: schema.TypeString, + Optional: true, + Description: "The UUID of the NetFlow Record.", + }, + "netflow_exporter_uuids": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "The list of NetFlow Exporter UUIDs.", + }, + }, + } +} + +func buildExporterUUIDsPayload(exportersRaw interface{}) []string { + exportersList := exportersRaw.(*schema.Set).List() + exporters := make([]string, 0, len(exportersList)) + for _, item := range exportersList { + exporters = append(exporters, item.(string)) + } + return exporters +} + +func setNetflowMonitorData(d *schema.ResourceData, response *container.Container, templateId string) error { + d.SetId(fmt.Sprintf("templateId/%s/NetflowMonitor/%s", templateId, models.StripQuotes(response.S("name").String()))) + d.Set("template_id", templateId) + d.Set("name", models.StripQuotes(response.S("name").String())) + if response.Exists("description") { + d.Set("description", models.StripQuotes(response.S("description").String())) + } + d.Set("uuid", models.StripQuotes(response.S("uuid").String())) + if response.Exists("recordRef") { + d.Set("netflow_record_uuid", models.StripQuotes(response.S("recordRef").String())) + } + + if response.Exists("exporterRefs") { + exporterCount, _ := response.ArrayCount("exporterRefs") + exporters := make([]string, exporterCount) + for i := range exporterCount { + exporters[i] = models.StripQuotes(response.S("exporterRefs").Index(i).String()) + } + d.Set("netflow_exporter_uuids", exporters) + } + + return nil +} + +func resourceMSOTenantPoliciesNetflowMonitorImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Beginning Import: %v", d.Id()) + err := resourceMSOTenantPoliciesNetflowMonitorRead(d, m) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Import Complete: %v", d.Id()) + return []*schema.ResourceData{d}, nil +} + +func resourceMSOTenantPoliciesNetflowMonitorCreate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Beginning Create: %v", d.Id()) + + msoClient := m.(*client.Client) + + payload := map[string]interface{}{ + "name": d.Get("name").(string), + "description": d.Get("description").(string), + "exporterRefs": buildExporterUUIDsPayload(d.Get("netflow_exporter_uuids")), + } + + if recordUUID, ok := d.GetOk("netflow_record_uuid"); ok { + payload["recordRef"] = recordUUID.(string) + } + + payloadModel := models.GetPatchPayload("add", "/tenantPolicyTemplate/template/netFlowMonitors/-", payload) + templateId := d.Get("template_id").(string) + + _, err := msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("templateId/%s/NetflowMonitor/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Create Complete: %v", d.Id()) + return resourceMSOTenantPoliciesNetflowMonitorRead(d, m) +} + +func resourceMSOTenantPoliciesNetflowMonitorRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Monitor 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 + } + + monitorName, err := GetPolicyNameFromResourceId(d.Id(), "NetflowMonitor") + if err != nil { + return err + } + + monitor, err := GetPolicyByName(response, monitorName, "tenantPolicyTemplate", "template", "netFlowMonitors") + if err != nil { + return err + } + + err = setNetflowMonitorData(d, monitor, templateId) + if err != nil { + return err + } + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Read Complete: %v", d.Id()) + return nil +} + +func resourceMSOTenantPoliciesNetflowMonitorUpdate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - 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 + } + + monitorIndex, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "netFlowMonitors") + if err != nil { + return err + } + + payloadCont := container.New() + payloadCont.Array() + + monitorPath := fmt.Sprintf("/tenantPolicyTemplate/template/netFlowMonitors/%d", monitorIndex) + + if d.HasChange("name") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/name", monitorPath), d.Get("name").(string)) + if err != nil { + return err + } + } + + if d.HasChange("description") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/description", monitorPath), d.Get("description").(string)) + if err != nil { + return err + } + } + + if d.HasChange("netflow_record_uuid") { + recordUUID := d.Get("netflow_record_uuid").(string) + if recordUUID != "" { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/recordRef", monitorPath), recordUUID) + if err != nil { + return err + } + } else { + err := addPatchPayloadToContainer(payloadCont, "remove", fmt.Sprintf("%s/recordRef", monitorPath), nil) + if err != nil { + return err + } + } + } + + if d.HasChange("netflow_exporter_uuids") { + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/exporterRefs", monitorPath), buildExporterUUIDsPayload(d.Get("netflow_exporter_uuids"))) + 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/NetflowMonitor/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Update Complete: %v", d.Id()) + return resourceMSOTenantPoliciesNetflowMonitorRead(d, m) +} + +func resourceMSOTenantPoliciesNetflowMonitorDelete(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Beginning Delete: %v", d.Id()) + msoClient := m.(*client.Client) + + templateCont, err := msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", d.Get("template_id").(string))) + if err != nil { + return err + } + + monitorIndex, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "netFlowMonitors") + if err != nil { + return err + } + + payloadModel := models.GetRemovePatchPayload(fmt.Sprintf("/tenantPolicyTemplate/template/netFlowMonitors/%d", monitorIndex)) + + _, err = msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", d.Get("template_id").(string)), payloadModel) + if err != nil { + return err + } + + d.SetId("") + log.Printf("[DEBUG] MSO NetFlow Monitor Resource - Delete Complete: %v", d.Id()) + return nil +} diff --git a/mso/resource_mso_tenant_policies_netflow_monitor_test.go b/mso/resource_mso_tenant_policies_netflow_monitor_test.go new file mode 100644 index 00000000..7ca54dc0 --- /dev/null +++ b/mso/resource_mso_tenant_policies_netflow_monitor_test.go @@ -0,0 +1,112 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesNetflowMonitorResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: Create NetFlow Monitor") }, + Config: testAccMSOTenantPoliciesNetflowMonitorConfigCreate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "name", "test_netflow_monitor"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "description", "Test NetFlow Monitor"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_monitor.netflow_monitor", "uuid"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_record_uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_exporter_uuids.#", "1"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update NetFlow Monitor") }, + Config: testAccMSOTenantPoliciesNetflowMonitorConfigUpdate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "name", "test_netflow_monitor_updated"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "description", "Updated NetFlow Monitor"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_monitor.netflow_monitor", "uuid"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_record_uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_exporter_uuids.#", "2"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update NetFlow Remove Description and Record UUID") }, + Config: testAccMSOTenantPoliciesNetflowMonitorConfigRemoveOptionals(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "name", "test_netflow_monitor_updated"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "description", ""), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_monitor.netflow_monitor", "uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_record_uuid", ""), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_monitor.netflow_monitor", "netflow_exporter_uuids.#", "2"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Import NetFlow Monitor") }, + ResourceName: "mso_tenant_policies_netflow_monitor.netflow_monitor", + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testCheckResourceDestroyPolicyWithPathAttributesAndArguments("mso_tenant_policies_netflow_monitor", "tenantPolicyTemplate", "template", "netFlowMonitors"), + }) +} + +func testAccNeflowMonitorBaseConfig(extra_exporter bool) string { + base := fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.template_tenant.id + name = "test_netflow_exporter" + } + resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.template_tenant.id + name = "test_netflow_record" + }`, testAccMSOTemplateResourceTenantConfig()) + + if extra_exporter { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_exporter" "netflow_exporter_2" { + template_id = mso_template.template_tenant.id + name = "test_netflow_exporter_2" + // depends_on ensures exporters are deleted sequentially to avoid index conflicts. + depends_on = [mso_tenant_policies_netflow_exporter.netflow_exporter] + }`, base) + } + + return base +} + +func testAccMSOTenantPoliciesNetflowMonitorConfigCreate() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_template.template_tenant.id + name = "test_netflow_monitor" + description = "Test NetFlow Monitor" + netflow_record_uuid = mso_tenant_policies_netflow_record.netflow_record.uuid + netflow_exporter_uuids = [mso_tenant_policies_netflow_exporter.netflow_exporter.uuid] + }`, testAccNeflowMonitorBaseConfig(false)) +} + +func testAccMSOTenantPoliciesNetflowMonitorConfigUpdate() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_template.template_tenant.id + name = "test_netflow_monitor_updated" + description = "Updated NetFlow Monitor" + netflow_record_uuid = mso_tenant_policies_netflow_record.netflow_record.uuid + netflow_exporter_uuids = [mso_tenant_policies_netflow_exporter.netflow_exporter.uuid, mso_tenant_policies_netflow_exporter.netflow_exporter_2.uuid] + }`, testAccNeflowMonitorBaseConfig(true)) +} + +func testAccMSOTenantPoliciesNetflowMonitorConfigRemoveOptionals() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_template.template_tenant.id + name = "test_netflow_monitor_updated" + netflow_exporter_uuids = [mso_tenant_policies_netflow_exporter.netflow_exporter.uuid, mso_tenant_policies_netflow_exporter.netflow_exporter_2.uuid] + }`, testAccNeflowMonitorBaseConfig(true)) +} diff --git a/mso/resource_mso_tenant_policies_netflow_record.go b/mso/resource_mso_tenant_policies_netflow_record.go new file mode 100644 index 00000000..3477c03d --- /dev/null +++ b/mso/resource_mso_tenant_policies_netflow_record.go @@ -0,0 +1,252 @@ +package mso + +import ( + "fmt" + "log" + + "github.com/ciscoecosystem/mso-go-client/client" + "github.com/ciscoecosystem/mso-go-client/container" + "github.com/ciscoecosystem/mso-go-client/models" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceMSONetflowRecord() *schema.Resource { + return &schema.Resource{ + Create: resourceMSONetflowRecordCreate, + Read: resourceMSONetflowRecordRead, + Update: resourceMSONetflowRecordUpdate, + Delete: resourceMSONetflowRecordDelete, + Importer: &schema.ResourceImporter{ + State: resourceMSONetflowRecordImport, + }, + + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "template_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the tenant policy template.", + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 64), + Description: "The name of the NetFlow Record.", + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the NetFlow Record.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "The description of the NetFlow Record.", + }, + "match_parameters": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Description: "The match parameters of the NetFlow Record.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "destination_ip", + "destination_ipv4", + "destination_ipv6", + "destination_mac", + "destination_port", + "ethertype", + "ip_protocol", + "source_ip", + "source_ipv4", + "source_ipv6", + "source_mac", + "source_port", + }, false), + }, + }, + }, + } +} + +func buildMatchParametersPayload(matchParamsRaw interface{}) []string { + matchParamsList := matchParamsRaw.(*schema.Set).List() + matchParams := make([]string, 0, len(matchParamsList)) + for _, item := range matchParamsList { + if param, ok := matchParameterMap[item.(string)]; ok { + matchParams = append(matchParams, param) + } + } + return matchParams +} + +func setNetflowRecordData(d *schema.ResourceData, response *container.Container, templateId string) error { + name := models.StripQuotes(response.S("name").String()) + d.SetId(fmt.Sprintf("templateId/%s/NetflowRecord/%s", templateId, name)) + d.Set("template_id", templateId) + d.Set("name", name) + d.Set("uuid", models.StripQuotes(response.S("uuid").String())) + if response.Exists("description") { + d.Set("description", models.StripQuotes(response.S("description").String())) + } + + if response.Exists("match") { + matchCount, _ := response.ArrayCount("match") + matchParams := make([]string, matchCount) + for i := range matchCount { + matchParams[i] = convertValueWithMap(models.StripQuotes(response.S("match").Index(i).String()), matchParameterMap) + } + d.Set("match_parameters", matchParams) + } + + return nil +} + +func resourceMSONetflowRecordImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + log.Printf("[DEBUG] MSO NetFlow Record Resource - Beginning Import: %v", d.Id()) + err := resourceMSONetflowRecordRead(d, m) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] MSO NetFlow Record Resource - Import Complete: %v", d.Id()) + return []*schema.ResourceData{d}, nil +} + +func resourceMSONetflowRecordCreate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Record Resource - Beginning Create: %v", d.Id()) + msoClient := m.(*client.Client) + + payload := map[string]interface{}{ + "name": d.Get("name").(string), + "description": d.Get("description").(string), + } + + if matchParams, ok := d.GetOk("match_parameters"); ok { + payload["match"] = buildMatchParametersPayload(matchParams) + } + + payloadModel := models.GetPatchPayload("add", "/tenantPolicyTemplate/template/netFlowRecords/-", payload) + templateId := d.Get("template_id").(string) + + _, err := msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("templateId/%s/NetflowRecord/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO NetFlow Record Resource - Create Complete: %v", d.Id()) + return resourceMSONetflowRecordRead(d, m) +} + +func resourceMSONetflowRecordRead(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Record 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(), "NetflowRecord") + if err != nil { + return err + } + + policy, err := GetPolicyByName(response, policyName, "tenantPolicyTemplate", "template", "netFlowRecords") + if err != nil { + return err + } + + err = setNetflowRecordData(d, policy, templateId) + if err != nil { + return err + } + log.Printf("[DEBUG] MSO NetFlow Record Resource - Read Complete: %v", d.Id()) + return nil +} + +func resourceMSONetflowRecordUpdate(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Record Resource - 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 + } + + policyIndex, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "netFlowRecords") + if err != nil { + return err + } + + updatePath := fmt.Sprintf("/tenantPolicyTemplate/template/netFlowRecords/%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("match_parameters") { + matchParams := buildMatchParametersPayload(d.Get("match_parameters")) + err := addPatchPayloadToContainer(payloadCont, "replace", fmt.Sprintf("%s/match", updatePath), matchParams) + 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/NetflowRecord/%s", templateId, d.Get("name").(string))) + log.Printf("[DEBUG] MSO NetFlow Record Resource - Update Complete: %v", d.Id()) + return resourceMSONetflowRecordRead(d, m) +} + +func resourceMSONetflowRecordDelete(d *schema.ResourceData, m interface{}) error { + log.Printf("[DEBUG] MSO NetFlow Record Resource - 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 + } + + policyIndex, err := GetPolicyIndexByKeyAndValue(templateCont, "uuid", d.Get("uuid").(string), "tenantPolicyTemplate", "template", "netFlowRecords") + if err != nil { + return err + } + + payloadModel := models.GetRemovePatchPayload(fmt.Sprintf("/tenantPolicyTemplate/template/netFlowRecords/%d", policyIndex)) + + _, err = msoClient.PatchbyID(fmt.Sprintf("api/v1/templates/%s", templateId), payloadModel) + if err != nil { + return err + } + + d.SetId("") + log.Printf("[DEBUG] MSO NetFlow Record Resource - Delete Complete: %v", d.Id()) + return nil +} diff --git a/mso/resource_mso_tenant_policies_netflow_record_test.go b/mso/resource_mso_tenant_policies_netflow_record_test.go new file mode 100644 index 00000000..93c63aa2 --- /dev/null +++ b/mso/resource_mso_tenant_policies_netflow_record_test.go @@ -0,0 +1,107 @@ +package mso + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccMSOTenantPoliciesNetflowRecordResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + PreConfig: func() { fmt.Println("Test: Create NetFlow Record") }, + Config: testAccMSOTenantPoliciesNetflowRecordConfigCreate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "name", "test_netflow_record"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "description", "Test NetFlow Record"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters.#", "0"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_record.netflow_record", "uuid"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update NetFlow Record with Match Parameters") }, + Config: testAccMSOTenantPoliciesNetflowRecordConfigUpdateMatchParams(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "name", "test_netflow_record"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "description", "Updated NetFlow Record"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_record.netflow_record", "uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters.#", "2"), + testCheckTypeSetStringElemAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters", "ethertype"), + testCheckTypeSetStringElemAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters", "destination_mac"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update NetFlow Record Name") }, + Config: testAccMSOTenantPoliciesNetflowRecordConfigUpdateName(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "name", "test_netflow_record_updated"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "description", "Updated NetFlow Record"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_record.netflow_record", "uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters.#", "2"), + testCheckTypeSetStringElemAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters", "ethertype"), + testCheckTypeSetStringElemAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters", "destination_mac"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Update NetFlow Record Remove Match Params") }, + Config: testAccMSOTenantPoliciesNetflowRecordConfigRemoveMatch(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "name", "test_netflow_record_updated"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "description", "Updated NetFlow Record (Removed Match Params)"), + resource.TestCheckResourceAttrSet("mso_tenant_policies_netflow_record.netflow_record", "uuid"), + resource.TestCheckResourceAttr("mso_tenant_policies_netflow_record.netflow_record", "match_parameters.#", "0"), + ), + }, + { + PreConfig: func() { fmt.Println("Test: Import NetFlow Record") }, + ResourceName: "mso_tenant_policies_netflow_record.netflow_record", + ImportState: true, + ImportStateVerify: true, + }, + }, + CheckDestroy: testCheckResourceDestroyPolicyWithPathAttributesAndArguments("mso_tenant_policies_netflow_record", "tenantPolicyTemplate", "template", "netFlowRecords"), + }) +} + +func testAccMSOTenantPoliciesNetflowRecordConfigCreate() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.template_tenant.id + name = "test_netflow_record" + description = "Test NetFlow Record" + }`, testAccMSOTemplateResourceTenantConfig()) +} + +func testAccMSOTenantPoliciesNetflowRecordConfigUpdateMatchParams() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.template_tenant.id + name = "test_netflow_record" + description = "Updated NetFlow Record" + match_parameters = ["ethertype", "destination_mac"] + }`, testAccMSOTemplateResourceTenantConfig()) +} + +func testAccMSOTenantPoliciesNetflowRecordConfigUpdateName() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.template_tenant.id + name = "test_netflow_record_updated" + description = "Updated NetFlow Record" + match_parameters = ["ethertype", "destination_mac"] + }`, testAccMSOTemplateResourceTenantConfig()) +} + +func testAccMSOTenantPoliciesNetflowRecordConfigRemoveMatch() string { + return fmt.Sprintf(`%s + resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.template_tenant.id + name = "test_netflow_record_updated" + description = "Updated NetFlow Record (Removed Match Params)" + match_parameters = [] + }`, testAccMSOTemplateResourceTenantConfig()) +} diff --git a/website/docs/d/tenant_policies_netflow_exporter.html.markdown b/website/docs/d/tenant_policies_netflow_exporter.html.markdown new file mode 100644 index 00000000..2d018c56 --- /dev/null +++ b/website/docs/d/tenant_policies_netflow_exporter.html.markdown @@ -0,0 +1,34 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_netflow_exporter" +sidebar_current: "docs-mso-data-source-tenant_policies_netflow_exporter" +description: |- + Data source for NetFlow Exporter Policies. +--- + +# mso_tenant_policies_netflow_exporter # + +Data source for NetFlow Exporter Policies. This data source is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> NetFlow Exporter + +## Example Usage ## + +```hcl +data "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.tenant_template.id + name = "netflow_exporter_1" +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the template. +* `name` - (Required) The name of the NetFlow Exporter. + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the NetFlow Exporter. +* `id` - (Read-Only) The unique terraform identifier of the NetFlow Exporter in the template. diff --git a/website/docs/d/tenant_policies_netflow_monitor.html.markdown b/website/docs/d/tenant_policies_netflow_monitor.html.markdown new file mode 100644 index 00000000..cf7238a2 --- /dev/null +++ b/website/docs/d/tenant_policies_netflow_monitor.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_netflow_monitor" +sidebar_current: "docs-mso-data-source-tenant_policies_netflow_monitor" +description: |- + Data source for NetFlow Monitor Policies. +--- + +# mso_tenant_policies_netflow_monitor # + +Data source for NetFlow Monitor Policies. This data source is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> NetFlow Monitor + +## Example Usage ## + +```hcl +data "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_template.tenant_template.id + name = "netflow_monitor_1" +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the template. +* `name` - (Required) The name of the NetFlow Monitor. + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the NetFlow Monitor. +* `description` - (Read-Only) The description of the NetFlow Monitor. +* `netflow_record_uuid` - (Read-Only) The UUID of the associated NetFlow Record. +* `netflow_exporter_uuids` - (Read-Only) The list of UUIDs of the associated NetFlow Exporters. +* `id` - (Read-Only) The unique terraform identifier of the NetFlow Monitor in the template. diff --git a/website/docs/d/tenant_policies_netflow_record.html.markdown b/website/docs/d/tenant_policies_netflow_record.html.markdown new file mode 100644 index 00000000..04097889 --- /dev/null +++ b/website/docs/d/tenant_policies_netflow_record.html.markdown @@ -0,0 +1,36 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_netflow_record" +sidebar_current: "docs-mso-data-source-tenant_policies_netflow_record" +description: |- + Data source for NetFlow Record Policies. +--- + +# mso_tenant_policies_netflow_record # + +Data source for NetFlow Record Policies. This data source is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> NetFlow Record + +## Example Usage ## + +```hcl +data "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.tenant_template.id + name = "netflow_record_1" +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the template. +* `name` - (Required) The name of the NetFlow Record. + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the NetFlow Record. +* `description` - (Read-Only) The description of the NetFlow Record. +* `match_parameters` - (Read-Only) The match parameters of the NetFlow Record. +* `id` - (Read-Only) The unique terraform identifier of the NetFlow Record in the template. diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index e9b71028..ab731669 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -11,6 +11,23 @@ MSO Provider Cisco ACI Multi-Site Orchestrator (MSO) is responsible for provisioning, health monitoring, and managing the full lifecycle of Cisco ACI networking policies and tenant policies across Cisco ACI sites around the world. Terraform provider MSO is a Terraform plugin which will be used to manage the MSO Fabric Constructs on the Cisco MSO platform with leveraging advantages of Terraform. The provider needs to be configured with the proper credentials before it can be used. +Limitations +-------------- + +When creating, updating, or destroying multiple instances of the same resource type within the same template, use [`-parallelism=1`](https://developer.hashicorp.com/terraform/cli/commands/apply#parallelism-n) to avoid index conflicts in the MSO API: + +```sh +terraform apply -parallelism=1 +``` + +or + +```sh +terraform destroy -parallelism=1 +``` + +To avoid setting this flag on every command, you can configure it persistently using the [`TF_CLI_ARGS` or `TF_CLI_ARGS_name` environment variables](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_cli_args-and-tf_cli_args_name). + Authentication -------------- diff --git a/website/docs/r/tenant_policies_netflow_exporter.html.markdown b/website/docs/r/tenant_policies_netflow_exporter.html.markdown new file mode 100644 index 00000000..cafb8404 --- /dev/null +++ b/website/docs/r/tenant_policies_netflow_exporter.html.markdown @@ -0,0 +1,42 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_netflow_exporter" +sidebar_current: "docs-mso-resource-tenant_policies_netflow_exporter" +description: |- + Manages NetFlow Exporter Policies on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_tenant_policies_netflow_exporter # + +Manages NetFlow Exporter Policies on Cisco Nexus Dashboard Orchestrator (NDO). This resource is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> NetFlow Exporter + +## Example Usage ## + +```hcl +resource "mso_tenant_policies_netflow_exporter" "netflow_exporter" { + template_id = mso_template.tenant_template.id + name = "netflow_exporter_1" +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the tenant policy template. +* `name` - (Required) The name of the NetFlow Exporter. + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the NetFlow Exporter. +* `id` - (Read-Only) The unique terraform identifier of the NetFlow Exporter in the template. + +## Importing ## + +An existing MSO NetFlow Exporter can be [imported][docs-import] into this resource via its ID/path, via the following command: [docs-import]: https://www.terraform.io/docs/import/index.html + +```bash +terraform import mso_tenant_policies_netflow_exporter.netflow_exporter templateId/{template_id}/NetflowExporter/{name} +``` diff --git a/website/docs/r/tenant_policies_netflow_monitor.html.markdown b/website/docs/r/tenant_policies_netflow_monitor.html.markdown new file mode 100644 index 00000000..1e506ea3 --- /dev/null +++ b/website/docs/r/tenant_policies_netflow_monitor.html.markdown @@ -0,0 +1,48 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_netflow_monitor" +sidebar_current: "docs-mso-resource-tenant_policies_netflow_monitor" +description: |- + Manages NetFlow Monitor Policies on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_tenant_policies_netflow_monitor # + +Manages NetFlow Monitor Policies on Cisco Nexus Dashboard Orchestrator (NDO). This resource is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> NetFlow Monitor + +## Example Usage ## + +```hcl +resource "mso_tenant_policies_netflow_monitor" "netflow_monitor" { + template_id = mso_template.tenant_template.id + name = "netflow_monitor_1" + description = "Test NetFlow Monitor" + netflow_record_uuid = mso_tenant_policies_netflow_record.netflow_record.uuid + netflow_exporter_uuids = [mso_tenant_policies_netflow_exporter.netflow_exporter.uuid] +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the tenant policy template. +* `name` - (Required) The name of the NetFlow Monitor. Maximum 64 characters. +* `description` - (Optional) The description of the NetFlow Monitor. +* `netflow_record_uuid` - (Optional) The UUID of the NetFlow Record to associate with this monitor. +* `netflow_exporter_uuids` - (Required) The list of UUIDs of the NetFlow Exporters to associate with this monitor. At least one exporter must be provided. + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the NetFlow Monitor. +* `id` - (Read-Only) The unique terraform identifier of the NetFlow Monitor in the template. + +## Importing ## + +An existing MSO NetFlow Monitor can be [imported][docs-import] into this resource via its ID/path, via the following command: [docs-import]: https://www.terraform.io/docs/import/index.html + +```bash +terraform import mso_tenant_policies_netflow_monitor.netflow_monitor templateId/{template_id}/NetflowMonitor/{name} +``` diff --git a/website/docs/r/tenant_policies_netflow_record.html.markdown b/website/docs/r/tenant_policies_netflow_record.html.markdown new file mode 100644 index 00000000..04571eed --- /dev/null +++ b/website/docs/r/tenant_policies_netflow_record.html.markdown @@ -0,0 +1,46 @@ +--- +layout: "mso" +page_title: "MSO: mso_tenant_policies_netflow_record" +sidebar_current: "docs-mso-resource-tenant_policies_netflow_record" +description: |- + Manages NetFlow Record Policies on Cisco Nexus Dashboard Orchestrator (NDO) +--- + +# mso_tenant_policies_netflow_record # + +Manages NetFlow Record Policies on Cisco Nexus Dashboard Orchestrator (NDO). This resource is supported in NDO v4.1 and higher. + +## GUI Information ## + +* `Location` - Manage -> Tenant Template -> Tenant Policies -> NetFlow Record + +## Example Usage ## + +```hcl +resource "mso_tenant_policies_netflow_record" "netflow_record" { + template_id = mso_template.tenant_template.id + name = "netflow_record_1" + description = "Test NetFlow Record" + match_parameters = ["destination_ip", "source_ip", "ip_protocol"] +} +``` + +## Argument Reference ## + +* `template_id` - (Required) The unique ID of the tenant policy template. +* `name` - (Required) The name of the NetFlow Record. Maximum 64 characters. +* `description` - (Optional) The description of the NetFlow Record. +* `match_parameters` - (Optional) The set of match parameters for the NetFlow Record. Valid values are: `destination_ip`, `destination_ipv4`, `destination_ipv6`, `destination_mac`, `destination_port`, `ethertype`, `ip_protocol`, `source_ip`, `source_ipv4`, `source_ipv6`, `source_mac`, `source_port`. + +## Attribute Reference ## + +* `uuid` - (Read-Only) The NDO UUID of the NetFlow Record. +* `id` - (Read-Only) The unique terraform identifier of the NetFlow Record in the template. + +## Importing ## + +An existing MSO NetFlow Record can be [imported][docs-import] into this resource via its ID/path, via the following command: [docs-import]: https://www.terraform.io/docs/import/index.html + +```bash +terraform import mso_tenant_policies_netflow_record.netflow_record templateId/{template_id}/NetflowRecord/{name} +```