From bb962ee52127e611900b03f64f458b81a23d52c1 Mon Sep 17 00:00:00 2001 From: smutel Date: Sat, 12 Dec 2020 20:53:51 +0100 Subject: [PATCH] feat: Add custom_fields to IP address resource --- docs/resources/ipam_ip_addresses.md | 16 ++++++++ docs/resources/tenancy_tenant.md | 12 +++--- docs/resources/virtualization_vm.md | 12 +++--- netbox/resource_netbox_ipam_ip_addresses.go | 45 ++++++++++++++++++--- netbox/resource_netbox_tenancy_tenant.go | 2 +- netbox/resource_netbox_virtualization_vm.go | 6 +-- netbox/util.go | 6 ++- 7 files changed, 76 insertions(+), 23 deletions(-) diff --git a/docs/resources/ipam_ip_addresses.md b/docs/resources/ipam_ip_addresses.md index 3bbe1c2d8..c0079fb15 100644 --- a/docs/resources/ipam_ip_addresses.md +++ b/docs/resources/ipam_ip_addresses.md @@ -14,6 +14,15 @@ resource "netbox_ipam_ip_addresses" "ip_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" + } } ``` @@ -21,6 +30,13 @@ resource "netbox_ipam_ip_addresses" "ip_test" { The following arguments are supported: * ``address`` - (Required) The IP address (with mask) used 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 of this object. * ``dns_name`` - (Optional) The DNS name of this object. * ``nat_inside_id`` - (Optional) The ID of the NAT inside of this object. diff --git a/docs/resources/tenancy_tenant.md b/docs/resources/tenancy_tenant.md index b62801e75..d71fed197 100644 --- a/docs/resources/tenancy_tenant.md +++ b/docs/resources/tenancy_tenant.md @@ -33,12 +33,12 @@ 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 + * 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 054a066db..97312edd8 100644 --- a/docs/resources/virtualization_vm.md +++ b/docs/resources/virtualization_vm.md @@ -34,12 +34,12 @@ 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 + * 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. diff --git a/netbox/resource_netbox_ipam_ip_addresses.go b/netbox/resource_netbox_ipam_ip_addresses.go index ae7d56294..717d6c81f 100644 --- a/netbox/resource_netbox_ipam_ip_addresses.go +++ b/netbox/resource_netbox_ipam_ip_addresses.go @@ -28,6 +28,24 @@ func resourceNetboxIpamIPAddresses() *schema.Resource { regexp.MustCompile("^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/"+ "[0-9]{1,2}$"), "Must be like 192.168.56.1/24"), }, + "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 == CustomFieldsRegex { + return true + } + return old == new + }, + }, "description": { Type: schema.TypeString, Optional: true, @@ -116,6 +134,8 @@ func resourceNetboxIpamIPAddressesCreate(d *schema.ResourceData, client := m.(*netboxclient.NetBoxAPI) address := d.Get("address").(string) + resourceCustomFields := d.Get("custom_fields").(map[string]interface{}) + customFields := convertCustomFieldsFromTerraformToAPICreate(resourceCustomFields) description := d.Get("description").(string) dnsName := d.Get("dns_name").(string) natInsideID := int64(d.Get("nat_inside_id").(int)) @@ -130,12 +150,13 @@ func resourceNetboxIpamIPAddressesCreate(d *schema.ResourceData, vrfID := int64(d.Get("vrf_id").(int)) newResource := &models.WritableIPAddress{ - Address: &address, - Description: description, - DNSName: dnsName, - Role: role, - Status: status, - Tags: convertTagsToNestedTags(tags), + Address: &address, + CustomFields: &customFields, + Description: description, + DNSName: dnsName, + Role: role, + Status: status, + Tags: convertTagsToNestedTags(tags), } if natInsideID != 0 { @@ -204,6 +225,12 @@ func resourceNetboxIpamIPAddressesRead(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 == "" { description = " " @@ -352,6 +379,12 @@ func resourceNetboxIpamIPAddressesUpdate(d *schema.ResourceData, address := d.Get("address").(string) params.Address = &address + if d.HasChange("custom_fields") { + stateCustomFields, resourceCustomFields := d.GetChange("custom_fields") + customFields := convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCustomFields) + params.CustomFields = &customFields + } + if d.HasChange("description") { if description, exist := d.GetOk("description"); exist { params.Description = description.(string) diff --git a/netbox/resource_netbox_tenancy_tenant.go b/netbox/resource_netbox_tenancy_tenant.go index 6aaac9529..fdc5f38a9 100644 --- a/netbox/resource_netbox_tenancy_tenant.go +++ b/netbox/resource_netbox_tenancy_tenant.go @@ -38,7 +38,7 @@ func resourceNetboxTenancyTenant() *schema.Resource { // 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.%" { + if k == CustomFieldsRegex { return true } return old == new diff --git a/netbox/resource_netbox_virtualization_vm.go b/netbox/resource_netbox_virtualization_vm.go index 6f414d7a1..3e5aaa30f 100644 --- a/netbox/resource_netbox_virtualization_vm.go +++ b/netbox/resource_netbox_virtualization_vm.go @@ -41,7 +41,7 @@ func resourceNetboxVirtualizationVM() *schema.Resource { // 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.%" { + if k == CustomFieldsRegex { return true } return old == new @@ -118,6 +118,8 @@ func resourceNetboxVirtualizationVMCreate(d *schema.ResourceData, clusterID := int64(d.Get("cluster_id").(int)) comments := d.Get("comments").(string) + resourceCustomFields := d.Get("custom_fields").(map[string]interface{}) + customFields := convertCustomFieldsFromTerraformToAPICreate(resourceCustomFields) disk := int64(d.Get("disk").(int)) localContextData := d.Get("local_context_data").(string) memory := int64(d.Get("memory").(int)) @@ -128,8 +130,6 @@ func resourceNetboxVirtualizationVMCreate(d *schema.ResourceData, tags := d.Get("tag").(*schema.Set).List() tenantID := int64(d.Get("tenant_id").(int)) vcpus := int64(d.Get("vcpus").(int)) - resourceCustomFields := d.Get("custom_fields").(map[string]interface{}) - customFields := convertCustomFieldsFromTerraformToAPICreate(resourceCustomFields) newResource := &models.WritableVirtualMachineWithConfigContext{ Cluster: &clusterID, diff --git a/netbox/util.go b/netbox/util.go index eeefab6af..605fcefc9 100644 --- a/netbox/util.go +++ b/netbox/util.go @@ -16,6 +16,7 @@ type InfosForPrimary struct { } const VMInterfaceType string = "virtualization.vminterface" +const CustomFieldsRegex = "custom_fields.%" func expandToStringSlice(v []interface{}) []string { s := make([]string, len(v)) @@ -144,6 +145,7 @@ func updatePrimaryStatus(m interface{}, info InfosForPrimary, id int64) error { func convertCustomFieldsFromAPIToTerraform(customFields interface{}) map[string]string { toReturn := make(map[string]string) switch t := customFields.(type) { + case map[string]interface{}: for key, value := range t { var strValue string @@ -155,8 +157,8 @@ func convertCustomFieldsFromAPIToTerraform(customFields interface{}) map[string] strValue = fmt.Sprintf("%v", v["value"]) } } - toReturn[key] = strValue + toReturn[key] = strValue } } @@ -188,6 +190,7 @@ func convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCust for key := range stateCustomFields.(map[string]interface{}) { toReturn[key] = nil } + // then we override the values that still exist in the terraform code with their respective value for key, value := range resourceCustomFields.(map[string]interface{}) { toReturn[key] = value @@ -199,5 +202,6 @@ func convertCustomFieldsFromTerraformToAPIUpdate(stateCustomFields, resourceCust toReturn[key] = 0 } } + return toReturn }