homelabby/internal/queue/handler.go
Mikkel Georgsen 73eab561cf feat(02-02): WAQ real NetBox op handler replacing NoOpHandler
- NewNetBoxOpHandler routes create_device → CreateDevice, patch_custom_fields → PatchCustomFields
- NetBoxOpsClient interface enables test injection without importing netbox package
- Unknown op types return error (re-queued by worker, not silently dropped — T-02-08)
- JSON payloads decoded into typed structs (T-02-07 tampering mitigation)
- 6 handler tests all passing (TDD green); NoOpHandler untouched in worker.go
2026-04-10 05:48:30 +00:00

66 lines
2.3 KiB
Go

package queue
import (
"context"
"encoding/json"
"fmt"
)
// Op type string constants for WAQ operations.
const (
OpNetBoxCreateDevice = "netbox.create_device"
OpNetBoxPatchCustomFields = "netbox.patch_custom_fields"
)
// CreateDevicePayload is the JSON payload for OpNetBoxCreateDevice ops.
type CreateDevicePayload struct {
Name string `json:"name"`
AssetTag string `json:"asset_tag"`
DeviceTypeID int32 `json:"device_type_id"`
RoleID int32 `json:"role_id"`
SiteID int32 `json:"site_id"`
}
// PatchCustomFieldsPayload is the JSON payload for OpNetBoxPatchCustomFields ops.
type PatchCustomFieldsPayload struct {
DeviceID int64 `json:"device_id"`
Patch map[string]interface{} `json:"patch"`
}
// NetBoxOpsClient is the subset of netbox.Client that the WAQ handler needs.
// Using an interface here allows tests to inject a mock without importing netbox.
type NetBoxOpsClient interface {
CreateDevice(ctx context.Context, name, assetTag string, deviceTypeID, roleID, siteID int32) (int64, error)
PatchCustomFields(ctx context.Context, deviceID int64, patch map[string]interface{}) error
}
// NewNetBoxOpHandler returns an OpHandler that processes netbox WAQ operations.
// Pass a *netbox.Client as the client argument (it satisfies NetBoxOpsClient).
//
// Routing:
// - OpNetBoxCreateDevice → client.CreateDevice
// - OpNetBoxPatchCustomFields → client.PatchCustomFields
// - Unknown op type → error (op will be re-queued, not silently dropped)
func NewNetBoxOpHandler(client NetBoxOpsClient) OpHandler {
return func(ctx context.Context, op PendingOp) error {
switch op.Type {
case OpNetBoxCreateDevice:
var p CreateDevicePayload
if err := json.Unmarshal(op.Payload, &p); err != nil {
return fmt.Errorf("decode create_device payload: %w", err)
}
_, err := client.CreateDevice(ctx, p.Name, p.AssetTag, p.DeviceTypeID, p.RoleID, p.SiteID)
return err
case OpNetBoxPatchCustomFields:
var p PatchCustomFieldsPayload
if err := json.Unmarshal(op.Payload, &p); err != nil {
return fmt.Errorf("decode patch_custom_fields payload: %w", err)
}
return client.PatchCustomFields(ctx, p.DeviceID, p.Patch)
default:
return fmt.Errorf("unknown op type: %q", op.Type)
}
}
}