Skip to content

Commit 2ad4cf9

Browse files
committed
Fix state drift in netbox_prefix custom fields by normalizing values, handling object references, and suppressing nil/empty diffs.
Signed-off-by: Lorenzo Buitizon <the.keikun@gmail.com>
1 parent 74fb89e commit 2ad4cf9

30 files changed

+74
-40
lines changed

netbox/client.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package netbox
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
"io"
57
"net/http"
68
"time"
79

810
netboxclient "github.com/fbreckle/go-netbox/netbox/client"
11+
"github.com/go-openapi/runtime"
912
httptransport "github.com/go-openapi/runtime/client"
1013
"github.com/goware/urlx"
1114
log "github.com/sirupsen/logrus"
@@ -83,6 +86,16 @@ func (cfg *Config) Client() (*netboxclient.NetBoxAPI, error) {
8386
transport := httptransport.NewWithClient(parsedURL.Host, parsedURL.Path+netboxclient.DefaultBasePath, desiredRuntimeClientSchemes, httpClient)
8487
transport.DefaultAuthentication = httptransport.APIKeyAuth("Authorization", "header", fmt.Sprintf("Token %v", cfg.APIToken))
8588
transport.SetLogger(log.StandardLogger())
89+
90+
// Configure the transport to handle interface{} values properly
91+
// This helps prevent TextConsumer issues with interface{} types
92+
transport.Consumers["application/json"] = runtime.ConsumerFunc(func(reader io.Reader, data interface{}) error {
93+
dec := json.NewDecoder(reader)
94+
// Use json.Number to handle numeric values that might be interface{}
95+
dec.UseNumber()
96+
return dec.Decode(data)
97+
})
98+
8699
netboxClient := netboxclient.New(transport, nil)
87100

88101
return netboxClient, nil

netbox/custom_fields.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ var customFieldsSchema = &schema.Schema{
2020
Type: schema.TypeString,
2121
Default: nil,
2222
},
23+
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
24+
if old == "" && new == "0" {
25+
return true // treat empty and "0" as equal? Wait, for maps it's different
26+
}
27+
// For maps, old and new are JSON strings
28+
if old == "{}" && new == "" {
29+
return true
30+
}
31+
if old == "" && new == "{}" {
32+
return true
33+
}
34+
return false
35+
},
2336
}
2437

