docs(01-03): complete NetBox provisioning plan summary

This commit is contained in:
Mikkel Georgsen 2026-04-10 05:22:44 +00:00
parent d1192c3380
commit 52e3e9cd9c

View file

@ -0,0 +1,111 @@
---
phase: 01-foundation
plan: "03"
subsystem: netbox
tags: [go, netbox, provisioning, custom-fields, dcim, tdd]
dependency_graph:
requires: [internal/netbox.Client]
provides: [internal/netbox.Provision, internal/netbox.ProvisionCustomFields, internal/netbox.ProvisionLocationHierarchy]
affects: [internal/quality, internal/hwid, scripts/provision-netbox.go]
tech_stack:
added: []
patterns: [idempotent-provisioning, check-before-create, tdd-red-green]
key_files:
created:
- internal/netbox/provision.go
- internal/netbox/provision_test.go
- scripts/provision-netbox.go
modified: []
decisions:
- id: NB-PROV-01
summary: "Use PatchedWritableCustomFieldRequestType(spec.Type) to convert string type to go-netbox enum — no separate mapping table needed"
- id: NB-PROV-02
summary: "Use Int32AsDeviceWithConfigContextRequestSite/Location helpers to pass IDs in oneOf union types for location/rack creation"
- id: NB-PROV-03
summary: "photo_urls stored as text (comma-separated) not multiselect — avoids NetBox multi-object custom field complexity"
metrics:
duration: "~20 minutes"
completed: "2026-04-10T06:15:00Z"
tasks_completed: 2
files_created: 3
files_modified: 0
---
# Phase 1 Plan 03: NetBox Provisioning Summary
**One-liner:** Idempotent NetBox provisioner for 8 HWLab custom fields and Site→Location→Rack hierarchy using go-netbox v4 typed API.
## What Was Built
### `internal/netbox/provision.go`
Provisioning functions implementing check-before-create idempotency:
- `CustomFieldSpec` — typed struct describing a NetBox custom field to provision
- `hwlabCustomFields` — canonical slice of all 8 HWLab custom field specs
- `customFieldSpec(name)` — lookup by name (used in tests)
- `ProvisionCustomFields(ctx)` — creates missing fields, skips existing; returns count created
- `customFieldExists(ctx, name)` — checks via `ExtrasCustomFieldsList().Name([]string{name})`
- `createCustomField(ctx, spec)` — uses `WritableCustomFieldRequest` with `PatchedWritableCustomFieldRequestType` enum
- `ProvisionLocationHierarchy(ctx)` — creates Site "Homelab" → Location "Lab Bench" → Rack "Primary Rack"
- `ensureSite` / `ensureLocation` / `ensureRack` — check-then-create with proper go-netbox v4 types
- `CheckNetBoxInventoryPlugin(ctx)` — GET /api/plugins/ to detect netbox-inventory plugin
- `Provision(ctx)` — top-level entry point calling both custom fields and location hierarchy
### `internal/netbox/provision_test.go`
4 unit tests, all passing:
- `TestCustomFieldSpec` — hw_id spec has type=text and ObjectTypes contains dcim.device
- `TestCustomFieldSpecCatalogStatus` — catalog_status is type=text (not selection)
- `TestCustomFieldSpecPhotoURLs` — photo_urls has a non-empty description
- `TestAllEightFieldsDefined` — all 8 expected field names present, len=8
### `scripts/provision-netbox.go`
Standalone CLI with `//go:build ignore` tag:
- Loads .env via `godotenv.Load()`
- Reads `HWLAB_NETBOX_URL` and `HWLAB_NETBOX_TOKEN`
- Calls `client.Provision(ctx)` then `client.CheckNetBoxInventoryPlugin(ctx)`
- Usage: `go run scripts/provision-netbox.go`
## Provisioning Status
**Ran against live NetBox:** No — `HWLAB_NETBOX_TOKEN=homelab-netbox-api-token-2024` is a placeholder (22 chars, not a real NetBox token). Integration tests correctly skip.
To provision the live NetBox instance (LXC 130):
1. Obtain a real API token from NetBox UI at http://10.5.0.130:8000/
2. Set `HWLAB_NETBOX_TOKEN=<40-char-token>` in `.env`
3. Run: `go run scripts/provision-netbox.go`
## go-netbox v4 API Notes
- **Custom field type enum:** `PatchedWritableCustomFieldRequestType` (shared between `WritableCustomFieldRequest` and `PatchedWritableCustomFieldRequest`) — casting string directly works: `nb.PatchedWritableCustomFieldRequestType("text")`
- **Site/Location/Rack oneOf site field:** `DeviceWithConfigContextRequestSite` is a oneOf union type; use `nb.Int32AsDeviceWithConfigContextRequestSite(&siteID)` to pass an integer ID
- **Location oneOf site field:** Same pattern — `DeviceWithConfigContextRequestSite` used by `WritableLocationRequest`
- **Rack location field:** `NullableDeviceWithConfigContextRequestLocation` — use `nb.Int32AsDeviceWithConfigContextRequestLocation(&locationID)` then `req.SetLocation(loc)`
- **DcimRacksList Name filter:** `DcimRacksList(ctx).Name([]string{name})` works correctly (same pattern as custom fields)
- **GetId()** returns `int32` on Site, Location, and Rack models — consistent
## Deviations from Plan
None — plan executed exactly as written. All stub pseudocode was replaced with real go-netbox v4 API calls. The `CheckNetBoxInventoryPlugin` function uses the HTTP client from `c.api.GetConfig().HTTPClient` to avoid importing net/http separately, with the base URL derived by stripping the `/api` suffix from `c.url`.
## Known Stubs
None — all functions are fully implemented. Live provisioning requires a real NetBox token (currently placeholder in .env).
## Self-Check
Files created:
- internal/netbox/provision.go: FOUND
- internal/netbox/provision_test.go: FOUND
- scripts/provision-netbox.go: FOUND
Commits:
- 07130d2 — test(01-03): add failing tests for custom field spec and provisioning
- 9b4cc9a — feat(01-03): implement custom field provisioning with go-netbox v4
- 49a729a — feat(01-03): add location hierarchy provisioning and provision CLI script
`go build ./...`: PASS
`go vet ./...`: PASS
`go test ./internal/netbox/... -run "TestCustomFieldSpec|TestAllEight"`: PASS (4 tests)
Integration tests: SKIP (placeholder token — correct behavior)
## Self-Check: PASSED