diff --git a/.github/workflows/branch_preparation.yml b/.github/workflows/branch_preparation.yml new file mode 100644 index 000000000..3deb6367f --- /dev/null +++ b/.github/workflows/branch_preparation.yml @@ -0,0 +1,121 @@ +name: Branch Preparation CI + +on: + push: + branches: + - 'release/**' + tags: + - 'v[0-9]+.[0-9]+.[0-9]+**' + +jobs: + prepare_develop_branch_after_release: + runs-on: ubuntu-latest + if: ${{ github.ref_type == 'tag' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: | + git checkout develop + + - uses: cachix/install-nix-action@v22 + - name: Pre-populate nix-shell + run: | + export NIX_PATH=nixpkgs=$(jq '.nixpkgs.url' nix/sources.json -r) + echo "NIX_PATH=$NIX_PATH" >> $GITHUB_ENV + nix-shell --pure --run "echo" ./shell.nix + - name: Test the chart version updater script + run: | + nix-shell --pure --run "./scripts/test-update-chart-version.sh" ./shell.nix + - name: Check if the chart is publishable + run: | + tag=${{ github.ref_name }} + echo "BASE=$(nix-shell --pure --run "./scripts/update-chart-version.sh --tag $tag --type develop" ./shell.nix)" >> $GITHUB_ENV + - name: Create Pull Request to develop + if: ${{ env.BASE }} + id: cpr + uses: peter-evans/create-pull-request@v5 + with: + base: ${{ env.BASE }} + commit-message: "chore(ci): update helm chart versions and/or git submodules" + committer: GitHub + author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + title: "Prepare develop branch after release ${{ github.ref_name }}" + labels: | + prepare-develop-branch + automated-pr + draft: false + signoff: true + branch: "create-pull-request/patch-${{ env.BASE }}" + token: ${{ secrets.GITHUB_TOKEN }} + + prepare_release_branch_after_release: + runs-on: ubuntu-latest + if: ${{ github.ref_type == 'tag' }} + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v22 + - name: Pre-populate nix-shell + run: | + export NIX_PATH=nixpkgs=$(jq '.nixpkgs.url' nix/sources.json -r) + echo "NIX_PATH=$NIX_PATH" >> $GITHUB_ENV + nix-shell --pure --run "echo" ./shell.nix + - name: Test the chart version updater script + run: | + nix-shell --pure --run "./scripts/test-update-chart-version.sh" ./shell.nix + - name: Check if the chart is publishable + run: | + tag=${{ github.ref_name }} + echo "BASE=$(nix-shell --pure --run "./scripts/update-chart-version.sh --tag $tag --type release" ./shell.nix)" >> $GITHUB_ENV + - name: Create Pull Request to release + if: ${{ env.BASE }} + id: cpr + uses: peter-evans/create-pull-request@v5 + with: + base: ${{ env.BASE }} + commit-message: "chore(ci): update helm chart versions and/or git submodules" + committer: GitHub + author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + title: "Prepare release branch after release ${{ github.ref_name }}" + labels: | + prepare-release-branch + automated-pr + draft: false + signoff: true + branch: "create-pull-request/patch-${{ env.BASE }}" + token: ${{ secrets.GITHUB_TOKEN }} + + prepare_release_branch_on_creation: + runs-on: ubuntu-latest + if: ${{ github.ref_type == 'branch' }} + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v22 + - name: Pre-populate nix-shell + run: | + export NIX_PATH=nixpkgs=$(jq '.nixpkgs.url' nix/sources.json -r) + echo "NIX_PATH=$NIX_PATH" >> $GITHUB_ENV + nix-shell --pure --run "echo" ./shell.nix + - name: Test the chart version updater script + run: | + nix-shell --pure --run "./scripts/test-update-chart-version.sh" ./shell.nix + - name: Check if the chart is publishable + run: | + branch_name=${{ github.ref_name }} + nix-shell --pure --run "./scripts/update-chart-version.sh --branch $branch_name" ./shell.nix + - name: Create Pull Request to release + id: cpr + uses: peter-evans/create-pull-request@v5 + with: + base: ${{ github.ref_name }} + commit-message: "chore(ci): update helm chart versions and/or git submodules" + committer: GitHub + author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com> + title: "Prepare ${{ github.ref_name }} branch" + labels: | + prepare-release-branch + automated-pr + draft: false + signoff: true + branch: "create-pull-request/patch-${{ github.ref_name }}" + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/test-update-chart-version.sh b/scripts/test-update-chart-version.sh new file mode 100755 index 000000000..20c1b988c --- /dev/null +++ b/scripts/test-update-chart-version.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +# Path to the script to be tested +SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]:-"$0"}")")" +SCRIPT_TO_TEST="$SCRIPT_DIR/update-chart-version.sh" + +# Function to run a test case +run_test() { + local test_name=$1 + local expected_output=$2 + shift 2 + local output + + echo "Running: $test_name" + output=$("$SCRIPT_TO_TEST" "$@" 2>&1) + if [ "$output" == "$expected_output" ]; then + echo "PASS" + else + echo "FAIL" + echo "Expected: $expected_output" + echo "Got: $output" + fi + echo "----------------------------------------" +} + +# Define test cases +run_test "Test 1: When commits are added and chart type is develop [branch creation]" \ + "1.2.0-prerelease" \ + --branch "release/1.2" --dry-run --chart-version "1.3.0-develop" + +run_test "Test 2: When commits are added and chart type is prerelease [commit pushes]" \ + "" \ + --branch "release/1.2" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 3: Invalid branch name and chart type is prerelease" \ + "Invalid branch name format. Expected 'release/x.y' only" \ + --branch "feature/1.2" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 4: Invalid branch name and chart type is develop" \ + "Invalid branch name format. Expected 'release/x.y' only" \ + --branch "feature/1.2" --dry-run --chart-version "1.2.0-develop" + +run_test "Test 5: Valid tag with type release and chart type is prerelease [first release] " \ + "1.2.1-prerelease" \ + --tag "v1.2.0" --type "release" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 6: Valid tag with type release and chart type is prerelease [patch release]" \ + "1.2.2-prerelease" \ + --tag "v1.2.1" --type "release" --dry-run --chart-version "1.2.1-prerelease" + +run_test "Test 7: Tag greater than current chart type release" \ + "For prerelease, X.Y from current chart version (1.2.1-prerelease) must exactly match X.Y from tag (1.4.0)" \ + --tag "v1.4.0" --type "release" --dry-run --chart-version "1.2.1-prerelease" + +run_test "Test 8: Valid tag with type develop and chart type is prerelease [first release]" \ + "1.3.0-develop" \ + --tag "v1.2.0" --type "develop" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 9: Valid tag with type develop and chart type is prerelease [patch release]" \ + "1.3.0-develop" \ + --tag "v1.2.1" --type "develop" --dry-run --chart-version "1.2.1-prerelease" + +run_test "Test 10: Tag is lesser than chart type develop " \ + "" \ + --tag "v1.0.0" --type "develop" --dry-run --chart-version "1.2.1-develop" + +run_test "Test 11: Tag is greater than chart type release " \ + "1.5.0-develop" \ + --tag "v1.4.0" --type "develop" --dry-run --chart-version "1.2.1-develop" + +run_test "Test 12: rc tag, with type release and chart type prerelease" \ + "" \ + --tag "v1.2.3-rc" --type "develop" --dry-run --chart-version "1.2.3-develop" + +run_test "Test 13:rc tag, with type develop and chart type develop" \ + "" \ + --tag "v1.2.3-rc" --type "develop" --dry-run --chart-version "1.2.4-develop" diff --git a/scripts/update-chart-version.sh b/scripts/update-chart-version.sh new file mode 100755 index 000000000..d3e6671fb --- /dev/null +++ b/scripts/update-chart-version.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash + +# Write output to error output stream. +echo_stderr() { + echo -e "${1}" >&2 +} + +die() +{ + local _return="${2:-1}" + echo_stderr "$1" + exit "${_return}" +} + +help() { + cat < Name of the target branch. + --tag Release tag. + --chart-version Version of the current chart. + +Examples: + $(basename "$0") --branch release/x.y +EOF +} + +check_tag_is_valid() { + local tag="$1" + local current_chart_version="$2" + + if [[ "$current_chart_version" == *"-prerelease" ]]; then + allowed_diff=("" "patch" "prerelease") + diff="$(semver diff "$tag" "$current_chart_version")" + if ! [[ " ${allowed_diff[*]} " =~ " $diff " ]]; then + die "For prerelease, X.Y from current chart version ($current_chart_version) must exactly match X.Y from tag ($tag)" + fi + elif [[ "$current_chart_version" == *"-develop" ]]; then + compare="$(semver compare "$tag" "$current_chart_version")" + if [[ "$compare" == "-1" ]]; then + NO_OP=1 + fi + fi +} + +# yq-go eats up blank lines +# this function gets around that using diff with --ignore-blank-lines +yq_ibl() +{ + set +e + diff_out=$(diff -B <(yq '.' "$2") <(yq "$1" "$2")) + error=$? + if [ "$error" != "0" ] && [ "$error" != "1" ]; then + exit "$error" + fi + if [ -n "$diff_out" ]; then + echo "$diff_out" | patch --quiet --no-backup-if-mismatch "$2" - + fi + set -euo pipefail +} + +# RULES: This would run only when changes are pushed to a release/x.y branch. +# 1. Branch name can only be of format release/x.y +# 2. If current chart version of type develop(only possible on branch creation), +# then version generated is of format x.y.0-prerelease. +# 3. If current chart version of type prerelease(after branch creation and initial automated PR's merge), +# the workflow would be a no op, as it's already in a prerelease format. +create_version_from_release_branch() { + if [[ "$BRANCH_NAME" =~ ^(release/[0-9]+\.[0-9]+)$ ]]; then + local EXTRACTED_VERSION=$(echo "$BRANCH_NAME" | grep -oP '(?<=release/)\d+\.\d+') + if [[ "$CURRENT_CHART_VERSION" == *"-develop" ]]; then + VERSION="${EXTRACTED_VERSION}.0-prerelease" + elif [[ "$CURRENT_CHART_VERSION" == *"-prerelease" ]]; then + NO_OP=1 + else + die "Current chart version doesn't match a develop or prerel format" + fi + else + die "Invalid branch name format. Expected 'release/x.y' only" + fi +} + +# RULES: This would run only when tag is created. +# 1. Tag should be of format vX.Y.Z. +# 2. If tag is of format vX.Y.Z-rc, it would be a no op for the workflow. +# 3. The tag can only be vX.Y.Z if the current chart version is X.Y*-prerelease. Ex, v2.6.1 for v2.6.*-prerelease +# 4. The tag can only be vX.Y.Z if the current chart version is X.Y+1*-develop. Ex, v2.6.1 for v2.7.*-develop +# 5. For release branches if all the above holds then it bumps the patch version. Ex, v2.6.1 --> 2.6.2-prerelease +# 6. For develop branches if all the above holds then it bumps the minor version. Ex, v2.6.1 --> 2.7.0-develop +create_version_from_tag() { + if [[ "$TAG" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + local EXTRACTED_VERSION=$(echo "$TAG" | grep -oP '(?<=v)\d+\.\d+.\d+') + check_tag_is_valid "$EXTRACTED_VERSION" "$CURRENT_CHART_VERSION" + if [[ $TYPE == "release" ]]; then + VERSION="$(semver bump patch $EXTRACTED_VERSION)-prerelease" + if [[ -z $DRY_RUN ]];then + echo "release/$(echo $EXTRACTED_VERSION | cut -d'.' -f1,2)" + fi + elif [[ $TYPE == "develop" ]]; then + VERSION="$(semver bump minor $EXTRACTED_VERSION)-develop" + if [[ -z $DRY_RUN ]];then + echo "develop" + fi + else + die "Invalid type. Expected 'release' or 'develop'." + fi + elif [[ "$TAG" == *"-rc" ]]; then + NO_OP=1 + else + die "Invalid tag format. Expected 'vX.Y.Z'" + fi +} + +update_chart_yaml() { + local VERSION=$1 + local APP_VERSION=$2 + + yq_ibl ".version = \"$VERSION\" | .appVersion = \"$APP_VERSION\"" "$CHART_YAML" + yq_ibl ".version = \"$VERSION\"" "$CRD_CHART_YAML" + yq_ibl "(.dependencies[] | select(.name == \"crds\") | .version) = \"$VERSION\"" "$CHART_YAML" + yq_ibl ".zfsPlugin.image.tag = \"$VERSION\"" "$VALUES_YAML" +} + +set -euo pipefail + +DRY_RUN= +NO_OP= +CURRENT_CHART_VERSION= +# Set the path to the Chart.yaml file +SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]:-"$0"}")")" +ROOT_DIR="$SCRIPT_DIR/.." +CHART_DIR="$ROOT_DIR/deploy/helm/charts" +CHART_YAML="$CHART_DIR/Chart.yaml" +VALUES_YAML="$CHART_DIR/values.yaml" +CRD_CHART_NAME="crds" +CRD_CHART_YAML="$CHART_DIR/charts/$CRD_CHART_NAME/Chart.yaml" +# Final computed version to be set in this. +VERSION="" + +# Parse arguments +while [ "$#" -gt 0 ]; do + case $1 in + -d|--dry-run) + DRY_RUN=1 + shift + ;; + -h|--help) + help + exit 0 + ;; + -b|--branch) + shift + BRANCH_NAME=$1 + shift + ;; + -t|--tag) + shift + TAG=$1 + shift + ;; + --type) + shift + TYPE=$1 + shift + ;; + --chart-version) + shift + CURRENT_CHART_VERSION=$1 + shift + ;; + *) + help + die "Unknown option: $1" + ;; + esac +done + +if [[ -z $CURRENT_CHART_VERSION ]]; then + CURRENT_CHART_VERSION=$(yq e '.version' "$CHART_YAML") +fi + +if [[ -n "${BRANCH_NAME-}" ]]; then + create_version_from_release_branch +elif [[ -n "${TAG-}" && -n "${TYPE-}" ]]; then + create_version_from_tag +else + help + die "Either --branch or --tag and --type must be specified." +fi + +if [[ -z $NO_OP ]]; then + if [[ -n $VERSION ]]; then + if [[ -z $DRY_RUN ]];then + update_chart_yaml "$VERSION" "$VERSION" + else + echo "$VERSION" + fi + else + die "Failed to update the chart versions" + fi +fi