From 01a000ffa21018ea74ca3e354e17a087d465719f Mon Sep 17 00:00:00 2001 From: smutel Date: Sat, 12 Dec 2020 18:00:48 +0100 Subject: [PATCH] feat: Add custom_fields to tenant resource --- docs/resources/tenancy_tenant.md | 16 ++++ docs/resources/virtualization_vm.md | 14 ++-- examples/main.tf | 18 ++++ netbox/resource_netbox_tenancy_tenant.go | 43 ++++++++-- netbox/resource_netbox_virtualization_vm.go | 64 +++++++------- netbox/util.go | 92 --------------------- 6 files changed, 110 insertions(+), 137 deletions(-) diff --git a/docs/resources/tenancy_tenant.md b/docs/resources/tenancy_tenant.md index c34190b63..b62801e75 100644 --- a/docs/resources/tenancy_tenant.md +++ b/docs/resources/tenancy_tenant.md @@ -16,6 +16,15 @@ resource "netbox_tenancy_tenant" "tenant_test" { name = "tag1" slug = "tag1" } + + custom_fields = { + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" + cf_selection = "1" + cf_text = "Some text" + cf_url = "https://github.com" + } } ``` @@ -23,6 +32,13 @@ resource "netbox_tenancy_tenant" "tenant_test" { The following arguments are supported: * ``comments`` - (Optional) Comments for this object. +* ``custom_fields`` - (Optional) Custom Field Keys and Values for this object +** For boolean, use the string value "true" or "false" +** For data, use the string format "YYYY-MM-DD" +** For integer, use the value between double quote "10" +** For selection, use the level id +** For text, use the string value +** For URL, use the URL as string * ``description`` - (Optional) The description for this object. * ``tenant_group_id`` - (Optional) ID of the group where this object is located. * ``name`` - (Required) The name for this object. diff --git a/docs/resources/virtualization_vm.md b/docs/resources/virtualization_vm.md index df971318b..054a066db 100644 --- a/docs/resources/virtualization_vm.md +++ b/docs/resources/virtualization_vm.md @@ -33,6 +33,13 @@ resource "netbox_virtualization_vm" "vm_test" { The following arguments are supported: * ``cluster_id`` - (Required) ID of the cluster which host this object. * ``comments`` - (Optional) Comments for this object. +* ``custom_fields`` - (Optional) Custom Field Keys and Values for this object +** For boolean, use the string value "true" or "false" +** For data, use the string format "YYYY-MM-DD" +** For integer, use the value between double quote "10" +** For selection, use the level id +** For text, use the string value +** For URL, use the URL as string * ``disk`` - (Optional) The size in GB of the disk for this object. * ``local_context_data`` - (Optional) Local context data for this object. * ``memory`` - (Optional) The size in MB of the memory of this object. @@ -45,13 +52,6 @@ The following arguments are supported: The ``tag`` block supports: * ``name`` - (Required) Name of the existing tag to associate with this resource. * ``slug`` - (Required) Slug of the existing tag to associate with this resource. -* ``custom_fields`` - (Optional) Custom Field Keys and Values for this object -** For boolean, use the string value "true" or "false" -** For data, use the string format "YYYY-MM-DD" -** For integer, use the value between double quote "10" -** For selection, use the level id -** For text, use the string value -** For URL, use the URL as string ## Attributes Reference diff --git a/examples/main.tf b/examples/main.tf index 5c796217d..1d05e1463 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -13,6 +13,15 @@ resource "netbox_tenancy_tenant" "tenant_test" { name = "tag2" slug = "tag2" } + + custom_fields = { + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" + cf_selection = "1" + cf_text = "Some text" + cf_url = "https://github.com" + } } resource "netbox_tenancy_tenant_group" "tenant_group_test" { @@ -116,6 +125,15 @@ resource "netbox_virtualization_vm" "vm_test" { name = "tag1" slug = "tag1" } + + custom_fields = { + cf_boolean = "true" + cf_date = "2020-12-25" + cf_integer = "10" + cf_selection = "1" + cf_text = "Some text" + cf_url = "https://github.com" + } } resource "netbox_virtualization_interface" "interface_test" { diff --git a/netbox/resource_netbox_tenancy_tenant.go b/netbox/resource_netbox_tenancy_tenant.go index 187e86b0a..6aaac9529 100644 --- a/netbox/resource_netbox_tenancy_tenant.go +++ b/netbox/resource_netbox_tenancy_tenant.go @@ -26,6 +26,24 @@ func resourceNetboxTenancyTenant() *schema.Resource { Optional: true, Default: " ", }, + "custom_fields": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + // terraform default behavior sees a difference between null and an empty string + // therefore we override the default, because null in terraform results in empty string in netbox + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // function is called for each member of map + // including additional call on the amount of entries + // we ignore the count, because the actual state always returns the amount of existing custom_fields and all are optional in terraform + if k == "custom_fields.%" { + return true + } + return old == new + }, + }, "description": { Type: schema.TypeString, Optional: true, @@ -74,6 +92,8 @@ func resourceNetboxTenancyTenantCreate(d *schema.ResourceData, client := m.(*netboxclient.NetBoxAPI) comments := d.Get("comments").(string) + resourceCustomFields := d.Get("custom_fields").(map[string]interface{}) + customFields := convertCustomFieldsFromTerraformToAPICreate(resourceCustomFields) description := d.Get("description").(string) groupID := int64(d.Get("tenant_group_id").(int)) name := d.Get("name").(string) @@ -81,11 +101,12 @@ func resourceNetboxTenancyTenantCreate(d *schema.ResourceData, tags := d.Get("tag").(*schema.Set).List() newResource := &models.WritableTenant{ - Comments: comments, - Description: description, - Name: &name, - Slug: &slug, - Tags: convertTagsToNestedTags(tags), + Comments: comments, + CustomFields: &customFields, + Description: description, + Name: &name, + Slug: &slug, + Tags: convertTagsToNestedTags(tags), } if groupID != 0 { @@ -129,6 +150,12 @@ func resourceNetboxTenancyTenantRead(d *schema.ResourceData, return err } + customFields := convertCustomFieldsFromAPIToTerraform(resource.CustomFields) + + if err = d.Set("custom_fields", customFields); err != nil { + return err + } + var description string if resource.Description == "" { @@ -188,6 +215,12 @@ func resourceNetboxTenancyTenantUpdate(d *schema.ResourceData, params.Comments = comments } + if d.HasChange("custom_fields") { + stateCustomFields, resourceCustomFields := d.GetChange("custom_fields") + customFields := convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCustomFields) + params.CustomFields = &customFields + } + if d.HasChange("description") { description := d.Get("description").(string) params.Description = description diff --git a/netbox/resource_netbox_virtualization_vm.go b/netbox/resource_netbox_virtualization_vm.go index de5ef7ee7..6f414d7a1 100644 --- a/netbox/resource_netbox_virtualization_vm.go +++ b/netbox/resource_netbox_virtualization_vm.go @@ -29,6 +29,24 @@ func resourceNetboxVirtualizationVM() *schema.Resource { Optional: true, Default: " ", }, + "custom_fields": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + // terraform default behavior sees a difference between null and an empty string + // therefore we override the default, because null in terraform results in empty string in netbox + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // function is called for each member of map + // including additional call on the amount of entries + // we ignore the count, because the actual state always returns the amount of existing custom_fields and all are optional in terraform + if k == "custom_fields.%" { + return true + } + return old == new + }, + }, "disk": { Type: schema.TypeInt, Optional: true, @@ -90,24 +108,6 @@ func resourceNetboxVirtualizationVM() *schema.Resource { Optional: true, Default: 0, }, - "custom_fields": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - // terraform default behavior sees a difference between null and an empty string - // therefore we override the default, because null in terraform results in empty string in netbox - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // function is called for each member of map - // including additional call on the amount of entries - // we ignore the count, because the actual state always returns the amount of existing custom_fields and all are optional in terraform - if k == "custom_fields.%" { - return true - } - return old == new - }, - }, }, } } @@ -134,6 +134,7 @@ func resourceNetboxVirtualizationVMCreate(d *schema.ResourceData, newResource := &models.WritableVirtualMachineWithConfigContext{ Cluster: &clusterID, Comments: comments, + CustomFields: &customFields, LocalContextData: &localContextData, Name: &name, Status: status, @@ -164,8 +165,6 @@ func resourceNetboxVirtualizationVMCreate(d *schema.ResourceData, newResource.Vcpus = &vcpus } - newResource.CustomFields = &customFields - resource := virtualization.NewVirtualizationVirtualMachinesCreateParams().WithData(newResource) resourceCreated, err := client.Virtualization.VirtualizationVirtualMachinesCreate(resource, nil) @@ -210,6 +209,12 @@ func resourceNetboxVirtualizationVMRead(d *schema.ResourceData, return err } + customFields := convertCustomFieldsFromAPIToTerraform(resource.CustomFields) + + if err = d.Set("custom_fields", customFields); err != nil { + return err + } + if err = d.Set("disk", resource.Disk); err != nil { return err } @@ -269,12 +274,6 @@ func resourceNetboxVirtualizationVMRead(d *schema.ResourceData, return err } - customFields := convertCustomFieldsFromAPIToTerraform(resource.CustomFields) - - if err = d.Set("custom_fields", customFields); err != nil { - return err - } - return nil } } @@ -299,6 +298,12 @@ func resourceNetboxVirtualizationVMUpdate(d *schema.ResourceData, params.Comments = comments } + if d.HasChange("custom_fields") { + stateCustomFields, resourceCustomFields := d.GetChange("custom_fields") + customFields := convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCustomFields) + params.CustomFields = &customFields + } + if d.HasChange("disk") { disk := int64(d.Get("disk").(int)) params.Disk = &disk @@ -342,13 +347,6 @@ func resourceNetboxVirtualizationVMUpdate(d *schema.ResourceData, params.Vcpus = &vcpus } - // - if d.HasChange("custom_fields") { - stateCustomFields, resourceCustomFields := d.GetChange("custom_fields") - customFields := convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCustomFields) - params.CustomFields = &customFields - } - resource := virtualization.NewVirtualizationVirtualMachinesPartialUpdateParams().WithData(params) resourceID, err := strconv.ParseInt(d.Id(), 10, 64) diff --git a/netbox/util.go b/netbox/util.go index 607813fa8..eeefab6af 100644 --- a/netbox/util.go +++ b/netbox/util.go @@ -139,98 +139,6 @@ func updatePrimaryStatus(m interface{}, info InfosForPrimary, id int64) error { return nil } -/* - * func diffSlices(oldSlice []string, newSlice []string) []string { - * var diff []string - * - * for _, x := range oldSlice { - * found := false - * for _, y := range newSlice { - * if x == y { - * found = true - * } - * } - * - * if found == false { - * diff = append(diff, x) - * } - * } - * - * return diff - * } - */ - -/* - * func convertCFToAPI(customFields []interface{}) (cf map[string]interface{}, - * e error) { - * - * customFieldsAPI := make(map[string]interface{}) - * for _, customFieldRaw := range customFields { - * customField := customFieldRaw.(map[string]interface{}) - * customFieldName := customField["name"].(string) - * customFieldType := customField["kind"].(string) - * customFieldValue := customField["value"].(string) - * - * if customFieldType == "string" { - * customFieldsAPI[customFieldName] = customFieldValue - * } else if customFieldType == "int" { - * cfIntValue, err := strconv.ParseInt(customFieldValue, 10, 64) - * if err != nil { - * return nil, err - * } - * customFieldsAPI[customFieldName] = cfIntValue - * } else if customFieldType == "bool" { - * cfBoolValue, err := strconv.ParseBool(customFieldValue) - * if err != nil { - * return nil, err - * } - * customFieldsAPI[customFieldName] = cfBoolValue - * } - * } - * - * return customFieldsAPI, nil - * } - */ - -/* - * func convertAPIToCF(customFields interface{}) (cf []map[string]string) { - * var cfAPI map[string]string - * cfAPISize := 0 - * - * for _, v := range customFields.(map[string]interface{}) { - * if v != nil { - * cfAPISize++ - * } - * } - * - * customFieldsAPI := make([]map[string]string, cfAPISize) - * - * i := 0 - * for k, v := range customFields.(map[string]interface{}) { - * cfAPI = make(map[string]string) - * cfAPI["name"] = k - * - * if v != nil { - * switch v.(type) { - * case json.Number: - * cfAPI["value"] = v.(json.Number).String() - * cfAPI["kind"] = "int" - * case bool: - * cfAPI["value"] = strconv.FormatBool(v.(bool)) - * cfAPI["kind"] = "bool" - * default: - * cfAPI["value"] = v.(string) - * cfAPI["kind"] = "string" - * } - * customFieldsAPI[i] = cfAPI - * i++ - * } - * } - * - * return customFieldsAPI - * } - */ - // custom_fields have multiple data type returns based on field type // but terraform only supports map[string]string, so we convert all to strings func convertCustomFieldsFromAPIToTerraform(customFields interface{}) map[string]string {