Skip to content

release-on-upstream #11

release-on-upstream

release-on-upstream #11

name: release-on-upstream
# Cut this CLI's next release when busbar core ships. The admin client (src/client.rs) is hand-rolled
# against the committed openapi.json, and ci.yml's spec-drift job fails when that spec falls behind
# core's latest release. So on a new core release this refreshes openapi.json, proves the CLI still
# builds against it, then patch-bumps + tags v* — release.yml then builds + uploads the binaries.
#
# There is no code generator here: a refreshed spec never rewrites client.rs, so this only ever
# patch-bumps (semantic client changes are a human's job, surfaced by the failing spec-drift check).
#
# RUNAWAY-SAFE: no plain `push:` trigger, so merging this file cannot cut a release.
# - repository_dispatch [upstream-release]: acts on the dispatched core tag (client_payload).
# - schedule: reads GetBusbar/busbar's latest release and acts ONLY if its spec version is newer
# than the committed openapi.json — an idle day is a no-op, so cron can never runaway-tag.
# - workflow_dispatch: always acts (a human explicitly asked).
# The final gate is a green `cargo build --locked` on the refreshed spec: a broken build never tags.
on:
repository_dispatch:
types: [upstream-release]
schedule:
- cron: "53 5 * * *" # daily; minute staggered across the fleet so crons don't all fire at once
workflow_dispatch:
inputs:
tag:
description: "busbar core tag to build against (e.g. v1.6.0). Blank = core's latest release."
required: false
type: string
permissions:
contents: write
concurrency:
group: release-on-upstream-${{ github.repository }}
cancel-in-progress: false
jobs:
cut:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
persist-credentials: true
token: ${{ secrets.RELEASE_DISPATCH_TOKEN }}
- name: Configure git identity
run: |
git config user.name "busbar-bot"
git config user.email "bot@getbusbar.com"
- uses: dtolnay/rust-toolchain@stable
- name: Resolve target core release
id: resolve
env:
GH_TOKEN: ${{ secrets.RELEASE_DISPATCH_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
DISPATCH_TAG: ${{ github.event.client_payload.tag }}
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
set -euo pipefail
committed="$(jq -r '.info.version' openapi.json)"
echo "committed spec version: $committed"
tag=""
case "$EVENT_NAME" in
repository_dispatch) tag="${DISPATCH_TAG:-}" ;;
workflow_dispatch) tag="${INPUT_TAG:-}" ;;
esac
if [ -z "$tag" ]; then
tag="$(gh api repos/GetBusbar/busbar/releases/latest --jq .tag_name 2>/dev/null || true)"
fi
if [ -z "$tag" ]; then
echo "::notice::could not resolve a core release tag -> nothing to do"
echo "proceed=no" >> "$GITHUB_OUTPUT"; exit 0
fi
ver="${tag#v}"
echo "target core tag: $tag (spec version $ver)"
proceed=no
if [ "$EVENT_NAME" = workflow_dispatch ]; then
proceed=yes
elif [ "$(printf '%s\n%s\n' "$committed" "$ver" | sort -V | tail -1)" = "$ver" ] && [ "$ver" != "$committed" ]; then
proceed=yes
else
echo "::notice::core spec v$ver not newer than committed v$committed -> nothing to do"
fi
{
echo "proceed=$proceed"
echo "tag=$tag"
echo "ver=$ver"
} >> "$GITHUB_OUTPUT"
- name: Refresh spec, prove it builds, and cut a patch release
if: steps.resolve.outputs.proceed == 'yes'
env:
GH_TOKEN: ${{ secrets.RELEASE_DISPATCH_TOKEN }}
CORE_TAG: ${{ steps.resolve.outputs.tag }}
CORE_VER: ${{ steps.resolve.outputs.ver }}
run: |
set -euo pipefail
gh release download "$CORE_TAG" --repo GetBusbar/busbar \
--pattern "busbar-openapi-v${CORE_VER}.json" --output openapi.json.new
mv openapi.json.new openapi.json
if git diff --quiet -- openapi.json; then
echo "::notice::spec already current -> idempotent no-op"; exit 0
fi
# Green gate: the hand-rolled client must still build against the refreshed spec.
cargo build --locked --verbose
next="$(git tag -l 'v*' --sort=-v:refname | head -1)"
base="${next#v}"; IFS=. read -r MA MI PA <<< "$base"
next="v${MA}.${MI}.$((PA + 1))"
if git rev-parse -q --verify "refs/tags/${next}" >/dev/null; then
echo "::notice::${next} already exists -> idempotent no-op"; exit 0
fi
nver="${next#v}"
# Bump the package version; a plain build re-syncs Cargo.lock so release.yml's --locked holds.
sed -i -E "s/^version = \"[^\"]*\"/version = \"${nver}\"/" Cargo.toml
cargo build
git add openapi.json Cargo.toml Cargo.lock
git commit -m "release: ${next} — refresh openapi.json to busbar ${CORE_TAG}"
git push origin HEAD:main
git tag "$next"
git push origin "refs/tags/${next}"
echo "::notice::pushed ${next} — release.yml will build + upload the binaries"