114 lines
3.0 KiB
Go
114 lines
3.0 KiB
Go
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 }
|