Skip to content

Release

Release #37

Workflow file for this run

# Cuts tagged releases for the public code-interpreter repo. Like ci.yml this
# file is inert inside the monorepo — GitHub only runs workflows from the repo
# root — and becomes a root workflow in the published repo.
#
# Three entry points feed one job:
#
# * successful CI on main — automatically releases deployable changes. The
# next repository version follows Conventional Commit intent; changes that
# only touch docs, workflows, or tests do not cut a release.
#
# * workflow_dispatch — pick a version in the Actions UI. The chart is
# packaged before the tag is created, so a packaging failure aborts while
# the release is still un-cut and the version is still free to reuse.
# * push of a v* tag — for tags cut locally with `git tag -a … && git push`.
# Tags this workflow pushes itself carry GITHUB_TOKEN, and GitHub does not
# re-trigger workflows for those, so the two paths never double-publish.
#
# `main` accepts no direct pushes (see CONTRIBUTING.md), but the branch
# ruleset does not cover tags, so the job can create them. GITHUB_TOKEN
# defaults to read-only in this repository; the explicit `contents: write`
# below is what lets the tag push and the release upload through.
name: Release
on:
workflow_run:
workflows: ['CI']
branches: [main]
types: [completed]
workflow_dispatch:
inputs:
version:
description: 'Repository version to release, e.g. v1.0.0 or v1.1.0-rc1.'
required: true
type: string
draft:
description: 'Publish as a draft so the notes can be edited before going public'
type: boolean
default: false
push:
tags:
- 'v*'
permissions:
contents: write
concurrency:
# Automatic runs serialize against one another. If main advances before an
# older run starts, version resolution skips the stale SHA and the newest
# successful run releases the full range instead.
group: release-${{ github.event_name == 'workflow_run' && 'main' || github.event.inputs.version || github.ref_name }}
cancel-in-progress: false
jobs:
release:
name: Tag and publish
if: >-
github.event_name != 'workflow_run' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# `workflow_run` can be rerun for a commit older than this resolver. Save
# the helper from the revision that supplied this workflow before the
# release checkout replaces the working tree with that historical SHA.
- name: Checkout release workflow
if: github.event_name == 'workflow_run'
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 1
ref: ${{ github.workflow_sha }}
- name: Preserve release resolver
if: github.event_name == 'workflow_run'
run: install -m 755 .github/scripts/resolve-release-version.sh "$RUNNER_TEMP/resolve-release-version.sh"
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
# Full history and tags: resolving whether this release is the newest
# stable one compares it against every other tag in the repository.
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref }}
- name: Resolve and validate version
id: version
env:
EVENT_NAME: ${{ github.event_name }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
INPUT_VERSION: ${{ github.event.inputs.version }}
INPUT_DRAFT: ${{ github.event.inputs.draft }}
REF_NAME: ${{ github.ref_name }}
REF_TYPE: ${{ github.ref_type }}
GH_TOKEN: ${{ github.token }}
RESOLVER_PATH: ${{ github.event_name == 'workflow_run' && format('{0}/resolve-release-version.sh', runner.temp) || '.github/scripts/resolve-release-version.sh' }}
# The resolution itself lives in a script so that every path through it
# — automatic release, resumed release, dispatch, pushed tag, and the
# runs that must skip or fail — is covered by
# tests/release-version-resolution.sh in CI.
run: "$RESOLVER_PATH"
# helm is preinstalled on ubuntu-latest, the same way the chart tests in
# ci.yml depend on it.
- name: Package Helm chart
id: chart
if: steps.version.outputs.skip != 'true'
run: |
set -euo pipefail
# Subcharts resolve through the Bitnami OCI mirror on Docker Hub,
# which rate-limits anonymous pulls. A transient 429 should cost a
# retry, not the release.
for attempt in 1 2 3; do
if helm dependency update helm/codeapi; then
break
fi
if [ "$attempt" = 3 ]; then
echo "::error::helm dependency update failed after 3 attempts"
exit 1
fi
sleep $(( attempt * 15 ))
done
helm package helm/codeapi --destination dist
CHART_PATH="$(ls dist/codeapi-*.tgz)"
{
echo "path=$CHART_PATH"
echo "name=$(basename "$CHART_PATH")"
} >> "$GITHUB_OUTPUT"
- name: Create tag
if: steps.version.outputs.skip != 'true' && github.event_name != 'push'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
if ! git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
git tag -a "$VERSION" -m "$VERSION"
git push origin "refs/tags/$VERSION"
fi
- name: Publish release
if: steps.version.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.version.outputs.version }}
APP_VERSION: ${{ steps.version.outputs.app_version }}
CHART_VERSION: ${{ steps.version.outputs.chart_version }}
CHART_PATH: ${{ steps.chart.outputs.path }}
CHART_NAME: ${{ steps.chart.outputs.name }}
PRERELEASE: ${{ steps.version.outputs.prerelease }}
LATEST: ${{ steps.version.outputs.latest }}
DRAFT: ${{ steps.version.outputs.draft }}
REPO_URL: ${{ github.server_url }}/${{ github.repository }}
run: |
set -euo pipefail
if gh release view "$VERSION" >/dev/null 2>&1; then
echo "::error::Release $VERSION already exists"
exit 1
fi
# Quoted heredoc so the markdown backticks stay literal; the
# placeholders are filled in afterwards.
cat > release-notes.md <<'NOTES'
Pin deployments to this tag instead of tracking `main`:
```bash
git clone --branch __VERSION__ --depth 1 __REPO_URL__.git
```
The attached `__CHART_NAME__` is the packaged Helm chart (chart `__CHART_VERSION__`, appVersion `__APP_VERSION__`) with its Redis and MinIO subcharts vendored, so it installs without adding any chart repositories:
```bash
helm install codeapi ./__CHART_NAME__ -f my-values.yaml
```
Chart configuration is documented in [helm/codeapi/README.md](__REPO_URL__/blob/__VERSION__/helm/codeapi/README.md).
NOTES
sed -i \
-e "s|__VERSION__|$VERSION|g" \
-e "s|__REPO_URL__|$REPO_URL|g" \
-e "s|__CHART_NAME__|$CHART_NAME|g" \
-e "s|__CHART_VERSION__|$CHART_VERSION|g" \
-e "s|__APP_VERSION__|$APP_VERSION|g" \
release-notes.md
# --generate-notes appends the merged-pull-request changelog below
# the body from --notes-file, categorised per .github/release.yml.
gh release create "$VERSION" \
--title "$VERSION" \
--notes-file release-notes.md \
--generate-notes \
--verify-tag \
--prerelease="$PRERELEASE" \
--latest="$LATEST" \
--draft="$DRAFT" \
"$CHART_PATH#Helm chart ($CHART_NAME)"