Add pburl and pblastreport companion tools to the RPM
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.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package puppet
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StdinReader returns a buffered reader over f and true only when f actually
|
||||
// carries piped/redirected data. Terminals and character devices such as
|
||||
// /dev/null return false, and an empty pipe or empty file (immediate EOF on
|
||||
// peek) also returns false. This mirrors node-lookup's no-TTY behaviour: when
|
||||
// invoked without a real pipe the caller can fall back to arguments instead of
|
||||
// blocking on or silently consuming empty input.
|
||||
func StdinReader(f *os.File) (*bufio.Reader, bool) {
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if (fi.Mode() & os.ModeCharDevice) != 0 {
|
||||
return nil, false // terminal or /dev/null
|
||||
}
|
||||
r := bufio.NewReader(f)
|
||||
if _, err := r.Peek(1); err != nil {
|
||||
return nil, false // empty pipe / empty file (EOF)
|
||||
}
|
||||
return r, true
|
||||
}
|
||||
|
||||
// ReadHosts resolves the list of hostnames to act on. Explicit args win; failing
|
||||
// that it reads the first whitespace-separated field of each non-empty line from
|
||||
// stdin (so `node-lookup -R | pburl` and `node-lookup -1 | pburl` both work).
|
||||
// Order is preserved and duplicates are removed. Returns nil when neither args
|
||||
// nor piped stdin data are present.
|
||||
func ReadHosts(stdin *os.File, args []string) []string {
|
||||
if len(args) > 0 {
|
||||
return dedupe(args)
|
||||
}
|
||||
r, ok := StdinReader(stdin)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var hosts []string
|
||||
sc := bufio.NewScanner(r)
|
||||
for sc.Scan() {
|
||||
fields := strings.Fields(sc.Text())
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
hosts = append(hosts, fields[0])
|
||||
}
|
||||
return dedupe(hosts)
|
||||
}
|
||||
|
||||
func dedupe(in []string) []string {
|
||||
seen := make(map[string]struct{}, len(in))
|
||||
out := make([]string, 0, len(in))
|
||||
for _, s := range in {
|
||||
if _, ok := seen[s]; ok {
|
||||
continue
|
||||
}
|
||||
seen[s] = struct{}{}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user