- IntakeHandler with IntakeOrchestrator/IntakeNetBoxClient/IntakeCatalogUpdater/IntakeWAQ interfaces - Validates 1-3 photos, base64-encodes, calls Analyze, allocates HW-ID - Quick-add mode: confidence >= threshold skips review, creates NetBox record immediately - WAQ enqueue on NetBox failure returns 202 with queued=true - nil WAQ + NetBox down returns 503 - Six unit tests: reject-0, reject-4, high-confidence, low-confidence, quick-add, netbox-down - [Rule 1 - Bug] PatchCustomFields signature changed int -> int64 to match NetBoxOpsClient interface - [Rule 1 - Bug] UpdateCatalogStatus signature changed int -> int64 for consistency with CreateDevice return type
119 lines
3.4 KiB
Go
119 lines
3.4 KiB
Go
package netbox
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
nb "github.com/netbox-community/go-netbox/v4"
|
|
)
|
|
|
|
// ParseCustomFields maps NetBox's map[string]interface{} custom fields response
|
|
// to the typed CustomFields struct. NetBox returns values as interface{} — we
|
|
// perform safe type assertions for each expected field.
|
|
func ParseCustomFields(raw map[string]interface{}) CustomFields {
|
|
cf := CustomFields{}
|
|
if raw == nil {
|
|
return cf
|
|
}
|
|
|
|
if v, ok := raw["hw_id"].(string); ok {
|
|
cf.HWID = v
|
|
}
|
|
if v, ok := raw["catalog_status"].(string); ok {
|
|
cf.CatalogStatus = v
|
|
}
|
|
if v, ok := raw["product_url"].(string); ok {
|
|
cf.ProductURL = v
|
|
}
|
|
if v, ok := raw["firmware_version"].(string); ok {
|
|
cf.FirmwareVersion = v
|
|
}
|
|
if v, ok := raw["test_date"].(string); ok {
|
|
cf.TestDate = v
|
|
}
|
|
if v, ok := raw["test_data"].(string); ok {
|
|
cf.TestData = v
|
|
}
|
|
if v, ok := raw["ai_notes"].(string); ok {
|
|
cf.AINotes = v
|
|
}
|
|
// photo_urls is a multi-value field — NetBox returns []interface{}
|
|
if v, ok := raw["photo_urls"].([]interface{}); ok {
|
|
urls := make([]string, 0, len(v))
|
|
for _, u := range v {
|
|
if s, ok := u.(string); ok {
|
|
urls = append(urls, s)
|
|
}
|
|
}
|
|
cf.PhotoURLs = urls
|
|
}
|
|
|
|
return cf
|
|
}
|
|
|
|
// BuildCustomFieldsPatch constructs the flat map[string]interface{} payload
|
|
// required by NetBox PATCH endpoints. Only include fields that are non-empty
|
|
// to avoid accidentally clearing existing values.
|
|
//
|
|
// NetBox custom field write format differs from read format:
|
|
// - Text/URL/date fields: send string value directly
|
|
// - Selection fields (catalog_status): send the choice value as string
|
|
// - Multi-value fields (photo_urls): send []string directly
|
|
func BuildCustomFieldsPatch(hwID, catalogStatus string, photoURLs []string) map[string]interface{} {
|
|
patch := make(map[string]interface{})
|
|
if hwID != "" {
|
|
patch["hw_id"] = hwID
|
|
}
|
|
if catalogStatus != "" {
|
|
patch["catalog_status"] = catalogStatus
|
|
}
|
|
if len(photoURLs) > 0 {
|
|
patch["photo_urls"] = photoURLs
|
|
}
|
|
return patch
|
|
}
|
|
|
|
// BuildFullCustomFieldsPatch constructs a patch with all custom fields.
|
|
// Use for initial record creation where all fields should be set.
|
|
func BuildFullCustomFieldsPatch(cf CustomFields) map[string]interface{} {
|
|
patch := make(map[string]interface{})
|
|
if cf.HWID != "" {
|
|
patch["hw_id"] = cf.HWID
|
|
}
|
|
if cf.CatalogStatus != "" {
|
|
patch["catalog_status"] = cf.CatalogStatus
|
|
}
|
|
if cf.ProductURL != "" {
|
|
patch["product_url"] = cf.ProductURL
|
|
}
|
|
if cf.FirmwareVersion != "" {
|
|
patch["firmware_version"] = cf.FirmwareVersion
|
|
}
|
|
if cf.TestDate != "" {
|
|
patch["test_date"] = cf.TestDate
|
|
}
|
|
if cf.TestData != "" {
|
|
patch["test_data"] = cf.TestData
|
|
}
|
|
if cf.AINotes != "" {
|
|
patch["ai_notes"] = cf.AINotes
|
|
}
|
|
if len(cf.PhotoURLs) > 0 {
|
|
patch["photo_urls"] = cf.PhotoURLs
|
|
}
|
|
return patch
|
|
}
|
|
|
|
// PatchCustomFields updates the custom fields of a device identified by deviceID.
|
|
// Uses go-netbox v4 PatchedWritableDeviceWithConfigContextRequest to send a partial update.
|
|
func (c *Client) PatchCustomFields(ctx context.Context, deviceID int64, patch map[string]interface{}) error {
|
|
patchReq := nb.PatchedWritableDeviceWithConfigContextRequest{}
|
|
patchReq.SetCustomFields(patch)
|
|
|
|
_, _, err := c.api.DcimAPI.DcimDevicesPartialUpdate(ctx, int32(deviceID)).
|
|
PatchedWritableDeviceWithConfigContextRequest(patchReq).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("patch custom fields for device %d: %w", deviceID, err)
|
|
}
|
|
return nil
|
|
}
|