Drive the pipeline through make, the way riksdata and rd-web do
CI / chart (push) Successful in 0s
CI / test (push) Successful in 57s

Both workflows restated the build in YAML: gofmt, go vet and go test inline
in two places, buildx inline in a third, and the chart's version sed'd into
Chart.yaml before packaging. The Makefile added in 1081260 then described
the same checks a second time for local use, which made "green locally means
green in CI" a promise about keeping two files in step rather than a
property of the setup.

riksdata and rd-web never had that problem — their workflows call make and
have done all along. This repo was the odd one out, and only because it had
no Makefile until today. Now ci.yaml runs `make fmt lint test`, release.yaml
runs the same plus `make binaries`, `make push`, `make helm-package` and
`make helm-push`, and the reasoning behind each check lives on the target
rather than in whichever YAML file was edited last.

Three things change rather than just move:

The chart is linted before it is published. release.yaml packaged and pushed
without ever rendering the templates, so a chart that did not compile would
have reached the registry and been found by Flux. ci.yaml gained a chart job
for the same reason.

helm package --version --app-version replaces the sed. The published
metadata is identical, but the tree is no longer mutated mid-build, and it
is what the rest of the release process already assumed happened.

`make push` refuses VERSION=dev. Publishing is one command now, so it is
also one command to run by accident; dev is not a version anyone releases.

Deliberately not moved: uploading the release assets. Compiling them is
`make binaries` and runs anywhere, but the upload needs a token and the
Gitea release API, which is the workflow's business and not something worth
a target.

`push` builds and pushes in one step, unlike riksdata's separate build and
push, because buildx cannot load a multi-platform image into the local store
— it can only push it. `build` stays single-platform and local-only.

Claude-Session: https://claude.ai/code/session_01S7R4gWTz5wh5xCY4nCSJjN
This commit is contained in:
Niklas Ye
2026-09-01 22:36:54 +02:00
parent 6a4f902e38
commit 69fcc24a4d
4 changed files with 145 additions and 95 deletions
+84
View File
@@ -82,3 +82,87 @@ release-vars: ## Print the variables the release process reads
# the multi-arch tag, which is both easy to do by accident and invisible afterwards
# — the tag would still resolve, just not on arm64. Publishing happens by pushing a
# tag; nothing else.
## --- publishing ---
#
# These exist so .gitea/workflows/release.yaml can call `make release` instead of
# restating the build in YAML, the way riksdata and rd-web already do. One definition
# of how this is built and published, runnable locally, reviewable in a diff.
#
# VERSION is the git tag, passed in by the workflow. The guard below is why a stray
# local `make release` cannot publish: dev is not a version anyone releases.
VERSION ?= dev
# Helm requires strict SemVer — strip a leading 'v' if present.
CHART_VERSION := $(shell echo "$(VERSION)" | sed 's/^v//')
PLATFORMS ?= linux/amd64,linux/arm64
BUILDX_BUILDER ?= terdut
# --pull, not --no-cache: refresh the base image without discarding the layer cache.
DOCKER_BUILD_FLAGS ?= --pull
# An isolated repo list. The machine-wide one is not this build's business, and one
# unreachable entry in it aborts otherwise-fine helm commands — there is a dead
# TrueCharts repo on this host that does exactly that. HELM_REPOSITORY_CACHE is
# deliberately NOT overridden alongside it: helm writes a refreshed index to the default
# cache and then looks for it in the overridden one.
HELM_ISOLATED = HELM_REPOSITORY_CONFIG=$(CURDIR)/.helm-repos.yaml
.PHONY: require-version
require-version:
@test "$(VERSION)" != "dev" || \
(echo "refusing to publish VERSION=dev — pass VERSION=vX.Y.Z (the workflow passes the tag)" && exit 1)
.PHONY: build
build: ## Build the image for this host only, without pushing (local check / CI smoke)
docker build $(DOCKER_BUILD_FLAGS) \
--build-arg VERSION=$(VERSION) \
-t $(IMAGE):$(VERSION) .
# Multi-arch, so unlike riksdata and rd-web this cannot be a separate build then push:
# buildx cannot load a multi-platform result into the local image store, it can only
# push it. `build` above stays single-platform and local-only for that reason.
#
# No QEMU: the Dockerfile's builder stage runs on $$BUILDPLATFORM and cross-compiles from
# TARGETARCH, so both platforms build natively. The default "docker" driver cannot build
# more than one platform at a time; the docker-container driver can.
.PHONY: push
push: require-version ## Build and publish the multi-arch image
docker buildx create --name $(BUILDX_BUILDER) --use 2>/dev/null || docker buildx use $(BUILDX_BUILDER)
docker buildx build \
--platform $(PLATFORMS) \
--build-arg "VERSION=$(VERSION)" \
--tag "$(IMAGE):latest" \
--tag "$(IMAGE):$(VERSION)" \
--push .
# --version and --app-version come from the tag, so Chart.yaml's own fields decide nothing
# about what is published. They used to be rewritten in place with sed before packaging;
# the flags do the same job without mutating the tree mid-build.
.PHONY: helm-package
helm-package: require-version ## Package the chart, versioned from the tag
$(HELM_ISOLATED) helm package $(HELM_CHART) \
--version $(CHART_VERSION) \
--app-version $(VERSION) \
--destination dist
.PHONY: helm-push
helm-push: require-version ## Push the packaged chart to the OCI registry
$(HELM_ISOLATED) helm push dist/terdut-server-$(CHART_VERSION).tgz $(HELM_REPO)
.PHONY: binaries
binaries: require-version ## Cross-compile the release binaries into dist/
@mkdir -p dist
@set -eu; for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do \
GOOS="$${target%/*}"; GOARCH="$${target#*/}"; \
out="dist/terdut-$(VERSION)-$${GOOS}-$${GOARCH}"; \
echo "building $$out"; \
GOOS="$$GOOS" GOARCH="$$GOARCH" go build \
-ldflags "-w -s -X main.version=$(VERSION)" \
-o "$$out" ./cmd/terdut; \
done
.PHONY: release
release: push helm-package helm-push ## Publish image + chart (the workflow's one call)