From a9cc2dc191ffdb8fe95d04a726cf39257b68b90a Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sun, 28 Jun 2026 15:17:55 -0700 Subject: [PATCH] docs(skills): add mise/melange/apko tooling skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the three operator tooling skills from template-go-api, adapted for this repository's toolset, and refresh the agent-facing docs. - .agents/skills/mise: the mise toolchain (tool list taken from mise.toml — go, moon, golangci-lint, kubebuilder, setup-envtest, kubectl, helm, chainsaw, ko, tilt, ctlptl, kind, melange, apko, cosign, with controller-gen on the go: backend and chainsaw via aqua:kyverno/chainsaw), fail-closed lock, moon-on- system, the bump/lock workflow, and the macos-x64/nested-worktree gotchas. - .agents/skills/melange: building the controller-manager apk (./cmd -> manager, no version-var stamping, native per-arch builds, --runner docker locally). - .agents/skills/apko: assembling the minimal multi-arch nonroot image (/usr/bin/manager, uid 65532), apko publish + the registry-resolved index digest, keyless cosign, syft SBOM, and the attest.yml SLSA L3 + Kyverno tie-in. - AGENTS.md / CLAUDE.md: reference the new skills in Local Skills. - DELETE_ME.md: add melange.yaml/apko.yaml and the tooling skills to the downstream-customization checklist; drop the stale Docker-cache-scope / OCI archive / OCI-label notes left over from the Dockerfile build. Each ported skill was adapted from the proven template-go-api originals and adversarially verified against this repo's mise.toml / melange.yaml / apko.yaml. Co-Authored-By: Claude Opus 4.8 (1M context) --- .agents/skills/apko/SKILL.md | 245 ++++++++++++++++ .../skills/apko/references/apko-commands.md | 183 ++++++++++++ .agents/skills/melange/SKILL.md | 144 ++++++++++ .../melange/references/melange-commands.md | 155 ++++++++++ .agents/skills/mise/SKILL.md | 220 +++++++++++++++ .../skills/mise/references/mise-commands.md | 266 ++++++++++++++++++ AGENTS.md | 6 + DELETE_ME.md | 28 +- 8 files changed, 1241 insertions(+), 6 deletions(-) create mode 100644 .agents/skills/apko/SKILL.md create mode 100644 .agents/skills/apko/references/apko-commands.md create mode 100644 .agents/skills/melange/SKILL.md create mode 100644 .agents/skills/melange/references/melange-commands.md create mode 100644 .agents/skills/mise/SKILL.md create mode 100644 .agents/skills/mise/references/mise-commands.md diff --git a/.agents/skills/apko/SKILL.md b/.agents/skills/apko/SKILL.md new file mode 100644 index 0000000..fc947e1 --- /dev/null +++ b/.agents/skills/apko/SKILL.md @@ -0,0 +1,245 @@ +--- +name: apko +description: > + Assemble the runtime OCI image for the template-k8s operator with apko (`apko.yaml`) — + the Dockerfile-free, multi-arch, nonroot image built from the melange-produced apk plus a + minimal Wolfi base. Use when changing image contents, the `dev/image-build.sh` local build, + the `apko build`/`apko publish` steps in `.github/workflows/release.yml`, + `release-dry-run.yml`, or `security-scan.yml`, runtime packages, the nonroot user, OCI + annotations, the per-build SBOM, or anything that moves the published image digest the + chart's Kyverno provenance check binds to. Pairs with the `melange` skill (the apk) and the + `mise` skill (the CLI). +--- + +# apko + +apko owns exactly one step in this repo: turn the melange-built apk plus a small set of Wolfi +base packages into the operator runtime image. It is the modern, distroless-equivalent +replacement for `gcr.io/distroless/static:nonroot` — no Dockerfile, no `RUN`, no shell. The +entire image is declared in `apko.yaml`; everything else (signing, attestation, the apk +itself) belongs to adjacent tools. + +This is the **operator manager image** (`ghcr.io/meigma/template-k8s`), the one the Helm chart +runs as the controller. Its published multi-arch digest is the subject of the cosign +signature, the SBOM/provenance attestations, and the chart's optional Kyverno image-verify +policy — so the apko/publish/attest path and the chart's `kyverno.imageVerification` defaults +are coupled (see "Supply-chain tie-in" below). + +## Verified against + +- apko `v1.2.19` (pinned in `mise.toml` as `aqua:chainguard-dev/apko`, locked in `mise.lock`), + alongside melange `0.54.0`. +- Grounded in the local `apko --help` for this version and the repo files (`apko.yaml`, + `mise.toml`, `.github/workflows/release.yml`, `.github/workflows/release-dry-run.yml`, + `.github/workflows/security-scan.yml`, `dev/image-build.sh`, `scripts/test-e2e.sh`, + `charts/template-k8s/values.yaml`), not from memory. +- Run apko through mise so the pinned binary is used: `mise exec -- apko --help`. + +## Use this skill when + +- Adding or removing a runtime dependency (a Wolfi package in `contents.packages`). +- Editing the nonroot account, entrypoint, archs, or OCI annotations in `apko.yaml`. +- Touching `apko build`/`apko publish` in the release, release-dry-run, or security-scan + workflows, or the `dev/image-build.sh` local/e2e build. +- Changing anything that moves the published image digest — because the chart's Kyverno + policy verifies provenance bound to that digest. +- Debugging the published image (wrong arch set, missing apk, untrusted `@local` package, + SBOM directory errors, manager that does not start nonroot/read-only). + +## apko's lane (do not cross it) + +1. The image is defined **only** in `apko.yaml`. There is no Dockerfile and there must not be + one. Never add `RUN`, `apt`, `apk add`, shell steps, or a base-image `FROM`. The inner-loop + dev stack (`moon run root:dev-up`) builds the manager with **ko**, not apko — do not fold + the two image paths together. +2. Add a runtime dependency by adding a **Wolfi package** to `contents.packages` — nothing + else. Keep the set minimal; every package is CVE surface in a distroless image. +3. apko consumes the apk; it never builds it. Source → apk is the `melange` skill's job + (`melange.yaml`). If the operator code changed, rebuild the apk first, then apko. +4. apko does not sign or attest. `cosign sign` and the `attest.yml` provenance are separate + release steps. Do not fold them into apko invocations. +5. Keep it nonroot. The image runs as uid/gid 65532 with no shell, matching the chart's + `containerSecurityContext` (`runAsNonRoot`, `runAsUser: 65532`, `readOnlyRootFilesystem`). + Do not add a shell, package manager, or root entrypoint for convenience. +6. Do not reintroduce `apko.lock.json` / `apko lock`. The Wolfi base floats by design (see + below). Pinning is recorded in the per-build SBOM + provenance, not a committed lockfile. + +## How the image is wired (`apko.yaml` anatomy) + +Read `apko.yaml` before changing anything. The load-bearing parts: + +- `contents.repositories`: the Wolfi os repo **and** `@local ./packages`. The `@local` + repository is melange's output directory — that is how the just-built apk is found. +- `contents.keyring`: the Wolfi signing key URL. The **ephemeral melange public key(s)** are + not listed here; they are appended at build/publish time with `--keyring-append` and are + never committed. Without the matching pub key in the keyring, apko refuses the `@local` apk + as unsigned. +- `contents.packages`: `wolfi-baselayout`, `ca-certificates-bundle`, `tzdata`, and + `template-k8s@local`. The `@local` suffix pins the package to the `@local` repository, i.e. + the apk melange just built (not anything from the Wolfi index). +- `accounts`: defines group+user `nonroot` (gid/uid **65532**) and `run-as: 65532`. Wolfi has + **no `nonroot` package**, so the user is created here. This mirrors `distroless:nonroot` and + must stay aligned with the chart's `containerSecurityContext.runAsUser: 65532`. +- `entrypoint.command: /usr/bin/manager` — where the `go/build` melange pipeline installs the + operator manager binary. +- `archs: [amd64, arm64]` — the index architectures. +- `annotations`: OCI labels (`title: template-k8s`, `source: https://github.com/meigma/template-k8s`). + `org.opencontainers.image.version` carries the `# x-release-please-version` marker; + release-please bumps it. Do not hand-edit it. + +## Local build (`dev/image-build.sh`) + +`dev/image-build.sh` builds a single host-arch image with melange + apko and `docker load`s +it, so local e2e exercises the same Wolfi nonroot image that ships. It defaults the tag to +`template-k8s:dev` and honors `IMG` to override it. `scripts/test-e2e.sh` (the +`root:test-e2e` Moon task) drives it with the e2e tag and then `kind load docker-image`s the +result. The apko step is: + +```bash +apko build apko.yaml "$image" image.tar \ + --arch "$arch" \ + --keyring-append ./melange.rsa.pub +docker load < image.tar +docker tag "${image}-${arch}" "$image" +``` + +Non-obvious points: + +- **The retag is required, not cosmetic.** A single-arch `apko build` loads into Docker under + an arch-suffixed tag (`-amd64` / `-arm64`, using the Go arch name from + `go env GOARCH`). Consumers expect the plain `` tag, so the script retags. + `release-dry-run.yml` does the same (`template-k8s:dry-run-amd64` → `template-k8s:dry-run`) + and `security-scan.yml` does the same (`template-k8s:security-scan-amd64` → + `template-k8s:security-scan`). +- `apko build` writes a tarball for `docker load`. The positional output can also be an + `oci-layout-dir/`, but the repo uses a `.tar`. +- `--keyring-append ./melange.rsa.pub` makes apko trust the locally-signed `@local` apk. The + local script mints one ephemeral key (`melange.rsa`); only its single pub key is appended. +- `--arch "$arch"` uses the explicit Go arch name. `host` is also accepted by apko, but the + script passes the resolved value. +- melange must have run first (`packages/` must contain the apk). The script does this in + order; if you run apko by hand, build the apk first — see the `melange` skill. +- For Kind-backed e2e, the image must be `kind load docker-image`ed and the Deployment must + use the exact loaded tag with `imagePullPolicy: IfNotPresent`, or Kind tries a remote pull. + +## CI publish (`release.yml` → multi-arch index) + +The `container-image-release` job assembles and pushes the multi-arch image: + +```bash +mkdir -p sbom # apko does NOT create --sbom-path; it must pre-exist +apko publish apko.yaml "$IMAGE_TAG" \ + --arch amd64,arm64 \ + --keyring-append ./melange-amd64.rsa.pub \ + --keyring-append ./melange-arm64.rsa.pub \ + --sbom-path ./sbom +``` + +Non-obvious points: + +- **`--sbom-path` must pre-exist.** apko moves SBOMs into the directory but does not create + it; a missing `sbom/` is a real release failure. The job runs `mkdir -p sbom` first. +- **Two keyring keys.** Each arch's apk was signed on its own native runner with its own + ephemeral key, so both `melange-amd64.rsa.pub` and `melange-arm64.rsa.pub` must be appended; + apko verifies each per-arch apk against the matching key. +- **`docker login` is a precondition.** `apko publish` authenticates via the Docker keychain. + release.yml runs `docker/login-action` against `ghcr.io` first; without it, the push fails. +- **The authoritative digest is resolved from the registry, NOT parsed from apko stdout.** + The next step runs `docker buildx imagetools inspect "$IMAGE_TAG" --format '{{json .}}'` and + takes `jq -r '.manifest.digest'` — the multi-arch **index** digest the tag points to. It + also asserts the platform set is exactly `linux/amd64,linux/arm64`. apko's `--image-refs` + flag could write refs to a file, but the repo deliberately does not trust apko stdout here: + cosign, the SBOM + provenance attestations, and the chart's Kyverno check all bind to this + index digest, so it must come from the registry's own view. +- The image is pushed even while the GitHub release is still a draft — GHCR has no draft + state. That is expected. +- apko emits its own SBOM (`--sbom-path ./sbom`, spdx by default). The **attested** image SBOM + is generated separately by `syft -o spdx-json=image.spdx.json` and attached with + `actions/attest-sbom`. Two different SBOMs; do not conflate them. + +After publish + digest resolution, release.yml (adjacent steps, not apko's job): smoke-tests +`docker run --rm --help` (accepting exit code 0 or 2, since the manager's flag parser +may exit non-zero on `--help`), then `cosign sign --yes` (keyless, Sigstore/Fulcio via OIDC), +`syft` image SBOM attestation, and finally the isolated `attest.yml` SLSA-L3 provenance via +the `attest-image` caller (passing the resolved `image-name` + `image-digest`, +`push-to-registry: true`). + +## Supply-chain tie-in (operator-specific) + +The published image is the controller the chart deploys, and the chart ships an **optional +Kyverno image-verification policy** (`kyverno.imageVerification` in +`charts/template-k8s/values.yaml`, default `enabled: false`). When enabled, it verifies the +image's **SLSA provenance** before admitting the operator: + +- attestation type `https://slsa.dev/provenance/v1` +- build type `https://actions.github.io/buildtypes/workflow/v1` +- signed by `attest.yml` (the attestor `subjectRegExp` pins + `…/.github/workflows/attest.yml@refs/tags/vX.Y.Z`), keyless via Fulcio/Rekor. + +That provenance is produced by the `attest-image` job over the **same index digest** apko +published. So the build/sign/attest chain and the chart's Kyverno defaults are coupled: +if you change how the image is published (digest resolution), who signs the provenance (the +`attest.yml` signer-workflow identity), or the attestation type/build type, you may need to +update `kyverno.imageVerification` defaults so the policy still matches real releases. Treat a +change to the apko → digest → attest path as a potential chart-policy change. + +## Multi-arch model + +apko does not emulate. It assembles a 2-arch index from per-arch apks that **melange already +built natively** (amd64 on `ubuntu-24.04`, arm64 on `ubuntu-24.04-arm`, no QEMU). The `archs:` +in `apko.yaml` and `--arch amd64,arm64` must line up with the apks present under `packages/` +(Wolfi arch dirs `x86_64`/`aarch64`). If an arch's apk is missing, publish fails. The +single-arch `apko build` paths (`dev/image-build.sh`, `release-dry-run.yml`, +`security-scan.yml`) only assemble one arch for a quick load/smoke/scan; they are not the +multi-arch artifact. + +## Read-only inspection + +Use these to reason about the image without building it: + +```bash +apko show-config apko.yaml # the fully-derived config apko will act on +apko show-packages apko.yaml # exact packages + versions that would install +``` + +`show-packages` resolves the live Wolfi index, so it shows what the floating base would pull +right now — the right tool to preview a CVE bump or confirm a new package resolves. Both accept +`--keyring-append`/`--repository-append` if you need them to see the `@local` apk. + +## Why `apko lock` is deliberately unused + +`apko lock` exists (it writes a `.lock.json` of pinned package versions) but the repo does not +use it, on purpose: + +- The app package is a per-build `@local` apk; a committed lock would pin a stale/foreign + checksum for it. +- The Wolfi base (`ca-certificates-bundle`, `tzdata`, …) is meant to float to latest for a + fresh CA bundle/timezones and low CVE surface. Pinning fights that model. +- Reproducibility comes from recording the exact resolved versions in the per-build SBOM + + provenance attestation, not from a lockfile. Do not add `apko.lock.json`. + +## Gotchas + +- Single-arch `apko build` Docker-loads under an **arch-suffixed tag**; retag before using it + (e2e, dry-run, scan). The suffix is the value passed to `--arch`; the repo passes the Go + arch name, e.g. `-amd64`, not the Wolfi `-x86_64`. +- `--sbom-path` directory must already exist (`mkdir -p sbom`). +- `--keyring-append` is mandatory for the `@local` apk and must cover **every** arch being + published. +- `apko publish` needs a prior `docker login`; `apko build` (tarball) does not. +- The release digest is the **registry index digest** from `imagetools inspect`, not apko + stdout. Do not switch the release to parse apko output — it would break the digest the + cosign/attest/Kyverno chain depends on. +- `org.opencontainers.image.version` in `apko.yaml` is release-please-owned — never hand-edit. +- Wolfi has no `nonroot` package; the uid/gid 65532 user is created via `accounts` in + `apko.yaml`. Keep it in sync with the chart's `runAsUser: 65532`. +- Local build artifacts (`packages/`, `sbom/`, `image.tar`, `*.oci`, `*.spdx.json`, + `melange*.rsa*`) are git-ignored; `apko.yaml` and `melange.yaml` ARE committed. +- Adding a runtime dep means a Wolfi package in `contents.packages`, then rebuild the apk and + the image. There is no Dockerfile to edit. +- apko is pinned by mise; invoke it via `mise exec -- apko …` (or a mise task / shimmed PATH), + not a system install. Bumping apko is a `mise.toml` + `mise lock` change — see the `mise` + skill. + +See [references/apko-commands.md](references/apko-commands.md) for the version-stamped command +and flag reference. diff --git a/.agents/skills/apko/references/apko-commands.md b/.agents/skills/apko/references/apko-commands.md new file mode 100644 index 0000000..a8f81b3 --- /dev/null +++ b/.agents/skills/apko/references/apko-commands.md @@ -0,0 +1,183 @@ +# apko Command Map + +Curated operator reference for `apko v1.2.19` (pinned in `mise.toml` as +`aqua:chainguard-dev/apko`, locked in `mise.lock`). Prefer local `--help` +(`mise exec -- apko --help`) and the official docs if anything here drifts. Only the +flags this repo uses, plus the few an operator reaches for, are listed — every flag below is +real and correctly spelled/typed for this version. + +## Global flags + +Apply to every `apko` subcommand: + +- `--log-level `: `debug|info|warn|error|fatal|panic` (default `INFO`). +- `-C, --workdir `: working directory (default is the current dir). apko searches for + config, base image, and `@local` paths relative to this. +- `-h, --help`. + +## `apko build` + +Purpose: build an image from `apko.yaml` into a `docker load`-able tarball (or an +`oci-layout-dir/`). Used by `dev/image-build.sh` (host-arch local/e2e build), +`release-dry-run.yml` (no-push rehearsal), and `security-scan.yml` (Trivy scan image). + +Usage: + +```bash +apko build [flags] +``` + +Flags that matter here: + +- `--arch `: architectures to build (e.g. `amd64`, `x86_64`, or `amd64,arm64`). + Accepts `host` for the host arch. Default is all archs in the config. The repo passes a + single resolved arch for local/dry-run/scan builds. +- `-k, --keyring-append `: extra public keys to trust. **Required** here to trust the + ephemeral melange-signed `@local` apk (`--keyring-append ./melange.rsa.pub`). Repeatable — + `release-dry-run.yml` appends both per-arch pub keys even though it builds only `--arch + amd64`, because both were downloaded. +- `--sbom`: generate SBOMs (default `true`; disable with `--sbom=false`). +- `--sbom-path `: write SBOMs to this dir (default: the image dir). Must already exist. +- `--sbom-formats `: SBOM formats (default `[spdx]`). +- `--annotations `: OCI annotations as `key:value` (colon-separated). The repo + declares annotations in `apko.yaml` instead. +- `-r, --repository-append `: extra package repositories. +- `-b, --build-repository-append `: extra repositories used only at build time. +- `-p, --package-append `: extra packages beyond `contents.packages`. +- `--build-date `: timestamp (RFC3339) for files inside the image. Repo relies on the + default; set only when you need a specific reproducible timestamp. +- `--vcs`: detect and embed VCS URLs (default `true`). +- `--offline`: do not fetch packages (cache must be pre-populated). +- `--cache-dir `: apk/index cache directory. +- `--lockfile `: constrain package versions to a `.lock.json`. Not used here (see the + SKILL on why `apko lock` is avoided). +- `--include-paths `: extra paths to resolve input files from. +- `--ignore-signatures`: skip repository signature verification. Do not use; it defeats the + `@local`/Wolfi signing checks. + +Notes: + +- A single-arch build loads into Docker under an **arch-suffixed tag** (`-`, Go arch + name). Retag to the plain `` before use (`docker tag -amd64 `). +- `build` has **no** `--local` or `--image-refs` flag — those are `publish`-only. + +## `apko publish` + +Purpose: build **and push** the (multi-arch) image to a registry. Used by `release.yml` +(`container-image-release`). The repo pushes a single multi-arch index, then resolves its +digest from the registry (not from apko output — see Notes). + +Usage: + +```bash +apko publish [flags] +``` + +Flags that matter here: + +- `--arch `: architectures for the index. Repo uses `--arch amd64,arm64`; must match + the per-arch apks present under `packages/`. +- `-k, --keyring-append `: trust keys. Repo appends **both** ephemeral melange pub + keys (`./melange-amd64.rsa.pub`, `./melange-arm64.rsa.pub`) — one per arch. Repeatable. +- `--sbom-path `: write SBOMs here. **apko does not create this dir** — `mkdir -p` + it first (real release bug otherwise). +- `--sbom` (default `true`; `--sbom=false` to disable), `--sbom-formats ` (default `[spdx]`). +- `--image-refs `: write the published refs to a file. **Not used.** The repo resolves + the authoritative multi-arch index digest from the registry via + `docker buildx imagetools inspect "$IMAGE_TAG" --format '{{json .}}'` → + `jq -r '.manifest.digest'`, because cosign, the SBOM/provenance attestations, and the chart's + Kyverno check all bind to that index digest. +- `--local`: publish only to the local Docker daemon (no registry push). Not used here. +- `--annotations `: OCI annotations `key:value`. Repo uses `apko.yaml`. +- `-r, --repository-append` / `-b, --build-repository-append` / `-p, --package-append`: as in + `build`. +- `--build-date`, `--vcs`, `--offline`, `--cache-dir`, `--lockfile`, `--ignore-signatures`: + as in `build`. + +Notes: + +- Authenticates via the **Docker keychain** — run `docker login ` first. +- Accepts multiple `` positionals; the repo passes one version tag + (`${IMAGE_NAME}:${RELEASE_TAG}`). +- Pushes even while the GitHub release is a draft (GHCR has no draft state). + +## `apko show-config` + +Purpose: print the fully-derived config apko will act on (YAML). Read-only. + +Usage: + +```bash +apko show-config [flags] +``` + +Flags: `-k, --keyring-append`, `-r, --repository-append`, `-b, --build-repository-append`, +`--offline`, `--cache-dir`. No `--arch`. + +## `apko show-packages` + +Purpose: resolve and print the exact packages + versions that would install, without building. +Best preview of what the floating Wolfi base pulls right now (CVE bumps, new packages). + +Usage: + +```bash +apko show-packages [flags] +``` + +Flags that matter here: + +- `--arch `: which arch's resolution to show (accepts `host`). +- `--format `: predefined name (e.g. `name-version`, `name=version`, `packagelock`, + `packagelock-source`) or a Go template over `.Name`, `.Version`, `.Source` (default + `{{ .Name }} {{ .Version }}`). `packagelock`/`packagelock-source` emit a YAML-list-ready form. +- `-k, --keyring-append`, `-r, --repository-append`, `-b, --build-repository-append`, + `--offline`, `--cache-dir`. Append the melange pub key if you need it to resolve `@local`. + +## `apko lock` + +Exists but intentionally **not used** in this repo (see the SKILL). Writes a `.lock.json` that +pins package versions. + +Usage: + +```bash +apko lock [flags] +``` + +Flags: `--arch`, `--output ` (lockfile path), `-k, --keyring-append`, +`-r, --repository-append`, `-b, --build-repository-append`, `--cache-dir`, +`--include-paths`, `--ignore-signatures`. Do not add `apko.lock.json` to this repo. + +## `apko version` + +Purpose: print the apko version. + +Usage: + +```bash +apko version [--json] +``` + +## Other subcommands (not used here) + +`build-minirootfs`, `clean`, `dot`, `install-keys`, `login`, `completion`, `help`. `apko login` +can authenticate to a registry, but the repo relies on `docker login` + the Docker keychain. +`apko dot` (dependency digraph) and `apko clean` (cache) are occasional debugging aids. + +## Repo invocation map + +Where each apko call lives, for cross-reference: + +- `dev/image-build.sh` — `apko build apko.yaml image.tar --arch + $(go env GOARCH) --keyring-append ./melange.rsa.pub`, then `docker load` + retag. Driven by + `scripts/test-e2e.sh` (`root:test-e2e`), which then `kind load docker-image`s it. +- `.github/workflows/release-dry-run.yml` (`container-image-dry-run`) — `apko build apko.yaml + template-k8s:dry-run image.tar --arch amd64 --keyring-append ./melange-amd64.rsa.pub + --keyring-append ./melange-arm64.rsa.pub`, load + retag, `docker run --rm … --help` smoke. +- `.github/workflows/security-scan.yml` (`container-vulnerability-scan`) — `apko build + apko.yaml template-k8s:security-scan scan.tar --arch amd64 --keyring-append ./melange.rsa.pub`, + load + retag, Trivy `image` scan. +- `.github/workflows/release.yml` (`container-image-release`) — `apko publish` (multi-arch), + then registry digest resolution, cosign, syft SBOM attest, and the `attest-image` SLSA-L3 + caller. diff --git a/.agents/skills/melange/SKILL.md b/.agents/skills/melange/SKILL.md new file mode 100644 index 0000000..7d70e1c --- /dev/null +++ b/.agents/skills/melange/SKILL.md @@ -0,0 +1,144 @@ +--- +name: melange +description: > + Build the operator's signed Wolfi apk with melange. Use when editing melange.yaml or its + go/build pipeline, adding a build-time package or Go toolchain, signing the apk, or debugging + `dev/image-build.sh` or the release/dry-run `melange-build` job. This is the source-to-signed-apk + step that apko later turns into the operator's runtime image. +--- + +# Melange + +melange has exactly one job in this repo: compile the operator's controller-manager binary into +a signed [Wolfi](https://github.com/wolfi-dev) apk described by `melange.yaml`. That apk is the +only artifact apko assembles into the runtime image. There is no Dockerfile (this replaced the +former multi-stage Dockerfile). Ground every command in `--help` and the repo files below, not +memory. + +## Verified against + +- melange `v0.54.0` (GitCommit `7fb1d6a`), pinned in `mise.toml` as + `aqua:chainguard-dev/melange = "0.54.0"` and locked per-platform in `mise.lock`. Run it via + mise (`mise exec -- melange ...`) or an activated mise shell; do not install it any other way. +- Grounded in local `melange --help` / `melange build --help` and the repo files: `melange.yaml`, + `mise.toml`, `dev/image-build.sh`, `.github/workflows/release.yml`, `release-dry-run.yml`, and + `security-scan.yml`. +- Sibling skills: `mise` provisions melange and puts it on PATH; `apko` consumes the apk this + step produces. See those skills for their lanes. + +## Use this skill when + +- Editing `melange.yaml` or the `go/build` pipeline. +- Adding a build-time package or Go toolchain to the apk. +- Signing an apk or reasoning about the melange-to-apko key handoff. +- Debugging a failed `dev/image-build.sh` (local) or the release / dry-run `melange-build` job. + +## melange's lane (non-negotiable) + +1. melange does ONE thing: source to signed Wolfi apk. It does not build the image (that is + apko) and it does not push anything anywhere. +2. The apk is the single artifact. apko reads it from `./packages` via `@local`; nothing else + consumes, tags, or distributes it. +3. Never hand-edit `package.version` in `melange.yaml`. It carries `# x-release-please-version` + and release-please owns it (registered via `extra-files` in `release-please-config.json`). Do + not run `melange bump` in this repo. +4. This operator stamps NO build vars. `cmd` has no `main.version`/`commit`/`date` symbol, so + `melange.yaml` has no `vars:` block, no `--vars-file`, and no `-X` ldflags. The only ldflag is + `-buildid=` (for reproducibility). Do not add version/commit/date plumbing unless `cmd` first + grows the variables to receive it. +5. Never commit signing keys. `melange*.rsa`, `melange*.rsa.pub`, `melange-vars.yaml`, and + `/packages/` are gitignored. Keys are ephemeral, minted per build; the private key never + leaves the machine that built the apk. +6. No Dockerfile, no `RUN`, no `apt`. Build-time tools come from + `environment.contents.packages` (the `go-1.26` Wolfi package). Runtime dependencies belong in + `apko.yaml`, not here. +7. Always pass `--runner docker`. Do not rely on the platform default runner. + +## melange.yaml anatomy + +- `package`: `name: template-k8s`, `version: "0.1.2"` (release-please marker), `epoch: 0`, and a + `description`. +- `environment.contents`: the Wolfi `os` repository + its signing keyring, plus + `packages: [go-1.26]` (the build toolchain). `environment.environment.CGO_ENABLED: "0"`. +- `pipeline: - uses: go/build` with `packages: ./cmd`, `output: manager`, `go-package: go-1.26`, + `modroot: .`, `strip: "-s -w"`, `ldflags: "-buildid="`, and + `extra-args: "-mod=readonly -buildvcs=false"`. The `go/build` builtin auto-adds `-trimpath` and + installs to `/usr/bin/` — apko's entrypoint `/usr/bin/manager` depends on that path. + `--source-dir .` mounts the module so `go/build` compiles `./cmd` against the rest of the + operator's packages (`api/`, `internal/`). +- There is no `vars:` block. Unlike a version-stamped build there is nothing to override per + build; this mirrors the prior Dockerfile (`go build -o manager ./cmd`), which also stamped no + version symbols. + +## Build the apk locally + +`dev/image-build.sh` is the supported local path; it runs melange then apko and loads the image +into Docker (default tag `template-k8s:dev`, override with `IMG`). `scripts/test-e2e.sh` calls it +with the e2e image tag, and the `moon root:test-e2e` task lists it as an input. The melange +portion is: + +```bash +arch="$(go env GOARCH)" +melange keygen melange.rsa +melange build melange.yaml \ + --arch "$arch" \ + --runner docker \ + --signing-key melange.rsa \ + --source-dir . +``` + +This drops a signed apk under `./packages//` (default `--out-dir` is `./packages/`). +`` is the Wolfi arch name, not the Go arch: `amd64` to `x86_64`, `arm64` to `aarch64`. +apko then reads the whole `./packages` directory as the `@local` repository. There is no +`--vars-file`: nothing is stamped. + +## How the release / CI build differs + +`release.yml` (`melange-build`) and `release-dry-run.yml` (`melange-build-dry-run`) run the same +`melange build` invocation under a matrix, one arch per NATIVE runner — `amd64` on `ubuntu-24.04`, +`arm64` on `ubuntu-24.04-arm`. No QEMU. Differences from local: + +- Each runner mints its own ephemeral key with a distinct name: `melange keygen melange-.rsa`. +- Each runner builds with `--arch --runner docker --signing-key melange-.rsa + --source-dir .` — the same invocation as local, still no vars file. +- Each runner uploads `packages//**` plus its `melange-.rsa.pub` (artifact + `apk-`). The private key never leaves the runner; apko later trusts the apk via the + uploaded public keys. +- The dry-run job is identical to the release build except it never pushes, signs, or attests — + it just feeds `apko build` (not `apko publish`) to assemble and smoke-test the image. + +`security-scan.yml` builds only `amd64` for the Trivy scan — the same `melange build`, but it +mints a single `melange.rsa` key (not the per-arch `melange-.rsa` naming above). + +## Signing and the apko handoff + +`--signing-key` signs the apk during the build. apko must trust that signature to install the +`@local` apk, so the matching public key is appended to apko's keyring with `--keyring-append`. +Locally that is `--keyring-append ./melange.rsa.pub`; in the release/dry-run jobs apko appends both +arches' keys (`./melange-amd64.rsa.pub`, `./melange-arm64.rsa.pub`). Omit the public key and apko +rejects the apk as untrusted. See the `apko` skill. + +## Gotchas + +- `--runner docker` is required wherever melange runs: it needs a Linux build sandbox, and on + macOS/Docker Desktop the Docker runner provides it. Docker must be running. Valid runners are + `bubblewrap`, `docker`, `qemu`; this repo always uses `docker`. +- Wolfi arch name is not the Go arch in output paths: `amd64`→`x86_64`, `arm64`→`aarch64`. Match + these when globbing `packages//**`. +- melange produces an SBOM for the apk (`--namespace` sets its package-URL namespace). The + image-level SBOM (syft) and SLSA provenance (`attest.yml`) are produced later, NOT by melange. + Do not add `--generate-provenance` to chase that; the repo does not use it. +- No build vars: this operator does not stamp version/commit/date. Do not add a `vars:` block, a + `--vars-file`, or `-X` ldflags unless `cmd` first gains the variables to receive them. The lone + `-buildid=` ldflag is for reproducibility, not metadata. +- Neither local nor CI uses melange's `--build-date` flag (that controls in-image file timestamps + for reproducibility, a separate concern). +- Do not override `output:` in the pipeline without updating `apko.yaml`'s `entrypoint.command` + and `contents.packages` — the binary path `/usr/bin/manager` is contractual. +- To add a build-time tool or a different Go toolchain, edit `environment.contents.packages` + (Wolfi package names) in `melange.yaml`. Do not install inside the sandbox. + +## Command reference + +See [references/melange-commands.md](references/melange-commands.md) for the version-stamped +command and flag reference. diff --git a/.agents/skills/melange/references/melange-commands.md b/.agents/skills/melange/references/melange-commands.md new file mode 100644 index 0000000..e667925 --- /dev/null +++ b/.agents/skills/melange/references/melange-commands.md @@ -0,0 +1,155 @@ +# Melange Command Map + +Curated operator reference for `melange v0.54.0`. Prefer local `melange --help` / +`melange --help` output and the official docs (https://github.com/chainguard-dev/melange) +if anything here drifts. Every flag below is copied from the pinned version's `--help`; flags +not used by this repo are marked so. + +## Global flags + +Apply across all `melange` commands: + +- `--log-level string`: log level — `debug`, `info`, `warn`, `error` (default `INFO`). +- `-h`, `--help`: help for the command. + +## `melange build` + +Purpose: build a package (the signed apk) from a YAML configuration file. This is the repo's +core operation. + +Usage: + +```bash +melange build [config.yaml] [flags] +``` + +Flags that matter here: + +- `--arch strings`: architectures to build for (e.g. `x86_64,arm64`). The repo passes a single + Go-style arch per invocation (`amd64` or `arm64`); default is all arches in the config. +- `--runner string`: runner used to execute build steps — `bubblewrap`, `docker`, or `qemu`. + The repo always passes `docker` (required on macOS; needs Docker running). +- `--signing-key string`: key used to sign the produced apk. +- `--source-dir string`: directory of included sources mounted into the build (the repo passes + `.` so `go/build` compiles `./cmd` against the rest of the module's packages). +- `-k`, `--keyring-append strings`: extra keys to include in the build environment keyring (for + pulling from signed repositories; the apko keyring handoff is a separate concern). +- `--out-dir string`: directory packages are written to (default `./packages/`). The apk lands + in `//`, where the Wolfi arch is `x86_64`/`aarch64`, not `amd64`/`arm64`. +- `-r`, `--repository-append strings`: extra repositories for the build environment. +- `--package-append strings`: extra packages to install into the build environment. +- `--namespace string`: namespace for package URLs in the generated apk SBOM (default `unknown`). +- `--generate-index`: whether to generate `APKINDEX.tar.gz` (default `true`). +- `--cache-dir string`: cached inputs directory (default `./melange-cache/`). +- `--debug`: enable debug logging of build pipelines. +- `--debug-runner`: keep the builder container after success/failure for inspection. +- `-i`, `--interactive`: attach a tty to the builder pod on failure. + +Not used by this repo (do not add without reason): + +- `--vars-file string`: file of preloaded build vars; overrides `vars:` in the config. A + version-stamped build would inject `version`/`commit`/`date` this way, but this operator stamps + no build vars (`cmd` has no `main.version` symbol), so `melange.yaml` has no `vars:` block and + no invocation passes `--vars-file`. +- `--generate-provenance`: emits SLSA provenance as a separate `.attest.tar.gz` next to the apk. + This repo produces image-level provenance via `attest.yml` instead, so this stays off. +- `--build-date string`: timestamp for files inside the image (reproducibility). The repo leaves + this at default. + +Notes: + +- Canonical repo invocation: `melange build melange.yaml --arch --runner docker + --signing-key .rsa --source-dir .` (no `--vars-file`). +- The output public key (the `.rsa.pub` matching `--signing-key`) is handed to `apko build` / + `apko publish` via `--keyring-append` so apko trusts the `@local` apk. + +## `melange keygen` + +Purpose: generate an RSA keypair for package signing. + +Usage: + +```bash +melange keygen [key.rsa] [flags] +``` + +Flags: + +- `--key-size int`: size of the RSA key in bits (default `4096`). + +Notes: + +- Writes `.rsa` (private) and `.rsa.pub` (public). In this repo keys are ephemeral + and gitignored; the release/dry-run jobs use per-arch names (`melange-amd64.rsa`, + `melange-arm64.rsa`), while local builds and the security scan use a single `melange.rsa`. + +## `melange sign` + +Purpose: sign an existing `.apk` on disk in place with the provided key. + +Usage: + +```bash +melange sign [--signing-key=key.rsa] package.apk +melange sign [--signing-key=key.rsa] *.apk +``` + +Flags: + +- `-k`, `--signing-key string`: signing key (default `local-melange.rsa`). + +Notes: + +- The repo signs during `build` via `--signing-key`, so this standalone command is rarely + needed. Use it only to re-sign a prebuilt apk. + +## `melange sign-index` + +Purpose: sign an APK repository index (`APKINDEX.tar.gz`). + +Usage: + +```bash +melange sign-index [--signing-key=key.rsa] +melange sign-index [--signing-key=key.rsa] --force +``` + +Flags: + +- `-f`, `--force`: overwrite the existing index with a newly signed one. +- `--signing-key string`: signing key (default `melange.rsa`). + +Note: the default key name here (`melange.rsa`) differs from `sign`'s default +(`local-melange.rsa`). Not part of the repo's build flow. + +## `melange package-version` + +Purpose: print the target package id for a config, i.e. +`{{ .Package.Name }}-{{ .Package.Version }}-r{{ .Package.Epoch }}`. + +Usage: + +```bash +melange package-version [config.yaml] +``` + +Notes: + +- Read-only; useful for scripting the expected apk filename (e.g. `template-k8s-0.1.2-r0`). It is + sugar over `melange query`. + +## `melange bump` + +Purpose: update a melange YAML to a new package version. + +Notes: + +- Do NOT use in this repo. `package.version` carries `# x-release-please-version` and is owned + by release-please. Flags are not captured here; run `melange bump --help` if ever needed + outside this repo. + +## Other subcommands (present, unused here) + +`compile`, `index`, `initramfs`, `license-check`, `lint` (experimental), `query`, `scan`, +`source`, `test`, `update-cache`, `version`, `completion`. None are part of the repo's build +path; consult `melange --help` before relying on any of them. diff --git a/.agents/skills/mise/SKILL.md b/.agents/skills/mise/SKILL.md new file mode 100644 index 0000000..f872cc5 --- /dev/null +++ b/.agents/skills/mise/SKILL.md @@ -0,0 +1,220 @@ +--- +name: mise +description: > + Operate mise as the single source of truth for tool versions and integrity in + template-k8s. Use when touching mise.toml or mise.lock, bumping or adding a + pinned tool (go, moon, golangci-lint, controller-gen, kubebuilder, setup-envtest, + kubectl, helm, chainsaw, ko, tilt, ctlptl, kind, melange, apko, cosign), + resolving "command not found"/PATH problems, fixing locked/trust failures, or + wiring mise into moon, the CI workflow, or the local dev/image-build tasks. +--- + +# mise + +mise owns the lifecycle of every pinned tool and the project's tool-related env in +this repo. It replaced Proto (`.prototools`, `.moon/proto/*`) and the `.envrc` +direnv shim activation. Treat `mise.toml` + `mise.lock` as the only place a +toolchain version is declared; everything else (moon, CI, the container image +build, the Kind dev stack) consumes what mise puts on PATH. + +## Verified against + +- `mise 2026.6.14` (`macos-arm64`, build 2026-06-25), grounded in the captured + `--help` for + `install/use/ls/lock/exec/run/trust/outdated/upgrade/settings/current/activate/env/which` + and `mise doctor --help`, plus this repo's `mise.toml`, `mise.lock`, `moon.yml`, + `.moon/toolchains.yml`, and `.github/workflows/ci.yml`. +- Advice is grounded in the local CLI and these files, not memory. Re-verify on a + mise minor/major bump. + +## Use this skill when + +- Bumping or adding a tool, or reviewing a diff that touches `mise.toml`/`mise.lock`. +- A tool is missing from PATH, or `mise install` fails closed under `locked`. +- mise prompts for trust (commonly inside a `.wt/` worktree that nests under the repo). +- Explaining how moon, `ci.yml`, `moon run root:dev-up`, or `moon run root:test-e2e` + get their binaries. + +## mise's lane (non-negotiables) + +mise manages **tool + env lifecycle only**. State these as rules: + +1. mise is **not the task runner and not the CI gate** — that is moon. Do not move + build/lint/test/codegen into mise tasks. +2. **This repo defines no mise `[tasks]`.** mise is purely `[tools]`, `[env]`, and + `[settings]`. The local conveniences — the melange/apko container image build and + the Kind dev stack — are **moon** tasks (`root:dev-up`, `root:dev-down`, + `root:test-e2e`) that run mise-provisioned binaries. Do not add general-purpose + mise tasks. +3. **Every tool an engineer needs goes through mise.** Never `go install`, + `go tool`, `brew install`, `apt`, `npm -g`, `cargo install`, or a manual + download for project tooling. Add it to `[tools]` and `mise lock` instead. +4. **Force the verifying backend.** Pin CLIs with an explicit `aqua:` ref, e.g. + `"aqua:kyverno/chainsaw" = "0.2.15"`. A bare short name (`chainsaw`, `kubectl`, + `helm`) is not in mise's curated registry and/or resolves through a backend with + no recorded checksum — always use the explicit `aqua:/` ref so the + tool lands with a pinned URL + checksum in `mise.lock`. The one deliberate + exception is `controller-gen` (see rule 5). +5. **`controller-gen` is the only non-aqua tool**, pinned via the **go: backend** + (`"go:sigs.k8s.io/controller-tools/cmd/controller-gen" = "0.21.0"`) because no + aqua package exists. Its integrity comes from the **Go module checksum database** + (`go.sum`/sumdb), not from a `mise.lock` URL+checksum. Keep it on the go: backend; + do not try to invent an aqua ref for it. +6. **Bump = edit `mise.toml`, then `mise lock`, then commit both together.** Never + hand-edit `mise.lock` (`# @generated`) except the one documented moon macos-x64 + workaround below, and never commit one file without the other. + +## How mise is wired here + +`mise.toml`: + +- `[tools]`: `go = "1.26.3"` (core backend, authoritative, matches `go.mod`'s + `go 1.26.3`); `controller-gen` via the **go: backend** (version-only lock entry); + and fourteen CLIs pinned via explicit `aqua:` refs, grouped by job: + - task runner / CI gate: `moonrepo/moon` + - lint: `golangci/golangci-lint` + - Kubebuilder operator toolchain: `kubernetes-sigs/kubebuilder`, + `kubernetes-sigs/controller-runtime/setup-envtest` (deeper `owner/repo/subpath` + aqua ref), `kubernetes/kubernetes/kubectl`, `helm/helm`, `kyverno/chainsaw` + - local Kind dev stack: `ko-build/ko`, `tilt-dev/tilt`, `tilt-dev/ctlptl`, + `kubernetes-sigs/kind` + - supply-chain / image build: `chainguard-dev/melange`, `chainguard-dev/apko`, + `sigstore/cosign` +- `[env] GOTOOLCHAIN = "local"`: never auto-download a Go toolchain other than the + pinned one; matches `go.mod`'s `go 1.26.3`. mise `[env]` is **not** carried by the + CI action's shims, so `ci.yml` also sets `GOTOOLCHAIN: local` at job level — keep + both in sync. +- `[settings] lockfile = true` (read/write `mise.lock`) and `locked = true` (the + integrity gate; equivalent to the `--locked` flag / `MISE_LOCKED=1`). + +moon consumes mise, it does not duplicate it: `.moon/toolchains.yml` declares no +language toolchain and `moon.yml` sets `toolchains.default: system`, so every moon +task command is a bare binary (`go`, `golangci-lint`, `controller-gen`, +`setup-envtest`, `chainsaw`, `helm`, `kubectl`, `ctlptl`, `tilt`, `ko`, `kind`) +resolved from PATH. `moon.yml` also collects `mise.toml` + `mise.lock` into the +`toolchainConfig` fileGroup and lists it as an input of every task. Under the +`system` toolchain moon does **not** hash a tool binary's version, so those pins are +the task inputs that force a re-run when a tool bumps (every task here runs +`cache: false`, so this is purely re-trigger, not result-cache invalidation). See +the `worktrunk` skill for worktree mechanics, the `k8s-operator` skill for the Moon +task surface, and the `melange`/`apko` skills for the image build those pinned tools +feed. + +CI (`.github/workflows/ci.yml`) installs via +`jdx/mise-action@…v4.2.0 with: version: 2026.6.14, cache: true`. The action installs +every tool from `mise.toml` honoring `mise.lock` (locked → fail closed), including +`moon`, and prepends the shim dir to PATH so moon's `system` tasks find the +binaries. CI uses mise-action, **not** `moonrepo/setup-toolchain`. + +## The lockfile, precisely + +`mise.lock` is `# @generated`. Per tool it records a `[[tools.""]]` block +(`version`, `backend`) and one `[tools.""."platforms."]` table for each +of the four platforms: `linux-x64`, `linux-arm64`, `macos-x64`, `macos-arm64`. + +- Every platform entry carries a `url`. **`locked = true` requires a pre-resolved + `url` per platform** and fails closed otherwise (per `mise install --help`: it + prevents API calls to GitHub/aqua at install time). +- Every aqua platform entry in this repo also carries an enforced + `checksum = "sha256:…"`. There is no missing-checksum exception here — do not + assume a tool may legitimately ship without one. +- A subset additionally records verification provenance, reflecting what the aqua + registry applies for that tool. In this repo: `provenance = "github-attestations"` + on `golangci-lint`; `provenance = "cosign"` on `cosign`; and + `[…"platforms.".provenance.slsa]` subtables on `ko` and `chainsaw`. The + remaining tools (`go`, `moon`, `kubebuilder`, `setup-envtest`, `kubectl`, `helm`, + `tilt`, `ctlptl`, `kind`, `melange`, `apko`) carry a pinned `url` + `checksum` but + no `provenance` field. Do **not** claim every tool is attestation/SLSA/cosign + verified; the always-on guarantees are the pinned `url` and `checksum`. +- **`controller-gen` is the single tool whose integrity lives outside `mise.lock`.** + Its block is `version` + `backend` only — no platform tables, no `url`, no + `checksum` — because the go: backend builds it from source and verifies against the + Go checksum database. `mise lock` will not (and should not) write platform + url/checksum rows for it. + +### moon macos-x64 lockfile quirk + +`mise lock` resolves moon's `macos-x64` artifact but does **not** persist its +`[tools."aqua:moonrepo/moon"."platforms.macos-x64"]` table — a mise write quirk. +That table is **hand-added** (with an inline comment) from moon's published v2.3.5 +`.sha256` sidecar. This is the one sanctioned hand-edit to `mise.lock`. After any +re-lock that touches moon, confirm the macos-x64 entry is still present and re-add it +from the upstream `.sha256` if it disappeared, before committing. + +## Bumping a tool (the canonical operation) + +```bash +# 1. edit the version in mise.toml (keep the aqua: / go: ref) +# 2. re-resolve url/checksum for all four platforms +mise lock --platform linux-x64,linux-arm64,macos-x64,macos-arm64 +# 3. commit mise.toml + mise.lock together +``` + +- `mise outdated` (add `--bump` to see latest across major lines, `-J` for JSON) + shows what could move before you decide. +- `mise upgrade --bump` is the one-shot equivalent (edits `mise.toml` and + re-locks), but the repo's committed convention is the explicit edit + `mise lock` + so the version change is a reviewable diff. +- After locking, confirm all four platform tables are present for each changed aqua + tool, and **re-check the moon macos-x64 entry** (it may have been dropped); do not + ship a partial lock entry. +- Bumping `controller-gen` is just the version edit in `mise.toml`; `mise lock` + records only its version/backend (no platform rows), and integrity follows from the + Go proxy/sumdb on next install. + +## Adding a tool + +1. Add `"aqua:/" = ""` to `[tools]` in `mise.toml` (or, only + when no aqua package exists, a `"go:" = ""` go: entry). +2. `mise lock --platform linux-x64,linux-arm64,macos-x64,macos-arm64` to populate + url/checksum for all platforms (a no-op for a go: entry beyond version/backend). +3. If a moon task uses it, it is already covered by the `toolchainConfig` input + fileGroup; add the tool's binary to any narrower task input only if needed. +4. `mise install` locally to materialize it, then commit `mise.toml` + `mise.lock`. + +## Worktree trust gotcha + +`.wt/` worktrees nest **under** the repo, so mise's upward config search loads both +the worktree's `mise.toml` and the parent checkout's `mise.toml` +(`/Users/josh/code/meigma/template-k8s/mise.toml` exists). When mise prompts, trust +both: + +```bash +mise trust --all # trust this dir and its parents +mise trust --show # inspect trust status without changing it +``` + +## Inspection / read-only ops + +```bash +mise ls # installed + active tool versions (-J for JSON) +mise current # active versions only, script-friendly +mise which controller-gen # resolved bin path; --version for just the version +mise which chainsaw +mise outdated # what could bump +mise doctor # diagnose install/PATH problems (doctor path prints PATH) +mise exec -- kubebuilder version # run a pinned tool ad hoc, no shell activation +``` + +## Gotchas + +- `mise install` installs but does **not** activate — tools are not on PATH until + `mise activate` runs in the shell, or you go through `mise exec` / shims. CI relies + on mise-action prepending the shim dir; locally use `eval "$(mise activate zsh)"` + once, or prefix one-off commands with `mise exec --`. +- `mise.local.toml` / `.mise.local.toml` are gitignored per-developer overrides. + Never commit them and never put project pins there — project pins belong in the + committed `mise.toml`. +- The moon `macos-x64` lock entry is the one hand-maintained row in `mise.lock` + (see above). A re-lock can silently drop it; re-add it before committing. +- `controller-gen` is the only go: backend tool. Treat the absence of platform + url/checksum rows for it as correct, not as an incomplete lock. +- There are no mise tasks here. The container image build (`melange.yaml`/`apko.yaml` + via `dev/image-build.sh`) and the Kind dev stack (`ctlptl` + `tilt` + `ko` + `kind`) + run through moon (`root:test-e2e`, `root:dev-up`, `root:dev-down`). See the + `melange`/`apko` and `k8s-operator` skills. + +## Command reference + +See [references/mise-commands.md](references/mise-commands.md) for the version-stamped +command and flag map. diff --git a/.agents/skills/mise/references/mise-commands.md b/.agents/skills/mise/references/mise-commands.md new file mode 100644 index 0000000..34c4960 --- /dev/null +++ b/.agents/skills/mise/references/mise-commands.md @@ -0,0 +1,266 @@ +# mise Command Map + +Curated operator reference for `mise 2026.6.14` (`macos-arm64`, build 2026-06-25). +Prefer local `--help` output and the official docs (https://mise.jdx.dev) if anything +here drifts. Every flag below is taken from this version's `--help`; do not invent +flags. + +## Global flags + +These appear on `mise` itself and on most subcommands: + +- `-C, --cd `: change directory before running. +- `-E, --env `: load `mise..toml` for this invocation. +- `-j, --jobs `: parallelism (`MISE_JOBS`). Root default 8; install/use/exec/run default 4. +- `-q, --quiet`: suppress non-error messages. +- `-v, --verbose...`: extra output (`-vv` for more). +- `-y, --yes`: answer yes to all prompts. +- `--locked`: require pre-resolved lockfile URLs for the current platform; fail + otherwise. Also via `MISE_LOCKED=1` or `settings.locked = true` (set in this repo). +- `--raw`: read/write directly to stdio (implies `--jobs=1` for backends). +- `--silent`: suppress all task output and mise non-error messages. + +Root-only options worth knowing: `--no-config` (`MISE_NO_CONFIG=1`), `--no-env` +(`MISE_NO_ENV=1`), `--no-hooks` (`MISE_NO_HOOKS=1`). + +Tool refs are `TOOL@VERSION`; backend-qualified tools use a prefix, e.g. +`aqua:kyverno/chainsaw`, `go:sigs.k8s.io/controller-tools/cmd/controller-gen`. + +## Tool lifecycle + +### `mise install` (alias `i`) + +Purpose: install tool versions to `~/.local/share/mise/installs/...`. Does **not** +activate — installed tools are not on PATH until `mise activate`/`exec`/shims. + +Usage: + +```bash +mise install # install everything in mise.toml (honors mise.lock) +mise install go@1.26.3 # install a specific version +``` + +Flags that matter here: + +- `-f, --force`: reinstall even if present. +- `-n, --dry-run`: show what would install. `--dry-run-code`: same but exit 1 if work remains. +- `--locked`: require lockfile URLs (already on via `settings.locked`). +- `-v, --verbose`: show backend download/build output. + +Notes: with `locked = true`, install fails closed if any tool lacks a pre-resolved +URL for the current platform (prevents GitHub/aqua API calls). `controller-gen` +(go: backend) is exempt from the URL requirement — it is built from source and +verified via the Go checksum database. + +### `mise lock` + +Purpose: update lockfile checksums and URLs for specified platforms. Operates on the +lockfile in the current config root. If no lockfile exists, prints what would be created. + +Usage: + +```bash +mise lock --platform linux-x64,linux-arm64,macos-x64,macos-arm64 # repo canonical +mise lock chainsaw # only one tool +mise lock --dry-run +``` + +Flags that matter here: + +- `-p, --platform `: comma-separated platforms (`linux-x64,linux-arm64,macos-x64,macos-arm64`). + If omitted, only platforms already present in the lockfile are refreshed. +- `[TOOL]...`: limit to named tools; default is all tools in the lockfile. +- `-n, --dry-run`: preview without writing. +- `-g, --global`: target global config lockfiles instead of the project root. +- `--local`: update `mise.local.lock` (for `.local.toml` configs) — not used here. + +Notes: this is the second half of every tool bump. Always pass all four platforms so +CI runners and both local archs stay covered, then confirm all four tables landed for +changed aqua tools before committing. Two repo-specific caveats: (1) `mise lock` does +not persist moon's `macos-x64` table — that row is hand-maintained from moon's +published `.sha256` and may need re-adding after any re-lock; (2) for `controller-gen` +(go: backend) `mise lock` records only version/backend, no platform url/checksum. + +### `mise use` (alias `u`) + +Purpose: install a tool and write its version into a config file. Writes `mise.toml` +by default (lowest-precedence file). + +Usage: + +```bash +mise use --pin "aqua:owner/repo@1.2.3" +``` + +Flags that matter here: + +- `--pin`: write the exact version (vs `--fuzzy`, the default). +- `-g, --global`: write to `~/.config/mise/config.toml` instead of the project. +- `--remove `: drop a tool from config. +- `-p, --path `: target a specific config file/dir. +- `-n, --dry-run` / `--dry-run-code`. + +Notes: the repo convention is to hand-edit `mise.toml` (preserving the `aqua:`/`go:` +ref) and run `mise lock`, so the version change is a clean reviewable diff. If you do +use `mise use`, keep the explicit verifying backend and follow with `mise lock`. + +### `mise upgrade` (alias `up`) + +Purpose: upgrade installed tools. By default stays within the `mise.toml` range and +updates `mise.lock`. + +Usage: + +```bash +mise upgrade kubectl +mise upgrade --bump # also rewrite mise.toml to the latest +``` + +Flags that matter here: + +- `-l, --bump`: bump the version in `mise.toml` to latest (keeps precision) and re-lock. +- `-n, --dry-run` / `--dry-run-code`. +- `-x, --exclude `: skip a tool. +- `-i, --interactive`: multiselect menu. + +Notes: convenient, but the committed bump workflow is explicit edit + `mise lock`. + +## Running tools + +### `mise exec` (alias `x`) + +Purpose: run a command with mise tools on PATH without modifying the shell. + +Usage: + +```bash +mise exec -- chainsaw version +mise exec go@1.26.3 -- go version # override one tool ad hoc +``` + +Flags that matter here: + +- `--` separates tool args from the command to run. +- `-c, --command `: command as a string. +- `--no-deps`: skip automatic dependency preparation. +- Sandbox flags exist (`--deny-all`, `--deny-net`, `--allow-read `, etc.) but + are not part of this repo's flow. + +### `mise run` (alias `r`) + +Purpose: run mise tasks. **This repo defines no mise tasks** — `mise.toml` is +`[tools]`/`[env]`/`[settings]` only. Day-to-day build/test/lint and the local +conveniences (image build, Kind dev stack) all go through **moon** (`moon run +root:check`, `root:test`, `root:test-e2e`, `root:dev-up`, `root:dev-down`), not mise. +The flags below are retained only for completeness. + +Flags: + +- `-f, --force`: run even if task outputs are up to date. +- `-n, --dry-run`: print execution order without running. +- `--skip-deps`: run only the named task, skipping dependencies. +- `--skip-tools`: do not auto-install tools first. +- `-t, --tool `: add a tool for this run. +- `-o, --output `: `prefix|interleave|replacing|timed|keep-order|quiet|silent`. + +## Inspection + +### `mise ls` (alias `list`) + +Purpose: list tools mise knows about (installed and/or config-declared). + +Flags: `-c, --current` (only config-specified), `-i, --installed`, `-l, --local`, +`-g, --global`, `-m, --missing`, `--outdated`, `--prunable`, `-J, --json`, +`--no-header`. + +### `mise current` + +Purpose: print active versions only, script-friendly (`.tool-versions` style). +Optional `[PLUGIN]` argument narrows to one tool. + +### `mise outdated` + +Purpose: show tools with newer versions available. + +Flags: `-l, --bump` (compare against latest across major lines, not just the +configured range), `--local`, `--inactive`, `-J, --json`, `--no-header`. + +### `mise which` + +Purpose: show the resolved path for a tool's binary. + +Usage: + +```bash +mise which controller-gen +mise which kubectl --version +``` + +Flags: `-t, --tool `, `--plugin` (print backend/plugin name), +`--version` (print version instead of path). + +### `mise doctor` (alias `dr`) + +Purpose: diagnose installation/PATH problems. Subcommand `mise doctor path` prints +the PATH entries mise provides. Flag: `-J, --json`. + +## Trust and config + +### `mise trust` + +Purpose: mark config files as trusted so mise will parse them. Needed when a config +uses templates/tool options or sits in a discovery path that requires approval — in +this repo, commonly the parent `mise.toml` seen from a nested `.wt/` worktree. + +Usage: + +```bash +mise trust --all # trust current dir and all parents +mise trust --show # show trust status without changing it +mise trust --untrust # revoke; --ignore to skip a config in future +``` + +Notes: configs that contain only `min_version`, plain `[tools]` strings, and plain +`[tasks]` load without a trust prompt; templates or tool options require trust. + +### `mise settings` + +Purpose: view/manage settings (this repo sets `lockfile = true`, `locked = true`). + +Usage: + +```bash +mise settings # list active settings +mise settings get lockfile +``` + +Subcommands: `add`, `get`, `ls`, `set`, `unset`. Flags: `-a, --all`, `-J, --json`, +`-T, --toml`, `-l, --local`. + +## Shell activation + +### `mise activate` + +Purpose: initialize mise in the current shell (PATH or shims). For rc files. + +Usage: + +```bash +eval "$(mise activate zsh)" +``` + +Flags: `[SHELL_TYPE]` one of `bash|zsh|fish|nu|xonsh|elvish|pwsh`; `--shims` (use +shims instead of mutating PATH); `--no-hook-env` (debugging). + +### `mise env` (alias `e`) + +Purpose: export env vars to activate mise once, without a persistent `activate`. + +Usage: + +```bash +eval "$(mise env -s zsh)" +``` + +Flags: `-s, --shell `, `-D, --dotenv`, `-J, --json`, `--json-extended`, +`--values`. diff --git a/AGENTS.md b/AGENTS.md index 6dd3a7e..9e4569c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,6 +30,12 @@ controllers, RBAC markers, envtest coverage, Chainsaw tests, or operator task wiring. That skill captures the local operator practices this repository expects agents to follow. +Load `.agents/skills/mise/SKILL.md` before changing pinned tool versions, +`mise.toml`/`mise.lock`, or moon's system toolchain. Load +`.agents/skills/melange/SKILL.md` and `.agents/skills/apko/SKILL.md` before +changing the container image build (`melange.yaml`/`apko.yaml`), its keyless +signing, or its SBOM/provenance. + ## Development Workflow mise is the tool front door: it provisions every pinned binary (Go, Moon, and diff --git a/DELETE_ME.md b/DELETE_ME.md index ec09b7f..c768b4a 100644 --- a/DELETE_ME.md +++ b/DELETE_ME.md @@ -285,11 +285,21 @@ that shape, trim the release files before the first release. - `.github/workflows/release.yml` - Update `IMAGE_NAME`, `CHART_NAME`, `CHART_REF`, and `CHART_REPOSITORY`. - Update binary smoke-test names and temp file names. - - Update OCI labels, especially image title and description. - - Update Docker cache scopes. - Update Helm chart paths, rendered-output assertions, install examples, and release inspection summary commands. +- `melange.yaml` + - Update `package.name`, `description`, and the `go/build` `packages` (`./cmd`) + and `output` (`manager`) if the binary entrypoint or name changes. + - `package.version` is bumped by Release Please (extra-files); keep the + `x-release-please-version` marker. + +- `apko.yaml` + - Update `entrypoint.command` (`/usr/bin/`), the `@local` package name, + and the `org.opencontainers.image.*` annotations (title, description, source). + - Keep the nonroot `accounts` (uid/gid 65532) unless the operator needs a + different runtime user. `version` is bumped by Release Please. + - `.github/workflows/attest.yml` - The reusable workflow that signs binary/image/chart provenance in isolation (SLSA Build L3). It has no project-specific identifiers, but it IS the signer @@ -298,12 +308,12 @@ that shape, trim the release files before the first release. - `.github/workflows/release-dry-run.yml` - Update image and chart refs. - - Update binary validation names, dry-run image names, OCI archive names, - cache scopes, chart paths, and rendered-output assertions. + - Update binary validation names, dry-run image names, chart paths, and + rendered-output assertions. - `.github/workflows/security-scan.yml` - - Update local scan image tag, Docker cache scope, scan image ref, and SARIF - category if the category should include the project name. + - Update the local scan image tag, scan image ref, and SARIF category if the + category should include the project name. - `.github/workflows/release-please.yml` - Confirm release app variable and secret names. @@ -400,6 +410,12 @@ that shape, trim the release files before the first release. - Keep the skill concise and workflow-oriented. Move deeper reference material under `references/` only if it becomes necessary. +- `.agents/skills/mise/`, `.agents/skills/melange/`, `.agents/skills/apko/` + - Repo-local tooling skills for the mise toolchain and the melange + apko image + build. Update tool lists, image names, and the `./cmd` / `/usr/bin/manager` + paths if the generated repository renames the binary or changes the toolset; + otherwise they carry over unchanged. + - `.session.md` and lifecycle skills under `.agents/skills/session-*` - Keep these files unless the generated repository will not use the local journal/session protocol.