homelabby/.planning/phases/01-foundation/01-02-SUMMARY.md
Mikkel Georgsen f15c0c7ea7 docs(01-02): complete NetBox client plan summary
- SUMMARY.md for plan 01-02 with go-netbox v4 API notes and deviation log
2026-04-10 05:18:13 +00:00

5.6 KiB

phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
01-foundation 02 netbox
go
netbox
client
custom-fields
tdd
requires provides affects
internal/netbox.Client
internal/netbox.ParseCustomFields
internal/netbox.BuildCustomFieldsPatch
internal/quality
internal/hwid
internal/waq
added patterns
github.com/netbox-community/go-netbox/v4 v4.3.0
gopkg.in/validator.v2 v2.0.1
wrapper-client
typed-custom-fields
integration-skip-guard
created modified
internal/netbox/client.go
internal/netbox/types.go
internal/netbox/client_test.go
internal/netbox/custom_fields.go
internal/netbox/custom_fields_test.go
go.mod
go.sum
id summary
NB-CLIENT-01 Strip /api suffix from base URL in NewClient to avoid go-netbox double-appending /api
id summary
NB-CLIENT-02 PatchedWritableDeviceWithConfigContextRequest.SetCustomFields(map) is the correct go-netbox v4 PATCH pattern
id summary
NB-CF-01 BuildCustomFieldsPatch only includes non-empty fields — avoids clearing existing NetBox values on partial update
duration completed tasks_completed files_created files_modified
~15 minutes 2026-04-10T05:17:22Z 2 5 2

Phase 1 Plan 02: NetBox Client Package Summary

One-liner: go-netbox v4 typed client wrapper with safe custom field read/write helpers and integration-skip guards for placeholder tokens.

What Was Built

internal/netbox/types.go

Domain types that decouple the rest of HWLab from go-netbox generated types:

  • Device — HWLab inventory item (ID, Name, AssetTag, CustomFields, Created, LastUpdated)
  • CustomFields — typed struct over NetBox's map[string]interface{} response (HWID, CatalogStatus, ProductURL, FirmwareVersion, TestDate, TestData, AINotes, PhotoURLs)

internal/netbox/client.go

Client struct wrapping *nb.APIClient with methods:

  • NewClient(url, token string) (*Client, error) — validates inputs, strips trailing /api from URL before passing to nb.NewAPIClientFor
  • Ping(ctx) — lightweight connectivity check via DcimDevicesList(limit=1)
  • ListDevices(ctx, limit) — returns []Device mapped via deviceFromNetBox
  • GetDevice(ctx, id) — retrieves single device by NetBox internal ID

internal/netbox/custom_fields.go

  • ParseCustomFields(raw map[string]interface{}) CustomFields — safe type assertions for all 8 HWLab custom field keys; handles nil input; handles []interface{} photo_urls
  • BuildCustomFieldsPatch(hwID, catalogStatus string, photoURLs []string) map[string]interface{} — selective patch (only non-empty fields included)
  • BuildFullCustomFieldsPatch(cf CustomFields) map[string]interface{} — full patch for initial record creation
  • (c *Client) PatchCustomFields(ctx, deviceID, patch) — uses nb.PatchedWritableDeviceWithConfigContextRequest.SetCustomFields() then .Execute()

Integration Test Status

Integration tests (TestPingLive, TestListDevicesLive, TestPatchCustomFieldsRoundTrip) SKIPPED because HWLAB_NETBOX_TOKEN=homelab-netbox-api-token-2024 is a placeholder (22 chars, not 40).

Skip guard: if len(token) != 40 { t.Skip(...) } — correct behavior, no errors.

To run integration tests: set HWLAB_NETBOX_TOKEN to a real 40-char hex token from NetBox UI, and HWLAB_TEST_DEVICE_ID to an existing device ID for round-trip test.

go-netbox v4 API Notes

  • Struct confirmed: nb.PatchedWritableDeviceWithConfigContextRequest with SetCustomFields(map[string]interface{}) method
  • PATCH pattern: c.api.DcimAPI.DcimDevicesPartialUpdate(ctx, int32(id)).PatchedWritableDeviceWithConfigContextRequest(req).Execute()
  • URL handling: NewAPIClientFor appends /api internally — must strip trailing /api from the URL in the config to avoid http://host:8000/api/api/
  • AssetTag type: NullableString on DeviceWithConfigContextGetAssetTag() returns empty string when null (safe)
  • CustomFields on read: map[string]interface{} — requires type assertions per field

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] custom_fields.go written in Task 1 to unblock compilation

  • Found during: Task 1 GREEN phase
  • Issue: client.go calls ParseCustomFields which was planned for Task 2; package would not compile without it
  • Fix: Wrote custom_fields.go as part of Task 1 implementation to make go test work; Task 2 then added only custom_fields_test.go
  • Files modified: internal/netbox/custom_fields.go (created during Task 1 pass)
  • Commit: 9f3ed9f (Task 1 commit includes custom_fields.go)

2. [Rule 2 - Missing] Added HWLAB_TEST_DEVICE_ID guard to round-trip integration test

  • Found during: Task 2 test design
  • Issue: Round-trip test needs an existing device ID — hardcoding one would fail on fresh NetBox installs
  • Fix: Added second t.Skip() guard when HWLAB_TEST_DEVICE_ID env var is absent
  • Files modified: internal/netbox/custom_fields_test.go

Self-Check

Files created:

  • internal/netbox/client.go: FOUND
  • internal/netbox/types.go: FOUND
  • internal/netbox/client_test.go: FOUND
  • internal/netbox/custom_fields.go: FOUND
  • internal/netbox/custom_fields_test.go: FOUND

Commits:

  • 9f3ed9f — Task 1 (NetBox client wrapper)
  • 17a2eb6 — Task 2 (custom field wrappers)

go build ./...: PASS go test ./internal/netbox/... -run TestNewClientValidation: PASS go test ./internal/netbox/... -run "TestParseCustomFields|TestBuildCustomFields": PASS (5 tests) Integration tests: SKIP (placeholder token — correct behavior)

Self-Check: PASSED