8800d5ce35
node-lookup output is useful 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 tools close that gap and ship in the same RPM so they are available wherever node-lookup is. - Add pburl: reads hostnames from args or piped node-lookup output 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, and no-TTY-safe stdin host reading. - Add puppetboard_url config key (env NODE_LOOKUP_PUPPETBOARD_URL) to the shared config so config init/show scaffold it for the whole tool family. - Build all three binaries individually (each is its own main package) and generate per-binary bash/zsh/fish completions in the Makefile, build-rpm.sh, and nfpm spec; cross-compile and attach all three per os/arch in the release pipeline. - Document the tools, config key, and env var in AGENTS.md.
54 lines
1.4 KiB
Go
54 lines
1.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"node-lookup/internal/puppet"
|
|
)
|
|
|
|
// formatWhen renders the "last report" column for a node lookup result. It
|
|
// handles the lookup error, the unknown-node, and the never-reported cases so
|
|
// the output always has a value in every column.
|
|
func formatWhen(node *puppet.Node, lookupErr error, relative bool, loc *time.Location, now time.Time) string {
|
|
if lookupErr != nil {
|
|
return "error: " + lookupErr.Error()
|
|
}
|
|
if node == nil {
|
|
return "unknown node"
|
|
}
|
|
if node.ReportTimestamp == "" {
|
|
return "no report"
|
|
}
|
|
ts, err := time.Parse(time.RFC3339Nano, node.ReportTimestamp)
|
|
if err != nil {
|
|
return node.ReportTimestamp // fall back to the raw value
|
|
}
|
|
if relative {
|
|
return humanizeSince(now, ts)
|
|
}
|
|
return ts.In(loc).Format("2006-01-02 15:04:05 MST")
|
|
}
|
|
|
|
// humanizeSince renders the gap between now and ts as a coarse relative string
|
|
// ("42s ago", "9m ago", "3h ago", "5d ago"). Future timestamps (clock skew)
|
|
// render as "in <d>".
|
|
func humanizeSince(now, ts time.Time) string {
|
|
d := now.Sub(ts)
|
|
suffix := "ago"
|
|
if d < 0 {
|
|
d = -d
|
|
suffix = "from now"
|
|
}
|
|
switch {
|
|
case d < time.Minute:
|
|
return fmt.Sprintf("%ds %s", int(d.Seconds()), suffix)
|
|
case d < time.Hour:
|
|
return fmt.Sprintf("%dm %s", int(d.Minutes()), suffix)
|
|
case d < 24*time.Hour:
|
|
return fmt.Sprintf("%dh %s", int(d.Hours()), suffix)
|
|
default:
|
|
return fmt.Sprintf("%dd %s", int(d.Hours()/24), suffix)
|
|
}
|
|
}
|