# 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. - **`catalog-impact`** — maps a control repo change onto the hosts it can affect, by reading hieradata paths as hierarchy positions and tracing manifest classes backwards to role classes. ## Structure ``` cmd/catalog-diff/ # the CLI (flags, exit codes, orchestration) cmd/catalog-impact/ # the CLI (flags, output modes) internal/catalog/ # catalog types, normalisation, diff engine, renderers, # branch.go (BranchToEnvironment) internal/puppetserver/ # mTLS client for POST /puppet/v4/catalog + /status/v1/simple internal/impact/ # path -> selector rules, repo index, reverse lookup, # estate fact table, renderers internal/pdbmux/ # unauthenticated fact client for pdbmux 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//`. The Makefile, `scripts/build-rpm.sh`, `packaging/nfpm.yaml` and the release pipeline all take the shipped list from `cmd/`, so nothing else needs editing. ## Transport - Catalogs: `POST /puppet/v4/catalog` with `persistence` off, `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 puppetserver `auth.conf`. - Facts: `GET /pdb/query/v4/nodes//facts` on pdbmux, falling back to `/pdb/query/v4/factsets/`. No auth, no client cert. - Estate-wide facts: one AST query against `/pdb/query/v4/facts` (`pdbmux.Client.FactsByName`). The estate is ~150 nodes, so `catalog-impact` pulls the whole table once and filters in Go rather than querying per host. - The two services have different auth models and stay separate clients. ## Gotchas - An ENC-supplied environment outranks the requested one. Every compile checks the returned catalog's `environment` and hard-fails on a mismatch — never relax that check. - 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. `--testonly` never returns 2. - Normalisation carries a pile of false-positive fixes (classes and exported resources skipped, `before`/`require` dropped, `notify`/`subscribe` sorted, File titles keyed by `path`, datatype-only changes suppressed). Removing one floods the output with noise. ## catalog-impact gotchas - The reverse lookup must search hieradata as well as manifests. `profiles::base` declares `($hiera_include - $hiera_exclude).include`, and some modules — the `jellyfin` one, for instance — are applied *only* by a `hiera_include` list in a role yaml. A manifest-only search reports them as orphans. - `hieradata/roles//.yaml` is a **prefix** match on the role tiers, not an exact one. `site/roles/manifests/.../.pp` is exact. - The bias is to over-include: an unmapped path widens to the whole estate and says so on stderr. Never turn a widening into a silent empty result. - The repo index comes from the `--repo` checkout, not from `--branch`. CI runs on the branch checkout, so the two agree there. ## Testing ```bash make test # go test -v -race ./... ``` Everything is stubbed with `httptest` (a TLS server for puppetserver, a plain one for pdbmux) — no live estate access is required. Renderer output is covered by golden files under `internal/catalog/testdata`; regenerate with `go test ./internal/catalog -update`.