From 935a2c293c9f5fa18cd91855605a844d2af3e6eb Mon Sep 17 00:00:00 2001 From: Herbert Origas Date: Sun, 28 Feb 2021 16:40:16 -0700 Subject: [PATCH 1/5] feat: adds numerous features, namely; better argument construction when passed to the 'hub' cli so that double and single quotes are appropriately handled, github action grouping of console prints for easier troubleshooting and status indication, more console prints with better details regarding internal state, bash set -x debugging parameter, generalize the "GITHUB_TOKEN" so Personal Action Tokens may be used as well, add support for 3rd party or repositories that are currently checked out, improved 'hub' cli error handling so that the action stops and errors when the pull-request fails to create, duplicate pull request handling where the head and base or destination and source branches are the same and in this case the action continues and finds the PR url and PR number. --- README.md | 15 ++-- action.yml | 11 +-- entrypoint.sh | 187 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 178 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index e52fd55..77eda11 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ jobs: uses: repo-sync/pull-request@v2 with: destination_branch: "main" - github_token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} ``` This will automatically create a pull request from `feature-1` to `main`. @@ -57,8 +57,14 @@ jobs: with: source_branch: "" # If blank, default: triggered branch destination_branch: "master" # If blank, default: master + repository: ${{ github.repository }} # repository with owner, can be another repository than currently checked out. pr_title: "Pulling ${{ github.ref }} into master" # Title of pull request - pr_body: ":crown: *An automated PR*" # Full markdown support, requires pr_title to be set + pr_body: | # Full markdown support, requires pr_title to be set + :crown: *An automated PR* + :arrow_heading_up: Closes: #issueid + + **Describe the Change** + "Put a description here" pr_template: ".github/PULL_REQUEST_TEMPLATE.md" # Path to pull request template, requires pr_title to be set, excludes pr_body pr_reviewer: "wei,worker" # Comma-separated list (no spaces) pr_assignee: "wei,worker" # Comma-separated list (no spaces) @@ -66,7 +72,8 @@ jobs: pr_milestone: "Milestone 1" # Milestone name pr_draft: true # Creates pull request as draft pr_allow_empty: true # Creates pull request even if there are no changes - github_token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_PERSONAL_ACTION_TOKEN }} + debug: false # bash set -x verbose debugging output ``` ### Outputs @@ -89,7 +96,7 @@ jobs: uses: repo-sync/pull-request@v2 with: destination_branch: "main" - github_token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} - name: output-url run: echo ${{steps.open-pr.outputs.pr_url}} - name: output-number diff --git a/action.yml b/action.yml index 5372cbd..bd810e6 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,9 @@ inputs: description: Branch name to sync to in this repo, default is master required: false default: master + repository: + required: false + description: Repository on which to perform the pull-request. Defaults to current repo. Can different from the current one. pr_title: description: Pull request title required: false @@ -39,9 +42,11 @@ inputs: pr_allow_empty: description: Create PR even if no changes required: false - github_token: - description: GitHub token secret + token: + description: GitHub token secret or Personal Action Token required: true + debug: + description: Bash set -x debugging mode outputs: pr_url: description: 'Pull request URL' @@ -52,5 +57,3 @@ outputs: runs: using: 'docker' image: 'Dockerfile' - env: - GITHUB_TOKEN: ${{ inputs.github_token }} diff --git a/entrypoint.sh b/entrypoint.sh index ce2dd73..02d422f 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -3,94 +3,221 @@ set -e set -o pipefail -if [[ -z "$GITHUB_TOKEN" ]]; then - echo "Set the GITHUB_TOKEN environment variable." +##################################### +# establish a few helper functions +reset_color="\\e[0m" +color_red="\\e[31m" +color_green="\\e[32m" +color_yellow="\\e[33m" +color_blue="\\e[36m" +color_gray="\\e[37m" +function echo_blue { echo -e "${color_blue}$*${reset_color}"; } +function echo_green { echo -e "${color_green}$*${reset_color}"; } +function echo_red { echo -e "${color_red}$*${reset_color}"; } +function echo_yellow { echo -e "${color_yellow}$*${reset_color}"; } +function echo_gray { echo -e "${color_gray}$*${reset_color}"; } +function echo_grey { echo -e "${color_gray}$*${reset_color}"; } +function echo_info { echo -e "${color_blue}info: $*${reset_color}"; } +function echo_error { echo -e "${color_red}error: $*${reset_color}"; } +function echo_warning { echo -e "${color_yellow}✔ $*${reset_color}"; } +function echo_success { echo -e "${color_green}✔ $*${reset_color}"; } +function echo_fail { echo -e "${color_red}✖ $*${reset_color}"; } +function enable_debug { + if [[ "${DEBUG}" == "true" ]]; then + echo_info "Enabling debug mode." + set -x + fi +} +function disable_debug { + if [[ "${DEBUG}" == "true" ]]; then + set +x + fi +} +# no more helper functions. +################################### + + +echo "::group::Check inputs" +echo "::add-mask::$INPUT_TOKEN" +if [[ -z "$INPUT_TOKEN" ]]; then + echo_fail "Set the INPUT_TOKEN environment variable." exit 1 fi +# enable debug after token handling +enable_debug + +if [[ -z "$GITHUB_ACTOR" ]]; then + echo_yellow "GITHUB_ACTOR is missing. This is usually provided as an environment variable by Github Actions." + echo_yellow "Setting GITHUB_ACTOR to 'github-actions'" + GITHUB_ACTOR='github-actions' +fi + +if [[ -z "$INPUT_REPOSITORY" ]]; then + echo_yellow "INPUT_REPOSITORY is empty to not set. Defaulting INPUT_REPOSITORY to GITHUB_REPOSITORY" + if [[ -z "$GITHUB_REPOSITORY" ]]; then + echo_fail "GITHUB_REPOSITORY is missing. This is usually provided as an environment variable by Github Actions." + echo_fail "Expected Format is generally /. i.e. aws-official/s3-sync" + exit 1 + fi + INPUT_REPOSITORY="$GITHUB_REPOSITORY" +fi + if [[ ! -z "$INPUT_SOURCE_BRANCH" ]]; then SOURCE_BRANCH="$INPUT_SOURCE_BRANCH" elif [[ ! -z "$GITHUB_REF" ]]; then SOURCE_BRANCH=${GITHUB_REF/refs\/heads\//} # Remove branch prefix else - echo "Set the INPUT_SOURCE_BRANCH environment variable or trigger from a branch." + echo_fail "Set the INPUT_SOURCE_BRANCH environment variable or trigger from a branch." exit 1 fi DESTINATION_BRANCH="${INPUT_DESTINATION_BRANCH:-"master"}" +echo "::endgroup::" + +echo "::group::Configure git" # Github actions no longer auto set the username and GITHUB_TOKEN -git remote set-url origin "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY" +GIT_ORIGIN_URL="$(git remote get-url origin)" +git remote set-url origin "https://$GITHUB_ACTOR:$INPUT_TOKEN@github.com/$INPUT_REPOSITORY" # Pull all branches references down locally so subsequent commands can see them -git fetch origin '+refs/heads/*:refs/heads/*' --update-head-ok +git fetch origin '+refs/heads/*:refs/heads/*' --update-head-ok --prune +git fetch --prune # Print out all branches git --no-pager branch -a -vv +echo "::endgroup::" + +echo "::group::Ensure pull-request contains differences" if [ "$(git rev-parse --revs-only "$SOURCE_BRANCH")" = "$(git rev-parse --revs-only "$DESTINATION_BRANCH")" ]; then - echo "Source and destination branches are the same." + echo_blue "Source and destination branches are the same." exit 0 fi # Do not proceed if there are no file differences, this avoids PRs with just a merge commit and no content LINES_CHANGED=$(git diff --name-only "$DESTINATION_BRANCH" "$SOURCE_BRANCH" -- | wc -l | awk '{print $1}') if [[ "$LINES_CHANGED" = "0" ]] && [[ ! "$INPUT_PR_ALLOW_EMPTY" == "true" ]]; then - echo "No file changes detected between source and destination branches." + echo_blue "No file changes detected between source and destination branches." exit 0 fi +echo_yellow "Source and Destination Branches Contain Differences." +echo_yellow "Number of lines changed: $LINES_CHANGED" +echo "::endgroup::" +echo "::group::Establish hub cli parameters" # Workaround for `hub` auth error https://github.com/github/hub/issues/2149#issuecomment-513214342 export GITHUB_USER="$GITHUB_ACTOR" +# set GITHUB_TOKEN envar so hub cli commands can authenticate. +export GITHUB_TOKEN="$INPUT_TOKEN" + +if [[ ! -z "$DESTINATION_BRANCH" ]]; then + FLAGS=(-b "$DESTINATION_BRANCH") +fi -PR_ARG="$INPUT_PR_TITLE" -if [[ ! -z "$PR_ARG" ]]; then - PR_ARG="-m \"$PR_ARG\"" +if [[ ! -z "$SOURCE_BRANCH" ]]; then + FLAGS+=(-h "$SOURCE_BRANCH") +fi + +FLAGS+=(--no-edit) +if [[ ! -z "$INPUT_PR_TITLE" ]]; then + FLAGS+=(-m "$INPUT_PR_TITLE") if [[ ! -z "$INPUT_PR_TEMPLATE" ]]; then sed -i 's/`/\\`/g; s/\$/\\\$/g' "$INPUT_PR_TEMPLATE" - PR_ARG="$PR_ARG -m \"$(echo -e "$(cat "$INPUT_PR_TEMPLATE")")\"" + FLAGS+=(-m "$(echo -e "$(cat "$INPUT_PR_TEMPLATE")")") elif [[ ! -z "$INPUT_PR_BODY" ]]; then - PR_ARG="$PR_ARG -m \"$INPUT_PR_BODY\"" + FLAGS+=(-m "$INPUT_PR_BODY") fi fi if [[ ! -z "$INPUT_PR_REVIEWER" ]]; then - PR_ARG="$PR_ARG -r \"$INPUT_PR_REVIEWER\"" + FLAGS+=(-r "$INPUT_PR_REVIEWER") fi if [[ ! -z "$INPUT_PR_ASSIGNEE" ]]; then - PR_ARG="$PR_ARG -a \"$INPUT_PR_ASSIGNEE\"" + FLAGS+=(-a "$INPUT_PR_ASSIGNEE") fi if [[ ! -z "$INPUT_PR_LABEL" ]]; then - PR_ARG="$PR_ARG -l \"$INPUT_PR_LABEL\"" + FLAGS+=(-l "$INPUT_PR_LABEL") fi if [[ ! -z "$INPUT_PR_MILESTONE" ]]; then - PR_ARG="$PR_ARG -M \"$INPUT_PR_MILESTONE\"" + FLAGS+=(-M "$INPUT_PR_MILESTONE") fi if [[ "$INPUT_PR_DRAFT" == "true" ]]; then - PR_ARG="$PR_ARG -d" + FLAGS+=(-d) fi -COMMAND="hub pull-request \ - -b $DESTINATION_BRANCH \ - -h $SOURCE_BRANCH \ - --no-edit \ - $PR_ARG \ - || true" +echo "${FLAGS[@]}" +echo "::endgroup::" + +echo "::group::Create Pull-Request $SOURCE_BRANCH -> $DESTINATION_BRANCH" +RAND_UUID=$(cat /proc/sys/kernel/random/uuid) +COMMAND="hub pull-request "${FLAGS[@]}" 2>\"./create-pull-request.$RAND_UUID.stderr\" || true" echo "$COMMAND" -PR_URL=$(sh -c "$COMMAND") -if [[ "$?" != "0" ]]; then - exit 1 +PR_URL=$( \ + hub pull-request "${FLAGS[@]}" \ + 2>"./create-pull-request.$RAND_UUID.stderr" || true \ +) +STD_ERROR="$( cat "./create-pull-request.$RAND_UUID.stderr" || true )" +rm -rf "./create-pull-request.$RAND_UUID.stderr" +echo "::endgroup::" + + +echo "::group::Revert Git Config Changes" +# set origin back as was previously configured. +git remote set-url origin "$GIT_ORIGIN_URL" +git fetch origin '+refs/heads/*:refs/heads/*' --update-head-ok +git fetch --prune +echo "::endgroup::" + + +# determine success / failure +# since various things can go wrong such as bad user input or non-existant branches, there is a need to handle outputs to determine if the pr was successfully created or not. +if [[ -z "$PR_URL" ]]; then + if [[ ! -z "$(echo "$STD_ERROR" | grep -oie "A pull request already exists for")" ]]; then + echo_yellow "Pull-Request Already Exists. This is the stderr output:" + echo_yellow "$STD_ERROR" + else + echo_fail "Pull-Request Command Failed. This is the stderr output:" + echo_red "$STD_ERROR" + exit 1 + fi +else + echo_success "Pull-Request was successfully created." + echo_success "pr_url: ${PR_URL}" +fi + +# attempt to obtain the pull-request details - pr already exists. +if [[ -z "$PR_URL" ]]; then + echo "::group::Retrieving Pull-Request Details" + RAND_UUID=$(cat /proc/sys/kernel/random/uuid) + PR_URL=$( \ + hub pr list -h $SOURCE_BRANCH -b $DESTINATION_BRANCH -f %U \ + 2>"./get-pull-request.$RAND_UUID.stderr" || true \ + ) + STD_ERROR="$( cat "./get-pull-request.$RAND_UUID.stderr" || true )" + rm -rf "./get-pull-request.$RAND_UUID.stderr" + + if [[ -z "$PR_URL" ]]; then + echo_fail "Pull-Request Already Exists, but was unable to retrieve url. This is the stderr output:" + echo_red "$STD_ERROR" + else + echo_success "Pull-Request details successfully obtained." + echo_success "pr_url: ${PR_URL}" + fi + echo "::endgroup::" fi -echo ${PR_URL} + +echo "::group::Set Outputs" echo "::set-output name=pr_url::${PR_URL}" echo "::set-output name=pr_number::${PR_URL##*/}" if [[ "$LINES_CHANGED" = "0" ]]; then @@ -98,3 +225,9 @@ if [[ "$LINES_CHANGED" = "0" ]]; then else echo "::set-output name=has_changed_files::true" fi +echo_yellow "Outputs Set." +echo "::endgroup::" + + +# disable debug mode +disable_debug From 93e40e1f69a9df9f3c41118eb3832c888c8355a4 Mon Sep 17 00:00:00 2001 From: Herbert Origas Date: Sun, 28 Feb 2021 16:47:21 -0700 Subject: [PATCH 2/5] fix: debug parameter not used. --- entrypoint.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 02d422f..f952e2a 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -23,13 +23,13 @@ function echo_warning { echo -e "${color_yellow}✔ $*${reset_color}"; } function echo_success { echo -e "${color_green}✔ $*${reset_color}"; } function echo_fail { echo -e "${color_red}✖ $*${reset_color}"; } function enable_debug { - if [[ "${DEBUG}" == "true" ]]; then + if [[ "${INPUT_DEBUG}" == "true" ]]; then echo_info "Enabling debug mode." set -x fi } function disable_debug { - if [[ "${DEBUG}" == "true" ]]; then + if [[ "${INPUT_DEBUG}" == "true" ]]; then set +x fi } From 9dc7911b43837df4df42e3f864653d796f3fa031 Mon Sep 17 00:00:00 2001 From: Herbert Origas Date: Sun, 28 Feb 2021 18:24:17 -0700 Subject: [PATCH 3/5] docs: add example of pull-request on another repo. --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 77eda11..751860b 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ jobs: with: source_branch: "" # If blank, default: triggered branch destination_branch: "master" # If blank, default: master - repository: ${{ github.repository }} # repository with owner, can be another repository than currently checked out. + repository: ${{ github.repository }} # repository with owner, can be another repository than currently checked out when using a PAT during checkout that has access to the other repo. pr_title: "Pulling ${{ github.ref }} into master" # Title of pull request pr_body: | # Full markdown support, requires pr_title to be set :crown: *An automated PR* @@ -106,6 +106,43 @@ jobs: ``` +### Example: Pull-Request on another repo +This example demonstrates how to create a pull-request in another repo. There are a few caveats such as the requirement of checking out the code with a [Github Personal Action Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). There are some pretty advanced use cases for this such as building an app on every push to develop, updating the docker image tag and repo url in the config repo, and creating a pull-request to the config repo. After the pr in the config repo is merged a deployment is kicked off. +```yaml +on: + push: + branches: + - 'develop' +jobs: + draft-new-pr: + name: "Create PR in my-apps-config-repo" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + token: ${{ secrets.ACTIONS_WORKFLOW_PAT }} + - name: Create Alt Repo Pull-Request + uses: ./.github/actions/actions-create-pull-request + with: + source_branch: 'develop' + destination_branch: 'master' + repository: '${{ github.repository_owner }}/my-apps-config-repo' + pr_title: "Pulling 'release/${{ steps.ver.outputs.version-after }}' into master" + pr_body: | + :crown: *An automated PR* + + This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. + ..And creates a pr in this other repo here: https://github.com/${{ github.repository_owner }}/my-apps-config-repo. + + "Put a description here" + 'Quotes are being handled' + pr_label: "some-label,another-label" + pr_allow_empty: false + token: ${{ secrets.ACTIONS_WORKFLOW_PAT }} + debug: false +``` + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): From a87ab1565f88f6d316f8c548cbd7f74ad8d5179c Mon Sep 17 00:00:00 2001 From: Herbert Origas Date: Sun, 28 Feb 2021 18:39:57 -0700 Subject: [PATCH 4/5] fix: better handling for missing INPUT_TOKEN and GITHUB_TOKEN --- entrypoint.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index f952e2a..c8c2212 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -38,12 +38,20 @@ function disable_debug { echo "::group::Check inputs" -echo "::add-mask::$INPUT_TOKEN" if [[ -z "$INPUT_TOKEN" ]]; then - echo_fail "Set the INPUT_TOKEN environment variable." + echo_yellow "INPUT_TOKEN unsupplied. Defaulting to GITHUB_TOKEN." + INPUT_TOKEN="$GITHUB_TOKEN" +fi + +if [[ -z "$INPUT_TOKEN" ]]; then + echo_fail "GITHUB_TOKEN is missing. This is usually provided as an environment variable by Github Actions. If INPUT_TOKEN or GITHUB_TOKEN are unsupplied, unable to continue." exit 1 fi +if [[ ! -z "$INPUT_TOKEN" ]]; then + echo "::add-mask::$INPUT_TOKEN" +fi + # enable debug after token handling enable_debug From 21216aa1fa1579e4a00b918e3cba06e969f414c2 Mon Sep 17 00:00:00 2001 From: Herbert Origas Date: Fri, 19 Mar 2021 09:17:23 -0700 Subject: [PATCH 5/5] docs: correct naming error personal action token -> personal access token --- README.md | 4 ++-- action.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 751860b..0a3efc4 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ jobs: pr_milestone: "Milestone 1" # Milestone name pr_draft: true # Creates pull request as draft pr_allow_empty: true # Creates pull request even if there are no changes - token: ${{ secrets.GITHUB_PERSONAL_ACTION_TOKEN }} + token: ${{ secrets.GITHUB_PERSONAL_ACCESS_TOKEN }} debug: false # bash set -x verbose debugging output ``` @@ -107,7 +107,7 @@ jobs: ``` ### Example: Pull-Request on another repo -This example demonstrates how to create a pull-request in another repo. There are a few caveats such as the requirement of checking out the code with a [Github Personal Action Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). There are some pretty advanced use cases for this such as building an app on every push to develop, updating the docker image tag and repo url in the config repo, and creating a pull-request to the config repo. After the pr in the config repo is merged a deployment is kicked off. +This example demonstrates how to create a pull-request in another repo. There are a few caveats such as the requirement of checking out the code with a [Github Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). There are some pretty advanced use cases for this such as building an app on every push to develop, updating the docker image tag and repo url in the config repo, and creating a pull-request to the config repo. After the pr in the config repo is merged a deployment is kicked off. ```yaml on: push: diff --git a/action.yml b/action.yml index bd810e6..86fcf62 100644 --- a/action.yml +++ b/action.yml @@ -43,7 +43,7 @@ inputs: description: Create PR even if no changes required: false token: - description: GitHub token secret or Personal Action Token + description: GitHub token secret or Personal Access Token required: true debug: description: Bash set -x debugging mode