From 322cd715cf34c8ccfe9fe7e92cffdcd8e647f184 Mon Sep 17 00:00:00 2001 From: akinross Date: Wed, 23 Oct 2024 16:13:27 +0200 Subject: [PATCH] [minor_change] Add resource and datasource for mso_template --- examples/template/main.tf | 86 ++++ mso/datasource_mso_template.go | 87 ++++ mso/provider.go | 2 + mso/resource_mso_template.go | 582 ++++++++++++++++++++++++++ website/docs/d/template.html.markdown | 37 ++ website/docs/r/template.html.markdown | 43 ++ 6 files changed, 837 insertions(+) create mode 100644 examples/template/main.tf create mode 100644 mso/datasource_mso_template.go create mode 100644 mso/resource_mso_template.go create mode 100644 website/docs/d/template.html.markdown create mode 100644 website/docs/r/template.html.markdown diff --git a/examples/template/main.tf b/examples/template/main.tf new file mode 100644 index 00000000..fa64f11b --- /dev/null +++ b/examples/template/main.tf @@ -0,0 +1,86 @@ +terraform { + required_providers { + mso = { + source = "CiscoDevNet/mso" + } + } +} + +provider "mso" { + username = "" # + password = "" # + url = "" # + insecure = true +} + +data "mso_site" "site_1" { + name = "example_site_1" +} + +data "mso_site" "site_2" { + name = "example_site_2" +} + +data "mso_tenant" "example_tenant" { + name = "example_tenant" +} + +# tenant template example + +resource "mso_template" "tenant_template" { + template_name = "tenant_template" + template_type = "tenant" + tenant_name = mso_tenant.example_tenant.display_name + sites = [data.mso_site.site_1.id, data.mso_site.site_2.id] +} + +# l3out template example + +resource "mso_template" "l3out_template" { + template_name = "l3out_template" + template_type = "l3out" + tenant_name = mso_tenant.example_tenant.display_name + sites = [data.mso_site.site_1.id] +} + +# fabric policy template example + +resource "mso_template" "fabric_policy_template" { + template_name = "fabric_policy_template" + template_type = "fabric_policy" + sites = [data.mso_site.site_1.id, data.mso_site.site_2.id] +} + +# fabric resource template example + +resource "mso_template" "fabric_resource_template" { + template_name = "fabric_resource_template" + template_type = "fabric_resource" + sites = [data.mso_site.site_1.id, data.mso_site.site_2.id] +} + +# monitoring tenant template example + +resource "mso_template" "monitoring_tenant_template" { + template_name = "monitoring_tenant_template" + template_type = "monitoring_tenant" + tenant_name = mso_tenant.example_tenant.display_name + sites = [data.mso_site.site_1.id] +} + +# monitoring access template example + +resource "mso_template" "monitoring_access_template" { + template_name = "monitoring_access_template" + template_type = "monitoring_access" + sites = [data.mso_site.site_1.id] +} + +# service device template example + +resource "mso_template" "service_device_template" { + template_name = "service_device_template" + template_type = "service_device" + tenant_name = mso_tenant.example_tenant.display_name + sites = [data.mso_site.site_1.id, data.mso_site.site_2.id] +} diff --git a/mso/datasource_mso_template.go b/mso/datasource_mso_template.go new file mode 100644 index 00000000..1a2fe309 --- /dev/null +++ b/mso/datasource_mso_template.go @@ -0,0 +1,87 @@ +package mso + +import ( + "fmt" + "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 datasourceMSOTemplate() *schema.Resource { + return &schema.Resource{ + + Read: datasourceMSOTemplateRead, + + SchemaVersion: version, + + Schema: (map[string]*schema.Schema{ + "template_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + "template_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + "template_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "tenant", + "l3out", + "fabric_policy", + "fabric_resource", + "monitoring_tenant", + "monitoring_access", + "service_device", + }, false), + }, + "tenant_name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "sites": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }), + } +} + +func datasourceMSOTemplateRead(d *schema.ResourceData, m interface{}) error { + log.Println("[DEBUG] MSO Template Datasource: Beginning Read") + + msoClient := m.(*client.Client) + id := d.Get("template_id").(string) + name := d.Get("template_name").(string) + templateType := d.Get("template_type").(string) + + if id == "" && name == "" { + return fmt.Errorf("either `template_id` or `template_name` must be provided") + } else if id != "" && name != "" { + return fmt.Errorf("only one of `template_id` or `template_name` must be provided") + } else if name != "" && templateType == "" { + return fmt.Errorf("`template_type` must be provided when `template_name` is provided") + } + + ndoTemplate := ndoTemplate{msoClient: msoClient, id: id, templateName: name, templateType: templateType} + err := ndoTemplate.getTemplate(true) + if err != nil { + return err + } + ndoTemplate.SetToSchema(d) + d.Set("template_id", d.Id()) + log.Println("[DEBUG] MSO Template Datasource: Read Completed", d.Id()) + return nil + +} diff --git a/mso/provider.go b/mso/provider.go index 8f511389..f773504e 100644 --- a/mso/provider.go +++ b/mso/provider.go @@ -116,6 +116,7 @@ func Provider() terraform.ResourceProvider { "mso_system_config": resourceMSOSystemConfig(), "mso_schema_site_contract_service_graph": resourceMSOSchemaSiteContractServiceGraph(), "mso_schema_site_contract_service_graph_listener": resourceMSOSchemaSiteContractServiceGraphListener(), + "mso_template": resourceMSOTemplate(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -172,6 +173,7 @@ func Provider() terraform.ResourceProvider { "mso_rest": datasourceMSORest(), "mso_schema_site_contract_service_graph": dataSourceMSOSchemaSiteContractServiceGraph(), "mso_schema_site_contract_service_graph_listener": dataSourceMSOSchemaSiteContractServiceGraphListener(), + "mso_template": datasourceMSOTemplate(), }, ConfigureFunc: configureClient, diff --git a/mso/resource_mso_template.go b/mso/resource_mso_template.go new file mode 100644 index 00000000..235d2be4 --- /dev/null +++ b/mso/resource_mso_template.go @@ -0,0 +1,582 @@ +package mso + +import ( + "errors" + "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 resourceMSOTemplate() *schema.Resource { + return &schema.Resource{ + Create: resourceMSOTemplateCreate, + Read: resourceMSOTemplateRead, + Update: resourceMSOTemplateUpdate, + Delete: resourceMSOTemplateDelete, + Importer: &schema.ResourceImporter{ + State: resourceMSOTemplateImport, + }, + SchemaVersion: 1, + Schema: map[string]*schema.Schema{ + "template_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 1000), + }, + "template_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "tenant", + "l3out", + "fabric_policy", + "fabric_resource", + "monitoring_tenant", + "monitoring_access", + "service_device", + }, false), + }, + "tenant_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "sites": { + Type: schema.TypeList, // Set cannot not be used because the order of sites is important for deletion, since full list replacement is not supported in API + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + // ValidateFunc not supported on type list / set else duplication could be caught with duplication.ListOfUniqueStrings + // │ Error: Internal validation of the provider failed! This is always a bug + // │ with the provider itself, and not a user issue. Please report + // │ this bug: + // │ + // │ 1 error occurred: + // │ * resource mso_template: sites: ValidateFunc is not yet supported on lists or sets. + }, + }, + // Can we do validation here due to unknown values? + CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error { + oldSites, newSites := diff.GetChange("sites") + + sites := convertToListOfStrings(newSites.([]interface{})) + + // ndoTemplate := ndoTemplate{ + // templateName: diff.Get("template_name").(string), + // templateType: diff.Get("template_type").(string), + // tenantName: diff.Get("tenant_name").(string), + // sites: sites, + // } + + // // This check is also done in the validateConfig function to prevent create and update scenarios with unknown values for sites + // validationErrors := ndoTemplate.validateConfig() + // if len(validationErrors) > 0 { + // return errors.Join(validationErrors...) + // } + + // // This check is also done in the validateConfig function to prevent create and update scenarios with unknown values for sites + // duplicates := duplicatesInList(sites) + // if len(duplicates) > 0 { + // duplicatesErrors := []error{fmt.Errorf("duplication found in the sites list")} + // for _, site := range duplicates { + // duplicatesErrors = append(duplicatesErrors, fmt.Errorf(fmt.Sprintf("Site %s is duplicated", site))) + // } + // return errors.Join(duplicatesErrors...) + // } + // // old = [a,b], new = [b,a] + + if len(oldSites.([]interface{})) != len(newSites.([]interface{})) { + return nil + } + for _, oldSite := range oldSites.([]interface{}) { + if !valueInSliceofStrings(oldSite.(string), sites) { + return nil + } + } + diff.SetNew("sites", oldSites) + + return nil + }, + } +} + +type ndoTemplateType struct { + templateType string // templateType in payload + templateTypeContainer string // templateType container in payload + tenant bool // tenant required + siteAmount int // 1 = 1 site, 2 = multiple sites + templateContainer bool // configuration is set in template container in payload +} + +var ndoTemplateTypes = map[string]ndoTemplateType{ + "tenant": ndoTemplateType{ + templateType: "tenantPolicy", + templateTypeContainer: "tenantPolicyTemplate", + tenant: true, + siteAmount: 2, + templateContainer: true, + }, + "l3out": ndoTemplateType{ + templateType: "l3out", + templateTypeContainer: "l3outTemplate", + tenant: true, + siteAmount: 1, + templateContainer: false, + }, + "fabric_policy": ndoTemplateType{ + templateType: "fabricPolicy", + templateTypeContainer: "fabricPolicyTemplate", + tenant: false, + siteAmount: 2, + templateContainer: true, + }, + "fabric_resource": ndoTemplateType{ + templateType: "fabricResource", + templateTypeContainer: "fabricResourceTemplate", + tenant: false, + siteAmount: 2, + templateContainer: true, + }, + "monitoring_tenant": ndoTemplateType{ + templateType: "monitoring", + templateTypeContainer: "monitoringTemplate", + tenant: true, + siteAmount: 1, + templateContainer: true, + }, + "monitoring_access": ndoTemplateType{ + templateType: "monitoring", + templateTypeContainer: "monitoringTemplate", + tenant: false, + siteAmount: 1, + templateContainer: true, + }, + "service_device": ndoTemplateType{ + templateType: "serviceDevice", + templateTypeContainer: "deviceTemplate", + tenant: true, + siteAmount: 2, + templateContainer: true, + }, +} + +type ndoTemplate struct { + id string + templateName string + templateType string + tenantName string + sites []string + msoClient *client.Client + ndoTenants map[string]string + ndoSites map[string]string +} + +func (ndoTemplate *ndoTemplate) SetToSchema(d *schema.ResourceData) { + d.SetId(ndoTemplate.id) + d.Set("template_name", ndoTemplate.templateName) + d.Set("template_type", ndoTemplate.templateType) + d.Set("tenant_name", ndoTemplate.tenantName) + d.Set("sites", ndoTemplate.sites) +} + +// Should we validate or let API handle it if possible? +func (ndoTemplate *ndoTemplate) validateConfig() []error { + errors := []error{} + + if ndoTemplate.tenantName != "" && !ndoTemplateTypes[ndoTemplate.templateType].tenant { + errors = append(errors, fmt.Errorf(fmt.Sprintf("Tenant cannot be attached to template of type %s.", ndoTemplate.templateType))) + } + if ndoTemplate.tenantName == "" && ndoTemplateTypes[ndoTemplate.templateType].tenant { + errors = append(errors, fmt.Errorf(fmt.Sprintf("Tenant is required for template of type %s.", ndoTemplate.templateType))) + } + if len(ndoTemplate.sites) == 0 && ndoTemplateTypes[ndoTemplate.templateType].siteAmount == 1 { + errors = append(errors, fmt.Errorf(fmt.Sprintf("Site is required for template of type %s.", ndoTemplate.templateType))) + } + if len(ndoTemplate.sites) > 1 && ndoTemplateTypes[ndoTemplate.templateType].siteAmount == 1 { + errors = append(errors, fmt.Errorf(fmt.Sprintf("Only one site is allowed for template of type %s.", ndoTemplate.templateType))) + } + duplicates := duplicatesInList(ndoTemplate.sites) + if len(duplicates) > 0 { + duplicatesErrors := []error{fmt.Errorf("duplication found in the sites list")} + for _, site := range duplicates { + duplicatesErrors = append(duplicatesErrors, fmt.Errorf(fmt.Sprintf("Site %s is duplicated", site))) + } + return duplicatesErrors + } + + return errors +} + +func (ndoTemplate *ndoTemplate) ToMap() (map[string]interface{}, error) { + return map[string]interface{}{ + "displayName": ndoTemplate.templateName, + "templateType": ndoTemplateTypes[ndoTemplate.templateType].templateType, + ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer: ndoTemplate.createTypeSpecificPayload(), + }, nil +} + +func (ndoTemplate *ndoTemplate) createTypeSpecificPayload() map[string]interface{} { + if ndoTemplate.templateType == "tenant" { + return map[string]interface{}{"template": map[string]interface{}{"tenantId": ndoTemplate.ndoTenants[ndoTemplate.tenantName]}, "sites": ndoTemplate.createSitesPayload()} + } else if ndoTemplate.templateType == "l3out" { + return map[string]interface{}{"tenantId": ndoTemplate.ndoTenants[ndoTemplate.tenantName], "siteId": ndoTemplate.createSitesPayload()[0]["siteId"]} + } else if ndoTemplate.templateType == "fabric_policy" { + return map[string]interface{}{"sites": ndoTemplate.createSitesPayload()} + } else if ndoTemplate.templateType == "fabric_resource" { + return map[string]interface{}{"sites": ndoTemplate.createSitesPayload()} + } else if ndoTemplate.templateType == "monitoring_tenant" { + return map[string]interface{}{"template": map[string]interface{}{"mtType": "tenant", "tenant": ndoTemplate.ndoTenants[ndoTemplate.tenantName]}, "sites": ndoTemplate.createSitesPayload()} + } else if ndoTemplate.templateType == "monitoring_access" { + return map[string]interface{}{"template": map[string]interface{}{"mtType": "access"}, "sites": ndoTemplate.createSitesPayload()} + } else if ndoTemplate.templateType == "service_device" { + return map[string]interface{}{"template": map[string]interface{}{"tenantId": ndoTemplate.ndoTenants[ndoTemplate.tenantName]}, "sites": ndoTemplate.createSitesPayload()} + } + return nil +} + +func (ndoTemplate *ndoTemplate) createSitesPayload() []map[string]interface{} { + siteIds := []map[string]interface{}{} + for _, site := range ndoTemplate.sites { + siteIds = append(siteIds, ndoTemplate.createSitePayload(site)) + } + return siteIds +} + +func (ndoTemplate *ndoTemplate) createSitePayload(site string) map[string]interface{} { + return map[string]interface{}{"siteId": ndoTemplate.ndoSites[site]} +} + +func (ndoTemplate *ndoTemplate) setNdoSites() error { + cont, err := ndoTemplate.msoClient.GetViaURL(fmt.Sprintf("api/v1/sites")) + if err != nil { + return err + } + if cont.Exists("sites") { + ndoTemplate.ndoSites = map[string]string{} + for siteIndex, _ := range cont.S("sites").Data().([]interface{}) { + siteCont, err := cont.ArrayElement(siteIndex, "sites") + if err != nil { + return err + } + ndoTemplate.ndoSites[models.StripQuotes(siteCont.S("id").String())] = models.StripQuotes(siteCont.S("name").String()) + ndoTemplate.ndoSites[models.StripQuotes(siteCont.S("name").String())] = models.StripQuotes(siteCont.S("id").String()) + } + } + return nil +} + +func (ndoTemplate *ndoTemplate) setNdoTenants() error { + cont, err := ndoTemplate.msoClient.GetViaURL(fmt.Sprintf("api/v1/tenants")) + if err != nil { + return err + } + if cont.Exists("tenants") { + ndoTemplate.ndoTenants = map[string]string{} + for tenantIndex, _ := range cont.S("tenants").Data().([]interface{}) { + tenantCont, err := cont.ArrayElement(tenantIndex, "tenants") + if err != nil { + return err + } + ndoTemplate.ndoTenants[models.StripQuotes(tenantCont.S("id").String())] = models.StripQuotes(tenantCont.S("displayName").String()) + ndoTemplate.ndoTenants[models.StripQuotes(tenantCont.S("displayName").String())] = models.StripQuotes(tenantCont.S("id").String()) + } + } + return nil +} + +func (ndoTemplate *ndoTemplate) getTemplate(errorNotFound bool) error { + + if ndoTemplate.id == "" { + err := ndoTemplate.setTemplateId() + if err != nil { + return err + } + } + + cont, err := ndoTemplate.msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/%s", ndoTemplate.id)) + if err != nil { + // If template is not found, we can remove the id and try to find the template by name + // Determine condition for this + if !errorNotFound && (cont.S("code").String() == "400" && strings.Contains(cont.S("message").String(), fmt.Sprintf("Template ID %s invalid", ndoTemplate.id))) { + ndoTemplate.id = "" + return nil + } + return err + } + + ndoTemplate.sites = []string{} + + err = ndoTemplate.setNdoTenants() + if err != nil { + return err + } + + err = ndoTemplate.setNdoSites() + if err != nil { + return err + } + + ndoTemplate.templateName = models.StripQuotes(cont.S("displayName").String()) + templateType := models.StripQuotes(cont.S("templateType").String()) + + if templateType == "tenantPolicy" { + ndoTemplate.templateType = "tenant" + ndoTemplate.tenantName = ndoTemplate.ndoTenants[models.StripQuotes(cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).S("template").S("tenantId").String())] + + } else if templateType == "l3out" { + ndoTemplate.templateType = "l3out" + ndoTemplate.tenantName = ndoTemplate.ndoTenants[models.StripQuotes(cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).S("tenantId").String())] + ndoTemplate.sites = append(ndoTemplate.sites, ndoTemplate.ndoSites[models.StripQuotes(cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).S("siteId").String())]) + + } else if templateType == "fabricPolicy" { + ndoTemplate.templateType = "fabric_policy" + + } else if templateType == "fabricResource" { + ndoTemplate.templateType = "fabric_resource" + + } else if templateType == "monitoring" { + ndoTemplate.templateType = "monitoring_access" + if models.StripQuotes(cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).S("template").S("mtType").String()) == "tenant" { + ndoTemplate.templateType = "monitoring_tenant" + ndoTemplate.tenantName = ndoTemplate.ndoTenants[models.StripQuotes(cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).S("template").S("tenant").String())] + } + + } else if templateType == "serviceDevice" { + ndoTemplate.templateType = "service_device" + ndoTemplate.tenantName = ndoTemplate.ndoTenants[models.StripQuotes(cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).S("template").S("tenantId").String())] + } + + if ndoTemplate.templateType != "l3out" { + if cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).Exists("sites") { + for _, site := range cont.S(ndoTemplateTypes[ndoTemplate.templateType].templateTypeContainer).S("sites").Data().([]interface{}) { + siteId := models.StripQuotes(site.(map[string]interface{})["siteId"].(string)) + ndoTemplate.sites = append(ndoTemplate.sites, ndoTemplate.ndoSites[siteId]) + } + } + } + + return nil + +} + +func (ndoTemplate *ndoTemplate) setTemplateId() error { + cont, err := ndoTemplate.msoClient.GetViaURL(fmt.Sprintf("api/v1/templates/summaries")) + if err != nil { + return err + } + + templates, err := cont.Children() + if err != nil { + return err + } + + for _, template := range templates { + if ndoTemplate.templateName == models.StripQuotes(template.S("templateName").String()) && ndoTemplateTypes[ndoTemplate.templateType].templateType == models.StripQuotes(template.S("templateType").String()) { + ndoTemplate.id = models.StripQuotes(template.S("templateId").String()) + return nil + } + } + + return fmt.Errorf("Template with name %s not found.", ndoTemplate.templateName) +} + +func resourceMSOTemplateCreate(d *schema.ResourceData, m interface{}) error { + log.Println("[DEBUG] MSO Template Resource: Beginning Create", d.Id()) + msoClient := m.(*client.Client) + + sites := getListOfStringsFromSchemaList(d, "sites") + tenant := d.Get("tenant_name").(string) + + ndoTemplate := ndoTemplate{ + msoClient: msoClient, + templateName: d.Get("template_name").(string), + templateType: d.Get("template_type").(string), + tenantName: tenant, + sites: sites, + } + + validationErrors := ndoTemplate.validateConfig() + + if len(sites) > 0 { + err := ndoTemplate.setNdoSites() + if err != nil { + return err + } + } + + if tenant != "" { + err := ndoTemplate.setNdoTenants() + if err != nil { + return err + } + } + + if len(validationErrors) > 0 { + d.SetId("") + return errors.Join(validationErrors...) + } + + response, err := msoClient.Save("api/v1/templates", &ndoTemplate) + if err != nil { + return err + } + + d.SetId(models.StripQuotes(response.S("templateId").String())) + log.Println("[DEBUG] MSO Template Resource: Create Completed", d.Id()) + return nil +} + +func resourceMSOTemplateRead(d *schema.ResourceData, m interface{}) error { + log.Println("[DEBUG] MSO Template Resource: Beginning Read", d.Id()) + msoClient := m.(*client.Client) + ndoTemplate := ndoTemplate{msoClient: msoClient, id: d.Id()} + err := ndoTemplate.getTemplate(false) + if err != nil { + return err + } + ndoTemplate.SetToSchema(d) + log.Println("[DEBUG] MSO Template Resource: Read Completed", d.Id()) + return nil +} + +func resourceMSOTemplateUpdate(d *schema.ResourceData, m interface{}) error { + log.Println("[DEBUG] MSO Template Resource: Beginning Update", d.Id()) + msoClient := m.(*client.Client) + + templateType := d.Get("template_type").(string) + templateName := d.Get("template_name").(string) + + if ndoTemplateTypes[templateType].siteAmount == 1 && d.HasChange("sites") { + return fmt.Errorf("Cannot change site for template of type %s.", templateType) + } + + sites := getListOfStringsFromSchemaList(d, "sites") + + ndoTemplate := ndoTemplate{ + msoClient: msoClient, + templateName: templateName, + templateType: templateType, + tenantName: d.Get("tenant_name").(string), + sites: sites, + } + + if len(sites) > 0 { + err := ndoTemplate.setNdoSites() + if err != nil { + return err + } + } + + validationErrors := ndoTemplate.validateConfig() + + if len(validationErrors) > 0 { + return errors.Join(validationErrors...) + } + + payloadCon := container.New() + payloadCon.Array() + + if d.HasChange("template_name") { + err := addPatchPayloadToContainer(payloadCon, "replace", "/displayName", templateName) + if err != nil { + return err + } + } + + if d.HasChange("sites") { + // Replace operation is not supported for sites, so we need to remove and add sites individually + + oldSites, _ := d.GetChange("sites") + + // Reversed loop to remove sites from the end of the list first, to prevent index shifts with wrong deletes + for index := len(oldSites.([]interface{})) - 1; index >= 0; index-- { + if !valueInSliceofStrings(oldSites.([]interface{})[index].(string), sites) { + err := addPatchPayloadToContainer(payloadCon, "remove", fmt.Sprintf("/%s/sites/%d", ndoTemplateTypes[templateType].templateTypeContainer, index), nil) + if err != nil { + return err + } + } + } + + for _, site := range sites { + if !valueInSliceofStrings(site, convertToListOfStrings(oldSites.([]interface{}))) { + err := addPatchPayloadToContainer(payloadCon, "add", fmt.Sprintf("/%s/sites/-", ndoTemplateTypes[templateType].templateTypeContainer), ndoTemplate.createSitePayload(site)) + if err != nil { + return err + } + } + } + } + + err := doPatchRequest(msoClient, fmt.Sprintf("api/v1/templates/%s", d.Id()), payloadCon) + if err != nil { + return err + } + + log.Println("[DEBUG] MSO Template Resource: Updating Completed", d.Id()) + return nil +} + +func resourceMSOTemplateDelete(d *schema.ResourceData, m interface{}) error { + log.Println("[DEBUG] MSO Template Resource: Beginning Delete", d.Id()) + msoClient := m.(*client.Client) + + err := msoClient.DeletebyId(fmt.Sprintf("api/v1/templates/%s", d.Id())) + if err != nil { + return err + } + log.Println("[DEBUG] MSO Template Resource: Delete Completed", d.Id()) + d.SetId("") + return nil +} + +func resourceMSOTemplateImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + log.Println("[DEBUG] MSO Template Resource: Beginning Import", d.Id()) + msoClient := m.(*client.Client) + ndoTemplate := ndoTemplate{msoClient: msoClient, id: d.Id()} + err := ndoTemplate.getTemplate(true) + if err != nil { + return nil, err + } + ndoTemplate.SetToSchema(d) + log.Println("[DEBUG] MSO Template Resource: Import Completed", d.Id()) + return []*schema.ResourceData{d}, nil +} + +func getListOfStringsFromSchemaList(d *schema.ResourceData, key string) []string { + if values, ok := d.GetOk(key); ok { + return convertToListOfStrings(values.([]interface{})) + } + return nil +} + +func convertToListOfStrings(values []interface{}) []string { + result := []string{} + for _, item := range values { + result = append(result, item.(string)) + } + return result +} + +func duplicatesInList(list []string) []string { + duplicates := []string{} + set := make(map[string]int) + for index, item := range list { + if _, ok := set[item]; ok { + duplicates = append(duplicates, item) + } else { + set[item] = index + } + } + return duplicates +} diff --git a/website/docs/d/template.html.markdown b/website/docs/d/template.html.markdown new file mode 100644 index 00000000..b746b54c --- /dev/null +++ b/website/docs/d/template.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "mso" +page_title: "MSO: mso_template" +sidebar_current: "docs-mso-data-source-template" +description: |- + Data source for MSO Template. +--- + +# mso_template # + +Data source for MSO Template. + +## Example Usage ## + +```hcl + +data "mso_template" "example_with_name" { + template_name = "tenant_template" + template_type = "tenant" +} + +data "mso_template" "example_with_id" { + template_id = "6718b46395400f3759523378" +} + +``` + +## Argument Reference ## + +* `template_id` - (Optional) The ID of the template. Mutually exclusive with `template_name`. +* `template_name` - (Optional) The name of the template. Mutually exclusive with `template_id`. +* `template_type` - (Optional) The type of the template. Allowed values are `tenant`, `l3out`, `fabric_policy`, `fabric_resource`, `monitoring_tenant`, `monitoring_access`, or `service_device`. Required when `template_name` is provided. + +## Attribute Reference ## + +* `tenant_name` - (Read-Only) The name of the tenant to associate with the template. +* `sites` - (Read-Only) A list of site names to associate with the template. diff --git a/website/docs/r/template.html.markdown b/website/docs/r/template.html.markdown new file mode 100644 index 00000000..9b6796b5 --- /dev/null +++ b/website/docs/r/template.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "mso" +page_title: "MSO: mso_template" +sidebar_current: "docs-mso-resource-template" +description: |- + Manages MSO Template +--- + +# mso_template # + +Manages MSO Template + +## Example Usage ## + +```hcl + +resource "mso_template" "tenant_template" { + template_name = "tenant_template" + template_type = "tenant" + tenant_name = mso_tenant.example_tenant.display_name + sites = [data.mso_site.site_1.id, data.mso_site.site_2.id] +} + +``` + +## Argument Reference ## + +* `template_name` - (Required) The name of the template. +* `template_type` - (Required) The type of the template. Allowed values are `tenant`, `l3out`, `fabric_policy`, `fabric_resource`, `monitoring_tenant`, `monitoring_access`, or `service_device`. +* `tenant_name` - (Optional) The name of the tenant to associate with the template. +* `sites` - (Optional) A list of site names to associate with the template. + +## Attribute Reference ## + +The only attribute exported with this resource is `id`. Which is set to the ID of the template. + +## Importing ## + +An existing MSO Schema Template can be [imported][docs-import] into this resource via its Id/path, via the following command: [docs-import]: + +```bash +terraform import mso_template.tenant_template {id} +```