- Add internal/netbox/types.go with Device and CustomFields domain types - Add internal/netbox/client.go with NewClient, Ping, ListDevices, GetDevice - Add client_test.go with validation unit tests and skippable integration tests - go-netbox v4.3.0 dependency added
87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
package netbox
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
nb "github.com/netbox-community/go-netbox/v4"
|
|
)
|
|
|
|
// Client wraps go-netbox v4 APIClient with typed HWLab methods.
|
|
// All NetBox calls MUST go through this Client — no direct go-netbox calls in other packages.
|
|
type Client struct {
|
|
api *nb.APIClient
|
|
url string
|
|
}
|
|
|
|
// NewClient creates a configured NetBox client. Returns error if url or token is empty.
|
|
func NewClient(url, token string) (*Client, error) {
|
|
if url == "" {
|
|
return nil, errors.New("netbox url is required")
|
|
}
|
|
if token == "" {
|
|
return nil, errors.New("netbox token is required")
|
|
}
|
|
// Note: NewAPIClientFor accepts the base URL WITHOUT /api suffix.
|
|
// Strip trailing /api if present to avoid double-appending.
|
|
baseURL := url
|
|
if len(baseURL) > 4 && baseURL[len(baseURL)-4:] == "/api" {
|
|
baseURL = baseURL[:len(baseURL)-4]
|
|
}
|
|
api := nb.NewAPIClientFor(baseURL, token)
|
|
return &Client{api: api, url: url}, nil
|
|
}
|
|
|
|
// Ping verifies the NetBox API is reachable by making a lightweight list call.
|
|
// Returns nil on success.
|
|
func (c *Client) Ping(ctx context.Context) error {
|
|
_, resp, err := c.api.DcimAPI.DcimDevicesList(ctx).Limit(1).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("netbox ping: %w", err)
|
|
}
|
|
if resp.StatusCode >= 500 {
|
|
return fmt.Errorf("netbox ping: server error %d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListDevices returns up to limit devices from NetBox.
|
|
func (c *Client) ListDevices(ctx context.Context, limit int) ([]Device, error) {
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
res, _, err := c.api.DcimAPI.DcimDevicesList(ctx).Limit(int32(limit)).Execute()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list devices: %w", err)
|
|
}
|
|
devices := make([]Device, 0, len(res.Results))
|
|
for _, d := range res.Results {
|
|
devices = append(devices, deviceFromNetBox(d))
|
|
}
|
|
return devices, nil
|
|
}
|
|
|
|
// GetDevice retrieves a single device by its NetBox internal ID.
|
|
func (c *Client) GetDevice(ctx context.Context, id int) (*Device, error) {
|
|
d, _, err := c.api.DcimAPI.DcimDevicesRetrieve(ctx, int32(id)).Execute()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get device %d: %w", id, err)
|
|
}
|
|
dev := deviceFromNetBox(*d)
|
|
return &dev, nil
|
|
}
|
|
|
|
// deviceFromNetBox maps a go-netbox DeviceWithConfigContext to our Device type.
|
|
// Custom fields are mapped separately via ParseCustomFields.
|
|
func deviceFromNetBox(d nb.DeviceWithConfigContext) Device {
|
|
dev := Device{
|
|
ID: int(d.GetId()),
|
|
Name: d.GetName(),
|
|
}
|
|
if tag := d.GetAssetTag(); tag != "" {
|
|
dev.AssetTag = tag
|
|
}
|
|
dev.CustomFields = ParseCustomFields(d.GetCustomFields())
|
|
return dev
|
|
}
|