Files
egress-proxies-operator/docs/plans/2026-08-11-1802-bake-commit-version.md

3.6 KiB

Plan: Bake the git commit into the operator binary and log it at startup

Created: 2026-08-11 18:02

Context

There is no versioning yet, and the image tag (egress-proxies-operator:dev, imagePullPolicy: IfNotPresent) says nothing about what code is actually running. The user wants the commit hash baked into the image at build time, and — the key requirement — the binary itself must know it and print it into the log stream during initialization, so kubectl logs | head answers "which version is running". Docker builds cannot use Go's automatic VCS stamp because .dockerignore excludes .git (correctly — re-including it would bust layer caching), so the hash must travel git → Makefile → --build-arg-ldflags -X.

Implementation

1. New package internal/version (version.go + version_test.go):

  • var Commit string — stamped at link time via -ldflags "-X gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/version.Commit=<hash>".
  • func Resolve() string — returns Commit if non-empty; otherwise falls back to debug.ReadBuildInfo() VCS settings (vcs.revision truncated to 12 chars, -dirty suffix when vcs.modified=true), so plain host builds (make build, make run, go run) are stamped for free since .git is present there; "unknown" when neither source is available (e.g. go test).
  • Internal resolve(ldflagsCommit string, readBuildInfo func() (*debug.BuildInfo, bool)) string so the fallback logic is table-testable with a fake build-info func (house style: stdlib testing, t.Parallel(), subtests, Test<Function>_<scenario> names).

2. cmd/main.go:

  • Add --version bool flag; immediately after the existing flag.Parse() (main.go:132), if set: print version.Resolve() to stdout and exit 0 (before logger/manager setup).
  • Right after ctrl.SetLogger(...) (main.go:134): setupLog.Info("Starting egress-proxies-operator", "commit", version.Resolve(), "goVersion", runtime.Version()) — first line of every run, plain V(0) so it appears at any log level.

3. Makefile:

  • Near the other variables: GIT_COMMIT ?= $(shell git rev-parse --short=12 HEAD 2>/dev/null || echo unknown)$(shell test -z "$$(git status --porcelain 2>/dev/null)" || echo -dirty) (git status --porcelain catches staged and untracked changes, which git diff --quiet misses).
  • docker-build: add --build-arg GIT_COMMIT=$(GIT_COMMIT).
  • docker-buildx: add the same --build-arg to the buildx build line.
  • build/run/run-dev stay untouched — the ReadBuildInfo fallback covers them.

4. Dockerfile:

  • ARG GIT_COMMIT=unknown in the builder stage; extend the existing go build with -ldflags "-X <module>/internal/version.Commit=${GIT_COMMIT}".
  • Re-declare ARG GIT_COMMIT in the distroless stage and add LABEL org.opencontainers.image.revision="${GIT_COMMIT}" so the hash is also visible via docker inspect without running the binary.

No changes to deploy manifests, providers, or the reconciler. CHANGELOG entry after the user confirms it works (house convention).

Verification

go test ./internal/version/            # resolve() table tests
go build ./... && go test ./...        # nothing else broke
go run ./cmd/main.go --version         # host build: VCS-stamped hash (+ -dirty), exit 0
make docker-build IMG=egress-proxies-operator:dev
docker run --rm egress-proxies-operator:dev --version          # prints the baked commit
docker inspect egress-proxies-operator:dev \
  --format '{{index .Config.Labels "org.opencontainers.image.revision"}}'

Live check: redeploy on the cluster and confirm the first log line carries "commit".