From a9e45a52f2e6da5fafa8c56d4ad3a4ca0dfc8af1 Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Wed, 24 Jun 2026 10:00:31 +0100 Subject: [PATCH 1/2] ADD: Two Phase Workflow This is based on RFC 0020. Signed-off-by: Jim Fitzpatrick --- .github/scripts/parse-version.sh | 31 ++ .github/scripts/validate-release-yaml.sh | 31 ++ .../build-images-for-tag-release.yaml | 200 --------- .github/workflows/pre-release.yaml | 159 +++++++ .github/workflows/release-helm-chart.yaml | 71 --- .github/workflows/release.yaml | 414 ++++++++++++++++++ .github/workflows/upload-cli.yml | 41 -- .github/workflows/version-gate.yaml | 29 ++ docs/RELEASE.md | 187 ++++---- release.yaml | 2 + 10 files changed, 771 insertions(+), 394 deletions(-) create mode 100755 .github/scripts/parse-version.sh create mode 100755 .github/scripts/validate-release-yaml.sh delete mode 100644 .github/workflows/build-images-for-tag-release.yaml create mode 100644 .github/workflows/pre-release.yaml delete mode 100644 .github/workflows/release-helm-chart.yaml create mode 100644 .github/workflows/release.yaml delete mode 100644 .github/workflows/upload-cli.yml create mode 100644 .github/workflows/version-gate.yaml create mode 100644 release.yaml diff --git a/.github/scripts/parse-version.sh b/.github/scripts/parse-version.sh new file mode 100755 index 00000000..dc2d2581 --- /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 '.dns-operator.version' "$RELEASE_YAML") +if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then + echo "::error::No version found in $RELEASE_YAML under dns-operator.version" + exit 1 +fi + +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "::error::Invalid semver: $VERSION" + exit 1 +fi + +MAJOR=$(echo "$VERSION" | cut -d. -f1) +MINOR=$(echo "$VERSION" | cut -d. -f2) +PATCH=$(echo "$VERSION" | cut -d. -f3 | cut -d- -f1) +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/validate-release-yaml.sh b/.github/scripts/validate-release-yaml.sh new file mode 100755 index 00000000..c837915f --- /dev/null +++ b/.github/scripts/validate-release-yaml.sh @@ -0,0 +1,31 @@ +#!/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 '.dns-operator.version' "$RELEASE_YAML") + +if [[ "$BRANCH" != "main" && "$VERSION" == "0.0.0" ]]; then + echo "::error::release.yaml version is 0.0.0 on branch '$BRANCH' -- must specify a release version on non-main branches" + exit 1 +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/build-images-for-tag-release.yaml b/.github/workflows/build-images-for-tag-release.yaml deleted file mode 100644 index 7940ff67..00000000 --- a/.github/workflows/build-images-for-tag-release.yaml +++ /dev/null @@ -1,200 +0,0 @@ -name: Build and Publish Images For Tag Release - -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" -env: - IMG_REGISTRY_HOST: quay.io - IMG_REGISTRY_ORG: kuadrant - IMG_REGISTRY_REPO: dns-operator - OPERATOR_NAME: dns-operator - -jobs: - build: - name: Build and Push image - runs-on: ubuntu-latest - outputs: - build-tags: ${{ steps.build-image.outputs.tags }} - image: ${{ steps.push-to-quay.outputs.registry-path }} - steps: - - name: Check out code - uses: actions/checkout@v4 - - - name: Read release string version - id: release - run: | - version=`make read-release-version` - echo version=$version >> $GITHUB_OUTPUT - - - name: Print tags - run: echo "Git reference name = ${{ github.ref_name }}, release version = ${{ steps.release.outputs.version }}" - - name: Verify git reference name matches the release version - if: ${{ github.ref_name != steps.release.outputs.version }} - run: exit 1 - - - name: Install qemu dependency - run: | - sudo apt-get update - sudo apt-get install -y qemu-user-static - - - name: Build Image - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - image: ${{ env.OPERATOR_NAME }} - tags: ${{ github.ref_name }} - platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le - build-args: | - GIT_SHA=${{ github.sha }} - DIRTY=false - VERSION=${{ github.ref_name }} - - dockerfiles: | - ./Dockerfile - - - name: Print Build Info - run: echo "Image = ${{ steps.build-image.outputs.image }}, Tags = ${{ steps.build-image.outputs.tags }}" - - - name: Push Image - if: github.repository_owner == 'kuadrant' - id: push-to-quay - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ steps.build-image.outputs.image }} - tags: ${{ steps.build-image.outputs.tags }} - 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 }}" - - build-bundle: - name: Build and Push bundle image - needs: [build] - runs-on: ubuntu-latest - outputs: - build-tags: ${{ steps.build-image.outputs.tags }} - image: ${{ steps.push-to-quay.outputs.registry-path }} - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Install yq tool - run: | - # following sub-shells running make target should have yq already installed - make yq - - name: Read operator image reference URL from the manifest bundle - id: parsed-operator-image - run: | - url=`make bundle-operator-image-url` - echo url=$url >> $GITHUB_OUTPUT - - name: Print tags and references - run: echo "Operator image tag = ${{ needs.build.outputs.image }}, Reference in bundle = ${{ steps.parsed-operator-image.outputs.url }}" - - name: Verify referenced operator image tag matches the tag currently being built - if: ${{ needs.build.outputs.image != steps.parsed-operator-image.outputs.url }} - run: exit 1 - - - name: Install qemu dependency - run: | - sudo apt-get update - sudo apt-get install -y qemu-user-static - - - name: Build Image - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - image: ${{ env.OPERATOR_NAME }}-bundle - tags: ${{ needs.build.outputs.build-tags }} - platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le - build-args: QUAY_IMAGE_EXPIRY=never - dockerfiles: | - ./bundle.Dockerfile - - - name: Print Build Info - run: echo "Image = ${{ steps.build-image.outputs.image }}, Tags = ${{ steps.build-image.outputs.tags }}, Operator IMG = ${{ steps.parsed-operator-image.outputs.url }}" - - - name: Push Image - if: github.repository_owner == 'kuadrant' - id: push-to-quay - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ steps.build-image.outputs.image }} - tags: ${{ steps.build-image.outputs.tags }} - 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 }}" - - build-catalog: - name: Build and Push catalog image - needs: [build, build-bundle] - runs-on: ubuntu-latest - outputs: - build-tags: ${{ steps.build-image.outputs.tags }} - image: ${{ steps.push-to-quay.outputs.registry-path }} - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Install yq tool - run: | - # following sub-shells running make target should have yq already installed - make yq - - name: Read operator bundle image reference - id: parsed-operator-bundle - run: | - image=`make print-bundle-image` - echo image=$image >> $GITHUB_OUTPUT - - name: Print tags and references - run: echo "Operator bundle image tag = ${{ needs.build-bundle.outputs.image }}, Reference in catalog = ${{ steps.parsed-operator-bundle.outputs.image }}" - - name: Verify referenced bundle tag matches the bundle tag currently being built - if: ${{ needs.build-bundle.outputs.image != steps.parsed-operator-bundle.outputs.image }} - run: exit 1 - - name: Run make catalog-build - run: make catalog-build - - name: Install qemu dependency - run: | - sudo apt-get update - sudo apt-get install -y qemu-user-static - - - name: Build Image - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - image: ${{ env.OPERATOR_NAME }}-catalog - tags: ${{ needs.build.outputs.build-tags }} - platforms: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le - context: ./tmp/catalog - dockerfiles: | - ./tmp/catalog/index.Dockerfile - - - name: Print Build Info - run: echo "Image = ${{ steps.build-image.outputs.image }}, Tags = ${{ steps.build-image.outputs.tags }}, Bundle IMG = ${{ steps.parsed-operator-bundle.outputs.image }}" - - - name: Push Image - if: github.repository_owner == 'kuadrant' - id: push-to-quay - uses: redhat-actions/push-to-registry@v2 - with: - image: ${{ steps.build-image.outputs.image }} - tags: ${{ steps.build-image.outputs.tags }} - 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 }}" - - verify-builds: - name: Ensure all image references are equal (operator, bundle, catalog) - needs: [build, build-bundle, build-catalog] - runs-on: ubuntu-latest - steps: - - name: Verify bundle and operator image tags match - if: ${{ needs.build.outputs.build-tags != needs.build-bundle.outputs.build-tags }} - run: exit 1 - - name: Verify catalog and bundle tags match - if: ${{ needs.build-bundle.outputs.build-tags != needs.build-catalog.outputs.build-tags }} - run: exit 1 diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml new file mode 100644 index 00000000..b6764e06 --- /dev/null +++ b/.github/workflows/pre-release.yaml @@ -0,0 +1,159 @@ +name: Pre-release + +on: + workflow_dispatch: + inputs: + version: + description: "Release version (semver, e.g. 1.5.0)" + required: true + type: string + source-branch: + description: "Branch to base the release on (default: main)" + required: false + type: string + default: "main" + +concurrency: + group: pre-release + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.validate.outputs.version }} + release-branch: ${{ steps.validate.outputs.release-branch }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.source-branch }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Validate version format + id: validate + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "::error::Invalid semver version: $INPUT_VERSION" + exit 1 + fi + MAJOR=$(echo "$INPUT_VERSION" | cut -d. -f1) + MINOR=$(echo "$INPUT_VERSION" | cut -d. -f2) + RELEASE_BRANCH="release-${MAJOR}.${MINOR}" + echo "version=$INPUT_VERSION" >> "$GITHUB_OUTPUT" + echo "release-branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT" + + - name: Create or verify release branch + env: + RELEASE_BRANCH: ${{ steps.validate.outputs.release-branch }} + SOURCE_BRANCH: ${{ inputs.source-branch }} + run: | + if git ls-remote --exit-code origin "refs/heads/${RELEASE_BRANCH}" >/dev/null 2>&1; then + echo "Release branch '${RELEASE_BRANCH}' already exists" + else + echo "Creating release branch '${RELEASE_BRANCH}' from '${SOURCE_BRANCH}'" + git checkout -b "${RELEASE_BRANCH}" + git push origin "${RELEASE_BRANCH}" + fi + + prepare-release: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.setup.outputs.release-branch }} + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Create pre-release branch + env: + VERSION: ${{ needs.setup.outputs.version }} + run: | + PRE_RELEASE_BRANCH="pre-release-v${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: Install yq + run: | + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Update release.yaml + env: + VERSION: ${{ needs.setup.outputs.version }} + run: yq -i ".dns-operator.version = \"${VERSION}\"" release.yaml + + - name: Run prepare-release + env: + VERSION: ${{ needs.setup.outputs.version }} + run: make prepare-release VERSION="${VERSION}" CHANNELS=stable + + - name: Commit and push changes + env: + VERSION: ${{ needs.setup.outputs.version }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add release.yaml make/release.mk bundle/ charts/ config/ bundle.Dockerfile + if git diff --cached --quiet; then + echo "::error::No changes generated by prepare-release" + exit 1 + fi + git commit -m "chore: prepare release v${VERSION}" + git push origin "pre-release-v${VERSION}" + + open-pr: + needs: [setup, prepare-release] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Open pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ needs.setup.outputs.version }} + RELEASE_BRANCH: ${{ needs.setup.outputs.release-branch }} + run: | + BODY=$(cat < ~/.gnupg/pubring.gpg - env: - GPG_SIGNING_KEY: ${{ secrets.HELM_CHARTS_SIGNING_KEY }} - - - name: Package Helm Chart - run: | - GPG_KEY_UID="Kuadrant Development Team" \ - make helm-package-sign - - - name: Parse Tag - run: | - tag=${{ github.event.release.tag_name || inputs.operatorTag }} - echo "OPERATOR_VERSION=${tag#v}" >> $GITHUB_ENV - echo "OPERATOR_TAG=${tag}" >> $GITHUB_ENV - - - name: Upload package to GitHub Release - uses: svenstaro/upload-release-action@v2 - id: upload-chart - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: dns-operator-${{ env.OPERATOR_VERSION }}.tgz - asset_name: chart-dns-operator-${{ env.OPERATOR_VERSION }}.tgz - tag: ${{ env.OPERATOR_TAG }} - overwrite: true - - - name: Upload provenance file to GitHub Release - uses: svenstaro/upload-release-action@v2 - id: upload-prov-file - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: dns-operator-${{ env.OPERATOR_VERSION }}.tgz.prov - asset_name: chart-dns-operator-${{ env.OPERATOR_VERSION }}.tgz.prov - tag: ${{ env.OPERATOR_TAG }} - overwrite: true - - - name: Sync Helm Chart with repository - run: | - make helm-sync-package-created \ - VERSION=${{env.OPERATOR_VERSION}} \ - HELM_WORKFLOWS_TOKEN=${{ secrets.HELM_WORKFLOWS_TOKEN }} \ - BROWSER_DOWNLOAD_URL=${{ steps.upload-chart.outputs.browser_download_url }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..8647515c --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,414 @@ +name: Release + +on: + workflow_dispatch: + inputs: + release-branch: + description: "Release branch (e.g. release-1.5)" + required: true + type: string + +concurrency: + group: release-${{ inputs.release-branch }} + cancel-in-progress: false + +permissions: + contents: write + +env: + IMG_REGISTRY_HOST: quay.io + IMG_REGISTRY_ORG: kuadrant + OPERATOR_NAME: dns-operator + COREDNS_NAME: coredns-kuadrant + PLATFORMS: linux/amd64,linux/arm64,linux/s390x,linux/ppc64le + +jobs: + read-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.parse.outputs.version }} + major: ${{ steps.parse.outputs.major }} + minor: ${{ steps.parse.outputs.minor }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Parse version from release.yaml + id: parse + run: .github/scripts/parse-version.sh + + - name: Validate branch matches version + env: + INPUT_BRANCH: ${{ inputs.release-branch }} + MAJOR: ${{ steps.parse.outputs.major }} + MINOR: ${{ steps.parse.outputs.minor }} + VERSION: ${{ steps.parse.outputs.version }} + run: | + EXPECTED_BRANCH="release-${MAJOR}.${MINOR}" + if [[ "$INPUT_BRANCH" != "$EXPECTED_BRANCH" ]]; then + echo "::error::Branch '${INPUT_BRANCH}' does not match version ${VERSION} (expected branch: ${EXPECTED_BRANCH})" + exit 1 + fi + + - name: Verify no existing release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.parse.outputs.version }} + run: | + TAG="v${VERSION}" + if gh release view "$TAG" &>/dev/null; then + echo "::error::GitHub Release $TAG already exists" + exit 1 + fi + + smoke-tests: + needs: read-version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Verify manifests + run: make verify-manifests + + - name: Verify bundle + run: make verify-bundle + + - name: Verify helm build + run: make verify-helm-build + + - name: Run unit tests + run: make test-unit + + - name: Run CoreDNS unit tests + run: make coredns-test-unit + + tag: + needs: [read-version, smoke-tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + fetch-depth: 0 + + - name: Create and push tag + env: + VERSION: ${{ needs.read-version.outputs.version }} + run: | + TAG="v${VERSION}" + if git rev-parse "$TAG" >/dev/null 2>&1; 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 -a "$TAG" -m "Release $TAG" + git push origin "$TAG" + + build-operator-image: + needs: [read-version, tag] + runs-on: ubuntu-latest + outputs: + build-tags: ${{ steps.build-image.outputs.tags }} + image: ${{ steps.push-to-quay.outputs.registry-path }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Verify release version matches + env: + EXPECTED: v${{ needs.read-version.outputs.version }} + run: | + version=$(make read-release-version) + if [[ "$version" != "$EXPECTED" ]]; then + echo "::error::Makefile version '$version' does not match release version '$EXPECTED'" + exit 1 + fi + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.OPERATOR_NAME }} + tags: v${{ needs.read-version.outputs.version }} + platforms: ${{ env.PLATFORMS }} + build-args: | + GIT_SHA=${{ github.sha }} + DIRTY=false + VERSION=v${{ needs.read-version.outputs.version }} + dockerfiles: | + ./Dockerfile + + - name: Push Image + if: github.repository_owner == 'kuadrant' + id: push-to-quay + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }} + registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }} + username: ${{ secrets.IMG_REGISTRY_USERNAME }} + password: ${{ secrets.IMG_REGISTRY_TOKEN }} + + build-bundle-image: + needs: [read-version, build-operator-image] + runs-on: ubuntu-latest + outputs: + build-tags: ${{ steps.build-image.outputs.tags }} + image: ${{ steps.push-to-quay.outputs.registry-path }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Install yq + run: make yq + + - name: Verify operator image reference in bundle + env: + EXPECTED: ${{ needs.build-operator-image.outputs.image }} + run: | + url=$(make bundle-operator-image-url) + if [[ -n "$EXPECTED" && "$url" != "$EXPECTED" ]]; then + echo "::error::Bundle references operator image '$url' but expected '$EXPECTED'" + exit 1 + fi + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.OPERATOR_NAME }}-bundle + tags: v${{ needs.read-version.outputs.version }} + platforms: ${{ env.PLATFORMS }} + build-args: QUAY_IMAGE_EXPIRY=never + dockerfiles: | + ./bundle.Dockerfile + + - name: Push Image + if: github.repository_owner == 'kuadrant' + id: push-to-quay + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }} + registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }} + username: ${{ secrets.IMG_REGISTRY_USERNAME }} + password: ${{ secrets.IMG_REGISTRY_TOKEN }} + + build-catalog-image: + needs: [read-version, build-bundle-image] + runs-on: ubuntu-latest + outputs: + build-tags: ${{ steps.build-image.outputs.tags }} + image: ${{ steps.push-to-quay.outputs.registry-path }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Install yq + run: make yq + + - name: Verify bundle image reference in catalog + env: + EXPECTED: ${{ needs.build-bundle-image.outputs.image }} + run: | + image=$(make print-bundle-image) + if [[ -n "$EXPECTED" && "$image" != "$EXPECTED" ]]; then + echo "::error::Catalog references bundle image '$image' but expected '$EXPECTED'" + exit 1 + fi + + - name: Run make catalog-build + run: make catalog-build + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.OPERATOR_NAME }}-catalog + tags: v${{ needs.read-version.outputs.version }} + platforms: ${{ env.PLATFORMS }} + context: ./tmp/catalog + dockerfiles: | + ./tmp/catalog/index.Dockerfile + + - name: Push Image + if: github.repository_owner == 'kuadrant' + id: push-to-quay + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }} + registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }} + username: ${{ secrets.IMG_REGISTRY_USERNAME }} + password: ${{ secrets.IMG_REGISTRY_TOKEN }} + + build-coredns-image: + needs: [read-version, tag] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Run make coredns-generate-demo-geo-db + run: make coredns-generate-demo-geo-db + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.COREDNS_NAME }} + tags: v${{ needs.read-version.outputs.version }} + platforms: ${{ env.PLATFORMS }} + context: ./coredns/plugin + dockerfiles: | + ./coredns/plugin/Dockerfile + + - name: Push Image + if: github.repository_owner == 'kuadrant' + id: push-to-quay + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }} + registry: ${{ env.IMG_REGISTRY_HOST }}/${{ env.IMG_REGISTRY_ORG }} + username: ${{ secrets.IMG_REGISTRY_USERNAME }} + password: ${{ secrets.IMG_REGISTRY_TOKEN }} + + build-helm-chart: + needs: [read-version, tag] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + fetch-depth: 0 + + - name: Configure GPG Key + env: + GPG_SIGNING_KEY: ${{ secrets.HELM_CHARTS_SIGNING_KEY }} + run: | + echo -n "$GPG_SIGNING_KEY" | base64 -d | gpg --import --batch + + - name: Package and sign Helm Chart + run: | + GPG_KEY_UID="Kuadrant Development Team" \ + make helm-package-sign + + - name: Upload chart artifacts + uses: actions/upload-artifact@v4 + with: + name: helm-chart + path: | + dns-operator-${{ needs.read-version.outputs.version }}.tgz + dns-operator-${{ needs.read-version.outputs.version }}.tgz.prov + + build-cli-binaries: + needs: [read-version, tag] + runs-on: ubuntu-latest + strategy: + matrix: + goos: [linux, darwin] + goarch: [amd64, arm64, s390x, ppc64le] + exclude: + - goos: darwin + goarch: s390x + - goos: darwin + goarch: ppc64le + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build CLI binary + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: "0" + VERSION: v${{ needs.read-version.outputs.version }} + run: | + OUTPUT_NAME="kubectl-kuadrant_dns" + go build -ldflags "-X main.gitSHA=${{ github.sha }} -X main.version=${VERSION}" \ + -o "${OUTPUT_NAME}" ./cmd/plugin/ + tar czf "kubectl-kuadrant_dns-${VERSION}-${GOOS}-${GOARCH}.tar.gz" "${OUTPUT_NAME}" + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: cli-${{ matrix.goos }}-${{ matrix.goarch }} + path: kubectl-kuadrant_dns-*.tar.gz + + create-release: + needs: [read-version, build-catalog-image, build-coredns-image, build-helm-chart, build-cli-binaries] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.release-branch }} + + - name: Download helm chart artifacts + uses: actions/download-artifact@v4 + with: + name: helm-chart + path: release-assets/ + + - name: Download CLI binary artifacts + uses: actions/download-artifact@v4 + with: + pattern: cli-* + path: release-assets/ + merge-multiple: true + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ needs.read-version.outputs.version }} + run: | + TAG="v${VERSION}" + gh release create "$TAG" \ + --title "Release $TAG" \ + --generate-notes \ + release-assets/* diff --git a/.github/workflows/upload-cli.yml b/.github/workflows/upload-cli.yml deleted file mode 100644 index 6371888d..00000000 --- a/.github/workflows/upload-cli.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Upload CLI binary -on: - release: - types: - - created - workflow_dispatch: - inputs: - operatorTag: - description: Release tag - default: v0.0.0 - type: string - -permissions: - contents: write - packages: write - -jobs: - release-matrix: - name: Upload dns-operator CLI Binary - runs-on: ubuntu-latest - strategy: - matrix: - # build and publish in parallel: linux/amd64, linux/arm64, linux/s390x, linux/ppc64le, darwin/amd64, darwin/arm64 - goos: [linux, darwin] - goarch: [amd64, arm64, s390x, ppc64le] - exclude: - - goos: darwin - goarch: s390x - - goos: darwin - goarch: ppc64le - steps: - - uses: actions/checkout@v4 - - uses: wangyoucao577/go-release-action@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - goos: ${{ matrix.goos }} - goarch: ${{ matrix.goarch }} - project_path: ./cmd/plugin/ - binary_name: kubectl-kuadrant_dns - release_tag: ${{ github.event.release.tag_name || inputs.operatorTag }} - ldflags: -X "main.gitSHA=${{ github.sha }}" -X "main.version=${{ github.ref_name }}" \ No newline at end of file diff --git a/.github/workflows/version-gate.yaml b/.github/workflows/version-gate.yaml new file mode 100644 index 00000000..2eb90533 --- /dev/null +++ b/.github/workflows/version-gate.yaml @@ -0,0 +1,29 @@ +name: Version Gate + +on: + pull_request: + branches: + - "release-**" + paths: + - "release.yaml" + +permissions: + contents: read + +jobs: + validate-release-yaml: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + + - name: Validate release.yaml + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + .github/scripts/validate-release-yaml.sh "${{ github.base_ref }}" "Kuadrant" diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 5072516e..b447925f 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -1,104 +1,133 @@ # Release -## New Major.Minor version +This repository uses a two-phase release workflow as defined in the [Two-Phase Release Workflow RFC](https://github.com/Kuadrant/architecture/pull/178). -1. Create a new minor release branch from the HEAD of main: -```sh -git checkout -b release-0.2 -``` -2. Run prepare release: -```sh -make prepare-release IMG_TAG=release-0.2 VERSION=0.2.0-dev CHANNELS=alpha -``` -3. Verify local changes, commit and push: -```sh -git add . -git commit -m "prepare-release: release-0.2" -git push upstream release-0.2 -``` -4. Verify that the build [image workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/build-images.yaml) is triggered and completes for the new branch +## Overview -5. Do any final testing and bug fixing against the release branch, see [Verify OLM Deployment](#verify-olm-deployment) +Every release is split into two GitHub Actions workflows with a human review gate between them: -6. Run prepare release for final version -```sh -make prepare-release VERSION=0.2.0 CHANNELS=stable -``` -7. Verify local changes, commit, push and tag: -```sh -git add . -git commit -m "prepare-release: v0.2.0" -git tag v0.2.0 -git push upstream release-0.2 -git push upstream v0.2.0 -``` -8. Verify that the build [release tag workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/build-images-for-tag-release.yaml) is triggered and completes for the new tag +1. **Pre-release** (`pre-release.yaml`) — Makes version-related code changes and opens a pull request to the release branch +2. **Release** (`release.yaml`) — Runs smoke tests, tags, builds all artifacts, and creates the GitHub Release as its final step -9. Verify the new version can be installed from the catalog image, see [Verify OLM Deployment](#verify-olm-deployment) +A `release.yaml` file at the repository root is the machine-readable source of truth for the component version. +A **version gate** CI check validates this file on pull requests to release branches. -## New Patch version +## New Minor Release (e.g. 0.18.0) -1. Checkout minor release branch: -```sh -git checkout release-0.2 -``` -2. Run prepare release: -```sh -make prepare-release VERSION=0.2.1 CHANNELS=stable -``` -3. Verify local changes, commit and push: -```sh -git add . -git commit -m "prepare-release: v0.2.1" -git tag v0.2.1 -git push upstream release-0.2 -git push upstream v0.2.1 +1. Trigger the [Pre-release workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/pre-release.yaml) with: + - **version**: `0.18.0` + - **source-branch**: `main` (default) + +2. The workflow will: + - Create the release branch `release-0.18` from main (if it doesn't exist) + - Update `release.yaml` with version `0.18.0` + - Run `make prepare-release VERSION=0.18.0 CHANNELS=stable` + - Open a PR from `pre-release-v0.18.0` to `release-0.18` + +3. Review the PR: + - CI runs tests and the version gate check + - Verify version numbers, bundle manifests, and helm chart + - Approve and merge + +4. Trigger the [Release workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/release.yaml) with: + - **release-branch**: `release-0.18` + +5. The workflow will (in order): + - Read the version from `release.yaml` + - Run smoke tests (verify-manifests, verify-bundle, verify-helm-build, unit tests) + - Create and push tag `v0.18.0` + - Build and push container images (operator, bundle, catalog, CoreDNS) + - Package and sign the Helm chart + - Build CLI binaries for all platforms + - Create the GitHub Release with all artifacts attached + +6. Verify the release: + - Check that all images are available on quay.io + - Verify OLM deployment (see [Verify OLM Deployment](#verify-olm-deployment)) + +## New Patch Release (e.g. 0.18.1) + +1. Trigger the [Pre-release workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/pre-release.yaml) with: + - **version**: `0.18.1` + - **source-branch**: `release-0.18` (or a branch with cherry-picked fixes) + +2. Review and merge the resulting PR to `release-0.18` + +3. Trigger the [Release workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/release.yaml) with: + - **release-branch**: `release-0.18` + +4. Verify the release as above + +## Release Artifacts + +The release workflow produces the following artifacts: + +| Artifact | Registry/Location | +|---|---| +| Operator image | `quay.io/kuadrant/dns-operator:vX.Y.Z` | +| Bundle image | `quay.io/kuadrant/dns-operator-bundle:vX.Y.Z` | +| Catalog image | `quay.io/kuadrant/dns-operator-catalog:vX.Y.Z` | +| CoreDNS image | `quay.io/kuadrant/coredns-kuadrant:vX.Y.Z` | +| Helm chart | GitHub Release asset (GPG signed) | +| CLI binaries | GitHub Release assets (linux/darwin, amd64/arm64/s390x/ppc64le) | + +All container images are built for platforms: `linux/amd64`, `linux/arm64`, `linux/s390x`, `linux/ppc64le`. + +## release.yaml + +The `release.yaml` file at the repository root declares the component version: + +```yaml +dns-operator: + version: "0.0.0" # 0.0.0 on main, concrete version on release branches ``` -4. Verify that the build [release tag workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/build-images-for-tag-release.yaml) is triggered and completes for the new tag -5. Verify the new version can be installed from the catalog image, see [Verify OLM Deployment](#verify-olm-deployment) +On the `main` branch, the version is always `0.0.0` (sentinel for "under active development"). +On release branches, the pre-release workflow updates it to the target version. ## Generated Files -During the release process a number of files will be generated, or modified. +During the pre-release process (`make prepare-release`), the following files are generated or modified: ### Modified files -- bundle.Dockerfile -- bundle/manifests/dns-operator.clusterserviceversion.yaml -- bundle/metadata/annotations.yaml -- charts/dns-operator/Chart.yaml -- charts/dns-operator/templates/manifests.yaml -- config/deploy/olm/catalogsource.yaml -- config/deploy/olm/subscription.yaml -- config/manager/kustomization.yaml -- config/manifests/bases/dns-operator.clusterserviceversion.yaml +- `release.yaml` +- `bundle.Dockerfile` +- `bundle/manifests/dns-operator.clusterserviceversion.yaml` +- `bundle/metadata/annotations.yaml` +- `charts/dns-operator/Chart.yaml` +- `charts/dns-operator/templates/manifests.yaml` +- `config/deploy/olm/catalogsource.yaml` +- `config/deploy/olm/subscription.yaml` +- `config/manager/kustomization.yaml` +- `config/manifests/bases/dns-operator.clusterserviceversion.yaml` ### Generated files -- make/release.mk (modified during patch releases) +- `make/release.mk` -The `make/release.mk` contains the variables for the modifications of the modified files listed above. -Below is a sample of this file. +The `make/release.mk` contains release-specific variable overrides: ```sh #Release default values -IMG=quay.io/kuadrant/dns-operator:v0.16.0 -BUNDLE_IMG=quay.io/kuadrant/dns-operator-bundle:v0.16.0 -CATALOG_IMG=quay.io/kuadrant/dns-operator-catalog:v0.16.0 +IMG=quay.io/kuadrant/dns-operator:v0.18.0 +BUNDLE_IMG=quay.io/kuadrant/dns-operator-bundle:v0.18.0 +CATALOG_IMG=quay.io/kuadrant/dns-operator-catalog:v0.18.0 +COREDNS_IMG=quay.io/kuadrant/coredns-kuadrant:v0.18.0 CHANNELS=stable BUNDLE_CHANNELS=--channels=stable -VERSION=0.16.0 +VERSION=0.18.0 ``` -Points to note. -The `VERSION` number is **not** prefixed with a `v`, 0.16.0. -Image tags for released version **are** prefixed with a `v`, v0.16.0. -Without the `v` prefix the [release tag workflow](https://github.com/Kuadrant/dns-operator/actions/workflows/build-images-for-tag-release.yaml) will fail. +Note: The `VERSION` number is **not** prefixed with `v` (e.g. `0.18.0`), but image tags **are** prefixed with `v` (e.g. `v0.18.0`). + +## Required GitHub Secrets -## Create GitHub Release +The release workflows require the following secrets to be configured in the repository settings: -Once the images have been published to quay.io, a GitHub Release is also required. -There are a number of workflows that depend on the release being created. -- [Release Helm Chart](https://github.com/Kuadrant/dns-operator/actions/workflows/release-helm-chart.yaml) -- [Upload CLI binary](https://github.com/Kuadrant/dns-operator/actions/workflows/upload-cli.yml) +| Secret | Used by | Description | +|---|---|---| +| `GITHUB_TOKEN` | Pre-release, Release, Version Gate | Built-in GitHub token. Used for creating branches, PRs, tags, and GitHub Releases. No manual configuration needed. | +| `IMG_REGISTRY_USERNAME` | Release | Quay.io username for pushing container images (operator, bundle, catalog, CoreDNS). | +| `IMG_REGISTRY_TOKEN` | Release | Quay.io API token or password for pushing container images. | +| `HELM_CHARTS_SIGNING_KEY` | Release | Base64-encoded GPG private key used to sign the Helm chart package. | ## Verify OLM Deployment @@ -110,7 +139,6 @@ make local-setup install-olm deploy-catalog 2. Wait for deployment: ```sh kubectl -n dns-operator-system wait --timeout=60s --for=condition=Available deployments --all -deployment.apps/dns-operator-controller-manager condition met ``` 3. Check the logs: @@ -120,12 +148,7 @@ kubectl -n dns-operator-system logs -f deployment/dns-operator-controller-manage 4. Check the version: ```sh -$ kubectl -n dns-operator-system get deployment dns-operator-controller-manager --show-labels -NAME READY UP-TO-DATE AVAILABLE AGE LABELS -dns-operator-controller-manager 1/1 1 1 5m42s app.kubernetes.io/component=manager,app.kubernetes.io/created-by=dns-operator, -app.kubernetes.io/instance=controller-manager,app.kubernetes.io/managed-by=kustomize,app.kubernetes.io/name=deployment,app.kubernetes.io/part-of=dns-operator, -control-plane=dns-operator-controller-manager,olm.deployment-spec-hash=1jPe8AuMpSKHh51nnDs4j25ZgoUrKhF45EP0Wa,olm.managed=true,olm.owner.kind=ClusterServiceVersion, -olm.owner.namespace=dns-operator-system,olm.owner=dns-operator.v0.2.0-dev,operators.coreos.com/dns-operator.dns-operator-system= +kubectl -n dns-operator-system get deployment dns-operator-controller-manager --show-labels ``` ## Community Operator Index Catalogs diff --git a/release.yaml b/release.yaml new file mode 100644 index 00000000..7a7f0ba5 --- /dev/null +++ b/release.yaml @@ -0,0 +1,2 @@ +dns-operator: + version: "0.0.0" From 0e17717c9894fd0a81d155efb708e506b9f692c5 Mon Sep 17 00:00:00 2001 From: Jim Fitzpatrick Date: Thu, 25 Jun 2026 18:35:37 +0100 Subject: [PATCH 2/2] UPDATE: Code Rabbit comments Signed-off-by: Jim Fitzpatrick --- .github/workflows/pre-release.yaml | 10 ++++++---- .github/workflows/release.yaml | 14 +++++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index b6764e06..cc837105 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -17,13 +17,11 @@ concurrency: group: pre-release cancel-in-progress: false -permissions: - contents: write - pull-requests: write - jobs: setup: runs-on: ubuntu-latest + permissions: + contents: write outputs: version: ${{ steps.validate.outputs.version }} release-branch: ${{ steps.validate.outputs.release-branch }} @@ -65,6 +63,8 @@ jobs: prepare-release: needs: setup runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 with: @@ -120,6 +120,8 @@ jobs: open-pr: needs: [setup, prepare-release] runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8647515c..a7e46556 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: false permissions: - contents: write + contents: read env: IMG_REGISTRY_HOST: quay.io @@ -33,6 +33,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.release-branch }} + persist-credentials: false - name: Install yq run: | @@ -75,6 +76,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.release-branch }} + persist-credentials: false - name: Set up Go uses: actions/setup-go@v5 @@ -99,6 +101,8 @@ jobs: tag: needs: [read-version, smoke-tests] runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 with: @@ -129,6 +133,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.release-branch }} + persist-credentials: false - name: Verify release version matches env: @@ -180,6 +185,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.release-branch }} + persist-credentials: false - name: Install yq run: make yq @@ -231,6 +237,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.release-branch }} + persist-credentials: false - name: Install yq run: make yq @@ -282,6 +289,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.release-branch }} + persist-credentials: false - name: Run make coredns-generate-demo-geo-db run: make coredns-generate-demo-geo-db @@ -321,6 +329,7 @@ jobs: with: ref: ${{ inputs.release-branch }} fetch-depth: 0 + persist-credentials: false - name: Configure GPG Key env: @@ -357,6 +366,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.release-branch }} + persist-credentials: false - name: Set up Go uses: actions/setup-go@v5 @@ -384,6 +394,8 @@ jobs: create-release: needs: [read-version, build-catalog-image, build-coredns-image, build-helm-chart, build-cli-binaries] runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 with: