Files
silo/.github/workflows/release.yml
T
Feng Ruohang 6740e6978f ci: move the workflow actions onto the Node 24 runtime
actions/checkout@v4 and actions/setup-go@v5 declare runs.using: node20, so
every Go CI job printed the runner's Node 20 deprecation warning. The
release, test-release and docker-release workflows carried six more node20
actions that never appeared in that warning only because those workflows
run on a tag or a dispatch rather than on every push: upload-artifact v4,
goreleaser-action v6, build-push v6 and the docker qemu/buildx/login v3
line. All of them move to the current majors, which declare node24. The
SHA-pinned rest already qualified -- attest and sbom-action are node24,
cosign-installer is composite.

Checked against what those majors removed. build-push v7 dropped
DOCKER_BUILD_NO_SUMMARY and DOCKER_BUILD_EXPORT_RETENTION_DAYS and
setup-buildx v4 dropped its deprecated inputs, none of which are used here.
checkout v7 refuses to check out fork PRs under pull_request_target and
workflow_run, and no workflow here triggers on either.

setup-go v6 exports GOTOOLCHAIN=local, the one change with teeth. go.mod
declares `go 1.26.5` and carries no toolchain directive, every job resolves
that same version through go-version-file, and nothing in the module graph
asks for more, so there is nothing left for the go command to fetch:
`go build ./...` passes under GOTOOLCHAIN=local. The tools installed from
outside the module are in range too -- nfpm v2.47.0 requires 1.26.4 and
govulncheck v1.6.0 requires 1.25.0 -- and golangci-lint arrives as a
prebuilt binary from upstream's install script, not through the toolchain.

The runner floor that comes with node24 (v2.327.1) is met by GitHub-hosted
runners, and every job here is ubuntu-latest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 10:46:29 +08:00

213 lines
7.9 KiB
YAML

