Files
cozystack/.github/workflows/build-release.yaml
T
IvanHunters 95e68bad86 ci(workflows): use a literal match in the flux version guards
The guard ran `grep -qx "flux: v${FLUX_VERSION}"`, where the dots in the
version are regex metacharacters, so a client printing `flux: v2X8X6`
would satisfy it. Use `grep -Fqx` for a fixed-string match in the build
toolchain guards this change added.

Also correct the step comments that still read "install if absent
(idempotent)": the step now installs whenever the pinned version is not
the one present, not only when flux is missing.

Assisted-by: LLM
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
2026-09-26 14:53:58 +03:00

165 lines
7.7 KiB
YAML

name: Build release line
# Publishes the per-line equivalent of what build-main.yaml publishes for main:
# every image tagged with the line's branch name, and the whole packages tree as
# the `cozystack-packages:<branch>` OCI artifact with each image reference
# digest-pinned to the images this run just pushed (`make build` ->
# packages/core/installer image-packages does the `flux push artifact`).
#
# Why it exists: pull-requests.yaml overlays image refs for every package a PR
# did NOT rebuild, so that e2e and the installer do not test last-release images
# for everything outside the PR's build matrix. That overlay had only
# `cozystack-packages:main` to read, which meant a release-line PR was handed
# MAIN's binaries to run against its own line's charts — a cross-generation mix
# that failed deterministically (see #3437: main's cozystack-controller served an
# aggregated OpenAPI release-1.6's charts could not validate against). With this
# artifact published per line, the overlay reads the PR's own base branch and the
# generations match.
#
# Cost: one full `make build` per push to a maintained line, i.e. per merged
# backport. That is the price of release-line PRs testing their line's tip rather
# than its last release.
env:
# Same per-CI registry as pull-requests.yaml and build-main.yaml.
REGISTRY: iad.ocir.io/idyksih5sir9/cozystack
on:
push:
# Maintained LINE branches only: `release-<major>.<minor>`. The `+` quantifies
# the preceding character class, and `.` is literal, so this matches
# release-1.6 but NOT the per-release staging branches promote-rc.yaml creates
# (release-1.6.1) nor rc staging branches (release-1.6.0-rc.4) — building
# those would be pure waste, since their images are built by tags.yaml.
branches: ['release-[0-9]+.[0-9]+']
paths-ignore:
- 'docs/**'
# Run against a line on request. The push trigger above cannot cover the moment
# a line is BORN: cut-prerelease.yaml creates release-X.Y at a commit that is
# already on main, so that push carries no new commits, and GitHub does not run
# a workflow whose `paths`/`paths-ignore` filter finds no changed files. Without
# a dispatch the line would have no `cozystack-packages:<line>` artifact until
# its first cherry-pick merged, and until then pull-requests.yaml's overlay
# would find nothing to pull and leave every package on its committed ref — at
# freeze time, the PREVIOUS release's, which is the cross-generation mix #3437
# fixed. The freeze step dispatches this workflow for the branch it just made.
#
# Also the recovery path when a line build fails or is cancelled: re-run it
# without having to push an empty commit to the line.
workflow_dispatch:
# Per line: a newer push to the same line supersedes an in-flight build, but
# different lines build independently.
concurrency:
group: build-release-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build-line:
name: Build ${{ github.ref_name }} images and packages artifact
runs-on: oracle-vm-24cpu-96gb-x86-64
timeout-minutes: 120
permissions:
contents: read
packages: write
steps:
# workflow_dispatch takes ANY ref, and the "Run workflow" ref selector
# preselects the default branch — which is exactly where the recovery
# advice above, and cut-prerelease.yaml's warning, send an operator. Since
# IMAGE_TAG below is github.ref_name, a dispatch from 'main' spends two
# hours republishing cozystack-packages:main and every main image tag while
# build-main.yaml may be writing the same ones: the 409 tag-collision class
# #2711 fixed. Nothing is mislabeled (the content really is that ref), but
# a runner and a cache generation are gone.
#
# The push trigger is already filtered to release-X.Y; this closes the
# dispatch path to the same set, and keeps holding if that filter is ever
# widened. Anchored at both ends on purpose: unanchored, the per-release
# staging branches (release-1.6.1, release-1.6.0-rc.4) would qualify, and
# those are built by tags.yaml. Fails rather than skips — a skipped job
# reads in the run list like one that produced the artifact.
- name: Refuse a build for a non-line ref
env:
REF_NAME: ${{ github.ref_name }}
REF_TYPE: ${{ github.ref_type }}
run: |
if [ "$REF_TYPE" != "branch" ] \
|| ! printf '%s' "$REF_NAME" | grep -qE '^release-[0-9]+\.[0-9]+$'; then
echo "::error::Refusing to build '$REF_NAME' ($REF_TYPE). This workflow publishes cozystack-packages:<ref> and every image tagged <ref>, so it may only run for a maintained release line (release-X.Y)."
echo "::error::Pick the release-X.Y branch in the 'Run workflow' ref selector. To build main, use build-main.yaml."
exit 1
fi
echo "Building line '$REF_NAME'."
- name: Checkout code
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
fetch-tags: true
# This workflow holds packages:write and an OCIR push token; it never
# pushes git, so don't leave the checkout's GITHUB_TOKEN in .git/config
# where a later build step could read it.
persist-credentials: false
# Ephemeral runners lack the flux CLI the installer's image-packages step
# shells out to; install the pinned version when it is not already present.
- name: Set up build toolchain
env:
FLUX_VERSION: "2.8.6"
run: |
if ! flux version --client 2>/dev/null | grep -Fqx "flux: v${FLUX_VERSION}"; then
install_script="$(mktemp)"
curl -fsSL https://fluxcd.io/install.sh -o "$install_script"
sudo env "FLUX_VERSION=$FLUX_VERSION" bash "$install_script"
rm -f "$install_script"
fi
- name: Set up Docker config
run: |
if [ -d ~/.docker ]; then
cp -r ~/.docker "${{ runner.temp }}/.docker"
fi
- name: Login to OCIR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ secrets.OCIR_USER }}
password: ${{ secrets.OCIR_TOKEN }}
registry: iad.ocir.io
env:
DOCKER_CONFIG: ${{ runner.temp }}/.docker
# Same isolated buildkit as build-main.yaml, so a line build never contends
# with PR builds on the shared embedded builder.
- name: Set up Buildx (docker-container driver)
id: buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
driver: docker-container
env:
DOCKER_CONFIG: ${{ runner.temp }}/.docker
- name: Build line images and publish the packages artifact
run: make build
env:
DOCKER_CONFIG: ${{ runner.temp }}/.docker
BUILDER: ${{ steps.buildx.outputs.name }}
# READ the shared mode=max cache, never write it. CACHE_REGISTRY/<img>:
# buildcache is a single ref per image and build-main.yaml is
# deliberately its only writer, serialized, precisely so concurrent
# builds cannot race on the cache manifest (the 409 collision class
# #2711 fixed for image tags). A line build can overlap a main build, so
# writing here would reintroduce exactly that race.
WRITE_CACHE: '0'
# Floating per-line handle: images and the packages artifact both land
# under the branch name (e.g. :release-1.6). Versioned and :latest tags
# belong to releases (tags.yaml / finalize), never to this workflow.
IMAGE_TAG: ${{ github.ref_name }}
PUBLISH_VERSIONED: '0'
PUBLISH_FLOATING: '0'