feat(02-01): install go-openai and add CreateDevice to NetBox client

- go get github.com/sashabaranov/go-openai v1.41.2
- Add CreateDevice(ctx, name, assetTag, deviceTypeID, roleID, siteID) → (int64, error)
- Add DeleteDevice(ctx, id) for test cleanup
- Use Int32As* oneOf helpers for go-netbox v4 FK fields
- TestCreateDeviceValidation PASS; TestCreateDeviceLive SKIP (no live token)
This commit is contained in:
Mikkel Georgsen 2026-04-10 05:42:51 +00:00
parent 7bebe2ed93
commit 6040ecc3cc
4 changed files with 94 additions and 0 deletions

1
go.mod
View file

@ -18,6 +18,7 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/redis/go-redis/v9 v9.18.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sashabaranov/go-openai v1.41.2 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect

2
go.sum
View file

@ -37,6 +37,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=

View file

@ -72,6 +72,41 @@ func (c *Client) GetDevice(ctx context.Context, id int) (*Device, error) {
return &dev, nil
}
// CreateDevice creates a new device in NetBox with the given name and asset tag.
// deviceTypeID, roleID, and siteID must be valid NetBox IDs (pre-existing objects).
// Returns the new device's NetBox ID or error.
func (c *Client) CreateDevice(ctx context.Context, name, assetTag string, deviceTypeID, roleID, siteID int32) (int64, error) {
if name == "" {
return 0, fmt.Errorf("device name must not be empty")
}
// go-netbox v4 uses oneOf wrapper types for FK fields — use the Int32As* helpers.
dtID := nb.Int32AsDeviceBayTemplateRequestDeviceType(&deviceTypeID)
role := nb.Int32AsDeviceWithConfigContextRequestRole(&roleID)
site := nb.Int32AsDeviceWithConfigContextRequestSite(&siteID)
req := nb.NewWritableDeviceWithConfigContextRequest(dtID, role, site)
req.SetName(name)
if assetTag != "" {
req.SetAssetTag(assetTag)
}
result, _, err := c.api.DcimAPI.DcimDevicesCreate(ctx).
WritableDeviceWithConfigContextRequest(*req).Execute()
if err != nil {
return 0, fmt.Errorf("CreateDevice %q: %w", name, err)
}
return int64(result.GetId()), nil
}
// DeleteDevice removes a device from NetBox by its internal ID.
// Used primarily for test cleanup after CreateDevice integration tests.
func (c *Client) DeleteDevice(ctx context.Context, id int64) error {
_, err := c.api.DcimAPI.DcimDevicesDestroy(ctx, int32(id)).Execute()
if err != nil {
return fmt.Errorf("DeleteDevice %d: %w", id, err)
}
return nil
}
// deviceFromNetBox maps a go-netbox DeviceWithConfigContext to our Device type.
// Custom fields are mapped separately via ParseCustomFields.
func deviceFromNetBox(d nb.DeviceWithConfigContext) Device {

View file

@ -2,6 +2,7 @@ package netbox_test
import (
"context"
"fmt"
"os"
"testing"
@ -60,3 +61,58 @@ func TestListDevicesLive(t *testing.T) {
t.Logf("found %d devices in NetBox", len(devices))
// Not asserting count — NetBox may be empty; just assert no error
}
// TestCreateDeviceValidation verifies that CreateDevice rejects an empty name
// without making any NetBox API call.
func TestCreateDeviceValidation(t *testing.T) {
c, err := netbox.NewClient("http://10.5.0.130:8000/api", "sometoken")
if err != nil {
t.Fatalf("NewClient: %v", err)
}
_, err = c.CreateDevice(context.Background(), "", "HW-00001", 1, 1, 1)
if err == nil {
t.Error("expected error for empty device name")
}
}
// TestCreateDeviceLive creates a real device in NetBox and deletes it as cleanup.
// Requires HWLAB_NETBOX_TOKEN (40 chars) and HWLAB_TEST_SITE_ID to be set.
func TestCreateDeviceLive(t *testing.T) {
token := integrationToken(t)
siteIDStr := os.Getenv("HWLAB_TEST_SITE_ID")
if siteIDStr == "" {
t.Skip("HWLAB_TEST_SITE_ID not set — skipping live CreateDevice test")
}
// Parse site ID
var siteID int32
if _, err := fmt.Sscanf(siteIDStr, "%d", &siteID); err != nil {
t.Fatalf("HWLAB_TEST_SITE_ID is not a valid int: %v", err)
}
// These must exist in the NetBox instance used for integration testing.
// Device type 1 and role 1 are typically pre-provisioned.
const (
testDeviceTypeID int32 = 1
testRoleID int32 = 1
)
c, err := netbox.NewClient("http://10.5.0.130:8000/api", token)
if err != nil {
t.Fatalf("NewClient: %v", err)
}
id, err := c.CreateDevice(context.Background(), "hwlab-test-device", "HW-TEST-01", testDeviceTypeID, testRoleID, siteID)
if err != nil {
t.Fatalf("CreateDevice: %v", err)
}
if id <= 0 {
t.Errorf("expected positive device ID, got %d", id)
}
t.Logf("created device id=%d — will clean up", id)
// Cleanup: delete the test device
if err := c.DeleteDevice(context.Background(), id); err != nil {
t.Logf("cleanup warning: DeleteDevice(%d): %v", id, err)
}
}