CI: leave :latest to the newest series once 27.1 ships (release/2026) #240

Merged
jcoffey-dev merged 1 commits from ci/latest-follows-the-newest-series into release/2026 2026-10-29 19:02:59 +00:00
2 changed files with 69 additions and 13 deletions

No files matched your search

+31 -6
View File
@@ -37,8 +37,10 @@
# fixes through pull requests into it, and is tagged there, so production
# can get a fix without everything that has landed on main since.
#
# :latest moves with every published tag: tags are cut by the weekly release
# (or by hand for a real release); there are no prerelease tags here.
# :latest moves with a published tag while 2026.x is the newest series. Once
# a final release numbered by series exists (v27.1, v27.1.3), a 2026.x
# security release from release/2026 leaves :latest where it is (the version
# job's `latest` output).
#
# The push logs in with PACKAGE_TOKEN (jcoffey-dev, write:package): the job's
# own token is refused by the container registry.
@@ -64,6 +66,7 @@ jobs:
image: python:3.13-slim@sha256:8d9d0b8bcf6506481eae4907c18f5e3e7902e629f5f6d684f9e7c32e85e3ddf0 # 3.13-slim
outputs:
version: ${{ steps.v.outputs.version }}
latest: ${{ steps.latest.outputs.latest }}
steps:
# Full history: the ancestry check cannot be answered from a shallow
# clone. The checkout also fetches every branch as origin/*.
@@ -95,6 +98,22 @@ jobs:
echo "$TAG is on $on"
echo "version=$V" >> "$GITHUB_OUTPUT"
echo "version $V"
# inbuxa: :latest belongs to the newest series; see the top of this file
- id: latest
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 - <<'EOF'
import json, os, re, urllib.request
url = (f"{os.environ['CI_SERVER_INTERNAL']}/api/v1/repos/{os.environ['GITHUB_REPOSITORY']}"
"/releases?draft=false&pre-release=false&limit=50")
req = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['TOKEN']}"})
newer = [r["tag_name"] for r in json.load(urllib.request.urlopen(req))
if re.match(r"v[0-9]{1,3}\.", r["tag_name"])]
print(f"{newer[0]} is out: :latest stays on its series" if newer else "no series release yet: :latest moves")
with open(os.environ["GITHUB_OUTPUT"], "a") as out:
out.write(f"latest={'false' if newer else 'true'}\n")
EOF
publish-amd64:
if: ${{ vars.BUILD_ON != 'github' }}
@@ -150,6 +169,7 @@ jobs:
REGISTRY: ${{ vars.REGISTRY }}
IMAGE: ${{ vars.REGISTRY }}/${{ github.repository }}
VERSION: ${{ needs.version.outputs.version }}
LATEST: ${{ needs.version.outputs.latest }}
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
@@ -165,9 +185,11 @@ jobs:
--provenance=false --sbom=false \
--tag "$IMAGE:$VERSION-arm64" \
--push .
if [ "$LATEST" = true ]; then latest="--tag $IMAGE:latest"; else latest=""; fi
# $latest unquoted on purpose: empty, or two words
docker buildx imagetools create \
--tag "$IMAGE:$VERSION" \
--tag "$IMAGE:latest" \
$latest \
"$IMAGE:$VERSION-amd64" "$IMAGE:$VERSION-arm64"
docker buildx imagetools inspect "$IMAGE:$VERSION"
- if: always()
@@ -222,6 +244,7 @@ jobs:
env:
TAG: ${{ github.ref_name }}
VERSION: ${{ needs.version.outputs.version }}
LATEST: ${{ needs.version.outputs.latest }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
REGISTRY: ${{ vars.REGISTRY }}
@@ -237,9 +260,11 @@ jobs:
except urllib.error.HTTPError as e:
if e.code != 404: raise
image = f"{os.environ['REGISTRY']}/{os.environ['REPO']}:{version}"
body = (f"Container image: `{image}` (linux/amd64, linux/arm64); also `:latest`. "
"amd64 is published first; arm64 is added to the same tag when its build "
"finishes, and `:latest` moves then.\n\n"
latest = os.environ["LATEST"] == "true"
body = (f"Container image: `{image}` (linux/amd64, linux/arm64)"
+ ("; also `:latest`. " if latest else ". `:latest` stays on the newer series. ")
+ "amd64 is published first; arm64 is added to the same tag when its build finishes"
+ (", and `:latest` moves then.\n\n" if latest else ".\n\n")
"Binaries for a host install are attached: `inbuxa-linux-amd64.tar.gz` and "
"`inbuxa-linux-arm64.tar.gz`, with `SHA256SUMS`. Each is the binary out of this "
"release's image for that architecture, so it is the same build. The image "
+38 -7
View File
@@ -176,6 +176,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.v.outputs.version }}
latest: ${{ steps.latest.outputs.latest }}
steps:
# Full history, and every branch as origin/*: the ancestry check cannot
# be answered from a shallow clone.
@@ -205,6 +206,26 @@ jobs:
[ -n "$on" ] || { echo "$TAG is not on main or a release/* branch" >&2; exit 1; }
echo "$TAG is on $on"
echo "version=$V" >> "$GITHUB_OUTPUT"
# inbuxa: :latest belongs to the newest series. Until 27.1 ships it is
# 2026.x's; once a final release numbered by series exists (v27.1,
# v27.1.3), a 2026.x security release from this branch leaves :latest,
# GHCR's :latest and the GitHub "latest release" where they are. The
# check fails rather than guess: a wrong :latest is worse than a re-run.
- id: latest
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
newer="$(curl -fsS --retry 5 --retry-all-errors --retry-delay 10 -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY/releases?draft=false&pre-release=false&limit=50" |
jq -r '[.[].tag_name | select(test("^v[0-9]{1,3}\\."))] | first // ""')"
if [ -n "$newer" ]; then
echo "$newer is out: :latest stays on its series"
echo "latest=false" >> "$GITHUB_OUTPUT"
else
echo "no series release yet: :latest moves"
echo "latest=true" >> "$GITHUB_OUTPUT"
fi
# Each architecture on its own native runner, side by side. The Dockerfile
# cross-compiles from the build platform, and on the self-hosted runners one
@@ -273,6 +294,7 @@ jobs:
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.version.outputs.version }}
LATEST: ${{ needs.version.outputs.latest }}
steps:
- run: echo "IMAGE=${{ vars.REGISTRY }}/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4.4.0
@@ -282,9 +304,9 @@ jobs:
username: jcoffey-dev
password: ${{ secrets.GITEA_TOKEN }}
- run: |
docker buildx imagetools create \
--tag "$IMAGE:$VERSION" \
--tag "$IMAGE:latest" \
tags=(--tag "$IMAGE:$VERSION")
[ "$LATEST" = true ] && tags+=(--tag "$IMAGE:latest")
docker buildx imagetools create "${tags[@]}" \
"$IMAGE:$VERSION-amd64" "$IMAGE:$VERSION-arm64"
docker buildx imagetools inspect "$IMAGE:$VERSION"
# Gitea keeps a container package on its owner; linking it shows it on
@@ -308,6 +330,7 @@ jobs:
- env:
TAG: ${{ github.ref_name }}
VERSION: ${{ needs.version.outputs.version }}
LATEST: ${{ needs.version.outputs.latest }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
REGISTRY: ${{ vars.REGISTRY }}
run: |
@@ -317,7 +340,9 @@ jobs:
if [ "$code" = 200 ]; then echo "$TAG already has a release"; exit 0; fi
[ "$code" = 404 ] || { echo "looking up the release for $TAG answered $code" >&2; exit 1; }
image="$REGISTRY/${GITHUB_REPOSITORY,,}:$VERSION"
body="Container image: \`$image\` (linux/amd64, linux/arm64); also \`:latest\`.
also="; also \`:latest\`."
[ "$LATEST" = true ] || also=". \`:latest\` stays on the newer series."
body="Container image: \`$image\` (linux/amd64, linux/arm64)$also
Binaries for a host install are attached: \`inbuxa-linux-amd64.tar.gz\` and \`inbuxa-linux-arm64.tar.gz\`, with \`SHA256SUMS\`. Each is the binary out of this release's image for that architecture, so it is the same build. The image grants it \`cap_net_bind_service\`; a host install has to grant that itself (\`setcap\`, or \`AmbientCapabilities\` in the unit) to bind port 25."
jq -n --arg tag "$TAG" --arg name "INBUXA $VERSION" --arg body "$body" \
@@ -398,13 +423,16 @@ jobs:
- env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.version.outputs.version }}
LATEST: ${{ needs.version.outputs.latest }}
run: |
set -euo pipefail
src="${{ vars.REGISTRY }}/${GITHUB_REPOSITORY,,}"
dst="ghcr.io/${GITHUB_REPOSITORY,,}"
tag="$TAG"
echo "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
docker buildx imagetools create -t "$dst:$tag" -t "$dst:latest" "$src:$tag"
tags=(-t "$dst:$tag")
[ "$LATEST" = true ] && tags+=(-t "$dst:latest")
docker buildx imagetools create "${tags[@]}" "$src:$tag"
want="$(docker buildx imagetools inspect "$src:$tag" --format '{{json .Manifest.Digest}}')"
got="$(docker buildx imagetools inspect "$dst:$tag" --format '{{json .Manifest.Digest}}')"
echo "registry $src:$tag = $want"
@@ -420,7 +448,7 @@ jobs:
# rewritten to Gitea links: on GitHub a bare #16 is some other PR.
github-release:
if: ${{ vars.BUILD_ON == 'github' && github.ref_type == 'tag' }}
needs: [binaries]
needs: [version, binaries]
runs-on: ubuntu-latest
permissions:
contents: write
@@ -428,6 +456,7 @@ jobs:
GITEA_URL: ${{ vars.GITEA_URL }}
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
LATEST: ${{ needs.version.outputs.latest }}
steps:
- run: |
set -euo pipefail
@@ -454,7 +483,9 @@ jobs:
curl -fsSL -o "files/$name" "$url"; files+=("files/$name")
done < <(jq -r '.assets[]? | [.name, .browser_download_url] | @tsv' rel.json)
title="$(jq -r '.name // ""' rel.json)"; [ -n "$title" ] || title="$TAG"
if [ "$(jq -r .prerelease rel.json)" = true ]; then kind=--prerelease; else kind=--latest; fi
if [ "$(jq -r .prerelease rel.json)" = true ]; then kind=--prerelease
elif [ "$LATEST" = true ]; then kind=--latest
else kind=--latest=false; fi
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$title" \
--notes-file notes.md "$kind" "${files[@]}"
echo "created the GitHub release for $TAG with ${#files[@]} file(s)"