feat: search multiple NetBox instances with isolated host identities
Release / release (push) Successful in 5m16s
Release / release (push) Successful in 5m16s
This commit is contained in:
1 parent
a2a6dd7591
commit
7f53b90266
26 files changed
+1299
-115
No files matched your search
@@ -0,0 +1,113 @@
|
||||
package netbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Source binds a name to its own API client and credentials.
|
||||
type Source struct {
|
||||
Name string
|
||||
URL string
|
||||
Token string
|
||||
TokenVersion int
|
||||
}
|
||||
|
||||
func NewMultiClient(sources []Source) *Client {
|
||||
c := &Client{sources: make(map[string]*Client)}
|
||||
for _, source := range sources {
|
||||
c.sources[strings.ToLower(source.Name)] = NewClient(source.URL, source.Token, source.TokenVersion)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) searchSources(ctx context.Context, query string, opts SearchOptions, all bool) ([]HostEntry, error) {
|
||||
requested, query := SplitTarget(query)
|
||||
if requested != "" {
|
||||
if _, ok := c.sources[requested]; !ok {
|
||||
return nil, fmt.Errorf("unknown NetBox instance %q", requested)
|
||||
}
|
||||
}
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
var entries []HostEntry
|
||||
var errs []error
|
||||
started := 0
|
||||
for name, client := range c.sources {
|
||||
if requested != "" && name != requested {
|
||||
continue
|
||||
}
|
||||
started++
|
||||
wg.Add(1)
|
||||
go func(name string, client *Client) {
|
||||
defer wg.Done()
|
||||
var found []HostEntry
|
||||
var err error
|
||||
if all {
|
||||
found, err = client.SearchAll(ctx, query, opts)
|
||||
} else {
|
||||
found, err = client.Search(ctx, query, opts)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("NetBox %s: %w", name, err))
|
||||
return
|
||||
}
|
||||
for i := range found {
|
||||
found[i].Source = name
|
||||
}
|
||||
entries = append(entries, found...)
|
||||
}(name, client)
|
||||
}
|
||||
wg.Wait()
|
||||
// Full lookups must see every source to rule out ambiguous hostnames.
|
||||
if len(errs) > 0 && (all || len(errs) == started) {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].Target() < entries[j].Target() })
|
||||
return entries, errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Client) sourceForHost(host HostEntry) (*Client, HostEntry, error) {
|
||||
prefix, name := SplitTarget(host.Name)
|
||||
if prefix != "" {
|
||||
if host.Source != "" && !strings.EqualFold(prefix, host.Source) {
|
||||
return nil, host, fmt.Errorf("conflicting NetBox instance for %q", host.Name)
|
||||
}
|
||||
host.Source, host.Name = prefix, name
|
||||
}
|
||||
if c.sources == nil {
|
||||
return c, host, nil
|
||||
}
|
||||
source, ok := c.sources[strings.ToLower(host.Source)]
|
||||
if !ok {
|
||||
return nil, host, fmt.Errorf("specify a configured NetBox instance as instance/hostname (got %q)", host.Target())
|
||||
}
|
||||
host.Source = strings.ToLower(host.Source)
|
||||
return source, host, nil
|
||||
}
|
||||
|
||||
// HostScope identifies a local note by its original NetBox URL and canonical
|
||||
// hostname, so renaming an instance does not move or mix its notes.
|
||||
func (c *Client) HostScope(host HostEntry) (HostEntry, string, error) {
|
||||
source, host, err := c.sourceForHost(host)
|
||||
if err != nil {
|
||||
return host, "", err
|
||||
}
|
||||
return host, source.baseURL, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetIPsWithFilterForHost(ctx context.Context, host HostEntry, filters string) ([]string, error) {
|
||||
source, _, err := c.sourceForHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return source.GetIPsWithFilter(ctx, filters)
|
||||
}
|
||||
|
||||
func (c *Client) MultipleSources() bool { return c.sources != nil }
|
||||
Reference in new issue
Block a user