1 Commits

Author SHA1 Message Date
vsoch
416ba30b87 making abort_if_fail more verbose
Signed-off-by: vsoch <vsochat@stanford.edu>
2020-12-11 10:35:03 -07:00
11 changed files with 277 additions and 427 deletions

View File

@@ -14,10 +14,6 @@ represented by the pull requests that fixed them. Critical items to know are:
Versions correspond with GitHub releases that can be referenced with @ using actions. Versions correspond with GitHub releases that can be referenced with @ using actions.
## [master](https://github.com/vsoch/pull-request-action/tree/master) (master) ## [master](https://github.com/vsoch/pull-request-action/tree/master) (master)
- bugfix of token handling if 401 error received (missing 401 case) (1.0.21)
- bugfix of writing to environment file (missing newline) (1.0.19)
- bugfix of missing from branch with scheduled run (1.0.16)
- forgot to add assignees (1.0.15)
- output and environment variables for PR number and return codes (1.0.5) - output and environment variables for PR number and return codes (1.0.5)
- added support for reviewer (individual and team) assignments (1.0.4) - added support for reviewer (individual and team) assignments (1.0.4)
- added support for maintainer can modify and assignees (1.0.3) - added support for maintainer can modify and assignees (1.0.3)

View File

@@ -22,16 +22,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@master uses: vsoch/pull-request-action@1.0.6
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: "update/" BRANCH_PREFIX: "update/"
PULL_REQUEST_BRANCH: "master" PULL_REQUEST_BRANCH: "master"
``` ```
**Important**: Make sure to use a stable [release](https://github.com/vsoch/pull-request-action/releases) instead of a branch for your workflow.
## Environment Variable Inputs ## Environment Variable Inputs
Unlike standard actions, this action just uses variables from the environment. Unlike standard actions, this action just uses variables from the environment.
@@ -39,8 +36,6 @@ Unlike standard actions, this action just uses variables from the environment.
| Name | Description | Required | Default | | Name | Description | Required | Default |
|------|-------------|----------|---------| |------|-------------|----------|---------|
| BRANCH_PREFIX | the prefix to filter to. If the branch doesn't start with the prefix, it will be ignored | false | "" | | BRANCH_PREFIX | the prefix to filter to. If the branch doesn't start with the prefix, it will be ignored | false | "" |
| PULL_REQUEST_REPOSITORY | Choose another repository instead of default GITHUB_REPOSITORY for the PR | false | |
| PULL_REQUEST_TOKEN | [Personal Access Token(PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) only if you define a different repository with PULL_REQUEST_REPOSITORY | false | |
| PULL_REQUEST_BRANCH | open pull request against this branch | false | master | | PULL_REQUEST_BRANCH | open pull request against this branch | false | master |
| PULL_REQUEST_FROM_BRANCH | if a branch isn't found in your GitHub payload, use this branch | false | | | PULL_REQUEST_FROM_BRANCH | if a branch isn't found in your GitHub payload, use this branch | false | |
| PULL_REQUEST_BODY | the body for the pull request | false | | | PULL_REQUEST_BODY | the body for the pull request | false | |
@@ -50,10 +45,8 @@ Unlike standard actions, this action just uses variables from the environment.
| PULL_REQUEST_ASSIGNEES | A list (string with spaces) of users to assign | false | unset | | PULL_REQUEST_ASSIGNEES | A list (string with spaces) of users to assign | false | unset |
| PULL_REQUEST_REVIEWERS | A list (string with spaces) of users to assign review | false | unset | | PULL_REQUEST_REVIEWERS | A list (string with spaces) of users to assign review | false | unset |
| PULL_REQUEST_TEAM_REVIEWERS | A list (string with spaces) of teams to assign review | false | unset | | PULL_REQUEST_TEAM_REVIEWERS | A list (string with spaces) of teams to assign review | false | unset |
| PASS_ON_ERROR | Instead of failing on an error response, pass | false | unset | | PASS_ON_ERROR | Instead of failing on an error response, pass | unset |
| PASS_IF_EXISTS | Instead of failing if the pull request already exists, pass | false | unset | | PASS_IF_EXISTS | Instead of failing if the pull request already exists, pass | unset |
| PULL_REQUEST_UPDATE | If the pull request already exists, update it | false | unset |
| PULL_REQUEST_STATE | If `PULL_REQUEST_UPDATE` is true, update to this state (open, closed) | false |open |
For `PULL_REQUEST_DRAFT`, `PASS_ON_ERROR`, `PASS_IF_EXISTS`, and `MAINTAINER_CANT_MODIFY`, these are For `PULL_REQUEST_DRAFT`, `PASS_ON_ERROR`, `PASS_IF_EXISTS`, and `MAINTAINER_CANT_MODIFY`, these are
treated as environment booleans. If they are defined in the environment, they trigger the treated as environment booleans. If they are defined in the environment, they trigger the
@@ -63,7 +56,6 @@ treated as environment booleans. If they are defined in the environment, they tr
- Define `PULL_REQUEST_DRAFT` if you want the PR to be a draft. - Define `PULL_REQUEST_DRAFT` if you want the PR to be a draft.
- Define `PASS_ON_ERROR` if you want the PR to not exit given any non 200/201 response. - Define `PASS_ON_ERROR` if you want the PR to not exit given any non 200/201 response.
- Define `PASS_IF_EXISTS` if you want the PR to not exit given the pull request is already open. - Define `PASS_IF_EXISTS` if you want the PR to not exit given the pull request is already open.
- Define `PULL_REQUEST_UPDATE` if you want the pull request to be updated if it already exits.
For `PULL_REQUEST_ASSIGNEES`, `PULL_REQUEST_REVIEWERS`, and `PULL_REQUEST_TEAM_REVIEWERS` For `PULL_REQUEST_ASSIGNEES`, `PULL_REQUEST_REVIEWERS`, and `PULL_REQUEST_TEAM_REVIEWERS`
you can provide a string of one or more GitHub usernames (or team names) to you can provide a string of one or more GitHub usernames (or team names) to
@@ -73,8 +65,6 @@ an issue or PR, they are ignored otherwise.
The `GITHUB_TOKEN` secret is required to interact and authenticate with the GitHub API to open The `GITHUB_TOKEN` secret is required to interact and authenticate with the GitHub API to open
the pull request. The example is [deployed here](https://github.com/vsoch/pull-request-action-example) with an example opened (and merged) [pull request here](https://github.com/vsoch/pull-request-action-example/pull/1) if needed. the pull request. The example is [deployed here](https://github.com/vsoch/pull-request-action-example) with an example opened (and merged) [pull request here](https://github.com/vsoch/pull-request-action-example/pull/1) if needed.
If you want to create a pull request to another repository, for example, a pull request to the upstream repository, you need to define PULL_REQUEST_REPOSITORY and PULL_REQUEST_TOKEN. The PULL_REQUEST_TOKEN is one [Personal Access Token(PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token), which can be save in the [encrypted secrets](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
## Outputs ## Outputs
The action sets a few useful output and environment variables. An output can The action sets a few useful output and environment variables. An output can
@@ -100,9 +90,7 @@ The screenshot below shows the example in action to interact with outputs in sev
## Examples ## Examples
Example workflows are provided in [examples](examples), and please contribute any Example workflows are provided in [examples](examples), and please contribute any
examples that you might have to help other users! You can get the same commit hashes examples that you might have to help other users! We will walk through a basic
and commented tags if you use the [action-updater](https://github.com/vsoch/action-updater)
also maintained by @vsoch. We will walk through a basic
example here for a niche case. Let's say that we are opening a pull request on the release event. This would mean example here for a niche case. Let's say that we are opening a pull request on the release event. This would mean
that the payload's branch variable would be null. We would need to define `PULL_REQUEST_FROM`. How would that the payload's branch variable would be null. We would need to define `PULL_REQUEST_FROM`. How would
we do that? We can [set environment variables](https://github.com/actions/toolkit/blob/main/docs/commands.md#environment-files) for next steps. Here is an example: we do that? We can [set environment variables](https://github.com/actions/toolkit/blob/main/docs/commands.md#environment-files) for next steps. Here is an example:
@@ -121,9 +109,9 @@ jobs:
run: | run: |
# do custom parsing of your code / date to derive a branch from # do custom parsing of your code / date to derive a branch from
PR_BRANCH_FROM=release-v$(cat VERSION) PR_BRANCH_FROM=release-v$(cat VERSION)
echo "PULL_REQUEST_FROM_BRANCH=${PR_BRANCH_FROM}" >> $GITHUB_ENV export "PULL_REQUEST_FROM_BRANCH=${PR_BRANCH_FROM}" >> $GITHUB_ENV
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@master uses: vsoch/pull-request-action@1.0.6
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST_BRANCH: "master" PULL_REQUEST_BRANCH: "master"

View File

@@ -2,16 +2,16 @@ name: Pull Request on Branch Push
on: on:
push: push:
branches-ignore: branches-ignore:
- devel - devel
jobs: jobs:
auto-pull-request: auto-pull-request:
name: PullRequestAction name: PullRequestAction
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@d703f40f3af5ae294f9816395ddf2e3d2d3feafa # 1.0.21 uses: vsoch/pull-request-action@1.0.3
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: update/ BRANCH_PREFIX: "update/"
PULL_REQUEST_BRANCH: master PULL_REQUEST_BRANCH: "master"
PULL_REQUEST_ASSIGNEES: vsoch PULL_REQUEST_ASSIGNEES: vsoch

View File

@@ -1,23 +1,24 @@
name: derive-branch-from-environment name: derive-branch-from-environment
on: on:
schedule: - schedule:
cron: 0 0 * * 0 # Weekly
- cron: 0 0 * * 0
jobs: jobs:
DoSomeUpdate: DoSomeUpdate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v3 uses: actions/checkout@v1
- name: Install or Do Something to Change repository - name: Install or Do Something to Change repository
run: | run: |
echo "This is a new file." >> newfile.txt echo "This is a new file." >> newfile.txt
- name: Checkout New Branch - name: Checkout New Branch
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_AGAINST: master BRANCH_AGAINST: "master"
run: | run: |
printf "GitHub Actor: ${GITHUB_ACTOR}\n" printf "GitHub Actor: ${GITHUB_ACTOR}\n"
export BRANCH_FROM="update/newfile-$(date '+%Y-%m-%d')" export BRANCH_FROM="update/newfile-$(date '+%Y-%m-%d')"
@@ -42,7 +43,7 @@ jobs:
echo "PULL_REQUEST_FROM_BRANCH=${BRANCH_FROM}" >> $GITHUB_ENV echo "PULL_REQUEST_FROM_BRANCH=${BRANCH_FROM}" >> $GITHUB_ENV
- name: Open Pull Request - name: Open Pull Request
uses: vsoch/pull-request-action@d703f40f3af5ae294f9816395ddf2e3d2d3feafa # 1.0.21 uses: vsoch/pull-request-action@1.0.6
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST_BRANCH: master PULL_REQUEST_BRANCH: "master"

View File

@@ -2,8 +2,8 @@ name: Hotfix Branch Pull Request
on: on:
push: push:
branches-ignore: branches-ignore:
- master - master
- production - production
# See https://github.com/vsoch/pull-request-action/issues/47#issuecomment-707109132 # See https://github.com/vsoch/pull-request-action/issues/47#issuecomment-707109132
@@ -12,44 +12,44 @@ jobs:
name: PullRequestAction name: PullRequestAction
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Generate branch name - name: Generate branch name
uses: actions/github-script@v6 uses: actions/github-script@v3
id: set-branch-name id: set-branch-name
with: with:
script: | script: |
const capitalize = (name) => name.charAt(0).toUpperCase() + name.slice(1); const capitalize = (name) => name.charAt(0).toUpperCase() + name.slice(1);
const emoji = context.payload.ref.startsWith("refs/heads/feature") const emoji = context.payload.ref.startsWith("refs/heads/feature")
? "✨ " ? "✨ "
: context.payload.ref.startsWith("refs/heads/hotfix") : context.payload.ref.startsWith("refs/heads/hotfix")
? "🚑 " ? "🚑 "
: ""; : "";
return `${emoji}${capitalize( return `${emoji}${capitalize(
context.payload.ref context.payload.ref
.replace("refs/heads/", "") .replace("refs/heads/", "")
.replace(/-/g, " ") .replace(/-/g, " ")
.replace("feature ", "") .replace("feature ", "")
.replace("hotfix ", "") .replace("hotfix ", "")
)}`; )}`;
result-encoding: string result-encoding: string
- name: Set branch name - name: Set branch name
run: echo "PULL_REQUEST_TITLE=${{steps.set-branch-name.outputs.result}}" >> $GITHUB_ENV run: echo "PULL_REQUEST_TITLE=${{steps.set-branch-name.outputs.result}}" >> $GITHUB_ENV
- name: Generate PR body - name: Generate PR body
uses: actions/github-script@v6 uses: actions/github-script@v3
id: set-pr-body id: set-pr-body
with: with:
script: | script: |
return `I'm opening this pull request for this branch, pushed by @${ return `I'm opening this pull request for this branch, pushed by @${
context.payload.head_commit.author.username context.payload.head_commit.author.username
} with ${context.payload.commits.length} commit${ } with ${context.payload.commits.length} commit${
context.payload.commits.length === 1 ? "" : "s" context.payload.commits.length === 1 ? "" : "s"
}.`; }.`;
result-encoding: string result-encoding: string
- name: Set PR body - name: Set PR body
run: echo "PULL_REQUEST_BODY=${{steps.set-pr-body.outputs.result}}" >> $GITHUB_ENV run: echo "PULL_REQUEST_BODY=${{steps.set-pr-body.outputs.result}}" >> $GITHUB_ENV
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@d703f40f3af5ae294f9816395ddf2e3d2d3feafa # 1.0.21 uses: vsoch/pull-request-action@1.0.10
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: hotfix- BRANCH_PREFIX: "hotfix-"
PULL_REQUEST_BRANCH: production PULL_REQUEST_BRANCH: "production"
PULL_REQUEST_REVIEWERS: AnandChowdhary PULL_REQUEST_REVIEWERS: "AnandChowdhary"

View File

@@ -2,27 +2,27 @@ name: Pull Request on Branch Push
on: on:
push: push:
branches-ignore: branches-ignore:
- devel - devel
jobs: jobs:
auto-pull-request: auto-pull-request:
name: PullRequestAction name: PullRequestAction
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
id: pull_request id: pull_request
uses: vsoch/pull-request-action@d703f40f3af5ae294f9816395ddf2e3d2d3feafa # 1.0.21 uses: vsoch/pull-request-action@1.0.6
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: update/ BRANCH_PREFIX: "update/"
PULL_REQUEST_BRANCH: master PULL_REQUEST_BRANCH: "master"
PULL_REQUEST_REVIEWERS: vsoch PULL_REQUEST_REVIEWERS: vsoch
- name: Test outputs - name: Test outputs
env: env:
pull_request_number_output: ${{ steps.pull_request.outputs.pull_request_number }} pull_request_number_output: ${{ steps.pull_request.outputs.pull_request_number }}
pull_request_url_output: ${{ steps.pull_request.outputs.pull_request_url }} pull_request_url_output: ${{ steps.pull_request.outputs.pull_request_url }}
run: | run: |
echo "Pull request number from output: ${pull_request_number_output}" echo "Pull request number from output: ${pull_request_number_output}"
echo "Pull request url from output: ${pull_request_url_output}" echo "Pull request url from output: ${pull_request_url_output}"
echo "Pull request number from environment: ${PULL_REQUEST_NUMBER}" echo "Pull request number from environment: ${PULL_REQUEST_NUMBER}"
echo "Pull request url from environment: ${PULL_REQUEST_URL}" echo "Pull request url from environment: ${PULL_REQUEST_URL}"
echo "Another way to specify from output ${{ steps.pull_request.outputs.pull_request_number }}" echo "Another way to specify from output ${{ steps.pull_request.outputs.pull_request_number }}"

View File

@@ -2,17 +2,17 @@ name: Pull Request on Branch Push
on: on:
push: push:
branches-ignore: branches-ignore:
- staging - staging
- launchpad - launchpad
- production - production
jobs: jobs:
auto-pull-request: auto-pull-request:
name: PullRequestAction name: PullRequestAction
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@d703f40f3af5ae294f9816395ddf2e3d2d3feafa # 1.0.21 uses: vsoch/pull-request-action@add/support-null-branch
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: update/ BRANCH_PREFIX: "update/"
PULL_REQUEST_BRANCH: master PULL_REQUEST_BRANCH: "master"

View File

@@ -1,7 +1,7 @@
on: on:
release: release:
types: types:
- published - published
jobs: jobs:
persist-new-suite-yml: persist-new-suite-yml:
@@ -18,27 +18,31 @@ jobs:
version=$(echo "$tag_name" | sed 's/^v//') version=$(echo "$tag_name" | sed 's/^v//')
echo "Version: $version" echo "Version: $version"
echo "suite_version=${version}" >> $GITHUB_OUTPUT echo "::set-output name=suite_version::${version}"
echo "suite_update_branch=suite_${version}" >> $GITHUB_OUTPUT echo "::set-output name=suite_update_branch::suite_${version}"
id: data id: data
- name: Permanently save the new suite release - name: Permanently save the new suite release
run: | run: |
mkdir -p releases mkdir -p releases
new_suite_version_yml="releases/suite_${{ steps.data.outputs.suite_version }}.yml" new_suite_version_yml="releases/suite_${{ steps.data.outputs.suite_version }}.yml"
echo "Suite target file: $new_suite_version_yml" echo "Suite target file: $new_suite_version_yml"
cp suite.yml "${new_suite_version_yml}" cp suite.yml "${new_suite_version_yml}"
git add "${new_suite_version_yml}" git add "${new_suite_version_yml}"
git commit -m "Suite v${{ steps.data.outputs.suite_version }} auto-commit of new release files" git commit -m "Suite v${{ steps.data.outputs.suite_version }} auto-commit of new release files"
- name: Push files - name: Push files
run: git push --force "https://${{ github.actor }}:${{secrets.GITHUB_TOKEN}}@github.com/${{ github.repository }}.git" "HEAD:${{ steps.data.outputs.suite_update_branch }}" run: |
git push --force "https://${{ github.actor }}:${{secrets.GITHUB_TOKEN}}@github.com/${{ github.repository }}.git" "HEAD:${{ steps.data.outputs.suite_update_branch }}"
- name: Open a PR to the default branch - name: Open a PR to the default branch
uses: vsoch/pull-request-action@d703f40f3af5ae294f9816395ddf2e3d2d3feafa # 1.0.21 uses: vsoch/pull-request-action@1.0.2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST_FROM_BRANCH: ${{ steps.data.outputs.suite_update_branch }} PULL_REQUEST_FROM_BRANCH: "${{ steps.data.outputs.suite_update_branch }}"
PULL_REQUEST_BRANCH: master PULL_REQUEST_BRANCH: master
PULL_REQUEST_TITLE: 'Action: Update suite release file for v${{ steps.data.outputs.suite_version }}' PULL_REQUEST_TITLE: "Action: Update suite release file for v${{ steps.data.outputs.suite_version }}"
PULL_REQUEST_BODY: Auto-generated PR! PULL_REQUEST_BODY: "Auto-generated PR!"

View File

@@ -2,16 +2,16 @@ name: Pull Request on Branch Push
on: on:
push: push:
branches-ignore: branches-ignore:
- devel - devel
jobs: jobs:
auto-pull-request: auto-pull-request:
name: PullRequestAction name: PullRequestAction
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@d703f40f3af5ae294f9816395ddf2e3d2d3feafa # 1.0.21 uses: vsoch/pull-request-action@1.0.4
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: update/ BRANCH_PREFIX: "update/"
PULL_REQUEST_BRANCH: master PULL_REQUEST_BRANCH: "master"
PULL_REQUEST_REVIEWERS: vsoch PULL_REQUEST_REVIEWERS: vsoch

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -11,13 +11,6 @@ import requests
def get_envar(name): def get_envar(name):
"""
Given a name, return the corresponding environment variable. Exit if not
defined, as using this function indicates the envar is required.
Parameters:
name (str): the name of the environment variable
"""
value = os.environ.get(name) value = os.environ.get(name)
if not value: if not value:
sys.exit("%s is required for vsoch/pull-request-action" % name) sys.exit("%s is required for vsoch/pull-request-action" % name)
@@ -25,9 +18,6 @@ def get_envar(name):
def check_events_json(): def check_events_json():
"""the github events json is required in order to indicate that we are
in an action environment.
"""
events = get_envar("GITHUB_EVENT_PATH") events = get_envar("GITHUB_EVENT_PATH")
if not os.path.exists(events): if not os.path.exists(events):
sys.exit("Cannot find Github events file at ${GITHUB_EVENT_PATH}") sys.exit("Cannot find Github events file at ${GITHUB_EVENT_PATH}")
@@ -36,13 +26,7 @@ def check_events_json():
def abort_if_fail(response, reason): def abort_if_fail(response, reason):
"""If PASS_ON_ERROR, don't exit. Otherwise exit with an error and print """If PASS_ON_ERROR, don't exit. Otherwise exit with an error and print the reason"""
the reason.
Parameters:
response (requests.Response) : an unparsed response from requests
reason (str) : a message to print to the user for fail.
"""
message = "%s: %s: %s\n %s" % ( message = "%s: %s: %s\n %s" % (
reason, reason,
response.status_code, response.status_code,
@@ -57,11 +41,6 @@ def abort_if_fail(response, reason):
def parse_into_list(values): def parse_into_list(values):
"""A list of reviewers or assignees to parse from a string to a list
Parameters:
values (str) : a list of space separated, quoted values to parse to a list
"""
if values: if values:
values = values.replace('"', "").replace("'", "") values = values.replace('"', "").replace("'", "")
if not values: if not values:
@@ -70,211 +49,11 @@ def parse_into_list(values):
def set_env(name, value): def set_env(name, value):
"""helper function to echo a key/value pair to the environement file """helper function to echo a key/value pair to the environement file"""
Parameters:
name (str) : the name of the environment variable
value (str) : the value to write to file
"""
environment_file_path = os.environ.get("GITHUB_ENV") environment_file_path = os.environ.get("GITHUB_ENV")
with open(environment_file_path, "a") as environment_file: with open(environment_file_path, "a") as environment_file:
environment_file.write("%s=%s\n" % (name, value)) environment_file.write("%s=%s" % (name, value))
def open_pull_request(title, body, target, source, is_draft=False, can_modify=True):
"""Open pull request opens a pull request with a given body and content,
and sets output variables. An unparsed response is returned.
Parameters:
title (str) : the title to set for the new pull request
body (str) : the body to set for the new pull request
target (str) : the target branch
source (str) : the source branch
is_draft (bool) : indicate the pull request is a draft
can_modify (bool) : indicate the maintainer can modify
"""
print("No pull request from %s to %s is open, continuing!" % (source, target))
# Post the pull request
data = {
"title": title,
"body": body,
"base": target,
"head": source,
"draft": is_draft,
"maintainer_can_modify": can_modify,
}
print("Data for opening pull request: %s" % data)
response = requests.post(PULLS_URL, json=data, headers=HEADERS)
if response.status_code != 201:
print(f"pull request url is {PULLS_URL}")
abort_if_fail(response, "Unable to create pull request")
return response
def update_pull_request(entry, title, body, target, state=None):
"""Given an existing pull request, update it.
Parameters:
entry (dict) : the pull request metadata
title (str) : the title to set for the new pull request
body (str) : the body to set for the new pull request
target (str) : the target branch
state (bool) : the state of the PR (open, closed)
"""
print("PULL_REQUEST_UPDATE is set, updating existing pull request.")
data = {
"title": title,
"body": body,
"base": target,
"state": state or "open",
}
# PATCH /repos/{owner}/{repo}/pulls/{pull_number}
url = "%s/%s" % (PULLS_URL, entry.get("number"))
print("Data for updating pull request: %s" % data)
response = requests.patch(url, json=data, headers=HEADERS)
if response.status_code != 200:
abort_if_fail(response, "Unable to create pull request")
return response
def set_pull_request_groups(response):
"""Given a response for an open or updated PR, set metadata
Parameters:
response (requests.Response) : a requests response, unparsed
"""
# Expected return codes are 0 for success
pull_request_return_code = (
0 if response.status_code == 201 else response.status_code
)
response = response.json()
print("::group::github response")
print(response)
print("::endgroup::github response")
number = response.get("number")
html_url = response.get("html_url")
print("Number opened for PR is %s" % number)
set_env("PULL_REQUEST_NUMBER", number)
print("pull_request_number=%s >> $GITHUB_OUTPUT" % number)
set_env("PULL_REQUEST_RETURN_CODE", pull_request_return_code)
print("pull_request_return_code=%s >> $GITHUB_OUTPUT" % pull_request_return_code)
set_env("PULL_REQUEST_URL", html_url)
print("pull_request_url=%s >> $GITHUB_OUTPUT" % html_url)
def list_pull_requests(target, source):
"""Given a target and source, return a list of pull requests that match
(or simply exit given some kind of error code)
Parameters:
target (str) : the target branch
source (str) : the source branch
"""
# Check if the branch already has a pull request open
params = {"base": target, "head": source, "state": "open"}
print("Params for checking if pull request exists: %s" % params)
response = requests.get(PULLS_URL, params=params)
# Case 1: 401, 404 might warrant needing a token
if response.status_code in [401, 404]:
response = requests.get(PULLS_URL, params=params, headers=HEADERS)
if response.status_code != 200:
abort_if_fail(response, "Unable to retrieve information about pull requests")
return response.json()
def add_assignees(entry, assignees):
"""Given a pull request metadata (from create or update) add assignees
Parameters:
entry (dict) : the pull request metadata
assignees (str) : comma separated assignees string set by action
"""
# Remove leading and trailing quotes
assignees = parse_into_list(assignees)
number = entry.get("number")
print(
"Attempting to assign %s to pull request with number %s" % (assignees, number)
)
# POST /repos/:owner/:repo/issues/:issue_number/assignees
data = {"assignees": assignees}
ASSIGNEES_URL = "%s/%s/assignees" % (ISSUE_URL, number)
response = requests.post(ASSIGNEES_URL, json=data, headers=HEADERS)
if response.status_code != 201:
abort_if_fail(response, "Unable to create assignees")
assignees_return_code = 0 if response.status_code == 201 else response.status_code
print("::group::github assignees response")
print(response.json())
print("::endgroup::github assignees response")
set_env("ASSIGNEES_RETURN_CODE", assignees_return_code)
print("assignees_return_code=%s >> $GITHUB_OUTPUT" % assignees_return_code)
def find_pull_request(listing, source):
"""Given a listing and a source, find a pull request based on the source
(the branch name).
Parameters:
listing (list) : the list of PR objects (dict) to parse over
source (str) : the source (head) branch to look for
"""
if listing:
for entry in listing:
if entry.get("head", {}).get("ref", "") == source:
print("Pull request from %s is already open!" % source)
return entry
def find_default_branch():
"""Find default branch for a repo (only called if branch not provided)"""
response = requests.get(REPO_URL)
# Case 1: 401, 404 might need a token
if response.status_code in [401, 404]:
response = requests.get(REPO_URL, headers=HEADERS)
if response.status_code != 200:
abort_if_fail(response, "Unable to retrieve default branch")
default_branch = response.json()["default_branch"]
print("Found default branch: %s" % default_branch)
return default_branch
def add_reviewers(entry, reviewers, team_reviewers):
"""Given regular or team reviewers, add them to a PR.
Parameters:
entry (dict) : the pull request metadata
"""
print("Found reviewers: %s and team reviewers: %s" % (reviewers, team_reviewers))
team_reviewers = parse_into_list(team_reviewers)
reviewers = parse_into_list(reviewers)
print("Parsed reviewers: %s and team reviewers: %s" % (reviewers, team_reviewers))
# POST /repos/:owner/:repo/pulls/:pull_number/requested_reviewers
REVIEWERS_URL = "%s/%s/requested_reviewers" % (PULLS_URL, entry.get("number"))
data = {"reviewers": reviewers, "team_reviewers": team_reviewers}
response = requests.post(REVIEWERS_URL, json=data, headers=HEADERS)
if response.status_code != 201:
abort_if_fail(response, "Unable to assign reviewers")
reviewers_return_code = 0 if response.status_code == 201 else response.status_code
print("::group::github reviewers response")
print(response.json())
print("::endgroup::github reviewers response")
set_env("REVIEWERS_RETURN_CODE", reviewers_return_code)
print("reviewers_return_code=%s >> $GITHUB_OUTPUT" % reviewers_return_code)
print("Add reviewers return code: %s" % reviewers_return_code)
################################################################################ ################################################################################
@@ -282,21 +61,16 @@ def add_reviewers(entry, reviewers, team_reviewers):
################################################################################ ################################################################################
API_VERSION = "v3" API_VERSION = "v3"
BASE = "https://api.github.com"
# Allow for a GitHub enterprise URL
BASE = os.environ.get("GITHUB_API_URL") or "https://api.github.com"
PR_TOKEN = os.environ.get("PULL_REQUEST_TOKEN") or get_envar("GITHUB_TOKEN")
PR_REPO = os.environ.get("PULL_REQUEST_REPOSITORY") or get_envar("GITHUB_REPOSITORY")
HEADERS = { HEADERS = {
"Authorization": "token %s" % PR_TOKEN, "Authorization": "token %s" % get_envar("GITHUB_TOKEN"),
"Accept": "application/vnd.github.%s+json;application/vnd.github.antiope-preview+json;application/vnd.github.shadow-cat-preview+json" "Accept": "application/vnd.github.%s+json;application/vnd.github.antiope-preview+json;application/vnd.github.shadow-cat-preview+json"
% API_VERSION, % API_VERSION,
} }
# URLs # URLs
REPO_URL = "%s/repos/%s" % (BASE, PR_REPO) REPO_URL = "%s/repos/%s" % (BASE, get_envar("GITHUB_REPOSITORY"))
ISSUE_URL = "%s/issues" % REPO_URL ISSUE_URL = "%s/issues" % REPO_URL
PULLS_URL = "%s/pulls" % REPO_URL PULLS_URL = "%s/pulls" % REPO_URL
@@ -311,44 +85,131 @@ def create_pull_request(
team_reviewers, team_reviewers,
is_draft=False, is_draft=False,
can_modify=True, can_modify=True,
state="open",
): ):
"""Create pull request is the base function that determines if the PR exists,
and then updates or creates it depending on user preferences.
"""
listing = list_pull_requests(target, source)
# Determine if the pull request is already open # Check if the branch already has a pull request open
entry = find_pull_request(listing, source) params = {"base": target, "head": source, "state": "open"}
response = None data = {"base": target, "head": source, "body": body}
print("Params for checking if pull request exists: %s" % params)
response = requests.get(PULLS_URL, params=params)
# Case 1: we found the PR, the user wants to pass # Case 1: 404 might warrant needing a token
if entry and os.environ.get("PASS_IF_EXISTS"): if response.status_code == 404:
print("PASS_IF_EXISTS is set, exiting with success status.") response = requests.get(PULLS_URL, params=params, headers=HEADERS)
sys.exit(0) if response.status_code != 200:
abort_if_fail(response, "Unable to retrieve information about pull requests")
# Does the user want to update the existing PR? response = response.json()
if entry and os.environ.get("PULL_REQUEST_UPDATE"):
response = update_pull_request(entry, title, body, target, state)
set_pull_request_groups(response)
# If it's not open, we open a new pull request # Option 1: The pull request is already open
elif not entry: is_open = False
response = open_pull_request(title, body, target, source, is_draft, can_modify) if response:
set_pull_request_groups(response) for entry in response:
if entry.get("head", {}).get("ref", "") == source:
print("Pull request from %s to %s is already open!" % (source, target))
is_open = True
# If we have a response, parse into json (no longer need retvals) # Does the user want to pass if the pull request exists?
response = response.json() if response else None if os.environ.get("PASS_IF_EXISTS"):
print("PASS_IF_EXISTS is set, exiting with success status.")
sys.exit(0)
break
# If we have opened or updated, we can add assignees # Option 2: Open a new pull request
if response and assignees: if not is_open:
add_assignees(response, assignees) print("No pull request from %s to %s is open, continuing!" % (source, target))
if response and (reviewers or team_reviewers):
add_reviewers(response, reviewers, team_reviewers) # Post the pull request
data = {
"title": title,
"body": body,
"base": target,
"head": source,
"draft": is_draft,
"maintainer_can_modify": can_modify,
}
print("Data for opening pull request: %s" % data)
response = requests.post(PULLS_URL, json=data, headers=HEADERS)
if response.status_code != 201:
abort_if_fail(response, "Unable to create pull request")
# Expected return codes are 0 for success
pull_request_return_code = (
0 if response.status_code == 201 else response.status_code
)
response = response.json()
print("::group::github response")
print(response)
print("::endgroup::github response")
number = response.get("number")
html_url = response.get("html_url")
print("Number opened for PR is %s" % number)
set_env("PULL_REQUEST_NUMBER", number)
print("::set-output name=pull_request_number::%s" % number)
set_env("PULL_REQUEST_RETURN_CODE", pull_request_return_code)
print(
"::set-output name=pull_request_return_code::%s" % pull_request_return_code
)
set_env("PULL_REQUEST_URL", html_url)
print("::set-output name=pull_request_url::%s" % html_url)
if assignees:
# Remove leading and trailing quotes
assignees = parse_into_list(assignees)
print(
"Attempting to assign %s to pull request with number %s"
% (assignees, number)
)
# POST /repos/:owner/:repo/issues/:issue_number/assignees
data = {"assignees": assignees}
ASSIGNEES_URL = "%s/%s/assignees" % (ISSUE_URL, number)
response = requests.post(ASSIGNEES_URL, json=data, headers=HEADERS)
if response.status_code != 201:
abort_if_fail(response, "Unable to create assignees")
assignees_return_code = (
0 if response.status_code == 201 else response.status_code
)
set_env("ASSIGNEES_RETURN_CODE", assignees_return_code)
print("::set-output name=assignees_return_code::%s" % assignees_return_code)
if reviewers or team_reviewers:
print(
"Found reviewers: %s and team reviewers: %s"
% (reviewers, team_reviewers)
)
team_reviewers = parse_into_list(team_reviewers)
reviewers = parse_into_list(reviewers)
print(
"Parsed reviewers: %s and team reviewers: %s"
% (reviewers, team_reviewers)
)
# POST /repos/:owner/:repo/pulls/:pull_number/requested_reviewers
REVIEWERS_URL = "%s/%s/requested_reviewers" % (PULLS_URL, number)
data = {"reviewers": reviewers, "team_reviewers": team_reviewers}
response = requests.post(REVIEWERS_URL, json=data, headers=HEADERS)
if response.status_code != 201:
abort_if_fail(response, "Unable to assign reviewers")
reviewers_return_code = (
0 if response.status_code == 201 else response.status_code
)
response = response.json()
print("::group::github reviewers response")
print(response)
print("::endgroup::github reviewers response")
set_env("REVIEWERS_RETURN_CODE", reviewers_return_code)
print("::set-output name=reviewers_return_code::%s" % reviewers_return_code)
print("Add reviewers return code: %s" % reviewers_return_code)
def main(): def main():
"""main primarily parses environment variables to prepare for creation"""
# path to file that contains the POST response of the event # path to file that contains the POST response of the event
# Example: https://github.com/actions/bin/tree/master/debug # Example: https://github.com/actions/bin/tree/master/debug
@@ -360,8 +221,8 @@ def main():
if not branch_prefix: if not branch_prefix:
print("No branch prefix is set, all branches will be used.") print("No branch prefix is set, all branches will be used.")
# Default to project default branch if none provided # Default to master to support older, will eventually change to main
pull_request_branch = os.environ.get("PULL_REQUEST_BRANCH", find_default_branch()) pull_request_branch = os.environ.get("PULL_REQUEST_BRANCH", "master")
print("Pull requests will go to %s" % pull_request_branch) print("Pull requests will go to %s" % pull_request_branch)
# Pull request draft # Pull request draft
@@ -370,22 +231,21 @@ def main():
print("No explicit preference for draft PR: created PRs will be normal PRs.") print("No explicit preference for draft PR: created PRs will be normal PRs.")
pull_request_draft = False pull_request_draft = False
else: else:
print("PULL_REQUEST_DRAFT set to a value: created PRs will be draft PRs.") print(
"Environment variable PULL_REQUEST_DRAFT set to a value: created PRs will be draft PRs."
)
pull_request_draft = True pull_request_draft = True
# If an update is true, we can change the state
pull_request_state = os.environ.get("PULL_REQUEST_STATE", "open")
if pull_request_state not in ["open", "closed"]:
sys.exit("State is required to be one of 'open' or 'closed'")
# Maintainer can modify, defaults to CAN, unless user sets MAINTAINER_CANT_MODIFY # Maintainer can modify, defaults to CAN, unless user sets MAINTAINER_CANT_MODIFY
maintainer_can_modify = os.environ.get("MAINTAINER_CANT_MODIFY") maintainer_can_modify = os.environ.get("MAINTAINER_CANT_MODIFY")
if not maintainer_can_modify: if not maintainer_can_modify:
print("No preference for maintainer being able to modify: default is true.") print(
"No explicit preference for maintainer being able to modify: default is true."
)
maintainer_can_modify = True maintainer_can_modify = True
else: else:
print( print(
"MAINTAINER_CANT_MODIFY set to a value: maintainer will not be able to modify." "Environment variable MAINTAINER_CANT_MODIFY set to a value: maintainer will not be able to modify."
) )
maintainer_can_modify = False maintainer_can_modify = False
@@ -415,7 +275,7 @@ def main():
if not from_branch: if not from_branch:
print("PULL_REQUEST_FROM_BRANCH is not set, checking branch in payload.") print("PULL_REQUEST_FROM_BRANCH is not set, checking branch in payload.")
with open(check_events_json(), "r") as fd: with open(check_events_json(), "r") as fd:
from_branch = json.loads(fd.read()).get("ref", "") from_branch = json.loads(fd.read()).get("ref")
from_branch = from_branch.replace("refs/heads/", "").strip("/") from_branch = from_branch.replace("refs/heads/", "").strip("/")
else: else:
print("PULL_REQUEST_FROM_BRANCH is set.") print("PULL_REQUEST_FROM_BRANCH is set.")
@@ -425,48 +285,49 @@ def main():
print("Found branch %s to open PR from" % from_branch) print("Found branch %s to open PR from" % from_branch)
else: else:
sys.exit( sys.exit(
"You are required to define PULL_REQUEST_FROM_BRANCH in the environment." "No branch in payload, you are required to define PULL_REQUEST_FROM_BRANCH in the environment."
) )
# If it's to the target branch, ignore it # If it's to the target branch, ignore it
if from_branch == pull_request_branch: if from_branch == pull_request_branch:
print("Target and current branch are identical (%s), skipping." % from_branch) print("Target and current branch are identical (%s), skipping." % from_branch)
sys.exit(0) else:
# If the prefix for the branch matches # If the prefix for the branch matches
if not branch_prefix or from_branch.startswith(branch_prefix): if not branch_prefix or from_branch.startswith(branch_prefix):
# Pull request body (optional) # Pull request body (optional)
pull_request_body = os.environ.get( pull_request_body = os.environ.get(
"PULL_REQUEST_BODY", "PULL_REQUEST_BODY",
"This is an automated pull request to update from branch %s" % from_branch, "This is an automated pull request to update from branch %s"
) % from_branch,
)
print("::group::pull request body") print("::group::pull request body")
print(pull_request_body) print(pull_request_body)
print("::endgroup::pull request body") print("::endgroup::pull request body")
# Pull request title (optional) # Pull request title (optional)
pull_request_title = os.environ.get( pull_request_title = os.environ.get(
"PULL_REQUEST_TITLE", "Update from %s" % from_branch "PULL_REQUEST_TITLE", "Update from %s" % from_branch
) )
print("::group::pull request title") print("::group::pull request title")
print(pull_request_title) print(pull_request_title)
print("::endgroup::pull request title") print("::endgroup::pull request title")
# Create the pull request # Create the pull request
create_pull_request( create_pull_request(
target=pull_request_branch, target=pull_request_branch,
source=from_branch, source=from_branch,
body=pull_request_body, body=pull_request_body,
title=pull_request_title, title=pull_request_title,
is_draft=pull_request_draft, is_draft=pull_request_draft,
can_modify=maintainer_can_modify, can_modify=maintainer_can_modify,
assignees=assignees, assignees=assignees,
reviewers=reviewers, reviewers=reviewers,
team_reviewers=team_reviewers, team_reviewers=team_reviewers,
state=pull_request_state, )
)
if __name__ == "__main__": if __name__ == "__main__":