- rename internal/pdbmux to internal/puppetdb - fetch facts with one GET /pdb/query/v4/facts AST query per node - drop the per-node fallback route and its two response shapes - rename --pdb to --puppetdb, CATALOG_DIFF_PDB to CATALOG_DIFF_PUPPETDB and pdb_url to puppetdb_url - describe the fact source generically in the README and AGENTS.md
4.3 KiB
AGENTS.md
Project Overview
Go CLI tooling for the Puppet estate, shipped as one RPM
(puppet-catalog-tools). Every tool is a separate main package under cmd/;
shared code lives in internal/.
catalog-diff— compiles a host's catalog twice (base environment and the environment built from the branch under test) over the puppetserver API and diffs the result.
Structure
cmd/catalog-diff/ # the CLI (flags, exit codes, orchestration)
internal/catalog/ # catalog types, normalisation, diff engine, renderers,
# branch.go (BranchToEnvironment)
internal/puppetserver/ # mTLS client for POST /puppet/v4/catalog + /status/v1/simple
internal/puppetdb/ # unauthenticated fact client for PuppetDB
internal/config/ # XDG config file + env var loading
packaging/nfpm.yaml # RPM spec (envsubst-templated)
scripts/build-rpm.sh # completions + nfpm packaging
.woodpecker/ # CI: build, test, pre-commit (PR) + release (tag)
Adding a binary: create cmd/<name>/, add the name to BINARIES in the
Makefile and to scripts/build-rpm.sh, and add its files to
packaging/nfpm.yaml.
Transport
- Catalogs:
POST /puppet/v4/catalogwithpersistenceoff,options.prefer_requested_environment: true, facts and synthesised trusted facts in the body. Auth is client mTLS against the estate Puppet CA; the certname must be allowed in the puppetserverauth.conf. - Facts:
GET /pdb/query/v4/factswith the AST query["=","certname","<certname>"]. No auth, no client cert. The response is a flat array of{certname, environment, name, value}records; a record for another node is never folded into the result. - The two services have different auth models and stay separate clients.
Gotchas
- The compiler wraps its response as
{"catalog": {...}}.decodeCatalogunwraps it and still accepts a bare catalog; decoding straight intocatalog.Catalogsilently yields an empty one. - Only
type,title,tagsandexportedare present on every resource.parametersis absent on class/stage/anchor resources andfile/lineon resources with no manifest location, soResource.Lineis a pointer andResource.Params()maps absent onto empty. - An ENC-supplied environment outranks the requested one. Every compile checks
the returned catalog's
environmentand hard-fails on a mismatch — never relax that check. A returned environment that is empty is a decode failure, not a mismatch, and is reported as such. - The compiler returns HTTP 500 for manifest errors too, so retryability is
decided from the error text: a failed PuppetDB query (
server_urls,/pdb/query/) is transient, a manifest error never is. - Branch names are converted the way g10k does it (
\W->_, case preserved). The rule lives in one function,catalog.BranchToEnvironment. - Exit codes are a contract: 0 same, 1 compile failed, 2 differences, 3
transport/auth/mismatch/usage.
--testonlynever returns 2. - Numeric flags are validated up front (
validate). A negative--retryused to skip the compile loop and leave a nil error insideexitError; that type now prints safely whatever it holds. - The two compiles share a cancellable context: the first failure abandons its sibling. An error that is only that cancellation is not the cause and is not what gets reported.
- Normalisation carries a pile of false-positive fixes (classes and exported
resources skipped,
before/requiredropped,notify/subscribesorted, File titles keyed bypath, datatype-only changes suppressed). Removing one floods the output with noise.
Testing
make test # go test -v -race ./...
Everything is stubbed with httptest (a TLS server for puppetserver, a plain
one for PuppetDB) — no live estate access is required. Renderer output is
covered by golden files under internal/catalog/testdata; regenerate with
go test ./internal/catalog -update.
internal/puppetserver/testdata/catalog_response.json is a synthetic
OpenVoxServer 8.8 response, kept for its shape: the catalog envelope, a
string version, a null code_id, and resources that omit parameters,
file and line, plus the exported and sensitive_parameters cases. This
repo is public — hostnames, class names and paths in fixtures stay fake.