name: Auto Release
concurrency:
group: auto-release-${{ github.event.workflow_run.head_branch || github.ref_name || 'main' }}
cancel-in-progress: true
on:
workflow_run:
workflows: ["CI", "Security", "Docker"]
types: [completed]
branches: [main]
schedule:
- cron: "0 9 * * 1"
workflow_dispatch:
inputs:
version_bump:
description: Version bump type
required: true
default: auto
type: choice
options:
- auto
- patch
- minor
- major
permissions:
contents: read
jobs:
check:
name: Check for Release
runs-on: ${{ vars.RUST_TEMPLATE_RUNNER_UBUNTU || 'ubuntu-latest' }}
if: >-
(github.event_name == 'workflow_run' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name == github.repository) ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
permissions:
actions: read
contents: read
outputs:
should_release: ${{ steps.decide.outputs.should_release }}
target_sha: ${{ steps.target.outputs.sha }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref_name }}
- name: Determine target SHA
id: target
env:
EVENT_NAME: ${{ github.event_name }}
WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
if [[ "${EVENT_NAME}" == "workflow_run" ]]; then
if [[ ! "${WORKFLOW_RUN_HEAD_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
echo "Invalid workflow-run head SHA" >&2
exit 1
fi
echo "sha=${WORKFLOW_RUN_HEAD_SHA}" >> "$GITHUB_OUTPUT"
else
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
fi
- name: Check CI status
id: ci_status
run: |
TARGET_SHA="${{ steps.target.outputs.sha }}"
CI_STATUS=$(gh run list --workflow=ci.yml --branch=main --commit="$TARGET_SHA" --event=push --limit=1 --json conclusion --jq '.[0].conclusion // ""')
SECURITY_STATUS=$(gh run list --workflow=security.yml --branch=main --commit="$TARGET_SHA" --event=push --limit=1 --json conclusion --jq '.[0].conclusion // ""')
# The runtime pin resolves the image docker.yml publishes for this
# commit, so a release before that publish would have nothing to pin.
DOCKER_STATUS=$(gh run list --workflow=docker.yml --branch=main --commit="$TARGET_SHA" --event=push --limit=1 --json conclusion --jq '.[0].conclusion // ""')
echo "ci=$CI_STATUS security=$SECURITY_STATUS docker=$DOCKER_STATUS"
if [[ "$CI_STATUS" == "success" ]] && [[ "$SECURITY_STATUS" == "success" ]] && [[ "$DOCKER_STATUS" == "success" ]]; then
echo "all_passed=true" >> "$GITHUB_OUTPUT"
else
echo "all_passed=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Decide whether to attempt a release
id: decide
env:
ALL_PASSED: ${{ steps.ci_status.outputs.all_passed }}
run: |
# The release CLI itself no-ops when no conventional commit warrants
# a release, so this gate only enforces green CI and Security runs —
# including for manual dispatches.
if [[ "${ALL_PASSED}" == "true" ]]; then
echo "should_release=true" >> "$GITHUB_OUTPUT"
else
echo "should_release=false" >> "$GITHUB_OUTPUT"
fi
release:
name: Create Release
needs: check
if: needs.check.outputs.should_release == 'true'
runs-on: ${{ vars.RUST_TEMPLATE_RUNNER_UBUNTU || 'ubuntu-latest' }}
permissions:
contents: write
actions: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
ref: ${{ needs.check.outputs.target_sha }}
- name: Pin runtime images to this commit
id: runtime_pin
env:
TARGET_SHA: ${{ needs.check.outputs.target_sha }}
PIN_FILES: runtime/Dockerfile release/Dockerfile
run: |
set -euo pipefail
repo_path="$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')"
short="${TARGET_SHA:0:7}"
token="$(curl -fsSL "https://ghcr.io/token?scope=repository:${repo_path}:pull&service=ghcr.io" | jq -r .token)"
digest="$(
curl -fsSL -o /dev/null -D - \
-H "Authorization: Bearer ${token}" \
-H 'Accept: application/vnd.oci.image.index.v1+json' \
-H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
"https://ghcr.io/v2/${repo_path}/manifests/${short}" \
| tr -d '\r' | awk 'tolower($1) == "docker-content-digest:" { print $2 }'
)"
if [[ -z "${digest}" ]]; then
echo "no published ghcr.io/${repo_path} image tagged ${short}; refusing to release a mismatched runtime" >&2
exit 1
fi
for file in ${PIN_FILES}; do
[[ -f "${file}" ]] || { echo "missing ${file}" >&2; exit 1; }
sed -i -E "s|^FROM [^[:space:]]+|FROM ghcr.io/${repo_path}:${short}@${digest}|" "${file}"
grep -m1 '^FROM ' "${file}"
done
echo "files=${PIN_FILES// /,}" >> "$GITHUB_OUTPUT"
- name: Release
id: release
uses: ./
with:
command: release
token: ${{ secrets.GITHUB_TOKEN }}
bump: ${{ github.event.inputs.version_bump || 'auto' }}
update-major-alias: "true"
extra-files: ${{ steps.runtime_pin.outputs.files }}
- name: Trigger tag pipelines
if: steps.release.outputs.released == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.release.outputs.version }}
TAG: ${{ steps.release.outputs.tag }}
run: |
gh workflow run release.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" -f version="$VERSION"
gh workflow run docker.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG"