From bb1176d3a6de4c81bf2f2b2e705d565893670d7c Mon Sep 17 00:00:00 2001 From: Siddhartha Sonker <158144589+siddharthasonker95@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:42:51 +0530 Subject: [PATCH] Added backport.yml to automate backport process (#1452) Signed-off-by: siddharthasonker95 <158144589+siddharthasonker95@users.noreply.github.com> --- .github/scripts/backport/README.md | 21 +++ .github/scripts/backport/main.sh | 237 ++++++++++++++++++++++++ .github/scripts/backport/manual_test.md | 182 ++++++++++++++++++ .github/workflows/backport.yml | 37 ++++ 4 files changed, 477 insertions(+) create mode 100644 .github/scripts/backport/README.md create mode 100644 .github/scripts/backport/main.sh create mode 100644 .github/scripts/backport/manual_test.md create mode 100644 .github/workflows/backport.yml diff --git a/.github/scripts/backport/README.md b/.github/scripts/backport/README.md new file mode 100644 index 0000000000..f881a2ec08 --- /dev/null +++ b/.github/scripts/backport/README.md @@ -0,0 +1,21 @@ +# Backport Merged Pull Request + +## Overview + +This feature automates the process of creating pull requests for backporting merged changes to a designated target branch. + +### How to Use + +1. **Label Your Pull Request**: To initiate a backport, simply add a label to your pull request in the format `backport `, where `` is the branch you want to backport changes to (eg. `backport v1.x`). The backport labels can be added to both open and closed pull requests. + +2. **Automatic Backport Pull Request Creation**: After the pull request is merged and closed, or if the backport label is added to an already closed pull request, backport Github Action will execute the `.github/scripts/backport/main.sh` script, which will automatically create a new pull request with the changes backported to the specified ``. A comment linking to the new Backport Pull Request will be added to the original pull request for easy navigation. + +### Handling Merge Conflicts + +- In some cases, the automatic backport process may encounter merge conflicts that cannot be resolved automatically. +- If this occurs, a comment will be posted on the original pull request indicating the conflict and providing the commit SHA causing the issue. +- When a merge conflict arises, you'll need to manually backport your changes to the target branch and resolve any conflicts as part of the manual pull request process. [Please see the contributing guide for more details](https://github.com/opentofu/opentofu/blob/main/CONTRIBUTING.md#backporting) + +### Note + +- **Use Clear Labels**: Ensure the `backport ` label is correctly formatted. \ No newline at end of file diff --git a/.github/scripts/backport/main.sh b/.github/scripts/backport/main.sh new file mode 100644 index 0000000000..160eaccf9c --- /dev/null +++ b/.github/scripts/backport/main.sh @@ -0,0 +1,237 @@ +# Copyright (c) The OpenTofu Authors +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2023 HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +#!/bin/bash + +set -eou pipefail + +# Color codes for logs +COLOR_RESET="\033[0m" +COLOR_GREEN="\033[32m" +COLOR_RED="\033[31m" + +# This function outputs error logs. +log_error() { + if [ -z "$1" ]; then + echo -e "${COLOR_RED}Error: No error message provided.${COLOR_RESET}" >&2 + exit 1 + fi + echo -e "${COLOR_RED}Error: $1${COLOR_RESET}" >&2 + exit 1 +} + +# This function outputs info logs. +log_info() { + if [ -z "$1" ]; then + echo -e "${COLOR_RED}Error: No info message provided.${COLOR_RESET}" + return + fi + echo -e "${COLOR_GREEN}$1${COLOR_RESET}" +} + +# This function sets up the committer identity. +setup_committer_identity() { + log_info "Setting up committer identity..." + if ! git config user.email "noreply@github.com"; then + log_error "Failed to set email." + fi + + if ! git config user.name "GitHub Actions"; then + log_error "Failed to set a username." + fi + log_info "Successfully set the committer identity." +} + +# This function checks the GITHUB_TOKEN, gh auth and environment variables are set or not. +validate_github_auth_and_env_vars() { + # Check if GitHub token is set + log_info "Checking if GITHUB_TOKEN is set..." + if [ -z "$GITHUB_TOKEN" ]; then + log_error "GitHub token is not available. Please ensure a secret named 'GITHUB_TOKEN' is defined." + fi + log_info "GITHUB_TOKEN is set successfully." + + log_info "Checking if required environment variables are set..." + if [ -z "$OWNER" ] || [ -z "$REPO" ] || [ -z "$PR_NUMBER" ] || [ -z "$ISSUE_NUMBER" ] || [ -z "$HEAD_BRANCH" ]; then + log_error "One or more required environment variables (OWNER, REPO, PR_NUMBER, ISSUE_NUMBER, HEAD_BRANCH) are not set." + fi + log_info "All the environment variables are set successfully." + + # Check if gh is logged in + log_info "Checking if 'gh' auth is set..." + if ! gh auth status >/dev/null 2>&1; then + log_error "Authentication check for 'gh' failed." + fi + log_info "'gh' authentication is set up successfully." +} + +# This function cherry-picks the commits onto the new backport branch. +cherry_pick_commits() { + # Get the commit SHAs associated with the pull request + log_info "Fetching commit SHAs from the pull request..." + if ! commit_shas=$(gh api "/repos/$OWNER/$REPO/pulls/$PR_NUMBER/commits" --jq '.[].sha'); then + log_error "Failed to fetch commit SHAs from the pull request #${PR_NUMBER}." + fi + + # Check if commit_shas is empty + log_info "Checking if any commit SHAs were fetched..." + if [ -z "$commit_shas" ]; then + log_error "No commit SHAs were found in the pull request #${PR_NUMBER}." + fi + log_info "Commit SHAs were successfully fetched." + + while IFS= read -r commit_sha; do + log_info "Fetching the commit id: '${commit_sha}'..." + if ! git fetch origin "$commit_sha"; then + log_error "Failed to fetch the commit id: '${commit_sha}'" + fi + log_info "Successfully fetched the commit id: '${commit_sha}'" + + log_info "Cherry-picking the commit id: '${commit_sha}'..." + if git cherry-pick "$commit_sha" ; then + log_info "Successfully cherry-picked the commit id: '${commit_sha}'" + else + # Add a failure comment to the pull request + failureComment="Error: Cherry-pick commit '$commit_sha' failed during the backport process to '$1'. Please resolve any conflicts and manually apply the changes." + if ! gh pr comment "$PR_NUMBER" --body "$failureComment"; then + log_error "Failed to add failure comment to the pull request #${PR_NUMBER}." + fi + log_info "Successfully added failure comment to the pull request." + + log_error "Failed to cherry-pick commit '${commit_sha}'. Added failure comment to the pull request." + fi + done <<< "$commit_shas" + + return 0 +} + +# This function creates the pull request. +create_pull_request() { + local target_branch=$1 + local backport_branch=$2 + title="Backport PR #$ISSUE_NUMBER: '$HEAD_BRANCH' to '$target_branch'" + body="This pull request backports changes from branch '$HEAD_BRANCH' to branch '$target_branch'." + if ! url=$(gh pr create --title "$title" --body "$body" --base "$target_branch" --head "$backport_branch"); then + return 1 + fi + echo "${url}" +} + +# This function performs all the operations required to backport a pull request to a target branch. +backport_changes() { + local target_branch=$1 + local backport_branch=$2 + + # Fetch and checkout the target branch. + log_info "Fetching branch '$target_branch'..." + if ! git fetch origin "$target_branch"; then + log_error "Failed to fetch branch: '$target_branch'." + fi + log_info "Successfully fetched branch: '$target_branch'." + + log_info "Checking out branch '$target_branch'..." + if ! git checkout "$target_branch"; then + log_error "Failed to checkout branch: '$target_branch'" + fi + log_info "Successfully checked out branch: '$target_branch'." + + # Check whether the new backport branch already exists. If it does, delete the branch to remove any unnecessary changes. + log_info "Checking if new backport branch already exists '$backport_branch'..." + if git ls-remote --exit-code --heads origin "$backport_branch" >/dev/null 2>&1; then + if git push origin --delete "$backport_branch" >/dev/null 2>&1; then + log_info "Successfully deleted existing backport branch: '$backport_branch'" + else + log_error "Failed to delete existing backport branch: '$backport_branch'" + fi + fi + + # Checking out new backport branch + log_info "Checking out new backport branch '$backport_branch'..." + if ! git checkout -b "$backport_branch"; then + log_error "Failed to create new backport branch: '$backport_branch'" + fi + log_info "Successfully checked out new backport branch: '$backport_branch'." + + # Cherry-pick commits + log_info "Starting the cherry-pick process for the pull request #${PR_NUMBER}..." + if ! cherry_pick_commits "$target_branch"; then + log_error "Failed to cherry-pick commits for the pull request." + fi + log_info "Cherry-pick completed successfully for the pull request #${PR_NUMBER}." + + # Push final changes to the backport branch + log_info "Pushing changes to the branch: '$backport_branch'..." + if ! git push origin "$backport_branch"; then + log_error "Failed to push changes to the branch: '$backport_branch'." + fi + log_info "Successfully pushed changes to the branch: '$backport_branch'" + + # Create the pull request + log_info "Creating pull request between '$target_branch' and '$backport_branch'..." + if ! pull_request_url=$(create_pull_request "$target_branch" "$backport_branch"); then + log_error "Failed to create the pull request." + fi + log_info "Pull request created successfully. You can view the pull request at: ${pull_request_url}." + + # Add comment to the pull request + comment="This pull request has been successfully backported. Link to the new pull request: ${pull_request_url}." + log_info "Adding a comment to the original pull request #${PR_NUMBER} created between '$target_branch' and '$backport_branch'..." + if ! gh pr comment "$PR_NUMBER" --body "$comment"; then + log_error "Failed to add a comment to the original pull request #${PR_NUMBER} created between '$target_branch' and '$backport_branch'." + fi + log_info "Successfully added a comment to the original pull request #${PR_NUMBER} created between '$target_branch' and '$backport_branch'." +} + +main() { + log_info "Starting the process of creating pull requests for backporting merged changes to a designated target branch." + + # Set up committer identity + setup_committer_identity + # Validate GitHub token, gh auth, and environment variables + validate_github_auth_and_env_vars + + # Retrieve the list of branches for backporting the pull request changes. + log_info "Starting the process of identifying a list of branches where pull request changes will be backported to... " + target_branches=() + readarray -t labelArray < <(echo "$LABELS" | jq -r '.[]') + for label in "${labelArray[@]}"; do + if [[ $label == "backport"* ]]; then + target_branches+=("${label#backport }") + fi + done + + log_info "Validate all the target branches to which the pull request changes will be backported..." + if [ ${#target_branches[@]} -eq 0 ]; then + log_info "No target branches found. The backport label is not found in any of the labels on the pull request. Skipping the backport process." + exit 0 + fi + log_info "Successfully found the following target branches: ${target_branches[*]}" + + # Iterate over each target branch and attempt to backport the changes + for branch in "${target_branches[@]}"; do + ( + new_backport_branch="backport/$branch/$ISSUE_NUMBER" + + log_info "Checking if pull request already exists between the branch '$branch' and '$new_backport_branch'." + pull_request_list=$(gh pr list --base="$branch" --head="$new_backport_branch" --json number,title) + + # If a pull request already exists between branch and new_backport_branch, + # then skip backporting the pull request changes. + if [ "$(echo "$pull_request_list" | jq length)" -eq 0 ]; then + log_info "Starting the backport process for branch: $branch." + if ! backport_changes "$branch" "$new_backport_branch"; then + log_error "Failed to backport changes to the branch: $branch." + fi + log_info "Successfully backported the pull request changes to the branch '$branch'." + else + log_info "Pull requests already exist between the branch '$branch' and '$new_backport_branch'. Skipping backport for '$branch'" + fi + ) + done + + log_info "Successfully, completed the backporting for the pull request #${PR_NUMBER}." +} + +main \ No newline at end of file diff --git a/.github/scripts/backport/manual_test.md b/.github/scripts/backport/manual_test.md new file mode 100644 index 0000000000..ce6437a2c8 --- /dev/null +++ b/.github/scripts/backport/manual_test.md @@ -0,0 +1,182 @@ +# Manual Test Cases # + +## Successful Test Cases ## + +**Test Case 1**: Valid backport pull request with single backport label. + +**Objective**: Verify that the script backports a valid pull request without issues after adding a backport label. + +**Steps**: +- Add a backport label to your pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x). +- Ensure that the pull request is fully approved and merged into the main branch. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Info logs confirming the successful backporting process are displayed, indicating the completion of backporting to **. + - For eg: Successfully backported the pull request changes to the branch **. +- The `Backport Merged Pull Request` workflow completes without encountering any errors. +- A new pull request is created successfully, incorporating only the changes from the labeled pull request that need to be backported. + - Format: Backport PR *#*: ** to **. +- A comment is added to the original pull request, indicating the completion of the backporting process. + +--- + +**Test Case 2**: Valid backport pull request with multiple backport label. + +**Objective**: Verify that the script backports a valid pull request without issues after adding multiple backport labels. + +**Steps**: +- Add multiple backport labels to your pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x) +- Ensure that the pull request is fully approved and merged into the main branch. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Info log about the successful backporting to each ** can be found. + - For eg: Successfully backported the pull request changes to the branch **. +- Info logs confirming the successful backporting process to all target branches are displayed, indicating the completion of backporting to **. +Successfully, completed the backporting for the pull request *#*. +- The `Backport Merged Pull Request` workflow completes without encountering any errors. +- A new pull request is created successfully for all the target branches, incorporating only the changes from the labeled pull request that need to be backported. + - Format: Backport PR *#*: ** to **. +- A comment is added to the original pull request, indicating the completion of the backporting process. + +--- + +**Test Case 3**: No backport labels provided. + +**Objective**: Verify that the script doesn’t fail when no backport label is provided. + +**Steps**: +- Ensure that the pull request is fully approved and merged into the main branch without adding any backport labels. +- Backport labels should have a `backport` prefix followed by the **. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- `Run custom bash script for backporting` step should be skipped in the workflow. +- The script */backport/main.sh* should be skipped. +- The `Backport Merged Pull Request` workflow completes without encountering any errors. + +--- + +**Test Case 4**: Pull request already exists. + +**Objective**: Verify that the script successfully shows the info log that pull request already exists between new backport branch and target branch where original pull request changes will be backported to. + +**Steps**: +- The pull request should already exists between target branch and new backport branch. +- Add a backport label to your original pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x). +- Ensure that the pull request is fully approved and merged into the main branch. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Info log mentioning details about skipping backport process can be found. + - For eg: Pull requests already exist between the branch ** and **. Skipping backport for **. +- The `Backport Merged Pull Request` workflow completes without encountering any errors. + + +## Failure Test Cases ## + +**Test Case 1**: Merge conflict while cherry picking. + +**Objective**: Verify the script add a comment to the original pull request when merge conflict happens while cherry picking. + +**Steps**: +- Add multiple backport labels to your pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x) +- Ensure that the pull request is fully approved and merged into the main branch. + +**Error log**: Failed to cherry-pick commit . Added failure comment to the pull request. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Error logs detailing the backport failure are generated. +- Backport Merged Pull Request workflow failed with errors. +- A comment is added to the original pull request, indicating the completion of the backporting process. + - Error: Cherry-pick commit failed during the backport process to **. Please resolve any conflicts and manually apply the changes. + +--- + +**Test Case 2**: Unable to fetch commit id. + +**Objective**: Verify that the script failed when commit id was not successfully fetched and proper error log should be generated to identify the issue. + +**Steps**: +- Add multiple backport labels to your pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x). +- Ensure that the pull request is fully approved and merged into the main branch. + +**Error log**: Failed to fetch the commit id: '. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Error log mentioning details about backport failure can be found. +- Backport Merged Pull Request workflow failed with errors. +- No comments will be added. + +--- + +**Test Case 3**: Unable to fetch target branch. + +**Objective**: Verify that the script failed when the target branch was not found. + +**Steps**: +- Add a backport label to your pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x) +- Ensure that the pull request is fully approved and merged into the main branch. + +**Error log**: Failed to fetch branch: '**’. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Error log mentioning details about backport failure can be found. +- Backport Merged Pull Request workflow failed with errors. +- No comments will be added. + +--- + +**Test Case 4**: Failed to create the pull request. + +**Objective**: Verify that the script failed when the pull request was not created. + +**Steps**: +- Add a backport label to your pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x) +- Ensure that the pull request is fully approved and merged into the main branch. + +**Error log**: Failed to create the pull request. + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Error log mentioning details about backport failure can be found. +- Backport Merged Pull Request workflow failed with errors. +- No comments will be added. + +--- + +**Test Case 5**: Failed to add comment to the original pull request. + +**Objective**: Verify that the script failed when the comment was not added to the original pull request. + +**Steps**: +- Add a backport label to your pull request. + - Format backport **, where ** is the branch you want to backport changes to (eg. backport v1.x) +- Ensure that the pull request is fully approved and merged into the main branch. + +**Error log**: Failed to add a comment to the original pull request *#* created between ** and . + +**Expected Results**: +- The `Backport Merged Pull Request` workflow initiates automatically upon the addition of the backport label. +- The script *backport/main.sh* begins backporting the changes from the labeled pull request to the specified target branch (**). +- Error log mentioning details about backport failure can be found. +- Backport Merged Pull Request workflow failed with errors. +- No comments will be added. diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 0000000000..cbc7928a0e --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,37 @@ +name: Backport Merged Pull Request + +on: + pull_request: + types: + - closed + - labeled + +jobs: + backport: + name: Backport pull request + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Check if backport label exists in any of the labels on the pull request + id: check_backport_label + run: | + label_names="${{ join(github.event.pull_request.labels.*.name, ' ') }}" + if [[ "$label_names" == *"backport"* ]]; then + echo "has_backport_label=true" | tee -a "${GITHUB_OUTPUT}" + else + echo "has_backport_label=false" | tee -a "${GITHUB_OUTPUT}" + fi + - name: Run custom bash script for backporting + if: ${{ github.event.pull_request.state == 'closed' && github.event.pull_request.merged && steps.check_backport_label.outputs.has_backport_label == 'true' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + OWNER: ${{ github.event.repository.owner.login }} + REPO: ${{ github.event.repository.name }} + ISSUE_NUMBER: ${{ github.event.pull_request.number }} + HEAD_BRANCH: ${{ github.event.pull_request.head.ref }} + LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} + run: | + bash ./.github/scripts/backport/main.sh + shell: bash \ No newline at end of file