Files
node-lookup/cmd/pburl/main.go
T
unkinben f296056360
ci/woodpecker/tag/release Pipeline failed
Add pburl and pblastreport companion tools to the RPM (#15)
## 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>
2026-07-16 22:37:26 +10:00

64 lines
1.7 KiB
Go

// Command pburl prints the Puppetboard node-page URL for each host it is given.
//
// It reads hostnames from its arguments or from piped node-lookup output and
// emits "<host> <puppetboard-url>" per line:
//
// node-lookup -R | pburl
// pburl host1.example.net host2.example.net
package main
import (
"fmt"
"os"
"node-lookup/internal/puppet"
"github.com/spf13/cobra"
)
var version = "dev"
func main() {
cfg, err := puppet.Load()
if err != nil {
fmt.Fprintln(os.Stderr, "config error:", err)
os.Exit(1)
}
var boardURL string
root := &cobra.Command{
Use: "pburl [host...]",
Short: "Print the Puppetboard node-page URL for each host.",
Long: "Reads hostnames from arguments or piped node-lookup output and prints\n" +
"'<host> <puppetboard-url>' for each. Example: node-lookup -R | pburl",
Args: cobra.ArbitraryArgs,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("puppetboard-url") {
cfg.PuppetboardURL = boardURL
}
hosts := puppet.ReadHosts(os.Stdin, args)
if len(hosts) == 0 {
return fmt.Errorf("no hosts given (pass as arguments or pipe node-lookup output)")
}
for _, h := range hosts {
fmt.Printf("%s %s\n", h, puppet.HostPageURL(cfg.PuppetboardURL, h))
}
return nil
},
}
root.Flags().StringVar(&boardURL, "puppetboard-url", cfg.PuppetboardURL, "Puppetboard base URL (overrides config and NODE_LOOKUP_PUPPETBOARD_URL)")
root.AddCommand(&cobra.Command{
Use: "version",
Short: "Print the version",
Run: func(cmd *cobra.Command, args []string) { fmt.Println(version) },
SilenceUsage: true,
})
if err := root.Execute(); err != nil {
os.Exit(1)
}
}