Skip to content

release

release #13

Workflow file for this run

name: release
# Everything a release publishes, driven by the tag release-please pushes.
# Nothing here decides a version — the tag already did.
#
# Publishes:
# * CLI binaries attached to the GitHub release: linux and macOS for
# amd64 and arm64, Windows for amd64 only (there is no meaningful
# arm64 Windows demand for this CLI)
# * a multi-arch container image, to Docker Hub and GHCR
# * the Helm chart, to GHCR as an OCI artifact
# * the protobuf schemas, to the Buf Schema Registry
#
# Every job re-runs the end-to-end suite's ingredients implicitly by using
# the same Dockerfile CI already exercised; nothing is built here that CI
# has not already built on main.
on:
push:
tags: ["v*.*.*"]
# For rebuilding a release whose publish step failed. The tag has
# already been cut, so this is safe to re-run.
workflow_dispatch:
inputs:
tag:
description: "Existing tag to build (e.g. v0.3.0)"
required: true
permissions:
contents: write
packages: write
env:
CARGO_TERM_COLOR: always
jobs:
binaries:
name: cli (${{ matrix.target }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
# gnu rather than musl for the x86_64 Linux build would tie the
# binary to the glibc of whatever built it. A CLI that people
# download and run on an unknown distro should not care.
- target: x86_64-unknown-linux-musl
runner: ubuntu-latest
- target: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
- target: x86_64-apple-darwin
runner: macos-latest
- target: aarch64-apple-darwin
runner: macos-latest
- target: x86_64-pc-windows-msvc
runner: windows-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag || github.ref }}
# build.rs stamps the commit into the binary, so the checkout
# has to be a real git repository.
fetch-depth: 0
- name: Install protoc
shell: bash
run: |
if [ "$RUNNER_OS" = "macOS" ]; then
brew install protobuf
elif [ "$RUNNER_OS" = "Windows" ]; then
choco install protoc -y
else
sudo apt-get update && sudo apt-get install -y protobuf-compiler musl-tools
fi
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: build
shell: bash
run: |
# A reproducible build stamp: the same tag built twice produces
# the same binary rather than differing by a timestamp. Derived
# from the built commit itself, as a plain Unix epoch integer —
# `github.event.repository.pushed_at` is an ISO 8601 string in
# this event context (and workflow_dispatch reruns have no push
# event at all), and cc/ring reject anything but a decimal int.
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)
export SOURCE_DATE_EPOCH
cargo build --release --locked --target ${{ matrix.target }} -p silo-cli
- name: package
shell: bash
run: |
VERSION="${{ inputs.tag || github.ref_name }}"
NAME="silo-${VERSION}-${{ matrix.target }}"
mkdir -p "dist/$NAME"
BIN="target/${{ matrix.target }}/release/silo"
[ "${{ matrix.target }}" = "x86_64-pc-windows-msvc" ] && BIN="$BIN.exe"
cp "$BIN" "dist/$NAME/"
cp README.md LICENSE "dist/$NAME/"
# tar rather than zip even for the Windows build: bsdtar ships in
# every image this matrix uses (Windows 10 1803+ included), so
# one archive format covers all three OSes with no extra tool.
tar -C dist -czf "dist/$NAME.tar.gz" "$NAME"
# A checksum per artifact rather than one combined file: each is
# verifiable on its own, without downloading the others. shasum
# is Perl and macOS-only; sha256sum (coreutils) is what Linux and
# Git Bash on Windows both ship instead.
if [ "$RUNNER_OS" = "macOS" ]; then
(cd dist && shasum -a 256 "$NAME.tar.gz" > "$NAME.tar.gz.sha256")
else
(cd dist && sha256sum "$NAME.tar.gz" > "$NAME.tar.gz.sha256")
fi
- name: the binary reports the version it was tagged with
shell: bash
run: |
VERSION="${{ inputs.tag || github.ref_name }}"
# Only the native-arch Linux and macOS builds can run here; the
# cross-built one is checked by the job that matches its arch.
if [ "${{ matrix.target }}" = "x86_64-apple-darwin" ] && [ "$(uname -m)" = "arm64" ]; then
echo "cross-built for x86_64 on arm64; skipping the run check"
exit 0
fi
BIN="target/${{ matrix.target }}/release/silo"
[ "${{ matrix.target }}" = "x86_64-pc-windows-msvc" ] && BIN="$BIN.exe"
reported=$("$BIN" --version | awk '{print $2}')
if [ "v$reported" != "$VERSION" ]; then
echo "::error::binary reports $reported but the tag is $VERSION"
exit 1
fi
echo "silo $reported"
- uses: actions/upload-artifact@v7
with:
name: silo-${{ matrix.target }}
path: dist/*.tar.gz*
if-no-files-found: error
attach:
needs: binaries
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
pattern: silo-*
path: dist
merge-multiple: true
- name: attach binaries to the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ inputs.tag || github.ref_name }}" dist/* \
--repo "${{ github.repository }}" --clobber
image-binaries:
name: image binaries (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: amd64
target: x86_64-unknown-linux-musl
runner: ubuntu-latest
- arch: arm64
target: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag || github.ref }}
fetch-depth: 0
- name: Install protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler musl-tools
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: image-${{ matrix.target }}
- name: build
run: |
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)
export SOURCE_DATE_EPOCH
cargo build --release --locked --target ${{ matrix.target }} -p silo-server -p silo-cli
# Staged under linux/<arch> so it matches buildx's TARGETPLATFORM
# exactly, and Dockerfile.release can COPY it with no arch mapping.
- name: stage for the image build
run: |
mkdir -p "image-bin/linux/${{ matrix.arch }}"
cp target/${{ matrix.target }}/release/silo-server \
target/${{ matrix.target }}/release/silo \
"image-bin/linux/${{ matrix.arch }}/"
- uses: actions/upload-artifact@v7
with:
name: image-bin-${{ matrix.arch }}
path: image-bin
image:
needs: image-binaries
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag || github.ref }}
fetch-depth: 0
- uses: actions/download-artifact@v8
with:
pattern: image-bin-*
path: image-ctx
merge-multiple: true
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: version
id: version
shell: bash
run: |
# docker/metadata-action's type=semver tags read the git ref,
# not the commit — they need GITHUB_REF to be refs/tags/vX.Y.Z.
# This job only ever runs via workflow_dispatch (release-please
# dispatches it because a GITHUB_TOKEN-pushed tag cannot trigger
# a workflow itself), where GITHUB_REF is refs/heads/main. So
# type=semver silently matched nothing and every push only ever
# got tagged `latest`. Computing the version ourselves and using
# type=raw sidesteps the ref-detection entirely.
VERSION="${{ inputs.tag || github.ref_name }}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "major_minor=${VERSION%.*}" >> "$GITHUB_OUTPUT"
- name: Docker metadata
id: meta
uses: docker/metadata-action@v6
with:
# Both registries, because Docker Hub's pull limits make GHCR
# the better default for a Kubernetes cluster.
images: |
${{ secrets.DOCKERHUB_USERNAME }}/silo
ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=${{ steps.version.outputs.major_minor }}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v7
with:
context: image-ctx
file: Dockerfile.release
# arm64 is not optional: it is what Graviton and Apple silicon
# clusters run. Both binaries are cross-compiled natively by the
# image-binaries job above (real arm64/amd64 runners, no QEMU),
# so this step only copies a prebuilt binary into the image
# rather than compiling Rust under emulation.
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
chart:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag || github.ref }}
- uses: azure/setup-helm@v5
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
- name: the chart version matches the tag
run: |
TAG="${{ inputs.tag || github.ref_name }}"
CHART=$(grep '^version:' charts/silo/Chart.yaml | awk '{print $2}')
if [ "v$CHART" != "$TAG" ]; then
echo "::error::chart version $CHART does not match tag $TAG"
exit 1
fi
- name: Package and push chart
run: |
helm package charts/silo -d .cr-release-packages
# OCI references must be lowercase; the org name is not
# guaranteed to be.
owner=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
helm push .cr-release-packages/silo-*.tgz "oci://ghcr.io/$owner/charts"
protos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.tag || github.ref }}
- uses: bufbuild/buf-action@v1
with:
token: ${{ secrets.BUF_TOKEN }}
input: proto
push: true
# There is no pull request on a tag build, but the action would
# still try; saying so keeps it from needing a permission this
# job has no other use for.
pr_comment: false