2538
func getCustomFields(cf interface{}) map[string]interface{} {
@@ -31,7 +44,8 @@ func getCustomFields(cf interface{}) map[string]interface{} {
3144
}
3245

3346
// flattenCustomFields converts custom fields to a map where all values are strings.
34-
// Complex nested objects (like IP address references) are converted to JSON strings.
47+
// Object references (maps with "id" field) are converted to just their ID string.
48+
// Other complex types are converted to JSON strings.
3549
func flattenCustomFields(cf interface{}) map[string]interface{} {
3650
cfm, ok := cf.(map[string]interface{})
3751
if !ok || len(cfm) == 0 {
@@ -51,8 +65,21 @@ func flattenCustomFields(cf interface{}) map[string]interface{} {
5165
result[key] = v
5266
case float64, int, int64, bool:
5367
result[key] = fmt.Sprintf("%v", v)
68+
case map[string]interface{}:
69+
// Check if this is an object reference with an ID
70+
if id, hasID := v["id"]; hasID {
71+
// Extract just the ID for object references
72+
result[key] = fmt.Sprintf("%v", id)
73+
} else {
74+
// For other complex objects without ID, convert to JSON
75+
if jsonBytes, err := json.Marshal(value); err == nil {
76+
result[key] = string(jsonBytes)
77+
} else {
78+
result[key] = fmt.Sprintf("%v", value)
79+
}
80+
}
5481
default:
55-
// For complex types (maps, arrays, objects), convert to JSON string
82+
// For other complex types (arrays, etc.), convert to JSON string
5683
if jsonBytes, err := json.Marshal(value); err == nil {
5784
result[key] = string(jsonBytes)
5885
} else {

netbox/custom_fields_test.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func TestFlattenCustomFields(t *testing.T) {
7676
},
7777
},
7878
expected: map[string]interface{}{
79-
"gateway": `{"address":"10.21.10.254/24","display":"10.21.10.254/24","family":{"label":"IPv4","value":4},"id":9,"url":"https://netbox.example.com/api/ipam/ip-addresses/9/"}`,
79+
"gateway": "9",
8080
},
8181
},
8282
{
@@ -191,21 +191,15 @@ func TestFlattenCustomFields_ComplexRealWorldExample(t *testing.T) {
191191
t.Fatal("expected non-nil result")
192192
}
193193

194-
// Check that gateway is a JSON string
194+
// Check that gateway is extracted to just the ID string
195195
gateway, ok := result["gateway"].(string)
196196
if !ok {
197197
t.Errorf("expected gateway to be a string, got %T", result["gateway"])
198198
}
199199

200-
// Verify we can parse the gateway JSON
201-
var gatewayObj map[string]interface{}
202-
if err := json.Unmarshal([]byte(gateway), &gatewayObj); err != nil {
203-
t.Errorf("failed to parse gateway JSON: %v", err)
204-
}
205-
206-
// Verify the gateway object has expected fields
207-
if gatewayObj["address"] != "10.21.10.254/24" {
208-
t.Errorf("expected address 10.21.10.254/24, got %v", gatewayObj["address"])
200+
// Verify the gateway is just the ID
201+
if gateway != "9" {
202+
t.Errorf("expected gateway=9, got %v", gateway)
209203
}
210204

211205
// Check simple fields

netbox/resource_netbox_cable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func resourceNetboxCableRead(d *schema.ResourceData, m interface{}) error {
181181
d.Set("description", cable.Description)
182182
d.Set("comments", cable.Comments)
183183

184-
cf := getCustomFields(res.GetPayload().CustomFields)
184+
cf := flattenCustomFields(res.GetPayload().CustomFields)
185185
if cf != nil {
186186
d.Set(customFieldsKey, cf)
187187
}

netbox/resource_netbox_circuit_termination.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ func resourceNetboxCircuitTerminationRead(d *schema.ResourceData, m interface{})
221221

222222
api.readTags(d, term.Tags)
223223

224-
cf := getCustomFields(term.CustomFields)
224+
cf := flattenCustomFields(term.CustomFields)
225225
if cf != nil {
226226
d.Set(customFieldsKey, cf)
227227
}

netbox/resource_netbox_device.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ func resourceNetboxDeviceRead(ctx context.Context, d *schema.ResourceData, m int
353353
d.Set("config_template_id", nil)
354354
}
355355

356-
cf := getCustomFields(res.GetPayload().CustomFields)
356+
cf := flattenCustomFields(res.GetPayload().CustomFields)
357357
if cf != nil {
358358
d.Set(customFieldsKey, cf)
359359
}

netbox/resource_netbox_device_bay.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func resourceNetboxDeviceBayRead(d *schema.ResourceData, m interface{}) error {
115115
}
116116
d.Set("description", deviceBay.Description)
117117

118-
cf := getCustomFields(res.GetPayload().CustomFields)
118+
cf := flattenCustomFields(res.GetPayload().CustomFields)
119119
if cf != nil {
120120
d.Set(customFieldsKey, cf)
121121
}

netbox/resource_netbox_device_console_port.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func resourceNetboxDeviceConsolePortRead(d *schema.ResourceData, m interface{})
150150
d.Set("description", consolePort.Description)
151151
d.Set("mark_connected", consolePort.MarkConnected)
152152

153-
cf := getCustomFields(res.GetPayload().CustomFields)
153+
cf := flattenCustomFields(res.GetPayload().CustomFields)
154154
if cf != nil {
155155
d.Set(customFieldsKey, cf)
156156
}

netbox/resource_netbox_device_console_server_port.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func resourceNetboxDeviceConsoleServerPortRead(d *schema.ResourceData, m interfa
150150
d.Set("description", consoleServerPort.Description)
151151
d.Set("mark_connected", consoleServerPort.MarkConnected)
152152

153-
cf := getCustomFields(res.GetPayload().CustomFields)
153+
cf := flattenCustomFields(res.GetPayload().CustomFields)
154154
if cf != nil {
155155
d.Set(customFieldsKey, cf)
156156
}

netbox/resource_netbox_device_front_port.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ func resourceNetboxDeviceFrontPortRead(d *schema.ResourceData, m interface{}) er
162162
d.Set("description", frontPort.Description)
163163
d.Set("mark_connected", frontPort.MarkConnected)
164164

165-
cf := getCustomFields(res.GetPayload().CustomFields)
165+
cf := flattenCustomFields(res.GetPayload().CustomFields)
166166
if cf != nil {
167167
d.Set(customFieldsKey, cf)
168168
}

0 commit comments

Comments
 (0)