From c92cc80f8dc7f39a896a277c277a102fd3b5dd00 Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Fri, 10 Jul 2026 10:30:35 +0100 Subject: [PATCH 1/5] CONNLINK-1226 Enforce RFC0020 in wasm-shim Signed-off-by: Jim Fitzpatrick --- .github/actions/prepare-release/action.yaml | 5 + .github/workflows/automated-release.yaml | 54 ------ .github/workflows/build-image.yaml | 43 ++++- .github/workflows/pre-release.yaml | 173 ++++++++++++++++++++ .github/workflows/release.yaml | 164 +++++++++++++++---- .github/workflows/sector-release.yaml | 43 ----- .github/workflows/version-gate.yaml | 64 ++++++++ RELEASE.md | 76 +++++---- release.yaml | 2 + 9 files changed, 453 insertions(+), 171 deletions(-) delete mode 100644 .github/workflows/automated-release.yaml create mode 100644 .github/workflows/pre-release.yaml delete mode 100644 .github/workflows/sector-release.yaml create mode 100644 .github/workflows/version-gate.yaml create mode 100644 release.yaml diff --git a/.github/actions/prepare-release/action.yaml b/.github/actions/prepare-release/action.yaml index c8d2f95e..318d9d63 100644 --- a/.github/actions/prepare-release/action.yaml +++ b/.github/actions/prepare-release/action.yaml @@ -55,6 +55,11 @@ runs: fi fi + - name: Update release.yaml version + shell: bash + run: | + yq -i '.wasm-shim.version = strenv(WASM_SHIM_VERSION)' release.yaml + - name: Update Cargo.toml version shell: bash run: | diff --git a/.github/workflows/automated-release.yaml b/.github/workflows/automated-release.yaml deleted file mode 100644 index eadf51fc..00000000 --- a/.github/workflows/automated-release.yaml +++ /dev/null @@ -1,54 +0,0 @@ -name: Automated Release WASM Shim - -on: - workflow_dispatch: - inputs: - gitRef: - description: Commit SHA, tag or branch name (usually main branch) - required: true - default: "main" - type: string - wasmShimVersion: - description: WASM Shim version (semver, e.g., 0.12.1) - required: true - default: "0.0.0" - type: string - -jobs: - auto-release: - name: Prepare Release - runs-on: ubuntu-latest - steps: - - name: Checkout code at git ref - uses: actions/checkout@v6 - with: - ref: ${{ github.event.inputs.gitRef }} - token: ${{ secrets.KUADRANT_DEV_PAT }} - - - name: Prepare release - uses: ./.github/actions/prepare-release - with: - version: ${{ github.event.inputs.wasmShimVersion }} - push-branch: "true" - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Create Pull Request - id: cpr - uses: peter-evans/create-pull-request@v8 - with: - token: ${{ secrets.KUADRANT_DEV_PAT }} - commit-message: Prepare release ${{ github.event.inputs.wasmShimVersion }} - committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> - author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> - signoff: true - base: ${{ env.BASE_BRANCH }} - branch: release-${{ env.WASM_SHIM_VERSION }} - delete-branch: true - title: "[Release] WASM Shim v${{ env.WASM_SHIM_VERSION }}" - body: | - Prepare release for WASM Shim version ${{ env.WASM_SHIM_VERSION }} - - Auto-generated by [create-pull-request][1] - - [1]: https://github.com/peter-evans/create-pull-request - draft: false diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 5545b87f..39b185b3 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -8,9 +8,23 @@ on: pull_request: branches: ["*"] workflow_dispatch: {} + workflow_call: + inputs: + image-tags: + description: Space-separated list of image tags + required: true + type: string + ref: + description: Git ref to checkout (tag, branch, or SHA) + required: true + type: string + secrets: + IMG_REGISTRY_USERNAME: + required: true + IMG_REGISTRY_TOKEN: + required: true env: - IMG_TAGS: ${{ github.ref_name }} IMG_REGISTRY_HOST: quay.io IMG_REGISTRY_ORG: kuadrant MAIN_BRANCH_NAME: main @@ -23,16 +37,31 @@ jobs: steps: - name: Check out code uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Determine image tags + run: | + if [ -n "${{ inputs.image-tags }}" ]; then + echo "IMG_TAGS=${{ inputs.image-tags }}" >> $GITHUB_ENV + else + echo "IMG_TAGS=${{ github.ref_name }}" >> $GITHUB_ENV + fi + - name: Add latest tag for the main branch - if: ${{ github.ref_name == env.MAIN_BRANCH_NAME }} - id: add-latest-tag + if: ${{ !inputs.image-tags && github.ref_name == env.MAIN_BRANCH_NAME }} run: | echo "IMG_TAGS=latest ${{ env.IMG_TAGS }}" >> $GITHUB_ENV + - name: Add git sha tag for the main branch - if: ${{ github.ref_name == env.MAIN_BRANCH_NAME }} - id: add-git-sha-tag + if: ${{ !inputs.image-tags && github.ref_name == env.MAIN_BRANCH_NAME }} run: | echo "IMG_TAGS=${{ github.sha }} ${{ env.IMG_TAGS }}" >> $GITHUB_ENV + + - name: Resolve git SHA from checkout + run: | + echo "GIT_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV + - name: Build Image id: build-image uses: redhat-actions/buildah-build@v2 @@ -40,9 +69,10 @@ jobs: image: wasm-shim tags: ${{ env.IMG_TAGS }} build-args: | - GITHUB_SHA=${{ github.sha }} + GITHUB_SHA=${{ env.GIT_SHA }} dockerfiles: | ./Dockerfile + - name: Push Image if: ${{ !env.ACT && github.event_name != 'pull_request' }} id: push-to-quay @@ -53,5 +83,6 @@ jobs: registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }} username: ${{ secrets.IMG_REGISTRY_USERNAME }} password: ${{ secrets.IMG_REGISTRY_TOKEN }} + - name: Print Image URL run: echo "Image pushed to ${{ steps.push-to-quay.outputs.registry-paths }}" diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml new file mode 100644 index 00000000..3b2b2c4f --- /dev/null +++ b/.github/workflows/pre-release.yaml @@ -0,0 +1,173 @@ +--- +name: Pre-release + +on: + workflow_dispatch: + inputs: + version: + description: Target semantic version (e.g., 0.13.0) + required: true + type: string + source-branch: + description: Branch to base the pre-release changes on (default main, use release branch for patches) + required: false + default: "main" + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + release-branch: ${{ steps.derive.outputs.release-branch }} + version: ${{ steps.validate.outputs.version }} + steps: + - name: Validate version + id: validate + run: | + VERSION="${{ inputs.version }}" + if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'; then + echo "::error::version must be valid semver (e.g., 0.13.0, 0.13.1-rc1)" + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Derive release branch + id: derive + run: | + RELEASE_BRANCH=release-$(echo "${{ steps.validate.outputs.version }}" | sed 's/[+-].*//; s/\.[0-9]*$//') + echo "release-branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" + + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ inputs.source-branch }} + fetch-depth: 0 + + - name: Create release branch if needed + run: | + RELEASE_BRANCH="${{ steps.derive.outputs.release-branch }}" + if git ls-remote --exit-code --heads origin "$RELEASE_BRANCH"; then + echo "Branch $RELEASE_BRANCH already exists" + else + echo "Creating branch $RELEASE_BRANCH from ${{ inputs.source-branch }}" + git checkout -b "$RELEASE_BRANCH" + git push --set-upstream origin "$RELEASE_BRANCH" + fi + + prepare-release: + name: Prepare Release + needs: setup + runs-on: ubuntu-latest + steps: + - name: Checkout source branch + uses: actions/checkout@v6 + with: + ref: ${{ inputs.source-branch }} + + - name: Create working branch + run: | + git checkout -b "pre-release-v${{ needs.setup.outputs.version }}" + + - name: Prepare release + uses: ./.github/actions/prepare-release + with: + version: ${{ needs.setup.outputs.version }} + push-branch: "false" + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Commit and push changes + run: | + git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" + git config user.name "${{ github.actor }}" + git add -A + git commit -s -m "chore: prepare release v${{ needs.setup.outputs.version }}" + git push --set-upstream origin "pre-release-v${{ needs.setup.outputs.version }}" + + open-release-pr: + name: Open Release Pull Request + needs: [setup, prepare-release] + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: "pre-release-v${{ needs.setup.outputs.version }}" + + - name: Create pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr create \ + --base "${{ needs.setup.outputs.release-branch }}" \ + --head "pre-release-v${{ needs.setup.outputs.version }}" \ + --title "chore: prepare release v${{ needs.setup.outputs.version }}" \ + --body "Prepare release for wasm-shim version v${{ needs.setup.outputs.version }}. + + This PR updates \`release.yaml\` and \`Cargo.toml\` to version \`${{ needs.setup.outputs.version }}\`. + + Once the version gate check passes and this PR is approved and merged, trigger the **Release** workflow with branch \`${{ needs.setup.outputs.release-branch }}\` to publish the release." + + bump-dev: + name: Bump Dev Version + needs: [setup, prepare-release] + runs-on: ubuntu-latest + steps: + - name: Checkout source branch + uses: actions/checkout@v6 + with: + ref: ${{ inputs.source-branch }} + + - name: Compute next dev version + id: next + run: | + VERSION="${{ needs.setup.outputs.version }}" + # Strip any pre-release/build metadata + BASE=$(echo "$VERSION" | sed 's/[+-].*//') + MAJOR=$(echo "$BASE" | cut -d. -f1) + MINOR=$(echo "$BASE" | cut -d. -f2) + PATCH=$(echo "$BASE" | cut -d. -f3) + if [ "$PATCH" = "0" ]; then + NEXT_DEV="${MAJOR}.$((MINOR + 1)).0-dev" + else + NEXT_DEV="${MAJOR}.${MINOR}.$((PATCH + 1))-dev" + fi + echo "next-dev=$NEXT_DEV" >> "$GITHUB_OUTPUT" + + - name: Create working branch + run: | + git checkout -b "post-release-v${{ needs.setup.outputs.version }}" + + - name: Update Cargo.toml to next dev version + run: | + sed -i '0,/^version = ".*"/s//version = "${{ steps.next.outputs.next-dev }}"/' Cargo.toml + + - name: Set up Rust and WASM environment + uses: ./.github/actions/setup-rust-wasm + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Update Cargo.lock + run: cargo check --target wasm32-wasip1 + + - name: Commit and push changes + run: | + git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" + git config user.name "${{ github.actor }}" + git add Cargo.toml Cargo.lock + git commit -s -m "chore: bump version to ${{ steps.next.outputs.next-dev }}" + git push --set-upstream origin "post-release-v${{ needs.setup.outputs.version }}" + + - name: Create pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr create \ + --base "${{ inputs.source-branch }}" \ + --head "post-release-v${{ needs.setup.outputs.version }}" \ + --title "chore: bump version to ${{ steps.next.outputs.next-dev }}" \ + --body "Bump \`Cargo.toml\` version to \`${{ steps.next.outputs.next-dev }}\` after the v${{ needs.setup.outputs.version }} release." diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index cde4270e..d6568de6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,48 +2,111 @@ name: Release on: - pull_request: - types: - - closed - branches: - - 'release-[0-9]+.[0-9]+' - workflow_dispatch: {} + workflow_dispatch: + inputs: + release-branch: + description: Release branch to release from (e.g., release-0.13) + required: true + type: string permissions: contents: write jobs: - release-wasm-shim: - if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - name: Publish Release + read-version: + name: Read Version runs-on: ubuntu-latest + outputs: + version: ${{ steps.parse.outputs.version }} + tag: ${{ steps.parse.outputs.tag }} + prerelease: ${{ steps.parse.outputs.prerelease }} steps: - - name: Checkout code + - name: Checkout release branch uses: actions/checkout@v6 with: - token: ${{ secrets.KUADRANT_DEV_PAT }} + ref: ${{ inputs.release-branch }} - - name: Extract version from Cargo.toml - id: extract_version + - name: Parse version from release.yaml + id: parse run: | - VERSION=$(grep -m1 '^version = ' Cargo.toml | sed 's/version = "\(.*\)"/\1/') - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "TAG=v$VERSION" >> $GITHUB_ENV - if [[ "$VERSION" =~ - ]]; then - echo "PRERELEASE=true" >> $GITHUB_ENV - else - echo "PRERELEASE=false" >> $GITHUB_ENV + VERSION=$(yq '.wasm-shim.version' release.yaml) + if [ -z "$VERSION" ] || [ "$VERSION" = "0.0.0" ]; then + echo "::error::release.yaml version is unset or 0.0.0 — run the pre-release workflow first" + exit 1 + fi + TAG="v$VERSION" + PRERELEASE=false + if echo "$VERSION" | grep -q -- '-'; then + PRERELEASE=true fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT" + echo "Releasing version $VERSION (tag: $TAG, prerelease: $PRERELEASE)" - - name: Check tag presence - id: check_tag + - name: Check for existing release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - if git ls-remote --tags origin | grep -q "refs/tags/${{ env.TAG }}$"; then - echo "Tag ${{ env.TAG }} already exists - skipping release" + TAG="${{ steps.parse.outputs.tag }}" + if gh release view "$TAG" > /dev/null 2>&1; then + echo "::error::GitHub Release $TAG already exists" exit 1 - else - echo "Tag ${{ env.TAG }} does not exist - proceeding with release" fi + echo "No existing release for $TAG — proceeding" + + smoke-tests: + name: Smoke Tests + needs: read-version + runs-on: ubuntu-latest + steps: + - name: Checkout release branch + uses: actions/checkout@v6 + with: + ref: ${{ inputs.release-branch }} + + - name: Set up Rust and WASM environment + uses: ./.github/actions/setup-rust-wasm + with: + rust-components: rustfmt, clippy + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: cargo build (wasm) + run: cargo build --release --target wasm32-wasip1 + + - name: cargo fmt + run: cargo fmt --all -- --check + + - name: cargo clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: cargo test + run: cargo test + + tag: + name: Tag + needs: [read-version, smoke-tests] + runs-on: ubuntu-latest + steps: + - name: Checkout release branch + uses: actions/checkout@v6 + with: + ref: ${{ inputs.release-branch }} + + - name: Create and push tag + run: | + git tag "${{ needs.read-version.outputs.tag }}" + git push origin "${{ needs.read-version.outputs.tag }}" + + build-wasm: + name: Build WASM Binary + needs: [read-version, tag] + runs-on: ubuntu-latest + steps: + - name: Checkout tag + uses: actions/checkout@v6 + with: + ref: ${{ needs.read-version.outputs.tag }} - name: Set up Rust and WASM environment uses: ./.github/actions/setup-rust-wasm @@ -55,19 +118,48 @@ jobs: - name: Rename WASM binary run: | - cp target/wasm32-wasip1/release/wasm_shim.wasm kuadrant-wasm-shim-${{ env.TAG }} + cp target/wasm32-wasip1/release/wasm_shim.wasm "kuadrant-wasm-shim-${{ needs.read-version.outputs.tag }}" - - name: Create and push tag - run: | - git tag ${{ env.TAG }} - git push origin ${{ env.TAG }} + - name: Upload WASM artifact + uses: actions/upload-artifact@v4 + with: + name: wasm-binary + path: "kuadrant-wasm-shim-${{ needs.read-version.outputs.tag }}" + + build-image: + name: Build Container Image + needs: [read-version, tag] + uses: ./.github/workflows/build-image.yaml + with: + ref: ${{ needs.read-version.outputs.tag }} + image-tags: >- + ${{ needs.read-version.outputs.tag }}${{ + needs.read-version.outputs.prerelease != 'true' && ' latest' || '' }} + secrets: + IMG_REGISTRY_USERNAME: ${{ secrets.IMG_REGISTRY_USERNAME }} + IMG_REGISTRY_TOKEN: ${{ secrets.IMG_REGISTRY_TOKEN }} + + create-release: + name: Create GitHub Release + needs: [read-version, build-wasm, build-image] + runs-on: ubuntu-latest + steps: + - name: Checkout tag + uses: actions/checkout@v6 + with: + ref: ${{ needs.read-version.outputs.tag }} + + - name: Download WASM artifact + uses: actions/download-artifact@v4 + with: + name: wasm-binary - name: Create GitHub release uses: softprops/action-gh-release@v2 with: - name: ${{ env.TAG }} - tag_name: ${{ env.TAG }} - prerelease: ${{ env.PRERELEASE }} + name: ${{ needs.read-version.outputs.tag }} + tag_name: ${{ needs.read-version.outputs.tag }} + prerelease: ${{ needs.read-version.outputs.prerelease }} generate_release_notes: true - files: kuadrant-wasm-shim-${{ env.TAG }} - token: ${{ secrets.KUADRANT_DEV_PAT }} + files: "kuadrant-wasm-shim-${{ needs.read-version.outputs.tag }}" + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sector-release.yaml b/.github/workflows/sector-release.yaml deleted file mode 100644 index ed73a31d..00000000 --- a/.github/workflows/sector-release.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: Sector Release WASM Shim - -on: - workflow_dispatch: - inputs: - gitRef: - description: Commit SHA, tag or branch name (usually main branch) - required: true - default: "main" - type: string - wasmShimVersion: - description: WASM Shim version (semver, e.g., 0.12.1) - required: true - default: "0.0.0" - type: string - -jobs: - sector-release: - name: Prepare Release - runs-on: ubuntu-latest - steps: - - name: Checkout code at git ref - uses: actions/checkout@v6 - with: - ref: ${{ github.event.inputs.gitRef }} - token: ${{ secrets.KUADRANT_DEV_PAT }} - - - name: Prepare release - uses: ./.github/actions/prepare-release - with: - version: ${{ github.event.inputs.wasmShimVersion }} - push-branch: 'false' - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Commit and push changes - id: commit-and-push-changes - shell: bash - run: | - git config --global user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" - git config --global user.name "${{ github.actor}}" - - git commit . -s -m "Prepare release ${{ github.event.inputs.wasmShimVersion }}" - git push --set-upstream origin "$BASE_BRANCH" diff --git a/.github/workflows/version-gate.yaml b/.github/workflows/version-gate.yaml new file mode 100644 index 00000000..fab47d60 --- /dev/null +++ b/.github/workflows/version-gate.yaml @@ -0,0 +1,64 @@ +--- +name: Version Gate + +on: + pull_request: + branches: + - 'release-[0-9]+.[0-9]+' + paths: + - 'release.yaml' + +permissions: + contents: read + +jobs: + validate-version: + name: Validate Release Version + runs-on: ubuntu-latest + steps: + - name: Checkout PR branch + uses: actions/checkout@v6 + + - name: Validate version is not sentinel + run: | + VERSION=$(yq '.wasm-shim.version' release.yaml) + if [ -z "$VERSION" ]; then + echo "::error::Could not parse version from release.yaml" + exit 1 + fi + if [ "$VERSION" = "0.0.0" ]; then + echo "::error::version in release.yaml is 0.0.0 — must be a concrete version on release branches" + exit 1 + fi + echo "Version: $VERSION" + + - name: Validate dependency versions + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + FAILED=0 + + DEPS=$(yq -o=json '.dependencies // {}' release.yaml) + for DEP_NAME in $(echo "$DEPS" | jq -r 'keys[]'); do + DEP_VERSION=$(echo "$DEPS" | jq -r --arg k "$DEP_NAME" '.[$k]') + + if [ "$DEP_VERSION" = "0.0.0" ]; then + echo "::error::Dependency $DEP_NAME has version 0.0.0 — must be a concrete released version" + FAILED=1 + continue + fi + + DEP_TAG="v$DEP_VERSION" + if ! gh api "repos/Kuadrant/$DEP_NAME/releases/tags/$DEP_TAG" > /dev/null 2>&1; then + echo "::error::GitHub Release $DEP_TAG not found for dependency $DEP_NAME in Kuadrant/$DEP_NAME" + FAILED=1 + else + echo "Dependency $DEP_NAME: release $DEP_TAG exists" + fi + done + + if [ "$FAILED" -ne 0 ]; then + exit 1 + fi + + echo "All dependency versions validated" diff --git a/RELEASE.md b/RELEASE.md index e4d9f02b..c68884a5 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,47 +1,59 @@ # How to release wasm-shim -The wasm-shim uses an automated release process with protected release branches. +The wasm-shim uses a two-phase release process as defined by [RFC 0020](https://github.com/Kuadrant/architecture/blob/main/rfcs/0020-two-phase-release-workflow.md). -## Quick Start - -1. **Run the workflow**: Actions → “Automated Release WASM Shim” → “Run workflow” - - **wasmShimVersion**: Version to release (e.g., `0.12.1`) - - **gitRef**: `main` for new minor, `release-0.12` for patches -2. **Review and merge the PR** that gets created -3. **Done** - tag and release happen automatically on merge +## Overview -## Standard Release +Every release is split into two workflows with a PR-based review gate between them: -1. Actions → “Automated Release WASM Shim” → “Run workflow” - - **gitRef**: `main` (for new minor like `0.13.0`) or `release-0.12` (for patch like `0.12.1`) - - **wasmShimVersion**: `0.12.1` (or whatever version) -2. Review and merge the PR -3. Tag and release created automatically +1. **Pre-release** — makes code changes and opens a PR to the release branch +2. **Release** — tests, tags, builds artifacts, and creates the GitHub Release -## Release with Cherry-picked Fixes +The `release.yaml` file at the repository root is the machine-readable source of truth for version information. -1. **First, cherry-pick and merge your fixes:** +## Quick Start +1. **Run the pre-release workflow**: Actions → "Pre-release" → "Run workflow" + - **version**: Target version (e.g., `0.13.0`) + - **source-branch**: `main` for new minor releases (default) +2. **Review and merge the PR** that gets created against the release branch + - The version gate check validates `release.yaml` before merge +3. **Run the release workflow**: Actions → "Release" → "Run workflow" + - **release-branch**: The release branch (e.g., `release-0.13`) +4. **Done** — smoke tests run, tag is created, artifacts are built, and the GitHub Release is published + +## Standard Minor Release + +1. Actions → "Pre-release" → "Run workflow" + - **version**: `0.13.0` + - **source-branch**: `main` +2. The workflow creates branch `release-0.13` (if it doesn't exist), updates `release.yaml` and `Cargo.toml`, and opens a PR +3. Review and merge the PR (version gate and CI checks must pass) +4. Actions → "Release" → "Run workflow" + - **release-branch**: `release-0.13` +5. The workflow reads the version from `release.yaml`, runs smoke tests, creates tag `v0.13.0`, builds the WASM binary and container image, and creates the GitHub Release + +## Patch Release + +1. Prepare a branch with the backported fixes: ```bash - git checkout -b backport-my-fix origin/release-0.12 + git checkout -b backport-my-fix origin/release-0.13 git cherry-pick - git push -u origin HEAD - # Create PR from backport-my-fix to release-0.12, get it merged + git push -u origin backport-my-fix ``` -2. **Then run the release workflow:** - - Actions → “Automated Release WASM Shim” → “Run workflow” - - **gitRef**: `release-0.12` (picks up the cherry-picks) - - **wasmShimVersion**: `0.12.1` - -3. Review and merge the version bump PR -4. Tag and release created automatically +2. Actions → "Pre-release" → "Run workflow" + - **version**: `0.13.1` + - **source-branch**: `backport-my-fix` +3. Review and merge the PR (contains both the backported fixes and the version bump) +4. Actions → "Release" → "Run workflow" + - **release-branch**: `release-0.13` ## Details -- Version format: semver without `v` prefix (e.g., `0.12.1`, not `v0.12.1`) -- Release branches: `release-0.12`, `release-0.13`, etc. -- One branch per minor version, shared by all patches -- Workflow creates the release branch if it doesn't exist - - For new minor versions, creates from specified `gitRef` - - For existing branches, keeps existing branch (use `gitRef` to catch up via PR) +- **Version format**: semver without `v` prefix in `release.yaml` (e.g., `0.13.0`, not `v0.13.0`) +- **Release branches**: `release-0.13`, `release-0.14`, etc. — one branch per minor version, shared by all patches +- **Version gate**: A CI check on release branch PRs validates that `release.yaml` has a concrete version (not `0.0.0`) +- **On `main`**: `release.yaml` always has version `0.0.0` (sentinel for active development) +- **Artifacts built during release**: WASM binary (attached to GitHub Release) and container image (pushed to `quay.io/kuadrant/wasm-shim`) +- **GitHub Release is always the last step** — if any preceding step fails, no release is created diff --git a/release.yaml b/release.yaml new file mode 100644 index 00000000..469bbfc2 --- /dev/null +++ b/release.yaml @@ -0,0 +1,2 @@ +wasm-shim: + version: "0.0.0" From 8ea4bd5a8834052703e5147d97356d565ecfe8a5 Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Fri, 10 Jul 2026 11:31:04 +0100 Subject: [PATCH 2/5] Allow for forks to build images Signed-off-by: Jim Fitzpatrick --- .github/workflows/build-image.yaml | 2 +- RELEASE.md | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 39b185b3..da2e3a6f 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -26,7 +26,7 @@ on: env: IMG_REGISTRY_HOST: quay.io - IMG_REGISTRY_ORG: kuadrant + IMG_REGISTRY_ORG: ${{ vars.IMG_REGISTRY_ORG || 'kuadrant' }} MAIN_BRANCH_NAME: main jobs: diff --git a/RELEASE.md b/RELEASE.md index c68884a5..c3e9ad50 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -49,11 +49,34 @@ The `release.yaml` file at the repository root is the machine-readable source of 4. Actions → "Release" → "Run workflow" - **release-branch**: `release-0.13` +## Repository Configuration + +### Required Secrets + +Configure these in Settings → Secrets and variables → Actions → Repository secrets: + +| Secret | Description | +|--------|-------------| +| `IMG_REGISTRY_USERNAME` | Container registry username or robot account | +| `IMG_REGISTRY_TOKEN` | Container registry password or token | + +### Optional Variables + +Configure these in Settings → Secrets and variables → Actions → Repository variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `IMG_REGISTRY_ORG` | `kuadrant` | Container registry organization/namespace (e.g., your Quay.io org for forks) | + +### Required Repository Settings + +- **Actions → General → Workflow permissions**: "Allow GitHub Actions to create and approve pull requests" must be enabled (required by the pre-release workflow to open PRs) + ## Details - **Version format**: semver without `v` prefix in `release.yaml` (e.g., `0.13.0`, not `v0.13.0`) - **Release branches**: `release-0.13`, `release-0.14`, etc. — one branch per minor version, shared by all patches - **Version gate**: A CI check on release branch PRs validates that `release.yaml` has a concrete version (not `0.0.0`) - **On `main`**: `release.yaml` always has version `0.0.0` (sentinel for active development) -- **Artifacts built during release**: WASM binary (attached to GitHub Release) and container image (pushed to `quay.io/kuadrant/wasm-shim`) +- **Artifacts built during release**: WASM binary (attached to GitHub Release) and container image (pushed to `quay.io//wasm-shim`) - **GitHub Release is always the last step** — if any preceding step fails, no release is created From a973f54b00e62d8cff48e5bae51ce521f24273b5 Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Tue, 21 Jul 2026 09:47:10 +0100 Subject: [PATCH 3/5] REFACTOR: yq install Move yq install to its own action allowings us to change the version in one place. Signed-off-by: Jim Fitzpatrick --- .github/actions/install-yq/action.yaml | 18 ++++++++++++++++++ .github/actions/prepare-release/action.yaml | 2 ++ .github/workflows/release.yaml | 2 ++ .github/workflows/version-gate.yaml | 2 ++ 4 files changed, 24 insertions(+) create mode 100644 .github/actions/install-yq/action.yaml diff --git a/.github/actions/install-yq/action.yaml b/.github/actions/install-yq/action.yaml new file mode 100644 index 00000000..9fd6bf04 --- /dev/null +++ b/.github/actions/install-yq/action.yaml @@ -0,0 +1,18 @@ +name: Install yq +description: Install mikefarah/yq at a pinned version + +inputs: + version: + description: "yq version to install" + required: false + default: "4.53.3" + +runs: + using: composite + steps: + - name: Install yq v${{ inputs.version }} + shell: bash + run: | + sudo wget -qO /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/v${{ inputs.version }}/yq_linux_amd64" + sudo chmod +x /usr/local/bin/yq diff --git a/.github/actions/prepare-release/action.yaml b/.github/actions/prepare-release/action.yaml index 318d9d63..1e9133fc 100644 --- a/.github/actions/prepare-release/action.yaml +++ b/.github/actions/prepare-release/action.yaml @@ -55,6 +55,8 @@ runs: fi fi + - uses: ./.github/actions/install-yq + - name: Update release.yaml version shell: bash run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d6568de6..2edd91a9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,6 +26,8 @@ jobs: with: ref: ${{ inputs.release-branch }} + - uses: ./.github/actions/install-yq + - name: Parse version from release.yaml id: parse run: | diff --git a/.github/workflows/version-gate.yaml b/.github/workflows/version-gate.yaml index fab47d60..cae2944e 100644 --- a/.github/workflows/version-gate.yaml +++ b/.github/workflows/version-gate.yaml @@ -19,6 +19,8 @@ jobs: - name: Checkout PR branch uses: actions/checkout@v6 + - uses: ./.github/actions/install-yq + - name: Validate version is not sentinel run: | VERSION=$(yq '.wasm-shim.version' release.yaml) From c56daf808966c2363a769dc16c84202625fb44ae Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Mon, 27 Jul 2026 12:43:27 +0100 Subject: [PATCH 4/5] UPDATE: cargo.toml is the source of truth Signed-off-by: Jim Fitzpatrick --- .github/actions/prepare-release/action.yaml | 15 ++--- .github/scripts/README.md | 57 +++++++++++++++++ .github/scripts/check-versions.sh | 35 +++++++++++ .github/scripts/parse-version.sh | 31 +++++++++ .github/scripts/sync-release-yaml.sh | 20 ++++++ .github/scripts/validate-release-yaml.sh | 38 +++++++++++ .github/workflows/pre-release.yaml | 9 ++- .github/workflows/release.yaml | 9 +++ .github/workflows/version-gate.yaml | 70 +++++++-------------- RELEASE.md | 38 ++++++++--- 10 files changed, 260 insertions(+), 62 deletions(-) create mode 100644 .github/scripts/README.md create mode 100755 .github/scripts/check-versions.sh create mode 100755 .github/scripts/parse-version.sh create mode 100755 .github/scripts/sync-release-yaml.sh create mode 100755 .github/scripts/validate-release-yaml.sh diff --git a/.github/actions/prepare-release/action.yaml b/.github/actions/prepare-release/action.yaml index 1e9133fc..b77aab9c 100644 --- a/.github/actions/prepare-release/action.yaml +++ b/.github/actions/prepare-release/action.yaml @@ -55,13 +55,6 @@ runs: fi fi - - uses: ./.github/actions/install-yq - - - name: Update release.yaml version - shell: bash - run: | - yq -i '.wasm-shim.version = strenv(WASM_SHIM_VERSION)' release.yaml - - name: Update Cargo.toml version shell: bash run: | @@ -75,3 +68,11 @@ runs: - name: Update Cargo.lock shell: bash run: cargo check --target wasm32-wasip1 + + - uses: ./.github/actions/install-yq + + - name: Sync release.yaml from Cargo.toml + shell: bash + run: | + chmod +x .github/scripts/sync-release-yaml.sh + .github/scripts/sync-release-yaml.sh diff --git a/.github/scripts/README.md b/.github/scripts/README.md new file mode 100644 index 00000000..84998596 --- /dev/null +++ b/.github/scripts/README.md @@ -0,0 +1,57 @@ +# Release Scripts + +Helper scripts for the two-phase release process. All scripts use `release.yaml` as the default path but accept an override as the first positional argument. + +## Source of Truth + +**`Cargo.toml` is the authoritative source for the wasm-shim version.** `release.yaml` is a derived mirror maintained by `sync-release-yaml.sh` for cross-repo tooling compatibility. + +## Scripts + +### `sync-release-yaml.sh` + +Reads the wasm-shim version from `cargo metadata` and writes it to `release.yaml`. If the Cargo.toml version contains `-dev`, the sentinel value `0.0.0` is written instead. + +```bash +.github/scripts/sync-release-yaml.sh [release.yaml] +``` + +**Requires:** `cargo`, `jq`, `yq` + +### `check-versions.sh` + +Validates that `release.yaml` and `Cargo.toml` are consistent: + +- If `release.yaml` has `0.0.0` (sentinel), `Cargo.toml` must end in `-dev` +- Otherwise, both must match exactly + +```bash +.github/scripts/check-versions.sh [release.yaml] +``` + +**Requires:** `cargo`, `jq`, `yq` + +### `parse-version.sh` + +Reads the version from `release.yaml`, validates it as semver, and outputs decomposed components to `$GITHUB_OUTPUT` (or stdout when run locally). + +```bash +.github/scripts/parse-version.sh [release.yaml] +``` + +**Outputs:** `version`, `major`, `minor`, `patch`, `release-branch` + +**Requires:** `yq` + +### `validate-release-yaml.sh` + +Validates `release.yaml` for release readiness: + +- On `release-*` branches: rejects `0.0.0` sentinel and `-dev` versions +- Checks that declared dependency versions have corresponding GitHub Releases + +```bash +.github/scripts/validate-release-yaml.sh [org] [release.yaml] +``` + +**Requires:** `yq`, `gh` (GitHub CLI) diff --git a/.github/scripts/check-versions.sh b/.github/scripts/check-versions.sh new file mode 100755 index 00000000..9851da59 --- /dev/null +++ b/.github/scripts/check-versions.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +RELEASE_YAML="${1:-release.yaml}" + +if [[ ! -f "$RELEASE_YAML" ]]; then + echo "::error::File not found: $RELEASE_YAML" + exit 1 +fi + +YAML_VERSION=$(yq '.wasm-shim.version' "$RELEASE_YAML") +CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 \ + | jq -r '.packages[] | select(.name=="wasm-shim") | .version') + +ERRORS=0 + +if [[ "$YAML_VERSION" == "0.0.0" ]]; then + if [[ "$CARGO_VERSION" != *-dev* ]]; then + echo "::error::release.yaml version is 0.0.0 but Cargo.toml version '${CARGO_VERSION}' does not end in -dev" + ERRORS=$((ERRORS + 1)) + fi +else + if [[ "$YAML_VERSION" != "$CARGO_VERSION" ]]; then + echo "::error::Version mismatch: release.yaml has '${YAML_VERSION}' but Cargo.toml has '${CARGO_VERSION}'" + ERRORS=$((ERRORS + 1)) + fi +fi + +if [[ "$ERRORS" -gt 0 ]]; then + echo "::error::Version consistency check failed with ${ERRORS} error(s)" + exit 1 +fi + +echo "Version consistency check passed: release.yaml and Cargo.toml agree" +echo " release.yaml=${YAML_VERSION} Cargo.toml=${CARGO_VERSION}" diff --git a/.github/scripts/parse-version.sh b/.github/scripts/parse-version.sh new file mode 100755 index 00000000..5932d35b --- /dev/null +++ b/.github/scripts/parse-version.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +RELEASE_YAML="${1:-release.yaml}" + +if [[ ! -f "$RELEASE_YAML" ]]; then + echo "::error::File not found: $RELEASE_YAML" + exit 1 +fi + +VERSION=$(yq '.wasm-shim.version' "$RELEASE_YAML") +if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then + echo "::error::No version found in $RELEASE_YAML under wasm-shim.version" + exit 1 +fi + +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "::error::Invalid semver for version: $VERSION" + exit 1 +fi + +MAJOR=$(echo "$VERSION" | cut --delimiter=. --fields=1) +MINOR=$(echo "$VERSION" | cut --delimiter=. --fields=2) +PATCH=$(echo "$VERSION" | cut --delimiter=. --fields=3 | cut --delimiter=- --fields=1) +RELEASE_BRANCH="release-${MAJOR}.${MINOR}" + +echo "version=$VERSION" >> "${GITHUB_OUTPUT:-/dev/stdout}" +echo "major=$MAJOR" >> "${GITHUB_OUTPUT:-/dev/stdout}" +echo "minor=$MINOR" >> "${GITHUB_OUTPUT:-/dev/stdout}" +echo "patch=$PATCH" >> "${GITHUB_OUTPUT:-/dev/stdout}" +echo "release-branch=$RELEASE_BRANCH" >> "${GITHUB_OUTPUT:-/dev/stdout}" diff --git a/.github/scripts/sync-release-yaml.sh b/.github/scripts/sync-release-yaml.sh new file mode 100755 index 00000000..bf4b101c --- /dev/null +++ b/.github/scripts/sync-release-yaml.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +RELEASE_YAML="${1:-release.yaml}" + +VERSION=$(cargo metadata --no-deps --format-version 1 \ + | jq -r '.packages[] | select(.name=="wasm-shim") | .version') + +if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then + echo "::error::Could not read wasm-shim version from cargo metadata" + exit 1 +fi + +if [[ "$VERSION" == *-dev* ]]; then + VERSION="0.0.0" +fi + +yq --inplace ".\"wasm-shim\".version = \"${VERSION}\"" "$RELEASE_YAML" + +echo "release.yaml synced: version=${VERSION}" diff --git a/.github/scripts/validate-release-yaml.sh b/.github/scripts/validate-release-yaml.sh new file mode 100755 index 00000000..0ed3c369 --- /dev/null +++ b/.github/scripts/validate-release-yaml.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +BRANCH="${1:?Branch name required}" +ORG="${2:-Kuadrant}" +RELEASE_YAML="${3:-release.yaml}" + +if [[ ! -f "$RELEASE_YAML" ]]; then + echo "::error::File not found: $RELEASE_YAML" + exit 1 +fi + +VERSION=$(yq '.wasm-shim.version' "$RELEASE_YAML") + +if [[ "$BRANCH" =~ ^release- ]]; then + if [[ "$VERSION" == "0.0.0" ]]; then + echo "::error::release.yaml version is 0.0.0 on branch '$BRANCH' -- must specify a release version on release branches" + exit 1 + fi + + if [[ "$VERSION" == *-dev* ]]; then + echo "::error::release.yaml version '${VERSION}' is a dev version on branch '$BRANCH' -- release versions must not contain '-dev'" + exit 1 + fi +fi + +DEPS=$(yq '.dependencies | keys | .[]' "$RELEASE_YAML" 2>/dev/null || true) +for dep in $DEPS; do + dep_version=$(yq ".dependencies.${dep}" "$RELEASE_YAML") + if [[ "$dep_version" != "0.0.0" && "$dep_version" != "null" && -n "$dep_version" ]]; then + if ! gh release view "v${dep_version}" --repo "${ORG}/${dep}" &>/dev/null; then + echo "::error::Dependency '${dep}' targets version '${dep_version}', but release v${dep_version} does not exist in ${ORG}/${dep}" + exit 1 + fi + fi +done + +echo "release.yaml validation passed" diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index 3b2b2c4f..aa2bead5 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -154,11 +154,18 @@ jobs: - name: Update Cargo.lock run: cargo check --target wasm32-wasip1 + - uses: ./.github/actions/install-yq + + - name: Sync release.yaml from Cargo.toml + run: | + chmod +x .github/scripts/sync-release-yaml.sh + .github/scripts/sync-release-yaml.sh + - name: Commit and push changes run: | git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" git config user.name "${{ github.actor }}" - git add Cargo.toml Cargo.lock + git add Cargo.toml Cargo.lock release.yaml git commit -s -m "chore: bump version to ${{ steps.next.outputs.next-dev }}" git push --set-upstream origin "post-release-v${{ needs.setup.outputs.version }}" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2edd91a9..b8fe146a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,8 +26,17 @@ jobs: with: ref: ${{ inputs.release-branch }} + - uses: ./.github/actions/setup-rust-wasm + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: ./.github/actions/install-yq + - name: Check version consistency + run: | + chmod +x .github/scripts/check-versions.sh + .github/scripts/check-versions.sh + - name: Parse version from release.yaml id: parse run: | diff --git a/.github/workflows/version-gate.yaml b/.github/workflows/version-gate.yaml index cae2944e..ea491916 100644 --- a/.github/workflows/version-gate.yaml +++ b/.github/workflows/version-gate.yaml @@ -3,64 +3,40 @@ name: Version Gate on: pull_request: - branches: - - 'release-[0-9]+.[0-9]+' paths: - - 'release.yaml' + - "release.yaml" + - "Cargo.toml" permissions: contents: read jobs: - validate-version: - name: Validate Release Version + check-version-consistency: + name: Check Version Consistency runs-on: ubuntu-latest steps: - - name: Checkout PR branch - uses: actions/checkout@v6 - + - uses: actions/checkout@v6 + - uses: ./.github/actions/setup-rust-wasm + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - uses: ./.github/actions/install-yq - - - name: Validate version is not sentinel + - name: Check Cargo.toml and release.yaml agree run: | - VERSION=$(yq '.wasm-shim.version' release.yaml) - if [ -z "$VERSION" ]; then - echo "::error::Could not parse version from release.yaml" - exit 1 - fi - if [ "$VERSION" = "0.0.0" ]; then - echo "::error::version in release.yaml is 0.0.0 — must be a concrete version on release branches" - exit 1 - fi - echo "Version: $VERSION" + chmod +x .github/scripts/check-versions.sh + .github/scripts/check-versions.sh - - name: Validate dependency versions + validate-release-yaml: + name: Validate Release Version + if: startsWith(github.base_ref, 'release-') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/install-yq + - name: Validate release.yaml env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BASE_REF: ${{ github.base_ref }} + ORG: ${{ github.repository_owner }} run: | - FAILED=0 - - DEPS=$(yq -o=json '.dependencies // {}' release.yaml) - for DEP_NAME in $(echo "$DEPS" | jq -r 'keys[]'); do - DEP_VERSION=$(echo "$DEPS" | jq -r --arg k "$DEP_NAME" '.[$k]') - - if [ "$DEP_VERSION" = "0.0.0" ]; then - echo "::error::Dependency $DEP_NAME has version 0.0.0 — must be a concrete released version" - FAILED=1 - continue - fi - - DEP_TAG="v$DEP_VERSION" - if ! gh api "repos/Kuadrant/$DEP_NAME/releases/tags/$DEP_TAG" > /dev/null 2>&1; then - echo "::error::GitHub Release $DEP_TAG not found for dependency $DEP_NAME in Kuadrant/$DEP_NAME" - FAILED=1 - else - echo "Dependency $DEP_NAME: release $DEP_TAG exists" - fi - done - - if [ "$FAILED" -ne 0 ]; then - exit 1 - fi - - echo "All dependency versions validated" + chmod +x .github/scripts/validate-release-yaml.sh + .github/scripts/validate-release-yaml.sh "$BASE_REF" "$ORG" diff --git a/RELEASE.md b/RELEASE.md index c3e9ad50..68008113 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,7 +9,16 @@ Every release is split into two workflows with a PR-based review gate between th 1. **Pre-release** — makes code changes and opens a PR to the release branch 2. **Release** — tests, tags, builds artifacts, and creates the GitHub Release -The `release.yaml` file at the repository root is the machine-readable source of truth for version information. +### Source of Truth + +**`Cargo.toml` is the authoritative source for the wasm-shim version.** The `release.yaml` file at the repository root is a derived mirror maintained by `sync-release-yaml.sh` for cross-repo tooling compatibility. + +Version flows one way: `Cargo.toml` → `sync-release-yaml.sh` → `release.yaml`. Nothing writes to `release.yaml` directly except the sync script. + +| State | Branch | `Cargo.toml` | `release.yaml` | +|-------|--------|-------------|---------------| +| Development | `main` | `X.Y.0-dev` | `0.0.0` (sentinel) | +| Release | `release-X.Y` | `X.Y.Z` | `X.Y.Z` (exact match) | ## Quick Start @@ -17,7 +26,7 @@ The `release.yaml` file at the repository root is the machine-readable source of - **version**: Target version (e.g., `0.13.0`) - **source-branch**: `main` for new minor releases (default) 2. **Review and merge the PR** that gets created against the release branch - - The version gate check validates `release.yaml` before merge + - The version gate checks that `Cargo.toml` and `release.yaml` agree 3. **Run the release workflow**: Actions → "Release" → "Run workflow" - **release-branch**: The release branch (e.g., `release-0.13`) 4. **Done** — smoke tests run, tag is created, artifacts are built, and the GitHub Release is published @@ -27,11 +36,11 @@ The `release.yaml` file at the repository root is the machine-readable source of 1. Actions → "Pre-release" → "Run workflow" - **version**: `0.13.0` - **source-branch**: `main` -2. The workflow creates branch `release-0.13` (if it doesn't exist), updates `release.yaml` and `Cargo.toml`, and opens a PR +2. The workflow creates branch `release-0.13` (if it doesn't exist), updates `Cargo.toml` (then syncs `release.yaml`), and opens a PR 3. Review and merge the PR (version gate and CI checks must pass) 4. Actions → "Release" → "Run workflow" - **release-branch**: `release-0.13` -5. The workflow reads the version from `release.yaml`, runs smoke tests, creates tag `v0.13.0`, builds the WASM binary and container image, and creates the GitHub Release +5. The workflow verifies `Cargo.toml` and `release.yaml` agree, reads the version, runs smoke tests, creates tag `v0.13.0`, builds the WASM binary and container image, and creates the GitHub Release ## Patch Release @@ -49,6 +58,21 @@ The `release.yaml` file at the repository root is the machine-readable source of 4. Actions → "Release" → "Run workflow" - **release-branch**: `release-0.13` +## File Inventory + +| File | Purpose | +|------|---------| +| `Cargo.toml` | **Source of truth** for version | +| `release.yaml` | Derived mirror for cross-repo tooling | +| `.github/scripts/sync-release-yaml.sh` | Syncs `release.yaml` from `Cargo.toml` | +| `.github/scripts/check-versions.sh` | Validates `Cargo.toml` and `release.yaml` agree | +| `.github/scripts/parse-version.sh` | Reads and decomposes version from `release.yaml` | +| `.github/scripts/validate-release-yaml.sh` | Validates `release.yaml` on release branches | +| `.github/actions/prepare-release/action.yaml` | Sets version in `Cargo.toml`, syncs `release.yaml` | +| `.github/workflows/pre-release.yaml` | Phase 1: prepare release PR | +| `.github/workflows/release.yaml` | Phase 2: test, tag, build, publish | +| `.github/workflows/version-gate.yaml` | CI gate: version consistency and release validation | + ## Repository Configuration ### Required Secrets @@ -74,9 +98,9 @@ Configure these in Settings → Secrets and variables → Actions → Repository ## Details -- **Version format**: semver without `v` prefix in `release.yaml` (e.g., `0.13.0`, not `v0.13.0`) +- **Version format**: semver without `v` prefix in `Cargo.toml` and `release.yaml` (e.g., `0.13.0`, not `v0.13.0`) - **Release branches**: `release-0.13`, `release-0.14`, etc. — one branch per minor version, shared by all patches -- **Version gate**: A CI check on release branch PRs validates that `release.yaml` has a concrete version (not `0.0.0`) -- **On `main`**: `release.yaml` always has version `0.0.0` (sentinel for active development) +- **Version gate**: A CI check on PRs that touch `Cargo.toml` or `release.yaml` validates consistency. On release branch PRs, it additionally rejects sentinel and dev versions. +- **On `main`**: `Cargo.toml` has `-dev` suffix, `release.yaml` has `0.0.0` (sentinel) - **Artifacts built during release**: WASM binary (attached to GitHub Release) and container image (pushed to `quay.io//wasm-shim`) - **GitHub Release is always the last step** — if any preceding step fails, no release is created From 39613cd127ee2ff9cc9b5744d84a083444e01258 Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Thu, 13 Aug 2026 12:21:20 +0100 Subject: [PATCH 5/5] UPDATE: use crago set-version Signed-off-by: Jim Fitzpatrick --- .github/actions/prepare-release/action.yaml | 37 +++------------- .github/actions/setup-rust-wasm/action.yaml | 4 ++ .github/scripts/sync-release-yaml.sh | 2 + .github/workflows/pre-release.yaml | 48 ++++++++++++++------- .github/workflows/release.yaml | 46 ++++++++++++++++---- 5 files changed, 84 insertions(+), 53 deletions(-) diff --git a/.github/actions/prepare-release/action.yaml b/.github/actions/prepare-release/action.yaml index b77aab9c..25e89ad4 100644 --- a/.github/actions/prepare-release/action.yaml +++ b/.github/actions/prepare-release/action.yaml @@ -5,10 +5,6 @@ inputs: version: description: 'WASM Shim version (semver, e.g., 0.12.1)' required: true - push-branch: - description: 'Whether to push the base branch immediately if it does not exist' - required: false - default: 'false' github-token: description: 'GitHub token for protoc setup' required: true @@ -17,9 +13,6 @@ outputs: wasm-shim-version: description: 'The validated WASM Shim version' value: ${{ steps.validate.outputs.version }} - base-branch: - description: 'The base release branch name' - value: ${{ steps.create-branch.outputs.base-branch }} runs: using: 'composite' @@ -36,34 +29,18 @@ runs: echo "WASM_SHIM_VERSION=$VERSION" >> $GITHUB_ENV echo "version=$VERSION" >> $GITHUB_OUTPUT - - name: Create release branch - id: create-branch - shell: bash - run: | - base_branch=release-$(echo "$WASM_SHIM_VERSION" | sed 's/[+-].*//; s/\.[0-9]*$//') - echo "BASE_BRANCH=$base_branch" >> $GITHUB_ENV - echo "base-branch=$base_branch" >> $GITHUB_OUTPUT + - name: Set up Rust and WASM environment + uses: ./.github/actions/setup-rust-wasm + with: + github-token: ${{ inputs.github-token }} - if git ls-remote --exit-code --heads origin "$base_branch" ; then - echo "Base branch $base_branch already exists" - else - echo "Creating branch $base_branch" - git checkout -b "$base_branch" - if [ "${{ inputs.push-branch }}" = "true" ]; then - echo "Pushing branch $base_branch to origin" - git push --set-upstream origin "$base_branch" - fi - fi + - shell: bash + run: cargo install cargo-edit --version 0.13.13 - name: Update Cargo.toml version shell: bash run: | - sed -i '0,/^version = ".*"/s//version = "'"$WASM_SHIM_VERSION"'"/' Cargo.toml - - - name: Set up Rust and WASM environment - uses: ./.github/actions/setup-rust-wasm - with: - github-token: ${{ inputs.github-token }} + cargo set-version --offline -p wasm-shim "$WASM_SHIM_VERSION" - name: Update Cargo.lock shell: bash diff --git a/.github/actions/setup-rust-wasm/action.yaml b/.github/actions/setup-rust-wasm/action.yaml index f5479353..dc53855b 100644 --- a/.github/actions/setup-rust-wasm/action.yaml +++ b/.github/actions/setup-rust-wasm/action.yaml @@ -31,3 +31,7 @@ runs: elif [ "$RUNNER_OS" == "macOS" ]; then brew install protobuf fi + + - name: Fetch crate sources and registry index entries + shell: bash + run: cargo fetch --locked diff --git a/.github/scripts/sync-release-yaml.sh b/.github/scripts/sync-release-yaml.sh index bf4b101c..3a658342 100755 --- a/.github/scripts/sync-release-yaml.sh +++ b/.github/scripts/sync-release-yaml.sh @@ -11,6 +11,8 @@ if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then exit 1 fi +# On main, Cargo.toml has -dev versions but release.yaml uses 0.0.0 sentinel. +# Strip -dev suffix: if present, write 0.0.0 instead. if [[ "$VERSION" == *-dev* ]]; then VERSION="0.0.0" fi diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index aa2bead5..4d0a5a3c 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -18,6 +18,10 @@ permissions: contents: write pull-requests: write +concurrency: + group: pre-release + cancel-in-progress: false + jobs: setup: name: Setup @@ -64,26 +68,32 @@ jobs: needs: setup runs-on: ubuntu-latest steps: - - name: Checkout source branch + - name: Checkout release branch uses: actions/checkout@v6 with: - ref: ${{ inputs.source-branch }} + ref: ${{ needs.setup.outputs.release-branch }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - name: Create working branch run: | - git checkout -b "pre-release-v${{ needs.setup.outputs.version }}" + PRE_RELEASE_BRANCH="pre-release-v${{ needs.setup.outputs.version }}" + if git ls-remote --exit-code origin "refs/heads/${PRE_RELEASE_BRANCH}" >/dev/null 2>&1; then + echo "::error::Pre-release branch '${PRE_RELEASE_BRANCH}' already exists. Delete it first or use a different version." + exit 1 + fi + git checkout -b "${PRE_RELEASE_BRANCH}" - name: Prepare release uses: ./.github/actions/prepare-release with: version: ${{ needs.setup.outputs.version }} - push-branch: "false" github-token: ${{ secrets.GITHUB_TOKEN }} - name: Commit and push changes run: | - git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" - git config user.name "${{ github.actor }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" git add -A git commit -s -m "chore: prepare release v${{ needs.setup.outputs.version }}" git push --set-upstream origin "pre-release-v${{ needs.setup.outputs.version }}" @@ -114,6 +124,7 @@ jobs: bump-dev: name: Bump Dev Version + if: inputs.source-branch == 'main' needs: [setup, prepare-release] runs-on: ubuntu-latest steps: @@ -140,19 +151,23 @@ jobs: - name: Create working branch run: | - git checkout -b "post-release-v${{ needs.setup.outputs.version }}" - - - name: Update Cargo.toml to next dev version - run: | - sed -i '0,/^version = ".*"/s//version = "${{ steps.next.outputs.next-dev }}"/' Cargo.toml + POST_RELEASE_BRANCH="post-release-v${{ needs.setup.outputs.version }}" + if git ls-remote --exit-code origin "refs/heads/${POST_RELEASE_BRANCH}" >/dev/null 2>&1; then + echo "::error::Post-release branch '${POST_RELEASE_BRANCH}' already exists. Delete it first or use a different version." + exit 1 + fi + git checkout -b "${POST_RELEASE_BRANCH}" - name: Set up Rust and WASM environment uses: ./.github/actions/setup-rust-wasm with: github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Update Cargo.lock - run: cargo check --target wasm32-wasip1 + - run: cargo install cargo-edit --version 0.13.13 + + - name: Update Cargo.toml to next dev version + run: | + cargo set-version --offline -p wasm-shim "${{ steps.next.outputs.next-dev }}" - uses: ./.github/actions/install-yq @@ -161,10 +176,13 @@ jobs: chmod +x .github/scripts/sync-release-yaml.sh .github/scripts/sync-release-yaml.sh + - name: Update Cargo.lock + run: cargo check --target wasm32-wasip1 + - name: Commit and push changes run: | - git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" - git config user.name "${{ github.actor }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" git add Cargo.toml Cargo.lock release.yaml git commit -s -m "chore: bump version to ${{ steps.next.outputs.next-dev }}" git push --set-upstream origin "post-release-v${{ needs.setup.outputs.version }}" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b8fe146a..bedcf34a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,11 @@ on: type: string permissions: - contents: write + contents: read + +concurrency: + group: release-${{ inputs.release-branch }} + cancel-in-progress: false jobs: read-version: @@ -55,6 +59,19 @@ jobs: echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT" echo "Releasing version $VERSION (tag: $TAG, prerelease: $PRERELEASE)" + - name: Validate branch matches version + env: + RELEASE_BRANCH: ${{ inputs.release-branch }} + VERSION: ${{ steps.parse.outputs.version }} + run: | + MAJOR=$(echo "$VERSION" | cut --delimiter=. --fields=1) + MINOR=$(echo "$VERSION" | cut --delimiter=. --fields=2) + EXPECTED_BRANCH="release-${MAJOR}.${MINOR}" + if [[ "$RELEASE_BRANCH" != "$EXPECTED_BRANCH" ]]; then + echo "::error::Branch '${RELEASE_BRANCH}' does not match version ${VERSION} (expected branch: $EXPECTED_BRANCH)" + exit 1 + fi + - name: Check for existing release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -82,32 +99,43 @@ jobs: rust-components: rustfmt, clippy github-token: ${{ secrets.GITHUB_TOKEN }} - - name: cargo build (wasm) - run: cargo build --release --target wasm32-wasip1 - - name: cargo fmt run: cargo fmt --all -- --check - name: cargo clippy - run: cargo clippy --all-targets --all-features -- -D warnings + run: cargo clippy --locked --all-targets --all-features -- -D warnings + + - name: cargo check (wasm) + run: cargo check --locked --release --target wasm32-wasip1 - name: cargo test - run: cargo test + run: cargo test --locked tag: name: Tag needs: [read-version, smoke-tests] runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout release branch uses: actions/checkout@v6 with: ref: ${{ inputs.release-branch }} + fetch-depth: 0 - name: Create and push tag + env: + TAG: ${{ needs.read-version.outputs.tag }} run: | - git tag "${{ needs.read-version.outputs.tag }}" - git push origin "${{ needs.read-version.outputs.tag }}" + if git tag --list "$TAG" | grep --quiet .; then + echo "::error::Tag $TAG already exists" + exit 1 + fi + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag --annotate "$TAG" --message "Release $TAG" + git push origin "$TAG" build-wasm: name: Build WASM Binary @@ -154,6 +182,8 @@ jobs: name: Create GitHub Release needs: [read-version, build-wasm, build-image] runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout tag uses: actions/checkout@v6