package config import ( "crypto/sha256" "fmt" "net/url" "path/filepath" "regexp" "sort" "strings" ) var instanceName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]*$`) // ValidateNetBoxes keeps source names usable as instance/hostname selectors. func (c *Config) ValidateNetBoxes() error { if len(c.NetBoxes) > 0 && c.NetBox.URL != "" { return fmt.Errorf("configure either netbox or netboxes, not both") } seen := make(map[string]bool) for _, source := range c.NetBoxes { if !instanceName.MatchString(source.Name) { return fmt.Errorf("netboxes.name %q must contain only letters, digits, underscores, or hyphens", source.Name) } name := strings.ToLower(source.Name) if seen[name] { return fmt.Errorf("duplicate NetBox instance name %q", source.Name) } seen[name] = true u, err := url.Parse(source.URL) if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" || u.RawQuery != "" || u.Fragment != "" { return fmt.Errorf("NetBox %q requires an HTTP(S) URL without query or fragment", source.Name) } if source.TokenVersion < 0 || source.TokenVersion > 2 { return fmt.Errorf("NetBox %q: token_version must be 0, 1, or 2", source.Name) } } return nil } func (c *Config) HasNetBox() bool { return c.NetBox.URL != "" || len(c.NetBoxes) > 0 } // CachePath isolates named sources from legacy caches and from changed endpoints. // Rotating a token or reordering the sources does not discard usage history. func (c *Config) CachePath() string { if len(c.NetBoxes) == 0 { return c.Cache.Path } var identities []string for _, source := range c.NetBoxes { identities = append(identities, strings.ToLower(source.Name)+"\x00"+strings.TrimRight(source.URL, "/")) } sort.Strings(identities) sum := sha256.Sum256([]byte(strings.Join(identities, "\x00"))) return filepath.Join(filepath.Dir(c.Cache.Path), fmt.Sprintf("instances-%x", sum[:8]), filepath.Base(c.Cache.Path)) }