node-lookup: auto-qualify short node names to .main.unkin.net
Short -n node names (no dot) now silently returned nothing because the PuppetDB certname filter needs a FQDN. Auto-qualify a dotless name to <name>.<domain> (domain defaults to main.unkin.net) before the query. - Add qualifyNode() pure helper: dotless names get .<domain> appended; names already containing a dot (any domain) are left unchanged; a single trailing dot is stripped; empty input is preserved. - Apply it to the -n value and to stdin-sourced node names in run(). - Make the domain configurable via config key domain, NODE_LOOKUP_DOMAIN env var, and --domain flag (default main.unkin.net). - Surface domain in config show / config init and document in AGENTS.md. - Add table-driven qualifyNode tests and a domain env-override test.
This commit is contained in:
@@ -22,6 +22,7 @@ const (
|
||||
defaultPuppetDBURL = "http://puppetdbapi.service.consul:8080/pdb/query/v4/facts"
|
||||
defaultRoleFact = "enc_role"
|
||||
defaultPuppetboardURL = "https://puppetboard.k8s.syd1.au.unkin.net"
|
||||
defaultDomain = "main.unkin.net"
|
||||
configFileName = "config.yaml"
|
||||
appName = "node-lookup"
|
||||
)
|
||||
@@ -37,6 +38,8 @@ type config struct {
|
||||
// the shared config file also configures the companion tools (pburl,
|
||||
// pblastreport) that read this same file.
|
||||
PuppetboardURL string `yaml:"puppetboard_url"`
|
||||
// Domain is appended to a short (dotless) -n node name to form its FQDN.
|
||||
Domain string `yaml:"domain"`
|
||||
}
|
||||
|
||||
func defaultConfig() config {
|
||||
@@ -44,6 +47,7 @@ func defaultConfig() config {
|
||||
PuppetDBURL: defaultPuppetDBURL,
|
||||
RoleFact: defaultRoleFact,
|
||||
PuppetboardURL: defaultPuppetboardURL,
|
||||
Domain: defaultDomain,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +91,9 @@ func loadConfig() (config, error) {
|
||||
if v := os.Getenv("NODE_LOOKUP_PUPPETBOARD_URL"); v != "" {
|
||||
cfg.PuppetboardURL = v
|
||||
}
|
||||
if v := os.Getenv("NODE_LOOKUP_DOMAIN"); v != "" {
|
||||
cfg.Domain = v
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -105,7 +112,7 @@ func writeDefaultConfig() error {
|
||||
|
||||
cfg := defaultConfig()
|
||||
data, _ := yaml.Marshal(cfg)
|
||||
header := []byte("# node-lookup configuration\n# Fields can be overridden with env vars: NODE_LOOKUP_URL, NODE_LOOKUP_ROLE_FACT, NODE_LOOKUP_PUPPETBOARD_URL\n# puppetboard_url is used by the companion tools (pburl, pblastreport).\n\n")
|
||||
header := []byte("# node-lookup configuration\n# Fields can be overridden with env vars: NODE_LOOKUP_URL, NODE_LOOKUP_ROLE_FACT, NODE_LOOKUP_PUPPETBOARD_URL, NODE_LOOKUP_DOMAIN\n# puppetboard_url is used by the companion tools (pburl, pblastreport).\n# domain is appended to short (dotless) -n node names to form their FQDN.\n\n")
|
||||
if err := os.WriteFile(path, append(header, data...), 0o644); err != nil {
|
||||
return fmt.Errorf("writing config: %w", err)
|
||||
}
|
||||
@@ -144,6 +151,21 @@ func nameFilter(names []string) []interface{} {
|
||||
return or
|
||||
}
|
||||
|
||||
// qualifyNode auto-qualifies a short (dotless) node name by appending
|
||||
// ".<domain>", so `-n ausyd1nxvm2120` resolves the same as its FQDN. A name
|
||||
// that already contains a dot is treated as already-qualified (including names
|
||||
// in other domains like *.k8s.syd1.au.unkin.net) and returned unchanged. A
|
||||
// single trailing dot is stripped first, so a dotless name with a trailing dot
|
||||
// is still qualified. Empty input is returned unchanged to preserve the
|
||||
// existing "no node given" behavior.
|
||||
func qualifyNode(name, domain string) string {
|
||||
name = strings.TrimSuffix(name, ".")
|
||||
if name == "" || strings.Contains(name, ".") {
|
||||
return name
|
||||
}
|
||||
return name + "." + domain
|
||||
}
|
||||
|
||||
func buildQuery(node, factName, match, roleFact string, showRole, partial, inverse bool) string {
|
||||
type filter = []interface{}
|
||||
var filters []filter
|
||||
@@ -318,6 +340,8 @@ func allFactsForNode(puppetDBURL, node string) ([]fact, error) {
|
||||
func run(cfg config, nodeName, factName, match string, showRole, partial, inverse, nodeOnly, valueOnly, count, ansible, jsonMode, allFacts bool) error {
|
||||
signal.Ignore(syscall.SIGPIPE)
|
||||
|
||||
nodeName = qualifyNode(nodeName, cfg.Domain)
|
||||
|
||||
if allFacts {
|
||||
if nodeName == "" {
|
||||
return fmt.Errorf("-a requires -n")
|
||||
@@ -365,7 +389,7 @@ func run(cfg config, nodeName, factName, match string, showRole, partial, invers
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := doQuery(fields[0]); err != nil {
|
||||
if err := doQuery(qualifyNode(fields[0], cfg.Domain)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
}
|
||||
}
|
||||
@@ -446,6 +470,7 @@ func main() {
|
||||
jsonMode bool
|
||||
allFacts bool
|
||||
puppetDBURL string
|
||||
domain string
|
||||
)
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
@@ -461,6 +486,9 @@ func main() {
|
||||
if cmd.Flags().Changed("url") {
|
||||
cfg.PuppetDBURL = puppetDBURL
|
||||
}
|
||||
if cmd.Flags().Changed("domain") {
|
||||
cfg.Domain = domain
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -482,6 +510,7 @@ func main() {
|
||||
f.BoolVarP(&ansible, "ansible", "A", false, "Output as Ansible inventory")
|
||||
f.BoolVarP(&jsonMode, "json", "j", false, "Emit valid JSON for all output")
|
||||
f.BoolVarP(&allFacts, "all", "a", false, "Show all facts for a node (requires -n)")
|
||||
f.StringVar(&domain, "domain", cfg.Domain, "Domain appended to short (dotless) -n node names (overrides config and NODE_LOOKUP_DOMAIN)")
|
||||
rootCmd.PersistentFlags().StringVar(&puppetDBURL, "url", cfg.PuppetDBURL, "PuppetDB facts URL (overrides config and NODE_LOOKUP_URL)")
|
||||
|
||||
configCmd := &cobra.Command{
|
||||
@@ -506,6 +535,7 @@ func main() {
|
||||
fmt.Printf("puppetdb_url : %s\n", cfg.PuppetDBURL)
|
||||
fmt.Printf("role_fact : %s\n", cfg.RoleFact)
|
||||
fmt.Printf("puppetboard_url: %s\n", cfg.PuppetboardURL)
|
||||
fmt.Printf("domain : %s\n", cfg.Domain)
|
||||
return nil
|
||||
},
|
||||
SilenceUsage: true,
|
||||
|
||||
Reference in New Issue
Block a user