diff --git a/.github/workflows/branch_preparation.yml b/.github/workflows/branch_preparation.yml new file mode 100644 index 00000000..10a59ff2 --- /dev/null +++ b/.github/workflows/branch_preparation.yml @@ -0,0 +1,121 @@ +name: Branch Preparation + +on: + push: + branches: + - 'release/**' + tags: + - 'v[0-9]+.[0-9]+.[0-9]+**' + +jobs: + update_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: Modify the chart version based on the tag + run: | + tag=${{ github.ref_name }} + echo "BASE=$(nix-shell --pure --run "./scripts/update-chart-version.sh --tag $tag" ./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: | + update-release-branch + automated-pr + draft: false + signoff: true + branch: "create-pull-request/patch-${{ env.BASE }}" + token: ${{ secrets.GITHUB_TOKEN }} + + update_develop_branch_on_release_branch_creation: + runs-on: ubuntu-latest + if: ${{ github.ref_type == 'branch' }} + 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: Modify the chart version based on the branch name for develop + run: | + branch_name=${{ github.ref_name }} + nix-shell --pure --run "./scripts/update-chart-version.sh --branch $branch_name --type develop" ./shell.nix + - name: Create Pull Request to develop + id: cpr + uses: peter-evans/create-pull-request@v5 + with: + base: develop + 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 on ${{ github.ref_name }} creation" + labels: | + update-develop-branch + automated-pr + draft: false + signoff: true + branch: "create-pull-request/patch-develop" + 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: Modify the chart version based on the branch name for release + run: | + branch_name=${{ github.ref_name }} + nix-shell --pure --run "./scripts/update-chart-version.sh --branch $branch_name --type release" ./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/.github/workflows/release.yml b/.github/workflows/release.yml index 64bfc281..70b1bf9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,17 +1,4 @@ -# Copyright 2020 The OpenEBS Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -name: release +name: Release Images and Charts on: release: @@ -19,13 +6,114 @@ on: - 'created' jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: cachix/install-nix-action@v22 + - uses: rrbutani/use-nix-shell-action@v1.1.0 + with: + file: shell.nix + + - name: Check if the chart is publishable + run: | + TAG=${{ github.event.release.tag_name }} + ./scripts/update-chart-version.sh --tag $TAG --publish-release + - name: Run chart-testing lint + run: | + ct lint --config ct.yaml + + unit-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Unit test + run: make test + + - name: Upload Coverage Report + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.txt + name: coverage-$(date +%s) + flags: unittests + + bdd-tests: + runs-on: ubuntu-latest + needs: ["unit-tests"] + strategy: + fail-fast: true + matrix: + kubernetes: [v1.27.3] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go 1.19 + uses: actions/setup-go@v4 + with: + go-version: 1.19.9 + cache: false + + - name: Build images locally + run: make lvm-driver-image || exit 1; + + - name: Setup Minikube-Kubernetes + uses: medyagh/setup-minikube@latest + with: + cache: false + minikube-version: 1.31.1 + driver: none + kubernetes-version: ${{ matrix.kubernetes }} + cni: calico + start-args: "--install-addons=false" + + - name: Setting environment variables + run: | + echo "KUBECONFIG=$HOME/.kube/config" >> $GITHUB_ENV + echo "OPENEBS_NAMESPACE=openebs" >> $GITHUB_ENV + + - name: bootstrap + run: make bootstrap + + - name: Running tests + run: | + make ci + make sanity + + - name: Upload CI Test Coverage Report + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./tests/bdd_coverage.txt + name: coverage-bdd_coverage-$(date +%s) + flags: bddtests + csi-driver: if: contains(github.ref, 'tags/v') runs-on: ubuntu-latest + needs: ["lint", "bdd-tests"] steps: - name: Checkout 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: Check if the chart is publishable + run: | + TAG=${{ github.event.release.tag_name }} + nix-shell --pure --run "./scripts/update-chart-version.sh --tag $TAG --publish-release" ./shell.nix + - name: Set Image Org # sets the default IMAGE_ORG to openebs run: | @@ -37,11 +125,11 @@ jobs: run: | echo "DATE=$(date -u +'%Y-%m-%dT%H:%M:%S%Z')" >> $GITHUB_OUTPUT - - name: Set Tag + - name: Set IMAGE_TAG and BRANCH run: | - TAG="${GITHUB_REF#refs/*/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV - echo "RELEASE_TAG=${TAG}" >> $GITHUB_ENV + BRANCH=${{ github.ref_name }} + echo "BRANCH=$BRANCH" >> $GITHUB_ENV + echo "IMAGE_TAG=$(awk -F': ' '/^version:/ {print $2}' deploy/helm/charts/Chart.yaml)" >> $GITHUB_ENV - name: Docker meta id: docker_meta @@ -54,11 +142,12 @@ jobs: ghcr.io/${{ env.IMAGE_ORG }}/lvm-driver tags: | type=semver,pattern={{version}} + type=raw,value=${{ env.IMAGE_TAG }} - name: Print Tag info run: | + echo "BRANCH: ${{ env.BRANCH }}" echo "${{ steps.docker_meta.outputs.tags }}" - echo "RELEASE TAG: ${RELEASE_TAG}" - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -72,7 +161,7 @@ jobs: version: v0.13.1 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -104,4 +193,27 @@ jobs: DBUILD_DATE=${{ steps.date.outputs.DATE }} DBUILD_REPO_URL=https://github.com/openebs/lvm-localpv DBUILD_SITE_URL=https://openebs.io - RELEASE_TAG=${{ env.RELEASE_TAG }} + BRANCH=${{ env.BRANCH }} + + release-chart: + runs-on: ubuntu-latest + needs: ["csi-driver"] + 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: Check if the chart is publishable + run: | + TAG=${{ github.event.release.tag_name }} + nix-shell --pure --run "./scripts/update-chart-version.sh --tag $TAG --publish-release" ./shell.nix + + - name: Publish lvm localpv develop or prerelease helm chart + uses: stefanprodan/helm-gh-pages@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + charts_dir: ./deploy/helm diff --git a/scripts/test-update-chart-version.sh b/scripts/test-update-chart-version.sh new file mode 100755 index 00000000..db15c732 --- /dev/null +++ b/scripts/test-update-chart-version.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Path to the script to be tested +SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]:-"$0"}")")" +SCRIPT_TO_TEST="$SCRIPT_DIR/update-chart-version.sh" +FAILED= + +# Function to run a test case +run_test() { + local test_name=$1 + local expected_output=$2 + shift 2 + local output + + echo "Running: $test_name" + set +e + output=$("$SCRIPT_TO_TEST" "$@" 2>&1) + set -e + if [ "$output" == "$expected_output" ]; then + echo "PASS" + else + echo "FAIL" + echo "Expected: $expected_output" + echo "Got: $output" + FAILED=1 + fi + echo "----------------------------------------" +} + +# Define test cases +run_test "Test 1: On branch creation, version to be reflected on release branch" \ + "1.2.0-prerelease" \ + --branch "release/1.2" --type "release" --dry-run --chart-version "1.2.0-develop" + +run_test "Test 2: On branch creation, version to be reflected on develop branch" \ + "1.3.0-develop" \ + --branch "release/1.2" --type "develop" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 3: After branch creation on push, version to be reflected on release branch" \ + "" \ + --branch "release/1.2" --type "release" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 4: After branch creation on push, version to be reflected on develop branch" \ + "" \ + --branch "release/1.2" --type "develop" --dry-run --chart-version "1.3.0-develop" + +run_test "Test 5: On branch creation, version to be reflected on release branch, x.y is newer" \ + "1.5.0-prerelease" \ + --branch "release/1.5" --type "release" --dry-run --chart-version "1.2.0-develop" + +run_test "Test 6: On branch creation, version to be reflected on develop branch, x.y is newer" \ + "1.6.0-develop" \ + --branch "release/1.5" --type "develop" --dry-run --chart-version "1.2.0-develop" + +run_test "Test 7: On branch creation, version to be reflected on release branch, x.y is older" \ + "1.2.0-prerelease" \ + --branch "release/1.2" --type "release" --dry-run --chart-version "1.5.0-develop" + +run_test "Test 8: On branch creation, version to be reflected on develop branch, x.y is older" \ + "" \ + --branch "release/1.2" --type "develop" --dry-run --chart-version "1.5.0-develop" + +run_test "Test 9: On tag creation, version to be reflected on release/x.y branch" \ + "1.2.1-prerelease" \ + --tag "v1.2.0" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 10: On tag creation, version to be reflected on release/x.y branch, tag is in future" \ + "For release/x.y branch the current chart version(1.2.0-prerelease)'s X.Y must exactly match X.Y from tag (1.5.0)" \ + --tag "v1.5.0" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 11: On tag creation, version to be reflected on release/x.y branch, tag is in past" \ + "For release/x.y branch the current chart version(1.2.0-prerelease)'s X.Y must exactly match X.Y from tag (1.0.0)" \ + --tag "v1.0.0" --dry-run --chart-version "1.2.0-prerelease" + +run_test "Test 12: On tag creation, version to be reflected on release/x.y branch, the current chart version is not prerelease" \ + "Chart version(1.2.0-develop) should be a prerelease format to proceed for tag creation flow" \ + --tag "v1.0.0" --dry-run --chart-version "1.2.0-develop" + +run_test "Test 13: rc tag, with chart type prerelease" \ + "" \ + --tag "v1.2.3-rc" --dry-run --chart-version "1.2.3-prerelease" + +run_test "Test 14: Actual release tag to modify the chart versions" \ + "1.2.3" \ + --tag "v1.2.3" --dry-run --chart-version "1.2.3-prerelease" --publish-release + +run_test "Test 15: Actual release rc tag to modify the chart versions" \ + "1.2.3-rc" \ + --tag "v1.2.3-rc" --dry-run --chart-version "1.2.3-prerelease" --publish-release + +if [ -n "$FAILED" ]; then + echo "Some of the tests have failed..." + exit 1 +fi diff --git a/scripts/update-chart-version.sh b/scripts/update-chart-version.sh new file mode 100755 index 00000000..2e6fa163 --- /dev/null +++ b/scripts/update-chart-version.sh @@ -0,0 +1,227 @@ +#!/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. + --type Which branch to modify. + --chart-version Version of the current chart. + --publish-release To modify the charts for a release. + +Examples: + $(basename "$0") --branch release/x.y +EOF +} + +check_tag_is_valid() { + local tag="$1" + local current_chart_version="$2" + + if [[ "$(semver validate $tag)" != "valid" ]]; then + die "Tag is not a valid sevmer complaint version" + fi + + if [[ $current_chart_version != *"-prerelease" ]]; then + die "Chart version($current_chart_version) should be a prerelease format to proceed for tag creation flow" + fi + + allowed_diff=("" "patch" "prerelease") + diff="$(semver diff "$tag" "$current_chart_version")" + if ! [[ " ${allowed_diff[*]} " =~ " $diff " ]]; then + die "For release/x.y branch the current chart version($current_chart_version)'s X.Y must exactly match X.Y from tag ($tag)" + 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(on branch creation), +# then version generated is of format x.y.0-prerelease if type is "release" +# 3. If current chart version of type develop(on branch creation), +# then version generated is of format x.y+1.0-develop if type is develop. +# 4. If current chart version of type prerelease(after branch creation), +# then for type release it's a no op as it's already a prerelease format. +# 5. If current chart version of type prerelease(after branch creation), +# then for type develop it's a no op as the version to be would be same as it is currently. +# 6. Let's say for somereason someone tries to create a release/x.y branch from develop but chart version +# on develop is newer than x.y, example, release/2.2 and develop chart version is 2.5.0-develop. +# In that case for type release, the version would be 2.2.0-prerelease and for type develop it would be +# a no op as develop is already newer. +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 [[ "$TYPE" == "release" ]]; then + 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 + elif [[ "$TYPE" == "develop" ]]; then + EXPECTED_VERSION="$(semver bump minor "$EXTRACTED_VERSION.0")-develop" + if [[ "$(semver compare $EXPECTED_VERSION $CURRENT_CHART_VERSION)" == 1 ]]; then + VERSION=$EXPECTED_VERSION + else + NO_OP=1 + fi + fi + else + die "Branch name($BRANCH_NAME) is not of format release/x.y" + 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. For release branches if all the above holds then it bumps the patch version. Ex, v2.6.1 --> 2.6.2-prerelease +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 [[ -z $PUBLISH_RELEASE ]]; then + VERSION="$(semver bump patch $EXTRACTED_VERSION)-prerelease" + if [[ -z $DRY_RUN ]]; then + echo "release/$(echo $EXTRACTED_VERSION | cut -d'.' -f1,2)" + fi + else + VERSION="$EXTRACTED_VERSION" + fi + elif [[ "$TAG" == *"-rc" ]]; then + if [[ -z $PUBLISH_RELEASE ]]; then + NO_OP=1 + else + local EXTRACTED_VERSION=$(echo "$TAG" | grep -oP '(?<=v)\d+\.\d+\.\d+(-\w+)?') + check_tag_is_valid "$EXTRACTED_VERSION" "$CURRENT_CHART_VERSION" + VERSION="$EXTRACTED_VERSION" + fi + 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 ".lvmPlugin.image.tag = \"$VERSION\"" "$VALUES_YAML" +} + +set -euo pipefail + +DRY_RUN= +NO_OP= +CURRENT_CHART_VERSION= +PUBLISH_RELEASE= +# 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 + ;; + --publish-release) + PUBLISH_RELEASE=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-}" && -n "${TYPE-}" ]]; then + create_version_from_release_branch +elif [[ -n "${TAG-}" ]]; then + create_version_from_tag +else + help + die "Either --branch and --type or --tag and 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 \ No newline at end of file