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 c8d2f95e..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,35 +29,27 @@ 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 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/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/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..3a658342 --- /dev/null +++ b/.github/scripts/sync-release-yaml.sh @@ -0,0 +1,22 @@ +#!/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 + +# 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 + +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/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..da2e3a6f 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -8,11 +8,25 @@ 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 + IMG_REGISTRY_ORG: ${{ vars.IMG_REGISTRY_ORG || 'kuadrant' }} MAIN_BRANCH_NAME: main jobs: @@ -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..4d0a5a3c --- /dev/null +++ b/.github/workflows/pre-release.yaml @@ -0,0 +1,198 @@ +--- +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 + +concurrency: + group: pre-release + cancel-in-progress: false + +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 release branch + uses: actions/checkout@v6 + with: + ref: ${{ needs.setup.outputs.release-branch }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create working branch + run: | + 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 }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Commit and push changes + run: | + 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 }}" + + 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 + if: inputs.source-branch == 'main' + 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: | + 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 }} + + - 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 + + - name: Sync release.yaml from Cargo.toml + run: | + 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.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 }}" + + - 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..bedcf34a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,49 +2,151 @@ 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 + contents: read + +concurrency: + group: release-${{ inputs.release-branch }} + cancel-in-progress: false 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 }} + + - 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: 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: Validate branch matches version + env: + RELEASE_BRANCH: ${{ inputs.release-branch }} + VERSION: ${{ steps.parse.outputs.version }} run: | - if git ls-remote --tags origin | grep -q "refs/tags/${{ env.TAG }}$"; then - echo "Tag ${{ env.TAG }} already exists - skipping release" + 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 - else - echo "Tag ${{ env.TAG }} does not exist - proceeding with release" fi + - name: Check for existing release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.parse.outputs.tag }}" + if gh release view "$TAG" > /dev/null 2>&1; then + echo "::error::GitHub Release $TAG already exists" + exit 1 + 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 fmt + run: cargo fmt --all -- --check + + - name: cargo clippy + 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 --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: | + 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 + 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 with: @@ -55,19 +157,50 @@ 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 + permissions: + contents: write + 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..ea491916 --- /dev/null +++ b/.github/workflows/version-gate.yaml @@ -0,0 +1,42 @@ +--- +name: Version Gate + +on: + pull_request: + paths: + - "release.yaml" + - "Cargo.toml" + +permissions: + contents: read + +jobs: + check-version-consistency: + name: Check Version Consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/setup-rust-wasm + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: ./.github/actions/install-yq + - name: Check Cargo.toml and release.yaml agree + run: | + chmod +x .github/scripts/check-versions.sh + .github/scripts/check-versions.sh + + 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: | + 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 e4d9f02b..68008113 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,47 +1,106 @@ # 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 +## Overview + +Every release is split into two workflows with a PR-based review gate between them: + +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 -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 +### Source of Truth -## Standard Release +**`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. -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 +Version flows one way: `Cargo.toml` → `sync-release-yaml.sh` → `release.yaml`. Nothing writes to `release.yaml` directly except the sync script. -## Release with Cherry-picked Fixes +| 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 -1. **First, cherry-pick and merge your fixes:** +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 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 +## 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 `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 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 + +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` +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` + +## 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 + +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 -3. Review and merge the version bump PR -4. Tag and release created automatically +- **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 (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 `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 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 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"