f296056360
ci/woodpecker/tag/release Pipeline failed
## Why `node-lookup` output is handy for pivoting to Puppetboard, but there was no quick way to turn a list of hosts into Puppetboard node-page URLs, or to see when each host last ran Puppet. These two small tools close that gap and ship in the **same RPM** so they're available wherever `node-lookup` is. ## Changes - Add **`pburl`**: reads hostnames from args or piped `node-lookup` output (first field of each line, de-duped) and prints `<host> <puppetboard-node-page-url>`. - Add **`pblastreport`**: prints `<host>\t<last-report-time>\t<url>` using `report_timestamp` from the PuppetDB v4 `nodes` endpoint. Supports `--relative`/`-r` (relative age) and `--timezone`/`-z <IANA>` (default: local timezone). - Add **`internal/puppet`** package shared by both tools: config load, PuppetDB `nodes` query, Puppetboard URL construction (`<base>/node/<certname>`), and no-TTY-safe stdin host reading. - Add **`puppetboard_url`** config key (env `NODE_LOOKUP_PUPPETBOARD_URL`, default `https://puppetboard.k8s.syd1.au.unkin.net`) to the shared config so `config init`/`config show` scaffold it for the whole tool family. `node-lookup`'s own query behaviour is unchanged. - Build all three binaries individually (each is its own `main` package — a single `go build ./...` can't emit multiple mains) and generate per-binary bash/zsh/fish completions in the Makefile, `build-rpm.sh`, and nfpm spec. - Cross-compile and attach all three tools per os/arch in the release pipeline; extend `.gitignore`; `go mod tidy` promotes cobra/yaml to direct deps. - Document the tools, config key, and env var in `AGENTS.md`. ## Testing - `go test -race ./...` passes (new tests cover config precedence, `nodes` endpoint derivation, host-page URLs, `LookupNode`, stdin host parsing, and the report-time formatting incl. timezone/relative/edge cases). - Built the RPM locally and confirmed it installs all 3 binaries + 9 completion files. - Smoke-tested both tools end-to-end against a mock PuppetDB (timezone conversion, relative time, and error handling all correct). No cross-repo changes needed: the release reuses the existing `default` ServiceAccount and the artifactapi `rpm-internal` upload. Reviewed-on: #15 Co-authored-by: Ben Vincent <ben@unkin.net> Co-committed-by: Ben Vincent <ben@unkin.net>
91 lines
2.6 KiB
Go
91 lines
2.6 KiB
Go
// Package puppet holds the small pieces of PuppetDB/Puppetboard plumbing shared
|
|
// by the node-lookup companion tools (pburl, pblastreport): config loading,
|
|
// PuppetDB "nodes" queries, Puppetboard URL construction, and reading hostnames
|
|
// from piped node-lookup output.
|
|
//
|
|
// It intentionally reads the SAME config file, env vars, and defaults as the
|
|
// node-lookup CLI so a single `~/.config/node-lookup/config.yaml` configures
|
|
// every tool in the family.
|
|
package puppet
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
// DefaultPuppetDBURL is the PuppetDB v4 facts endpoint (shared with node-lookup).
|
|
DefaultPuppetDBURL = "http://puppetdbapi.service.consul:8080/pdb/query/v4/facts"
|
|
// DefaultRoleFact is the role fact node-lookup queries with -R.
|
|
DefaultRoleFact = "enc_role"
|
|
// DefaultPuppetboardURL is the base URL of the Puppetboard web UI.
|
|
DefaultPuppetboardURL = "https://puppetboard.k8s.syd1.au.unkin.net"
|
|
|
|
appName = "node-lookup"
|
|
configFileName = "config.yaml"
|
|
)
|
|
|
|
// Config mirrors node-lookup's config plus the puppetboard_url key used by the
|
|
// companion tools. Fields map 1:1 to config file keys and env vars.
|
|
type Config struct {
|
|
PuppetDBURL string `yaml:"puppetdb_url"`
|
|
RoleFact string `yaml:"role_fact"`
|
|
PuppetboardURL string `yaml:"puppetboard_url"`
|
|
}
|
|
|
|
// DefaultConfig returns the built-in defaults.
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
PuppetDBURL: DefaultPuppetDBURL,
|
|
RoleFact: DefaultRoleFact,
|
|
PuppetboardURL: DefaultPuppetboardURL,
|
|
}
|
|
}
|
|
|
|
// ConfigDir returns the XDG_CONFIG_HOME/node-lookup directory.
|
|
func ConfigDir() string {
|
|
base := os.Getenv("XDG_CONFIG_HOME")
|
|
if base == "" {
|
|
home, _ := os.UserHomeDir()
|
|
base = filepath.Join(home, ".config")
|
|
}
|
|
return filepath.Join(base, appName)
|
|
}
|
|
|
|
// ConfigPath returns the full path to the shared config file.
|
|
func ConfigPath() string {
|
|
return filepath.Join(ConfigDir(), configFileName)
|
|
}
|
|
|
|
// Load reads the config file (if present), then applies env var overrides.
|
|
// Precedence (lowest → highest): defaults < config file < env vars.
|
|
func Load() (Config, error) {
|
|
cfg := DefaultConfig()
|
|
|
|
path := ConfigPath()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return cfg, fmt.Errorf("reading config %s: %w", path, err)
|
|
}
|
|
if err == nil {
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return cfg, fmt.Errorf("parsing config %s: %w", path, err)
|
|
}
|
|
}
|
|
|
|
if v := os.Getenv("NODE_LOOKUP_URL"); v != "" {
|
|
cfg.PuppetDBURL = v
|
|
}
|
|
if v := os.Getenv("NODE_LOOKUP_ROLE_FACT"); v != "" {
|
|
cfg.RoleFact = v
|
|
}
|
|
if v := os.Getenv("NODE_LOOKUP_PUPPETBOARD_URL"); v != "" {
|
|
cfg.PuppetboardURL = v
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|