18 Commits

Author SHA1 Message Date
Yunqi(Alice)
d703f40f3a Update the list_pull_request 401 case (#89)
* Update the list_pull_request 401 case
Hi, I find this http request is also missing the 401 case.
* Update CHANGELOG.md
2022-09-28 10:03:05 -06:00
Vanessasaurus
415544869c Merge pull request #88 from ohenak/fix/ghe-get-401-error
[Chore] Provide token when getting 401 error
2022-09-22 03:59:00 -06:00
Kane Ho
667293445b [Chore] Provide token when getting 401 error
Issue #87

In GHE, token is necessary when querying the repo API,
When it fails, it returns 401 error. Will need to specify the token in
this case.
2022-09-21 23:44:03 -07:00
Felix Breidenstein
e64deeba68 Fix example in README (#85) 2022-03-18 08:44:13 -06:00
Vanessasaurus
58a078aed9 Update CHANGELOG.md 2022-02-03 06:00:46 -07:00
Sviatoslav Sydorenko
408336c607 Fix emitting LF-separated GHA environment vars (#84)
Fixes #83 (hopefully)
2022-02-03 05:59:59 -07:00
Vanessasaurus
d99aa3ed71 Adding quick option to be able to specify a GitHub enterprise url (#80)
* adding quick option to be able to specify a GitHub enterprise url
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
* Use GITHUB_API_URL instead
Co-authored-by: Tobias <github@tobiasgabriel.de>

* Update README.md
* Update pull-request.py

Co-authored-by: vsoch <vsoch@users.noreply.github.com>
Co-authored-by: Tobias <github@tobiasgabriel.de>
2021-10-02 22:40:48 -06:00
Vanessasaurus
a48b660567 Merge pull request #81 from vsoch/add/open-source-halloween-2021
adding open source halloween!
2021-10-02 18:26:20 -06:00
vsoch
4b09070b0a adding open source halloween!
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
2021-10-02 18:25:26 -06:00
Vanessasaurus
29dbfc0acd Merge pull request #75 from Kong/feat/discover-default-branch
Use repo's default branch if no branch specified
2021-06-21 10:10:40 -06:00
Danny Sauer
c0bf76830b Use repo's default branch if no branch specified 2021-06-21 11:04:52 -05:00
Yi Zhang
1cb1956a68 Support to open pull request to another repo (#70)
* support custom token and repo
* print post url if there's error
* update as comments
* Update README.md

Co-authored-by: Vanessasaurus <814322+vsoch@users.noreply.github.com>
2021-04-27 06:04:20 -06:00
vsoch
2ca266185d updating changelog preparing for release
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
2021-04-18 19:54:26 -06:00
Max Anderson
e2a437cdbd Prevent unhandled exception when running via schedule (#69)
* Prevent unhandled exception when running via schedule
2021-04-18 19:53:12 -06:00
Vanessasaurus
2d5c80f215 dont use version for examples, stick to master (#67)
* dont use version for examples, stick to master
* add note about it being important to use stable release in README.md

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
2021-03-27 19:08:35 -06:00
Vanessasaurus
7f0d383d15 adding back assignees variable (#65)
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
2021-02-15 10:51:43 -07:00
Vanessasaurus
6d137cdd42 adding changes to update a PR (#60)
* adding changes to update a pr
including a second envar to set a state, along with refactor
of main body to be multiple functions, and adding docstrings
* fixing missing variable
* Try removing maintainer can modify
* Update pull-request.py

Signed-off-by: vsoch <vsoch@users.noreply.github.com>
2021-02-12 17:42:23 -07:00
Vanessasaurus
ebce8e4918 making abort_if_fail more verbose (#57)
Signed-off-by: vsoch <vsochat@stanford.edu>
2020-12-18 04:05:17 -07:00
11 changed files with 339 additions and 203 deletions

View File

@@ -14,6 +14,10 @@ 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,13 +22,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@1.0.6 uses: vsoch/pull-request-action@master
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.
@@ -36,6 +39,8 @@ 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 | |
@@ -45,8 +50,10 @@ 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 | unset | | PASS_ON_ERROR | Instead of failing on an error response, pass | false | unset |
| PASS_IF_EXISTS | Instead of failing if the pull request already exists, pass | unset | | PASS_IF_EXISTS | Instead of failing if the pull request already exists, pass | false | 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
@@ -56,6 +63,7 @@ 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
@@ -65,6 +73,8 @@ 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
@@ -109,9 +119,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)
export "PULL_REQUEST_FROM_BRANCH=${PR_BRANCH_FROM}" >> $GITHUB_ENV echo "PULL_REQUEST_FROM_BRANCH=${PR_BRANCH_FROM}" >> $GITHUB_ENV
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@1.0.6 uses: vsoch/pull-request-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST_BRANCH: "master" PULL_REQUEST_BRANCH: "master"

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@1.0.3 uses: vsoch/pull-request-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: "update/" BRANCH_PREFIX: "update/"

View File

@@ -43,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@1.0.6 uses: vsoch/pull-request-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PULL_REQUEST_BRANCH: "master" PULL_REQUEST_BRANCH: "master"

View File

@@ -47,7 +47,7 @@ jobs:
- 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@1.0.10 uses: vsoch/pull-request-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: "hotfix-" BRANCH_PREFIX: "hotfix-"

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
- name: pull-request-action - name: pull-request-action
id: pull_request id: pull_request
uses: vsoch/pull-request-action@1.0.6 uses: vsoch/pull-request-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: "update/" BRANCH_PREFIX: "update/"

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@add/support-null-branch uses: vsoch/pull-request-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: "update/" BRANCH_PREFIX: "update/"

View File

@@ -39,7 +39,7 @@ jobs:
git push --force "https://${{ github.actor }}:${{secrets.GITHUB_TOKEN}}@github.com/${{ github.repository }}.git" "HEAD:${{ steps.data.outputs.suite_update_branch }}" 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@1.0.2 uses: vsoch/pull-request-action@master
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 }}"

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull-request-action - name: pull-request-action
uses: vsoch/pull-request-action@1.0.4 uses: vsoch/pull-request-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_PREFIX: "update/" BRANCH_PREFIX: "update/"

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -11,6 +11,13 @@ 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)
@@ -18,6 +25,9 @@ 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}")
@@ -25,15 +35,33 @@ def check_events_json():
return events return events
def abort_if_fail(reason): def abort_if_fail(response, reason):
"""If PASS_ON_ERROR, don't exit. Otherwise exit with an error and print the reason""" """If PASS_ON_ERROR, don't exit. Otherwise exit with an error and print
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" % (
reason,
response.status_code,
response.reason,
response.json(),
)
if os.environ.get("PASS_ON_ERROR"): if os.environ.get("PASS_ON_ERROR"):
print("Error, but PASS_ON_ERROR is set, continuing: %s" % reason) print("Error, but PASS_ON_ERROR is set, continuing: %s" % message)
else: else:
sys.exit(reason) sys.exit(message)
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:
@@ -42,11 +70,211 @@ 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" % (name, value)) environment_file.write("%s=%s\n" % (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("::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)
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("::set-output name=assignees_return_code::%s" % 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("::set-output name=reviewers_return_code::%s" % reviewers_return_code)
print("Add reviewers return code: %s" % reviewers_return_code)
################################################################################ ################################################################################
@@ -54,16 +282,21 @@ def set_env(name, value):
################################################################################ ################################################################################
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" % get_envar("GITHUB_TOKEN"), "Authorization": "token %s" % PR_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, get_envar("GITHUB_REPOSITORY")) REPO_URL = "%s/repos/%s" % (BASE, PR_REPO)
ISSUE_URL = "%s/issues" % REPO_URL ISSUE_URL = "%s/issues" % REPO_URL
PULLS_URL = "%s/pulls" % REPO_URL PULLS_URL = "%s/pulls" % REPO_URL
@@ -78,155 +311,44 @@ 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)
# Check if the branch already has a pull request open # Determine if the pull request is already open
params = {"base": target, "head": source, "state": "open"} entry = find_pull_request(listing, source)
data = {"base": target, "head": source, "body": body} response = None
print("Params for checking if pull request exists: %s" % params)
response = requests.get(PULLS_URL, params=params)
# Case 1: 404 might warrant needing a token # Case 1: we found the PR, the user wants to pass
if response.status_code == 404: if entry and os.environ.get("PASS_IF_EXISTS"):
response = requests.get(PULLS_URL, params=params, headers=HEADERS) print("PASS_IF_EXISTS is set, exiting with success status.")
if response.status_code != 200: sys.exit(0)
abort_if_fail(
"Unable to retrieve information about pull requests: %s: %s"
% (response.status_code, response.reason)
)
response = response.json() # Does the user want to update the existing PR?
if entry and os.environ.get("PULL_REQUEST_UPDATE"):
response = update_pull_request(entry, title, body, target, state)
set_pull_request_groups(response)
# Option 1: The pull request is already open # If it's not open, we open a new pull request
is_open = False elif not entry:
if response: response = open_pull_request(title, body, target, source, is_draft, can_modify)
for entry in response: set_pull_request_groups(response)
if entry.get("head", {}).get("ref", "") == source:
print("Pull request from %s to %s is already open!" % (source, target))
is_open = True
# Does the user want to pass if the pull request exists? # If we have a response, parse into json (no longer need retvals)
if os.environ.get("PASS_IF_EXISTS"): response = response.json() if response else None
print("PASS_IF_EXISTS is set, exiting with success status.")
sys.exit(0)
break
# Option 2: Open a new pull request # If we have opened or updated, we can add assignees
if not is_open: if response and assignees:
print("No pull request from %s to %s is open, continuing!" % (source, target)) add_assignees(response, assignees)
if response and (reviewers or team_reviewers):
# Post the pull request add_reviewers(response, reviewers, team_reviewers)
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(
"Unable to create pull request: %s: %s, %s"
% (
response.status_code,
response.reason,
response.json().get("message", ""),
)
)
# 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(
"Unable to create assignees: %s: %s, %s"
% (
response.status_code,
response.reason,
response.json().get("message", ""),
)
)
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(
"Unable to assign reviewers: %s: %s, %s"
% (
response.status_code,
response.reason,
response.json().get("message", ""),
)
)
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
@@ -238,8 +360,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 master to support older, will eventually change to main # Default to project default branch if none provided
pull_request_branch = os.environ.get("PULL_REQUEST_BRANCH", "master") pull_request_branch = os.environ.get("PULL_REQUEST_BRANCH", find_default_branch())
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
@@ -248,21 +370,22 @@ 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( print("PULL_REQUEST_DRAFT set to a value: created PRs will be draft PRs.")
"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( print("No preference for maintainer being able to modify: default is true.")
"No explicit preference for maintainer being able to modify: default is true."
)
maintainer_can_modify = True maintainer_can_modify = True
else: else:
print( print(
"Environment variable MAINTAINER_CANT_MODIFY set to a value: maintainer will not be able to modify." "MAINTAINER_CANT_MODIFY set to a value: maintainer will not be able to modify."
) )
maintainer_can_modify = False maintainer_can_modify = False
@@ -292,7 +415,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.")
@@ -302,49 +425,48 @@ 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(
"No branch in payload, you are required to define PULL_REQUEST_FROM_BRANCH in the environment." "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)
else: sys.exit(0)
# 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" "This is an automated pull request to update from branch %s" % from_branch,
% 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__":