name: Release
on:
push:
tags:
- "RELEASE.*"
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. RELEASE.2026-03-24T12-00-00Z)"
required: true
permissions:
contents: write
id-token: write
attestations: write
artifact-metadata: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
# Build the code at the tag being released, not whatever branch the
# dispatch ran from. On a tag push this is the tag ref already; on
# workflow_dispatch it pins the checkout to the requested tag so the
# artifacts cannot be built from one ref and published under another.
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: true
- name: Verify clean checkout
run: |
set -euo pipefail
# GitHub's OIDC certificate records GITHUB_SHA, not the ref passed to
# actions/checkout. A manual dispatch must therefore be launched from
# the release tag itself; otherwise the provenance identity would
# describe different source from the bytes being published.
CHECKED_OUT_REVISION="$(git rev-parse HEAD)"
if [ "${CHECKED_OUT_REVISION}" != "${GITHUB_SHA}" ]; then
echo "Checked out ${CHECKED_OUT_REVISION}, but workflow identity is ${GITHUB_SHA}. Dispatch from the release tag." >&2
exit 1
fi
if [ -n "$(git status --porcelain)" ]; then
echo "Refusing to release from a dirty working tree:" >&2
git status --porcelain >&2
exit 1
fi
- name: Verify rebrand compatibility contracts
run: |
set -euo pipefail
go run ./buildscripts/rebrand-guard
buildscripts/verify-rebrand.sh
dockerscripts/docker-entrypoint_test.sh
- name: Compute release variables
env:
# Passed through the environment, never interpolated into the script
# body: a dispatch input reaches bash as data, so it cannot inject
# commands the way a `${{ ... }}` splice into the source would.
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
set -euo pipefail
TAG="${INPUT_TAG:-${GITHUB_REF_NAME}}"
# Whitelist the exact tag shape before the value is used anywhere. bash
# =~ anchors ^...$ to the whole string (not per line, as sed would), so
# a tag carrying a newline cannot pass and then smuggle extra lines into
# $GITHUB_ENV below.
if [[ ! "${TAG}" =~ ^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}Z$ ]]; then
echo "Invalid release tag format: ${TAG}" >&2
exit 1
fi
VERSION_HYPHEN="${TAG#RELEASE.}"
PKG_VERSION="$(echo "${VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
VERSION_COLON="$(echo "${VERSION_HYPHEN}" | sed -E 's/T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/T\1:\2:\3Z/')"
LDFLAGS="$(MINIO_RELEASE=RELEASE go run buildscripts/gen-ldflags.go "${VERSION_COLON}")"
{
echo "RELEASE_TAG=${TAG}"
echo "PKG_VERSION=${PKG_VERSION}"
echo "LDFLAGS=${LDFLAGS}"
} >> "${GITHUB_ENV}"
echo "Release tag: ${TAG}"
echo "Package version: ${PKG_VERSION}"
echo "LDFLAGS: ${LDFLAGS}"
# Both installer actions are pinned to immutable commits. The explicit
# tool versions keep the release format reproducible across workflow
# reruns while the installers verify the downloaded executables.
- name: Install Syft
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
syft-version: v1.50.0
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.1.2
- name: Build Draft release with GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
version: "~> v2"
args: release --clean --skip=validate --config .github/goreleaser.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LDFLAGS: ${{ env.LDFLAGS }}
PKG_VERSION: ${{ env.PKG_VERSION }}
- name: Verify binary provenance stamps
run: |
set -euo pipefail
buildscripts/verify-build-provenance.sh
- name: Install nFPM
run: |
set -euo pipefail
go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.47.0
echo "$(go env GOPATH)/bin" >> "${GITHUB_PATH}"
- name: Build nFPM packages
run: |
set -euo pipefail
buildscripts/package-release.sh
- name: Generate package SBOMs and signed checksum manifest
env:
SYFT_CHECK_FOR_APP_UPDATE: "false"
run: |
set -euo pipefail
packages_dir="dist/packages"
mapfile -t packages < <(find "${packages_dir}" -maxdepth 1 -type f \
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' \) | sort)
if [ "${#packages[@]}" -ne 6 ]; then
echo "Expected six Linux packages, found ${#packages[@]}" >&2
printf '%s\n' "${packages[@]}" >&2
exit 1
fi
for package in "${packages[@]}"; do
syft "${package}" --output "spdx-json=${package}.sbom.json"
done
manifest="${packages_dir}/silo_${PKG_VERSION}_packages_checksums.txt"
(
cd "${packages_dir}"
mapfile -t subjects < <(find . -maxdepth 1 -type f \
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sbom.json' \) | sort)
if [ "${#subjects[@]}" -ne 12 ]; then
echo "Expected six packages and six SBOMs, found ${#subjects[@]} subjects" >&2
exit 1
fi
sha256sum "${subjects[@]}" | sed 's# \./# #' > "$(basename "${manifest}")"
)
cosign sign-blob --bundle="${manifest}.sigstore.json" "${manifest}" --yes
- name: Attest downloadable release artifacts
id: attest-release
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: |
dist/*.tar.gz
dist/*.zip
dist/*.sbom.json
dist/*_checksums.txt
dist/*.sigstore.json
dist/packages/*.rpm
dist/packages/*.deb
dist/packages/*.apk
dist/packages/*.sha256sum
dist/packages/*.sbom.json
dist/packages/*_checksums.txt
dist/packages/*.sigstore.json
- name: Preserve provenance bundle as a release asset
env:
BUNDLE_PATH: ${{ steps.attest-release.outputs.bundle-path }}
run: |
set -euo pipefail
test -s "${BUNDLE_PATH}"
cp "${BUNDLE_PATH}" "dist/silo_${PKG_VERSION}_provenance.sigstore.json"
- name: Upload nFPM packages to Draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
mapfile -t files < <(find dist/packages -maxdepth 1 -type f \
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sha256sum' \
-o -name '*.sbom.json' -o -name '*_checksums.txt' -o -name '*.sigstore.json' \) | sort)
if [ "${#files[@]}" -eq 0 ]; then
echo "No packages were generated."
exit 1
fi
gh release upload "${RELEASE_TAG}" "${files[@]}" \
"dist/silo_${PKG_VERSION}_provenance.sigstore.json"
- name: Upload dist artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: dist
path: dist/