diff --git a/.github/scripts/create_pr_for_staged_changes.sh b/.github/scripts/create_pr_for_staged_changes.sh new file mode 100644 index 00000000000..a35d45cc9e9 --- /dev/null +++ b/.github/scripts/create_pr_for_staged_changes.sh @@ -0,0 +1,116 @@ +#!/bin/bash +set -uxo pipefail # enable debugging, prevent accessing unset env vars, prevent masking pipeline errors to the next command + +#docs +#title :create_pr_for_staged_changes.sh +#description :This script will create a PR for staged changes and detect and close duplicate PRs. +#author :@heitorlessa +#date :May 8th 2023 +#version :0.1 +#usage :bash create_pr_for_staged_changes.sh {git_staged_files_or_directories_separated_by_space} +#notes :Meant to use in GitHub Actions only. Temporary branch will be named $TEMP_BRANCH_PREFIX-$GITHUB_RUN_ID +#os_version :Ubuntu 22.04.2 LTS +#required_env_vars :COMMIT_MSG, PR_TITLE, TEMP_BRANCH_PREFIX, GH_TOKEN, GITHUB_RUN_ID, GITHUB_SERVER_URL, GITHUB_REPOSITORY +#============================================================================== + +PR_BODY="This is an automated PR created from the following workflow" +FILENAME=".github/scripts/$(basename "$0")" +readonly PR_BODY +readonly FILENAME + +# Sets GitHub Action with error message to ease troubleshooting +function raise_validation_error() { + echo "::error file=${FILENAME}::$1" + exit 1 +} + +function debug() { + echo "::debug::$1" +} + +function notice() { + echo "::notice file=${FILENAME}::$1" +} + +function has_required_config() { + # Default GitHub Actions Env Vars: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables + debug "Do we have required environment variables?" + test -z "${TEMP_BRANCH_PREFIX}" && raise_validation_error "TEMP_BRANCH_PREFIX env must be set to create a PR" + test -z "${GH_TOKEN}" && raise_validation_error "GH_TOKEN env must be set for GitHub CLI" + test -z "${COMMIT_MSG}" && raise_validation_error "COMMIT_MSG env must be set" + test -z "${PR_TITLE}" && raise_validation_error "PR_TITLE env must be set" + test -z "${GITHUB_RUN_ID}" && raise_validation_error "GITHUB_RUN_ID env must be set to trace Workflow Run ID back to PR" + test -z "${GITHUB_SERVER_URL}" && raise_validation_error "GITHUB_SERVER_URL env must be set to trace Workflow Run ID back to PR" + test -z "${GITHUB_REPOSITORY}" && raise_validation_error "GITHUB_REPOSITORY env must be set to trace Workflow Run ID back to PR" + + set_environment_variables +} + +function set_environment_variables() { + WORKFLOW_URL="${GITHUB_SERVER_URL}"/"${GITHUB_REPOSITORY}"/actions/runs/"${GITHUB_RUN_ID}" # e.g., heitorlessa/aws-lambda-powertools-test/actions/runs/4913570678 + TEMP_BRANCH="${TEMP_BRANCH_PREFIX}"-"${GITHUB_RUN_ID}" # e.g., ci-changelog-4894658712 + + export readonly WORKFLOW_URL + export readonly TEMP_BRANCH +} + +function has_anything_changed() { + debug "Is there an update to the source code?" + HAS_ANY_SOURCE_CODE_CHANGED="$(git status --porcelain)" + + test -z "${HAS_ANY_SOURCE_CODE_CHANGED}" && echo "Nothing to update" && exit 0 +} + +function create_temporary_branch_with_changes() { + debug "Creating branch ${TEMP_BRANCH}" + git checkout -b "${TEMP_BRANCH}" + + debug "Committing staged files: $*" + git add "$@" + git commit -m "${COMMIT_MSG}" + + debug "Creating branch remotely" + git push origin "${TEMP_BRANCH}" +} + +function create_pr() { + debug "Creating PR against ${BRANCH} branch" + NEW_PR_URL=$(gh pr create --title "${PR_TITLE}" --body "${PR_BODY}: ${WORKFLOW_URL}" --base "${BRANCH}") # e.g, https://github.com/awslabs/aws-lambda-powertools/pull/13 + + # greedy remove any string until the last URL path, including the last '/'. https://opensource.com/article/17/6/bash-parameter-expansion + NEW_PR_ID="${NEW_PR_URL##*/}" # 13 + export NEW_PR_URL + export NEW_PR_ID +} + +function close_duplicate_prs() { + debug "Do we have any duplicate PRs?" + DUPLICATE_PRS=$(gh pr list --search "${PR_TITLE}" --json number --jq ".[] | select(.number != ${NEW_PR_ID}) | .number") # e.g, 13\n14 + + debug "Closing duplicated PRs if any" + echo "${DUPLICATE_PRS}" | xargs -L1 gh pr close --delete-branch --comment "Superseded by #${NEW_PR_ID}" + export readonly DUPLICATE_PRS +} + +function report_summary() { + debug "Creating job summary" + echo "### Pull request created successfully :rocket: #${NEW_PR_URL}

Closed duplicated PRs (if any): ${DUPLICATE_PRS}" >>"$GITHUB_STEP_SUMMARY" + + notice "PR_URL is ${NEW_PR_URL}" + notice "PR_BRANCH is ${TEMP_BRANCH}" + notice "PR_DUPLICATES are ${DUPLICATE_PRS}" +} + +function main() { + # Sanity check + has_anything_changed + has_required_config + + create_temporary_branch_with_changes "$@" + create_pr + close_duplicate_prs + + report_summary +} + +main "$@" diff --git a/.github/workflows/reusable_publish_changelog.yml b/.github/workflows/reusable_publish_changelog.yml index 2e038eae924..4294dda4a94 100644 --- a/.github/workflows/reusable_publish_changelog.yml +++ b/.github/workflows/reusable_publish_changelog.yml @@ -3,9 +3,6 @@ name: Build and publish latest changelog on: workflow_call: -permissions: - contents: write - env: BRANCH: develop @@ -16,6 +13,9 @@ jobs: concurrency: group: changelog-build runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write steps: - name: Checkout repository # reusable workflows start clean, so we need to checkout again uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 @@ -30,11 +30,16 @@ jobs: git pull origin "${BRANCH}" - name: "Generate latest changelog" run: make changelog - - name: Update Changelog in trunk - run: | - HAS_CHANGE=$(git status --porcelain) - test -z "${HAS_CHANGE}" && echo "Nothing to update" && exit 0 - git add CHANGELOG.md - git commit -m "update changelog with latest changes" - git pull origin "${BRANCH}" # prevents concurrent branch update failing push - git push origin HEAD:refs/heads/"${BRANCH}" + - name: Create PR + run: bash .github/scripts/create_pr_for_staged_changes.sh CHANGELOG.md + env: + COMMIT_MSG: "chore(ci): update changelog with latest changes" + PR_TITLE: "chore(ci): changelog rebuild" + TEMP_BRANCH_PREFIX: "ci-changelog" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Cleanup orphaned branch + if: failure() + run: git push origin --delete "${TEMP_BRANCH_PREFIX}-${GITHUB_RUN_ID}" || echo "Must have failed before creating temporary branch; no cleanup needed." + env: + TEMP_BRANCH_PREFIX: "ci-changelog" + GITHUB_RUN_ID: ${{ github.run_id }}