Skip to content

Commit

Permalink
feat: allow any custom field type in netbox_device
Browse files Browse the repository at this point in the history
Updates the `netbox_device` resource to allow for any custom field
type to be set. This is done by using a string field with the
`jsonencode()` and `jsondecode()` terraform functions.

This allows for any complex custom fields or data types that are needed,
since the underlying types don't matter and are unmarshalled using
map[string]interface{}.

To set custom fields using this new method, use the following syntax.

```terraform
resource "netbox_device" "test" {
  name = "device1"
  tenant_id = netbox_tenant.test.id
  role_id = netbox_device_role.test.id
  device_type_id = netbox_device_type.test.id
  site_id = netbox_site.test.id
	custom_fields = jsonencode({
		"text_field" = "81"
		"bool_field" = true
	})
}
```

NOTE: This may be a breaking change for some users.
  • Loading branch information
tagur87 authored and fbreckle committed Aug 25, 2023
1 parent fa4e157 commit 55c5ab5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 8 deletions.
1 change: 1 addition & 0 deletions docs/resources/device.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ resource "netbox_device" "test" {
- `comments` (String)
- `custom_fields` (Map of String)
- `description` (String)
- `custom_fields` (String) A JSON string that defines the custom fields as defined under the `custom_fields` key in the object's api.This is best managed with the `jsonencode()` & `jsondecode()` functions.
- `location_id` (Number)
- `platform_id` (Number)
- `rack_face` (String) Valid values are `front` and `rear`. Required when `rack_position` is set.
Expand Down
26 changes: 18 additions & 8 deletions netbox/resource_netbox_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package netbox

import (
"context"
"encoding/json"
"strconv"

"github.com/fbreckle/go-netbox/netbox/client"
Expand Down Expand Up @@ -102,7 +103,7 @@ func resourceNetboxDevice() *schema.Resource {
Type: schema.TypeFloat,
Optional: true,
},
customFieldsKey: customFieldsSchema,
customFieldsKey: customFieldsSchemaFunc(),
},
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
Expand Down Expand Up @@ -179,9 +180,14 @@ func resourceNetboxDeviceCreate(ctx context.Context, d *schema.ResourceData, m i
data.Position = nil
}

ct, ok := d.GetOk(customFieldsKey)
cf, ok := d.GetOk(customFieldsKey)
if ok {
data.CustomFields = ct
var cfMap map[string]interface{}
err := json.Unmarshal([]byte(cf.(string)), &cfMap)
if err != nil {
return diag.Errorf("error in resourceNetboxDeviceCreate[CustomFields]: %v", err)
}
data.CustomFields = cfMap
}

data.Tags, _ = getNestedTagListFromResourceDataSet(api, d.Get(tagsKey))
Expand Down Expand Up @@ -276,10 +282,11 @@ func resourceNetboxDeviceRead(ctx context.Context, d *schema.ResourceData, m int
d.Set("site_id", nil)
}

cf := getCustomFields(res.GetPayload().CustomFields)
if cf != nil {
d.Set(customFieldsKey, cf)
cf, err := handleCustomFieldRead(device.CustomFields)
if err != nil {
return diag.FromErr(err)
}
d.Set(customFieldsKey, cf)

d.Set("comments", device.Comments)

Expand Down Expand Up @@ -377,8 +384,11 @@ func resourceNetboxDeviceUpdate(ctx context.Context, d *schema.ResourceData, m i
data.Face = getOptionalStr(d, "rack_face", false)
data.Position = getOptionalFloat(d, "rack_position")

cf, ok := d.GetOk(customFieldsKey)
if ok {
if d.HasChange(customFieldsKey) {
cf, err := handleCustomFieldUpdate(d.GetChange(customFieldsKey))
if err != nil {
return diag.Errorf("error in resourceNetboxDeviceUpdate[CustomFields]: %v", err)
}
data.CustomFields = cf
}

Expand Down
67 changes: 67 additions & 0 deletions netbox/resource_netbox_device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,73 @@ resource "netbox_device" "test" {
})
}

func TestAccNetboxDevice_CustomFields(t *testing.T) {
testSlug := "device_basic"
testName := testAccGetTestName(testSlug)
testField := strings.ReplaceAll(testAccGetTestName(testSlug), "-", "_")
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDeviceDestroy,
Steps: []resource.TestStep{
{
Config: testAccNetboxDeviceFullDependencies(testName) + fmt.Sprintf(`
resource "netbox_custom_field" "text" {
name = "%[1]s_text"
type = "text"
content_types = ["dcim.device"]
}
resource "netbox_custom_field" "boolean" {
name = "%[1]s_boolean"
type = "boolean"
content_types = ["dcim.device"]
}
resource "netbox_device" "test" {
name = "%[2]s"
tenant_id = netbox_tenant.test.id
role_id = netbox_device_role.test.id
device_type_id = netbox_device_type.test.id
site_id = netbox_site.test.id
custom_fields = jsonencode({
"${netbox_custom_field.text.name}" = "81"
"${netbox_custom_field.boolean.name}" = true
})
}`, testField, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netbox_device.test", "name", testName),
resource.TestCheckResourceAttrPair("netbox_device.test", "tenant_id", "netbox_tenant.test", "id"),
resource.TestCheckResourceAttrPair("netbox_device.test", "role_id", "netbox_device_role.test", "id"),
resource.TestCheckResourceAttrPair("netbox_device.test", "site_id", "netbox_site.test", "id"),
resource.TestCheckResourceAttr("netbox_device.test", "custom_fields", "{\""+testField+"_boolean\":true,\""+testField+"_text\":\"81\"}"),
),
},
{
Config: testAccNetboxDeviceFullDependencies(testName) + fmt.Sprintf(`
resource "netbox_custom_field" "text" {
name = "%[1]s_text"
type = "text"
content_types = ["dcim.device"]
}
resource "netbox_custom_field" "boolean" {
name = "%[1]s_boolean"
type = "boolean"
content_types = ["dcim.device"]
}
resource "netbox_device" "test" {
name = "%[2]s"
tenant_id = netbox_tenant.test.id
role_id = netbox_device_role.test.id
device_type_id = netbox_device_type.test.id
site_id = netbox_site.test.id
}`, testField, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("netbox_device.test", "custom_fields", ""),
),
},
},
})
}

func testAccCheckDeviceDestroy(s *terraform.State) error {
// retrieve the connection established in Provider configuration
conn := testAccProvider.Meta().(*client.NetBoxAPI)
Expand Down

0 comments on commit 55c5ab5

Please sign in to comment.