From 0204385b3e0c6c77140b78f4fa83d94481a98607 Mon Sep 17 00:00:00 2001 From: Yi Chen Date: Thu, 1 Aug 2024 20:26:06 +0800 Subject: [PATCH] Update workflow and docs for releasing Spark operator (#2089) * Update .helmignore Signed-off-by: Yi Chen * Add release docs Signed-off-by: Yi Chen * Update release workflow Signed-off-by: Yi Chen * Update integration test workflow Signed-off-by: Yi Chen * Add workflow for pushing tag when VERSION file changes Signed-off-by: Yi Chen * Update Signed-off-by: Yi Chen * Remove the leading 'v' from chart version Signed-off-by: Yi Chen * Update docker image tags Signed-off-by: Yi Chen --------- Signed-off-by: Yi Chen --- .../workflows/{main.yaml => integration.yaml} | 45 +++-- .github/workflows/push-tag.yaml | 44 +++++ .github/workflows/release-charts.yaml | 58 ++++++ .github/workflows/release-docker.yaml | 120 ++++++++++++ .github/workflows/release.yaml | 183 +++--------------- VERSION | 1 + charts/spark-operator-chart/.helmignore | 5 +- docs/release.md | 119 ++++++++++++ 8 files changed, 406 insertions(+), 169 deletions(-) rename .github/workflows/{main.yaml => integration.yaml} (82%) create mode 100644 .github/workflows/push-tag.yaml create mode 100644 .github/workflows/release-charts.yaml create mode 100644 .github/workflows/release-docker.yaml create mode 100644 VERSION create mode 100644 docs/release.md diff --git a/.github/workflows/main.yaml b/.github/workflows/integration.yaml similarity index 82% rename from .github/workflows/main.yaml rename to .github/workflows/integration.yaml index ba0ee2a65..be5200f9e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/integration.yaml @@ -1,13 +1,19 @@ -name: Pre-commit checks +name: Integration Test on: pull_request: branches: - master + - release-* push: branches: - master + - release-* + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.actor }} + cancel-in-progress: true jobs: build-api-docs: @@ -15,8 +21,6 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - with: - fetch-depth: "0" - name: Set up Go uses: actions/setup-go@v5 @@ -37,17 +41,14 @@ jobs: steps: - name: Checkout source code uses: actions/checkout@v4 - with: - fetch-depth: "0" - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: "go.mod" + go-version-file: go.mod - name: build sparkctl - run: | - make build-sparkctl + run: make build-sparkctl build-spark-operator: runs-on: ubuntu-latest @@ -55,17 +56,17 @@ jobs: - name: Checkout source code uses: actions/checkout@v4 with: - fetch-depth: "0" + fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: "go.mod" + go-version-file: go.mod - name: Run go fmt check run: make go-fmt - - name: Run go vet + - name: Run go vet check run: make go-vet - name: Run unit tests @@ -92,10 +93,22 @@ jobs: build-helm-chart: runs-on: ubuntu-20.04 steps: + - name: Determine branch name + id: get_branch + run: | + BRANCH="" + if [ "${{ github.event_name }}" == "push" ]; then + BRANCH=${{ github.ref }} + elif [ "${{ github.event_name }}" == "pull_request" ]; then + BRANCH=${{ github.base_ref }} + fi + echo "Branch name: $BRANCH" + echo "BRANCH=$BRANCH" >> "$GITHUB_OUTPUT" + - name: Checkout source code uses: actions/checkout@v4 with: - fetch-depth: "0" + fetch-depth: 0 - name: Install Helm uses: azure/setup-helm@v4 @@ -117,14 +130,16 @@ jobs: run: ct version - name: Run chart-testing (lint) - run: ct lint + run: ct lint --check-version-increment=false - name: Run chart-testing (list-changed) id: list-changed + env: + BRANCH: ${{ steps.get_branch.outputs.BRANCH }} run: | - changed=$(ct list-changed) + changed=$(ct list-changed --target-branch $BRANCH) if [[ -n "$changed" ]]; then - echo "::set-output name=changed::true" + echo "changed=true" >> "$GITHUB_OUTPUT" fi - name: Detect CRDs drift between chart and manifest diff --git a/.github/workflows/push-tag.yaml b/.github/workflows/push-tag.yaml new file mode 100644 index 000000000..f9329f080 --- /dev/null +++ b/.github/workflows/push-tag.yaml @@ -0,0 +1,44 @@ +name: Push Tag on VERSION change + +on: + push: + branches: + - master + - release-* + paths: + - VERSION + +jobs: + push_tag: + runs-on: ubuntu-latest + + steps: + - name: Checkout source code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Read version from VERSION file + run: | + VERSION=$(cat VERSION) + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Check if tag exists + run: | + git fetch --tags + if git tag -l | grep -q "^${VERSION}$"; then + echo "TAG_EXISTS=true" >> $GITHUB_ENV + else + echo "TAG_EXISTS=false" >> $GITHUB_ENV + fi + + - name: Create and push tag + if: env.TAG_EXISTS == 'false' + run: | + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" diff --git a/.github/workflows/release-charts.yaml b/.github/workflows/release-charts.yaml new file mode 100644 index 000000000..874696f09 --- /dev/null +++ b/.github/workflows/release-charts.yaml @@ -0,0 +1,58 @@ +name: Release Helm charts + +on: + release: + types: [published] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Set up Helm + uses: azure/setup-helm@v4.2.0 + with: + version: v3.14.4 + + - name: Package Helm charts + run: | + for chart in $(ls charts); do + helm package charts/$chart + done + + - name: Save packaged charts to temp directory + run: | + mkdir -p /tmp/charts + cp *.tgz /tmp/charts + + - name: Checkout to branch gh-pages + uses: actions/checkout@v4 + with: + ref: gh-pages + fetch-depth: 0 + + - name: Copy packages charts + run: | + cp /tmp/charts/*.tgz . + + - name: Update Helm charts repo index + env: + CHART_URL: https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }} + run: | + helm repo index --merge index.yaml --url $CHART_URL . + git add index.yaml + git commit -s -m "Update index.yaml" || exit 0 + git push diff --git a/.github/workflows/release-docker.yaml b/.github/workflows/release-docker.yaml new file mode 100644 index 000000000..849a0e109 --- /dev/null +++ b/.github/workflows/release-docker.yaml @@ -0,0 +1,120 @@ +name: Release Docker images + +on: + push: + tags: + - v*.*.* + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + IMAGE_REGISTRY: docker.io + IMAGE_REPOSITORY: kubeflow/spark-operator + +# Ref: https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners. +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + + steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + + - name: Login to container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_REPOSITORY }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 417ffb267..ebd0e62a5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,168 +1,47 @@ -name: Release Charts +name: Create draft release on: push: - branches: - - master -env: - REGISTRY_IMAGE: docker.io/kubeflow/spark-operator + tags: + - v*.*.* -jobs: - build-skip-check: - runs-on: ubuntu-latest - outputs: - image_changed: ${{ steps.skip-check.outputs.image_changed }} - chart_changed: ${{ steps.skip-check.outputs.chart_changed }} - app_version_tag: ${{ steps.skip-check.outputs.app_version_tag }} - chart_version_tag: ${{ steps.skip-check.outputs.chart_version_tag }} - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Check if build should be skipped - id: skip-check - run: | - app_version_tag=$(cat charts/spark-operator-chart/Chart.yaml | grep "appVersion: .*" | cut -c13-) - chart_version_tag=$(cat charts/spark-operator-chart/Chart.yaml | grep "version: .*" | cut -c10-) - - # Initialize flags - image_changed=false - chart_changed=false - - if ! git rev-parse -q --verify "refs/tags/$app_version_tag"; then - image_changed=true - git tag $app_version_tag - git push origin $app_version_tag - echo "Spark-Operator Docker Image new tag: $app_version_tag released" - fi - - if ! git rev-parse -q --verify "refs/tags/spark-operator-chart-$chart_version_tag"; then - chart_changed=true - git tag spark-operator-chart-$chart_version_tag - git push origin spark-operator-chart-$chart_version_tag - echo "Spark-Operator Helm Chart new tag: spark-operator-chart-$chart_version_tag released" - fi +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true - echo "image_changed=${image_changed}" >> "$GITHUB_OUTPUT" - echo "chart_changed=${chart_changed}" >> "$GITHUB_OUTPUT" - echo "app_version_tag=${app_version_tag}" >> "$GITHUB_OUTPUT" - echo "chart_version_tag=${chart_version_tag}" >> "$GITHUB_OUTPUT" +jobs: release: + permissions: + contents: write runs-on: ubuntu-latest - needs: - - build-skip-check - if: needs.build-skip-check.outputs.image_changed == 'true' - strategy: - fail-fast: false - matrix: - platform: - - linux/amd64 - - linux/arm64 steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 1 + - name: Configure Git run: | git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - echo "SCOPE=${platform//\//-}" >> $GITHUB_ENV - - name: Set up QEMU - timeout-minutes: 1 - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Install Helm - uses: azure/setup-helm@v4 - with: - version: v3.14.3 - - name: Login to Packages Container registry - uses: docker/login-action@v3 - with: - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and Push Spark-Operator Docker Image to Docker Hub - id: build - uses: docker/build-push-action@v5 - with: - context: . - platforms: ${{ matrix.platform }} - cache-to: type=gha,mode=max,scope=${{ env.SCOPE }} - cache-from: type=gha,scope=${{ env.SCOPE }} - push: true - outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - publish-image: - runs-on: ubuntu-latest - needs: - - release - - build-skip-check - if: needs.build-skip-check.outputs.image_changed == 'true' - steps: - - name: Download digests - uses: actions/download-artifact@v4 - with: - pattern: digests-* - path: /tmp/digests - merge-multiple: true - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY_IMAGE }} - tags: ${{ needs.build-skip-check.outputs.app_version_tag }} - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} - publish-chart: - runs-on: ubuntu-latest - if: needs.build-skip-check.outputs.chart_changed == 'true' - needs: - - build-skip-check - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install Helm - uses: azure/setup-helm@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4.2.0 with: - version: v3.14.3 - - name: Configure Git + version: v3.14.4 + + - name: Package Helm charts run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - name: Release Spark-Operator Helm Chart - uses: helm/chart-releaser-action@v1.6.0 - env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - CR_RELEASE_NAME_TEMPLATE: "spark-operator-chart-{{ .Version }}" + for chart in $(ls charts); do + helm package charts/$chart + done + + - name: Release + id: release + uses: softprops/action-gh-release@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + draft: true + prerelease: ${{ contains(github.ref, 'rc') }} + target_commitish: ${{ github.sha }} + files: | + *.tgz + diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..17da53b58 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +v1beta2-1.6.2-3.5.0 \ No newline at end of file diff --git a/charts/spark-operator-chart/.helmignore b/charts/spark-operator-chart/.helmignore index 4fbbbf5df..8cefbf465 100644 --- a/charts/spark-operator-chart/.helmignore +++ b/charts/spark-operator-chart/.helmignore @@ -3,6 +3,7 @@ # negation (prefixed with !). Only one pattern per line. ci/ +.helmignore # Common VCS dirs .git/ @@ -21,16 +22,16 @@ ci/ *~ # Various IDEs +*.tmproj .project .idea/ -*.tmproj .vscode/ # MacOS .DS_Store # helm-unittest -./tests +tests .debug __snapshot__ diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 000000000..d3a385b60 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,119 @@ +# Releasing the Spark operator + +## Prerequisites + +- [Write](https://docs.github.com/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#permission-levels-for-repositories-owned-by-an-organization) permission for the Spark operator repository. + +- Create a [GitHub Token](https://docs.github.com/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token). + +- Install `PyGithub` to generate the [Changelog](../CHANGELOG.md): + + ```bash + pip install PyGithub==2.3.0 + ``` + +## Versioning policy + +Spark Operator version format follows [Semantic Versioning](https://semver.org/). Spark Operator versions are in the format of `vX.Y.Z`, where `X` is the major version, `Y` is the minor version, and `Z` is the patch version. The patch version contains only bug fixes. + +Additionally, Spark Operator does pre-releases in this format: `vX.Y.Z-rc.N` where `N` is a number of the `Nth` release candidate (RC) before an upcoming public release named `vX.Y.Z`. + +## Release branches and tags + +Spark Operator releases are tagged with tags like `vX.Y.Z`, for example `v1.7.2`. + +Release branches are in the format of `release-X.Y`, where `X.Y` stands for the minor release. + +`vX.Y.Z` releases are released from the `release-X.Y` branch. For example, `v1.7.2` release should be on `release-1.7` branch. + +If you want to push changes to the `release-X.Y` release branch, you have to cherry pick your changes from the `master` branch and submit a PR. + +## Create a new release + +### Create release branch + +1. Depends on what version you want to release, + + - Major or Minor version - Use the GitHub UI to create a release branch from `master` and name the release branch `release-X.Y`. + - Patch version - You don't need to create a new release branch. + +2. Fetch the upstream changes into your local directory: + + ```bash + git fetch upstream + ``` + +3. Checkout into the release branch: + + ```bash + git checkout release-X.Y + git rebase upstream/release-X.Y + ``` + +### Create GitHub tag + +1. Modify `VERSION` file in the root directory of the project: + + - For the RC tag as follows: + + ```bash + vX.Y.Z-rc.N + ``` + + - For the official release tag as follows: + + ```bash + vX.Y.Z + ``` + +2. Modify `version` and `appVersion` in `Chart.yaml`: + + ```bash + # Get version and remove the leading 'v' + VERSION=$(cat VERSION | sed "s/^v//") + sed -i "s/^version.*/version: ${VERSION}/" charts/spark-operator-chart/Chart.yaml + sed -i "s/^appVersion.*/appVersion: ${VERSION}/" charts/spark-operator-chart/Chart.yaml + ``` + +3. Commit the changes: + + ```bash + git add VERSION + git add charts/spark-operator-chart/Chart.yaml + git commit -s -m "Release $VERSION" + git push + ``` + +4. Submit a PR to the release branch. After the PR is merged, a new tag will be automatically created if the `VERSION` file has changed. + +### Release Spark Operator Image + +After a pre-release/release tag is pushed, a release workflow will be triggered to build and push Spark operator docker image to Docker Hub. + +### Publish release + +After a pre-release/release tag is pushed, a release workflow will be triggered to create a new draft release. + +### Release Spark Operator Helm Chart + +After the draft release is published, a release workflow will be triggered to update the Helm chart repo index and publish it to the Helm repository. + +## Update Changelog + +Update the `CHANGELOG.md` file by running: + +```bash +python hack/generate-changelog.py \ + --token= \ + --range=.. +``` + +If you are creating the **first minor pre-release** or the **minor** release (`X.Y`), your `previous-release` is equal to the latest release on the `release-X.Y` branch. +For example: `--range=v1.7.1..v1.8.0`. + +Otherwise, your `previous-release` is equal to the latest release on the `release-X.Y` branch. +For example: `--range=v1.7.0..v1.8.0-rc.0` + +Group PRs in the Changelog into Features, Bug fixes, Documentation, etc. + +Finally, submit a PR with the updated Changelog.