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.
85 lines
2.4 KiB
Go
85 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"node-lookup/internal/puppet"
|
|
)
|
|
|
|
func mustTime(t *testing.T, s string) time.Time {
|
|
t.Helper()
|
|
ts, err := time.Parse(time.RFC3339Nano, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ts
|
|
}
|
|
|
|
func TestFormatWhen_Absolute(t *testing.T) {
|
|
node := &puppet.Node{ReportTimestamp: "2026-07-15T04:05:06.000Z"}
|
|
now := mustTime(t, "2026-07-15T10:00:00Z")
|
|
got := formatWhen(node, nil, false, time.UTC, now)
|
|
if got != "2026-07-15 04:05:06 UTC" {
|
|
t.Fatalf("unexpected absolute time: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatWhen_TimezoneApplied(t *testing.T) {
|
|
loc, err := time.LoadLocation("Asia/Singapore") // UTC+8, no DST
|
|
if err != nil {
|
|
t.Skipf("tzdata unavailable: %v", err)
|
|
}
|
|
node := &puppet.Node{ReportTimestamp: "2026-07-15T04:05:06Z"}
|
|
now := mustTime(t, "2026-07-15T10:00:00Z")
|
|
got := formatWhen(node, nil, false, loc, now)
|
|
if !strings.HasPrefix(got, "2026-07-15 12:05:06") {
|
|
t.Fatalf("expected +08 time, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatWhen_Relative(t *testing.T) {
|
|
node := &puppet.Node{ReportTimestamp: "2026-07-15T07:00:00Z"}
|
|
now := mustTime(t, "2026-07-15T10:00:00Z")
|
|
if got := formatWhen(node, nil, true, time.UTC, now); got != "3h ago" {
|
|
t.Fatalf("expected '3h ago', got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestFormatWhen_EdgeCases(t *testing.T) {
|
|
now := mustTime(t, "2026-07-15T10:00:00Z")
|
|
if got := formatWhen(nil, errors.New("down"), false, time.UTC, now); !strings.HasPrefix(got, "error:") {
|
|
t.Fatalf("expected error passthrough, got %q", got)
|
|
}
|
|
if got := formatWhen(nil, nil, false, time.UTC, now); got != "unknown node" {
|
|
t.Fatalf("expected unknown node, got %q", got)
|
|
}
|
|
if got := formatWhen(&puppet.Node{ReportTimestamp: ""}, nil, false, time.UTC, now); got != "no report" {
|
|
t.Fatalf("expected no report, got %q", got)
|
|
}
|
|
if got := formatWhen(&puppet.Node{ReportTimestamp: "garbage"}, nil, false, time.UTC, now); got != "garbage" {
|
|
t.Fatalf("expected raw fallback, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestHumanizeSince(t *testing.T) {
|
|
base := mustTime(t, "2026-07-15T10:00:00Z")
|
|
cases := []struct {
|
|
ts string
|
|
want string
|
|
}{
|
|
{"2026-07-15T09:59:30Z", "30s ago"},
|
|
{"2026-07-15T09:45:00Z", "15m ago"},
|
|
{"2026-07-15T05:00:00Z", "5h ago"},
|
|
{"2026-07-13T10:00:00Z", "2d ago"},
|
|
{"2026-07-15T10:01:00Z", "1m from now"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := humanizeSince(base, mustTime(t, c.ts)); got != c.want {
|
|
t.Errorf("humanizeSince(%s) = %q, want %q", c.ts, got, c.want)
|
|
}
|
|
}
|
|
}
|