fix(docker): report the real version and tree state in published images - #1137
Conversation
|
Claude finished @mbevc1's task in 6m 28s —— View job Review: PR #1137 — docker version/tree-state fix
Static analysis
No Go code changed, as expected — the whole fix is in the build path. Verification I ran against this commit
On your reply — "Would miss the original failure"Agreed, and it's the right call. Deriving Findings1. Release path still leans on tag auto-following — the caller can make it deterministic (medium) — inline
2. The arm64 image is never version-checked (medium, unresolved from the previous round)
3. Nothing enforces the The header says "keep the two in sync" and today they are, but only a human maintains that. A new 4. Re-run false-failure (low, accepted risk) Re-running a 5. Nit: What's good here
Net: the fix is correct and well-verified for the amd64 path. The arm64 blind spot (2) is the one I'd want closed before this is the thing you rely on to catch a regression. |
50bc4c5 to
2cf8f84
Compare
Published container images reported themselves as `dev+<sha>` with GitTreeState `dirty`, so `kosli version` inside a container could not name the release it was running. Two independent causes, both in the Docker build path rather than in the Go code: - The build context was a deny-all allow-list keeping only cmd/, internal/, Makefile, go.* and .git/. With .git/ present but 153 tracked files absent, `git status --porcelain` reported every excluded file as deleted and the Makefile's GIT_DIRTY resolved to "dirty". Topping the allow-list up was not an option: the tracked tree has 43 distinct top-level entries, so a correct allow-list names all 43, and any new top-level file would silently make images "dirty" again. Reversed to a deny-list of git-ignored artefacts. Measured cost: 1.10 MiB of extra context (6.17 -> 7.27 MiB of tracked files), against the 2.20 MiB .git/ the allow-list already shipped. - The build job checked out with fetch-depth: 3 and no fetch-tags, which per actions/checkout fetches no tags at all, so `git describe --exact-match` found nothing, BINARY_VERSION was empty and the version ldflag was skipped entirely, leaving the "dev" default in internal/version/version.go. Both failures were silent: an empty `BINARY_VERSION` omits the ldflag rather than failing, so a release build looked identical to a dev build, and `GIT_DIRTY` cannot distinguish an edited file from a missing one. Nothing ever read the published image's version back. This was affecting releases since at least May 2024. A `version` smoke case now asserts the image reports exactly the tag it is published under (`dev+<sha>` for non-release builds) from a clean tree, so neither cause can return unnoticed. Fixes #1133
Review follow-ups: - `.dockerignore` was missing six `.gitignore` entries: `/reporter`, `/merkely`, `kosli.yaml`, `merkely.yaml`, `pipe.json` and `*~`. The two yaml files can hold an API token, and `COPY . .` would carry them into the builder stage on a local build. None is tracked at any depth, and no tracked file matches any of the 27 resulting patterns. - `test_version` asserted Version and GitTreeState but not GitCommit. The merge job checks out the same ref as the build job, so HEAD is the built commit and the assertion costs nothing. It closes the case where the context is wholly an older commit, which is self-consistent and would otherwise read as clean.
With the allow-list gone, an untracked file left in the workspace reaches the build context and reads as dirty, alongside the excluded-tracked-path case the header already described. Comment only.
actions/checkout leaves an x-access-token extraheader in .git/config. The build job copies .git/ into the build context and cache-to mode=max exports every layer to the public buildcache ref; the merge job mounts its workspace into the smoke-test container. Nothing in either job talks to git over HTTP after checkout. Pre-existing, not introduced by the .dockerignore change.
2cf8f84 to
71d952f
Compare
|
Done. Correction accepted on the drift point. Not taking: arm64. The version mechanic is arch-independent Deferred: an explicit |
… tags The docker image derived its version from `git describe` inside the build, while the smoke test inferred the expected version from the image tag. Those two disagree whenever a release tag is cut at the same commit as an in-flight push-to-main pipeline: the build picks up the new tag and reports v<x.y.z>, the test still expects dev+<sha>, and the version smoke test fails. Resolve the version once in the `prepare` job and feed it to both sides — the build bakes it in as a VERSION build arg, the smoke test asserts the image reports it. This is what #1133 originally suggested; #1137 closed that issue with `fetch-tags: true` instead, which is what let a stray tag reach the build. The build job no longer fetches tags, so the image is a function of the build request rather than of whichever tags the checkout happens to have by the time it runs. The Makefile needs `origin` rather than `ifdef` to tell "VERSION not passed" (local build, fall back to the tag at HEAD) from "VERSION passed empty" (CI, not a release). Metadata clearing moves from GIT_TAG to BINARY_VERSION so an explicit version is self-consistent: `make build VERSION=v2.39.0` on an untagged tree previously produced v2.39.0+<sha>, matching neither goreleaser nor the smoke test. `make docker` passes the tag at HEAD so a local image build at a release tag still reports it; a bare `docker build .` reports dev+<sha>. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqXZamA5LHB3b3hGbBEig1
… tags The docker image derived its version from `git describe` inside the build, while the smoke test inferred the expected version from the image tag. Those two disagree whenever a release tag is cut at the same commit as an in-flight push-to-main pipeline: the build picks up the new tag and reports v<x.y.z>, the test still expects dev+<sha>, and the version smoke test fails. Resolve the version once in the `prepare` job and feed it to both sides — the build bakes it in as a VERSION build arg, the smoke test asserts the image reports it. This is what #1133 originally suggested; #1137 closed that issue with `fetch-tags: true` instead, which is what let a stray tag reach the build. The build job no longer fetches tags, so the image is a function of the build request rather than of whichever tags the checkout happens to have by the time it runs. The Makefile needs `origin` rather than `ifdef` to tell "VERSION not passed" (local build, fall back to the tag at HEAD) from "VERSION passed empty" (CI, not a release). Metadata clearing moves from GIT_TAG to BINARY_VERSION so an explicit version is self-consistent: `make build VERSION=v2.39.0` on an untagged tree previously produced v2.39.0+<sha>, matching neither goreleaser nor the smoke test. `make docker` passes the tag at HEAD so a local image build at a release tag still reports it; a bare `docker build .` reports dev+<sha>.
… tags The docker image derived its version from `git describe` inside the build, while the smoke test inferred the expected version from the image tag. Those two disagree whenever a release tag is cut at the same commit as an in-flight push-to-main pipeline: the build picks up the new tag and reports v<x.y.z>, the test still expects dev+<sha>, and the version smoke test fails. Resolve the version once in the `prepare` job and feed it to both sides — the build bakes it in as a VERSION build arg, the smoke test asserts the image reports it. This is what #1133 originally suggested; #1137 closed that issue with `fetch-tags: true` instead, which is what let a stray tag reach the build. The build job no longer fetches tags, so the image is a function of the build request rather than of whichever tags the checkout happens to have by the time it runs. The Makefile needs `origin` rather than `ifdef` to tell "VERSION not passed" (local build, fall back to the tag at HEAD) from "VERSION passed empty" (CI, not a release). Metadata clearing moves from GIT_TAG to BINARY_VERSION so an explicit version is self-consistent: `make build VERSION=v2.39.0` on an untagged tree previously produced v2.39.0+<sha>, matching neither goreleaser nor the smoke test. `make docker` passes the tag at HEAD so a local image build at a release tag still reports it; a bare `docker build .` reports dev+<sha>.
… tags (#1140) * fix(docker): build the image version from the workflow input, not git tags The docker image derived its version from `git describe` inside the build, while the smoke test inferred the expected version from the image tag. Those two disagree whenever a release tag is cut at the same commit as an in-flight push-to-main pipeline: the build picks up the new tag and reports v<x.y.z>, the test still expects dev+<sha>, and the version smoke test fails. Resolve the version once in the `prepare` job and feed it to both sides — the build bakes it in as a VERSION build arg, the smoke test asserts the image reports it. This is what #1133 originally suggested; #1137 closed that issue with `fetch-tags: true` instead, which is what let a stray tag reach the build. The build job no longer fetches tags, so the image is a function of the build request rather than of whichever tags the checkout happens to have by the time it runs. The Makefile needs `origin` rather than `ifdef` to tell "VERSION not passed" (local build, fall back to the tag at HEAD) from "VERSION passed empty" (CI, not a release). Metadata clearing moves from GIT_TAG to BINARY_VERSION so an explicit version is self-consistent: `make build VERSION=v2.39.0` on an untagged tree previously produced v2.39.0+<sha>, matching neither goreleaser nor the smoke test. `make docker` passes the tag at HEAD so a local image build at a release tag still reports it; a bare `docker build .` reports dev+<sha>. * fix(docker): harden the version classifier against silent mis-stamping Address review feedback on the version build-arg change. The Makefile now honours VERSION only as a command-line assignment. `origin` reports `environment` for an inherited variable, so an ambient VERSION in the caller's shell beat the tag at HEAD — and with metadata cleared it stamped a fake clean release. The Dockerfile passes VERSION on the command line, so the one caller that needs the override still has it. The tag classifier in `prepare` is anchored at both ends: the value reaches single-quoted ldflags, so a tag carrying a quote or whitespace is no longer treated as a release. Build arg and smoke-test expectation come from one workflow output, so nothing was checking the mapping itself — a misclassification would make both halves agree on the wrong version and pass. The smoke test now re-derives the release case from the image tag: a tag matching the looser ^v[0-9] must carry its own version, so a tag the strict classifier rejects fails loudly instead of being published as dev+<sha>. * fix(docker): close both nits in the version classifier `origin` returns "environment override" when the makefile assigns VERSION and `-e` makes the environment win. `filter` matched the trailing `override` and treated that as a command-line assignment, re-opening the hole the comment above it claims to close. `filter-out` keys on the leftover word instead, so only a genuine command-line or override-directive assignment counts. The Makefile does not assign VERSION today, so this is not currently reachable — it stops a later `VERSION ?=` default from quietly restoring the bug. The smoke-test tripwire only checked one direction: a release tag with a wrong or empty version. The mirror case — a sha tag carrying a release version, which would publish a dev image reporting v<x.y.z> — passed. Derive the expectation from the image tag once and compare, covering both. * fix(docker): report the published tag as the image version Drop the release classifier. The image now reports whatever tag it is published under — a release version from release.yml, a short sha from main.yml — so the build arg and the smoke-test expectation are the same expression and cannot drift. That removes the prepare output, both tag regexes and the smoke-test tripwire they needed. Non-release images now report the sha instead of dev+<sha>, so the update check keys on the v prefix a release always has rather than on the "dev" string that no longer appears. It skips for dev builds, dev+<sha> and shas like, and now does so before the HTTP call rather than after it. EXPECTED_VERSION is always set by CI, so the smoke test requires it and asserts equality; its dev+<sha> branch is gone. * fix(docker): mark non-release images as dev+<tag> Resolve the version once in prepare rather than passing the tag straight through: a v-prefixed tag is reported as-is, anything else becomes dev+<tag>. Both the build arg and the smoke-test expectation read that one output, so the baked version and the asserted one still cannot drift. Branch images therefore keep reporting dev+<sha> exactly as before, which makes the update check's existing "dev" prefix test correct again — so the v-prefix change to update_check.go and its test are reverted. The match is deliberately loose. This repo cuts -rc tags, which the stricter ^v[0-9]+\.[0-9]+\.[0-9]+$ used in install-script-tests.yml would misread as dev builds.
## Summary Adds the changelog entry for CLI **v2.39.2**, plus the Docker image version fix from v2.39.0 that was missing. Rebased on `main` after #376 merged. #376 had already documented v2.39.0 and v2.39.1, so this PR is now purely additive — no deletions. ## Conflict resolution Four conflicts in `changelog/index.mdx`, resolved as follows: - **v2.39.2** — new, kept as generated. Placed above the August 31 Platform entry, keeping the file newest-first. - **v2.39.0 and v2.39.1 prose** — kept the versions already on `main` from #376 and discarded this branch's rewordings, since they had been through review and use the correct command name (see below). - **The August 24–28 Platform entries** — kept `main`'s; this branch predated them. ## The Docker version fix This branch carried a Docker version bullet on both v2.39.0 and v2.39.1, and `main` had neither. Checking upstream, only one of them is a user-facing fix: - **v2.39.0** ([cli#1137](kosli-dev/cli#1137)) — the real fix. Published images identified themselves as `dev+<sha>` instead of their release tag, so `kosli version` in a container could not name the release. Two causes in the Docker build path, both silent. Any image published before v2.39.0 is affected. **Kept**, reworded around that symptom. - **v2.39.1** ([cli#1140](kosli-dev/cli#1140)) — not a user-facing fix. It resolves the version once in the workflow instead of via `git describe` in the build, superseding cli#1137's `fetch-tags` approach, and fixes a smoke-test flake when a release tag is cut at the same commit as an in-flight main pipeline. Its own description says images "report what they did before … except the racy case". **Dropped** as an internal build change. That also removes the problem of two consecutive releases each claiming to fix the image version with different causes. Both bullets appeared only in the releases' "What's Changed" lists rather than their curated sections, which is likely why the first pass missed them. ## One correction worth noting This branch described the v2.39.1 help-text change as affecting `kosli snapshot azure-apps`. That name comes from the upstream release note, but the command is `kosli snapshot azure` — the generated reference page is `client_reference/kosli_snapshot_azure.md` and there is no `azure-apps` page. `main`'s wording is correct and links the reference; that is what was kept. ## Checks - `mint broken-links` — clean apart from the pre-existing `/getting_started/service-accounts` break in `tutorials/working_with_controls.mdx`, which is on `main` and untouched here. - `<Update>` open/close tags balance at 117 each; no empty headings; entries remain in newest-first date order. --------- Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: Dan Grøndahl <dan@kosli.com>
Published container images reported themselves as
dev+<sha>with GitTreeStatedirty, sokosli versioninside a container could not name the release it was running. Two independent causes, both in the Docker build path rather than in the Go code:The build context was a deny-all allow-list keeping only cmd/, internal/, Makefile, go.* and .git/. With .git/ present but 153 tracked files absent,
git status --porcelainreported every excluded file as deleted and the Makefile's GIT_DIRTY resolved to "dirty". Topping the allow-list up was not an option: the tracked tree has 43 distinct top-level entries, so a correct allow-list names all 43, and any new top-level file would silently make images "dirty" again. Reversed to a deny-list of git-ignored artefacts. Measured cost: 1.10 MiB of extra context (6.17 -> 7.27 MiB of tracked files), against the 2.20 MiB .git/ the allow-list already shipped.The build job checked out with fetch-depth: 3 and no fetch-tags, which per actions/checkout fetches no tags at all, so
git describe --exact-matchfound nothing, BINARY_VERSION was empty and the version ldflag was skipped entirely, leaving the "dev" default in internal/version/version.go.Both failures were silent: an empty
BINARY_VERSIONomits the ldflag rather than failing, so a release build looked identical to a dev build, andGIT_DIRTYcannot distinguish an edited file from a missing one. Nothing ever read the published image's version back. This was affecting releases since at least May 2024.A
versionsmoke case now asserts the image reports exactly the tag it is published under (dev+<sha>for non-release builds) from a clean tree, so neither cause can return unnoticed.Fixes #1133
Checklist
charts/k8s-reporter/) updated, if needed. Note: these changes live in a separate PR