feat: search multiple NetBox instances with isolated host identities
Release / release (push) Successful in 5m16s

This commit is contained in:
Sebastian Unterschütz committed 2026-10-27 23:14:42 +01:00
1 parent a2a6dd7591
commit 7f53b90266
26 files changed
+1299 -115

No files matched your search

+36 -1
View File
@@ -15,6 +15,7 @@ import (
)
type Client struct {
sources map[string]*Client
baseURL string
token string
tokenVersion int
@@ -38,6 +39,9 @@ func NewClient(baseURL, token string, tokenVersion int) *Client {
// Search queries up to 50 devices and VMs in parallel and merges the results.
// Use SearchOptions to restrict by kind or tag.
func (c *Client) Search(ctx context.Context, query string, opts SearchOptions) ([]HostEntry, error) {
if c.sources != nil {
return c.searchSources(ctx, query, opts, false)
}
if opts.Kind != "" && opts.Kind != "device" && opts.Kind != "vm" {
return nil, fmt.Errorf("invalid host kind %q", opts.Kind)
}
@@ -95,6 +99,9 @@ func (c *Client) Search(ctx context.Context, query string, opts SearchOptions) (
// SearchAll paginates through all matching devices and VMs, fetching every page.
// Intended for cache refresh; use Search for interactive queries.
func (c *Client) SearchAll(ctx context.Context, query string, opts SearchOptions) ([]HostEntry, error) {
if c.sources != nil {
return c.searchSources(ctx, query, opts, true)
}
if opts.Kind != "" && opts.Kind != "device" && opts.Kind != "vm" {
return nil, fmt.Errorf("invalid host kind %q", opts.Kind)
}
@@ -146,6 +153,13 @@ func (c *Client) SearchAll(ctx context.Context, query string, opts SearchOptions
// GetIPs returns all IP addresses assigned to a host, used by resolver strategies
// that need more than just the primary IP.
func (c *Client) GetIPs(ctx context.Context, entry HostEntry) ([]string, error) {
if c.sources != nil {
source, host, err := c.sourceForHost(entry)
if err != nil {
return nil, err
}
return source.GetIPs(ctx, host)
}
var apiURL string
switch entry.Kind {
case "device":
@@ -170,6 +184,9 @@ func (c *Client) GetIPs(ctx context.Context, entry HostEntry) ([]string, error)
// GetIPsWithFilter calls /api/ipam/ip-addresses/ with arbitrary filter query parameters.
func (c *Client) GetIPsWithFilter(ctx context.Context, filterParams string) ([]string, error) {
if c.sources != nil {
return nil, fmt.Errorf("IP filters require a host instance; use GetIPsWithFilterForHost")
}
apiURL := fmt.Sprintf("%s/api/ipam/ip-addresses/?%s&limit=100", c.baseURL, filterParams)
var resp netboxIPListResponse
if err := c.get(ctx, apiURL, &resp); err != nil {
@@ -360,12 +377,13 @@ func (c *Client) FindHost(ctx context.Context, name, kind string) (HostEntry, er
return HostEntry{}, fmt.Errorf("invalid kind %q", kind)
}
entries, err := c.SearchAll(ctx, name, SearchOptions{Kind: kind})
_, hostname := SplitTarget(name)
if err != nil {
return HostEntry{}, err
}
var matches []HostEntry
for _, e := range entries {
if strings.EqualFold(e.Name, name) {
if strings.EqualFold(e.Name, hostname) {
matches = append(matches, e)
}
}
@@ -373,6 +391,9 @@ func (c *Client) FindHost(ctx context.Context, name, kind string) (HostEntry, er
return HostEntry{}, fmt.Errorf("%w: %q", ErrHostNotFound, name)
}
if len(matches) != 1 {
if c.sources != nil {
return HostEntry{}, fmt.Errorf("host %q: found %d exact matches; use instance/hostname and --kind to select a unique host", name, len(matches))
}
return HostEntry{}, fmt.Errorf("host %q: found %d exact matches; use --kind or a unique hostname", name, len(matches))
}
return matches[0], nil
@@ -395,6 +416,13 @@ func (c *Client) hostURL(host HostEntry) (string, error) {
}
func (c *Client) GetComments(ctx context.Context, host HostEntry) (string, string, error) {
if c.sources != nil {
source, host, err := c.sourceForHost(host)
if err != nil {
return "", "", err
}
return source.GetComments(ctx, host)
}
u, err := c.hostURL(host)
if err != nil {
return "", "", err
@@ -407,6 +435,13 @@ func (c *Client) GetComments(ctx context.Context, host HostEntry) (string, strin
}
func (c *Client) SetComments(ctx context.Context, host HostEntry, comments, etag string) error {
if c.sources != nil {
source, host, err := c.sourceForHost(host)
if err != nil {
return err
}
return source.SetComments(ctx, host, comments, etag)
}
u, err := c.hostURL(host)
if err != nil {
return err