Skip to content

Commit b657021

Browse files
committed
Implement GITHUB_TOKEN interoperability; accept tokens as inputs instead of env variables
1 parent f336494 commit b657021

File tree

6 files changed

+113
-69
lines changed

6 files changed

+113
-69
lines changed

README.md

+73-49
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66

77
Each of these actions are discrete, and can be enabled separately if desired.
88

9+
### `assign`: Automatically adjust PR assignees based on who is most responsible for pushing it forward
10+
11+
The heart and soul of the toolset: this action will automatically adjust assignees on a pull request such that the current assignees are the ones that must take action to move the pull request forward. Here's an example timeline of actions for a PR submitted by author X:
12+
13+
- PR from author X arrives. Any requested reviewers (let's say reviewers A, B, and C) are assigned.
14+
- Reviewer A leaves a review requesting changes. Reviewers A, B, and C are unassigned, and author X is assigned to address A's review.
15+
- X makes changes to the PR and re-requests review from reviewer A. Since there are no outstanding Changes Requested reviews, all requested reviewers that haven't yet approved (A, B, C) are put back on the PR.
16+
- Reviewer B approves the PR, and is unassigned. Reviewers A and C are still assigned.
17+
- Reviewer C approves the PR, and is unassigned. Reviewer A is still assigned.
18+
- etc...
19+
20+
In effect, this turns Github's Assignee field from a vague "point person" to a clear indicator of responsibility.
21+
922
### `request`: Automatically request reviewers on new pull requests
1023

1124
When a new PR arrives, this action will request a (configurable) number of reviewers, pulling from a mixture of:
@@ -20,20 +33,7 @@ Inputs:
2033
- `reviewers`: the team name to pull reviewers from in the parent repo's organization
2134
- `num_to_request`: the number of reviewers to request on new PRs
2235

23-
You can see an example of how inputs should be specified in the example workflow below.
24-
25-
### `assign`: Automatically adjust PR assignees based on who is most responsible for pushing it forward
26-
27-
The heart and soul of the toolset: this action will automatically adjust assignees on a pull request such that the current assignees are the ones that must take action to move the pull request forward. Here's an example timeline of actions for a PR submitted by author X:
28-
29-
- PR from author X arrives. Any requested reviewers (let's say reviewers A, B, and C) are assigned.
30-
- Reviewer A leaves a review requesting changes. Reviewers A, B, and C are unassigned, and author X is assigned to address A's review.
31-
- X makes changes to the PR and re-requests review from reviewer A. Since there are no outstanding Changes Requested reviews, all requested reviewers that haven't yet approved (A, B, C) are put back on the PR.
32-
- Reviewer B approves the PR, and is unassigned. Reviewers A and C are still assigned.
33-
- Reviewer C approves the PR, and is unassigned. Reviewer A is still assigned.
34-
- etc...
35-
36-
In effect, this turns Github's Assignee field from a vague "point person" to a clear indicator of responsibility.
36+
You can see an example of how inputs should be specified in the Usage section below.
3737

3838
### `copy-labels-linked`: Copy any labels present on linked issues to PRs
3939

@@ -45,26 +45,9 @@ When a pull request meets a repo's configured criteria for [mergeability](https:
4545

4646
## Usage
4747

48-
Setup takes just a few straightforward steps.
49-
50-
### Authenticating with Github
51-
52-
Many `actions-automation` actions rely on a [personal access token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)
53-
to interact with Github and work their magic. If you're setting up a new
54-
repository for use with `actions-automation` tools, you'll need to provision one with the `public_repo`, `read:org`, and `write:org`
55-
scopes enabled and make it available as a [shared secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets)
56-
to your repository, using the name `BOT_TOKEN`. (Organization-wide secrets also work.)
57-
58-
Github _strongly_
59-
[recommends](https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/security-hardening-for-github-actions#considering-cross-repository-access)
60-
creating a separate account for PATs like this, and scoping access to that
61-
account accordingly.
62-
63-
### Enabling `pull-request-responsibility` on a repository
48+
In most cases, all you need to start using `actions-automation` is a single workflow!
6449

65-
Once a token is available to your repo, the final step is to enable `pull-request-responsibility` via
66-
Github Actions. To do so, create a new workflow under `.github/workflows` with
67-
the following structure:
50+
Create a new workflow `yml` under `.github/workflows` with the following structure:
6851

6952
```yml
7053
name: pull-request-responsibility
@@ -115,23 +98,73 @@ on:
11598
jobs:
11699
pull-request-responsibility:
117100
runs-on: ubuntu-latest
118-
env:
119-
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
120101
name: pull-request-responsibility
121102
steps:
122103
- uses: actions-automation/pull-request-responsibility@main
123104
with:
124-
actions: "request,assign,copy-labels-linked,merge" # The actions to run.
125-
reviewers: "reviews" # The team to pull reviewers from for `request`.
126-
num_to_request: 3 # The number of reviewers that `request` should request on new PRs.
127-
105+
actions: "assign,copy-labels-linked,merge" # The actions to run.
106+
token: ${{ secrets.GITHUB_TOKEN }}
128107
```
129108
130-
Note that you'll need to configure the action's inputs accordingly. If none are provided, only the `assign` action will run by default; the rest are opt-in. The other parameters are required for the `request` action, if enabled.
109+
Note that you'll need to configure the action's inputs accordingly. If none are provided, only the `assign` action will run by default; the rest are opt-in via the `actions` input. Additionally, you'll need to provide a Github access token; the `GITHUB_TOKEN` should suffice for most cases.
131110

132111
Once this is in place, you're done! `pull-request-responsibility` should begin working on your repo
133112
when certain events (ex. an issue/PR gets opened) happen.
134113

114+
### Enabling the `request` action
115+
116+
You may have noticed that the `request` action is not included in the workflow above. That's because it performs cross-repo API calls and needs some additional setup.
117+
118+
### Github authentication with a personal access token
119+
120+
The Actions-provided `GITHUB_TOKEN` secret is good for most of what `pull-request-responsibility` does, but it does not have permission to read or write data from outside the repository that spawned it. This poses a problem with the `request` action, which looks at things like user availability and organization-level teams.
121+
122+
In order for `request` to function correctly, we'll need to authenticate with Github using a **personal access token** instead of the `GITHUB_TOKEN`. These are user-generated and effectively allow scripts to act as a user through the Github API. You can manage your active tokens [here](https://github.com/settings/tokens).
123+
124+
Provision one with the `public_repo`, `read:org`, and `write:org` scopes enabled and make it available as a [shared secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets)
125+
to your repository, using the name `BOT_TOKEN`. (Organization-wide secrets also work.)
126+
127+
Github _strongly_
128+
[recommends](https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/security-hardening-for-github-actions#considering-cross-repository-access)
129+
creating a separate account for PATs like this, and scoping access to that
130+
account accordingly. It's recommended to heed their advice; PATs are dangerous if leaked, and can result in malicious actors abusing the account that created it.
131+
132+
If you're using any other `actions-automation` tools, chances are this step will be necessary as well. Once the token is available as a secret, however, it should be straightforward to use it in perpetuity.
133+
134+
### Enabling `request` in your workflow
135+
136+
With the repository secret available, replace the `token` input with your new PAT:
137+
138+
```yml
139+
token: ${{ secrets.BOT_TOKEN }}
140+
```
141+
142+
You'll also need to provide some additional inputs for the action to work (documented in its description):
143+
144+
```yml
145+
reviewers: "reviews" # The team to pull reviewers from for `request`.
146+
num_to_request: 3 # The number of reviewers that `request` should request on new PRs.
147+
```
148+
149+
And, finally, enable the action in the `actions` input:
150+
151+
```yml
152+
actions: "request,assign,copy-labels-linked,merge" # The actions to run.
153+
```
154+
155+
Your completed step should look something like this:
156+
157+
```yml
158+
- uses: actions-automation/pull-request-responsibility@main
159+
with:
160+
actions: "request,assign,copy-labels-linked,merge" # The actions to run.
161+
token: ${{ secrets.BOT_TOKEN }}
162+
reviewers: "reviews" # The team to pull reviewers from for `request`.
163+
num_to_request: 3 # The number of reviewers that `request` should request on new PRs.
164+
```
165+
166+
And you should be done!
167+
135168
## FAQ
136169
137170
### Why do I need to listen for every single Actions event in my workflow?
@@ -144,15 +177,6 @@ The action doesn't _need_ every trigger to work properly -- in fact, it doesn't
144177
use most of them at all. However, there's little downside to enabling all of
145178
them, and doing so avoids the need to change workflows if new functionality using a new trigger is added.
146179

147-
### What happens if I don't set `BOT_TOKEN` properly?
148-
149-
If `pull-request-responsibility` can't find a valid token under `BOT_TOKEN`, it will fail gracefully
150-
with a note reminding you to add one if you want to opt into automation. It
151-
should _not_ send you a failure email about it.
152-
153-
This avoids forks of repositories that have `pull-request-responsibility` enabled from spamming their
154-
authors with needless Actions failure emails about improperly-set credentials.
155-
156180
## Credits
157181

158182
This action was originally developed for the Enarx project as part of [Enarxbot](https://github.com/enarx/bot), and is now available for any repo to use here.

action.yml

+13-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ inputs:
1818
Default: "assign"
1919
required: false
2020
default: 'assign'
21+
token:
22+
description: >
23+
A Github access token. Needed in order to read data from and write to
24+
Github.
25+
26+
In most cases, the GITHUB_TOKEN will suffice; however, to use the full
27+
feature set of the "request" action, a properly-scoped PAT is required.
28+
required: false
29+
default: ''
2130
reviewers:
2231
description: >
2332
The Github team to pull reviewers from. Must be a valid team name from the
@@ -41,22 +50,22 @@ runs:
4150
shell: bash
4251
- run: >
4352
if [[ "${{ inputs.actions }}" =~ (^|,)"merge"(,|$) ]]; then
44-
echo " --- MERGE --- " && $GITHUB_ACTION_PATH/src/merge
53+
echo " --- MERGE --- " && $GITHUB_ACTION_PATH/src/merge "${{ inputs.token }}"
4554
fi
4655
shell: bash
4756
- run: >
4857
if [[ "${{ inputs.actions }}" =~ (^|,)"copy-labels-linked"(,|$) ]]; then
49-
echo " --- COPY-LABELS-LINKED --- " && $GITHUB_ACTION_PATH/src/copy-labels-linked
58+
echo " --- COPY-LABELS-LINKED --- " && $GITHUB_ACTION_PATH/src/copy-labels-linked "${{ inputs.token }}"
5059
fi
5160
shell: bash
5261
- run: >
5362
if [[ "${{ inputs.actions }}" =~ (^|,)"request"(,|$) ]]; then
54-
echo " --- REQUEST --- " && $GITHUB_ACTION_PATH/src/request "${{ inputs.reviewers }}" "${{ inputs.num_to_request }}"
63+
echo " --- REQUEST --- " && $GITHUB_ACTION_PATH/src/request "${{ inputs.token }}" "${{ inputs.reviewers }}" "${{ inputs.num_to_request }}"
5564
fi
5665
shell: bash
5766
- run: >
5867
if [[ "${{ inputs.actions }}" =~ (^|,)"assign"(,|$) ]]; then
59-
echo " --- ASSIGN --- " && $GITHUB_ACTION_PATH/src/assign
68+
echo " --- ASSIGN --- " && $GITHUB_ACTION_PATH/src/assign "${{ inputs.token }}"
6069
fi
6170
shell: bash
6271
- run: bash -c "env | sort"

src/assign

+7-4
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,10 @@ def get_responsible(pr):
141141
continue
142142
break
143143

144-
def update_assignees(number_to_update=None):
144+
def update_assignees(token, number_to_update=None):
145145
"Updates assignees on all repo PRs by default, or a specified PR if input."
146146

147147
org, repo = os.environ["GITHUB_REPOSITORY"].split("/")
148-
token = os.environ.get('BOT_TOKEN', None)
149148

150149
try:
151150
pr_data = githubgql.graphql(QUERY, token=token, org=org, repo=repo, cursor=QUERY_CURSORS)
@@ -158,7 +157,7 @@ def update_assignees(number_to_update=None):
158157

159158
for pr in pr_data['repository']['pullRequests']['nodes']:
160159
if number_to_update not in [pr['number'], None]:
161-
continue
160+
continue
162161
slug = f"{org}/{repo}#{pr['number']}"
163162
try:
164163
responsible = set(get_responsible(pr))
@@ -217,7 +216,11 @@ def main():
217216
if os.environ["GITHUB_EVENT_NAME"] not in ["schedule", "workflow_dispatch"]:
218217
sys.exit(0)
219218

220-
update_assignees()
219+
token = sys.argv[1]
220+
if len(token) == 0:
221+
token = os.environ.get('BOT_TOKEN', None)
222+
223+
update_assignees(token=token)
221224

222225
if __name__ == "__main__":
223226
main()

src/copy-labels-linked

+6-3
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,8 @@ def get_related_issues(body, commits):
8686
for verb, num in regex.findall(c["commit"]["message"]):
8787
yield int(num)
8888

89-
def copy_labels_linked(id):
89+
def copy_labels_linked(token, id):
9090
owner, repo = os.environ["GITHUB_REPOSITORY"].split("/")
91-
token = os.environ.get('BOT_TOKEN', None)
9291

9392
# Get PR data and open issues in the repo.
9493
try:
@@ -164,9 +163,13 @@ def main():
164163
if event["action"] not in {"opened", "reopened"}:
165164
sys.exit(0)
166165

166+
token = sys.argv[1]
167+
if len(token) == 0:
168+
token = os.environ.get('BOT_TOKEN', None)
169+
167170
id = event['pull_request']['node_id']
168171

169-
copy_labels_linked(id)
172+
copy_labels_linked(token=token, id=id)
170173

171174
if __name__ == "__main__":
172175
main()

src/merge

+7-4
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ mutation($input: MergePullRequestInput!) {
3131
}
3232
'''
3333

34-
def merge(number_to_merge=None):
34+
def merge(token, number_to_merge=None):
3535
owner, repo = os.environ["GITHUB_REPOSITORY"].split("/")
36-
token = os.environ.get('BOT_TOKEN', None)
3736
cursors = {"cursor": ["repository", "pullRequests"]}
3837

3938
try:
@@ -51,7 +50,7 @@ def merge(number_to_merge=None):
5150

5251
for (id, pr) in prs.items():
5352
if number_to_merge not in [pr['number'], None]:
54-
continue
53+
continue
5554
# Status strings.
5655
merged_indicator = "✗"
5756
pr_identifier = f"{owner}/{repo}#{pr['number']}"
@@ -75,7 +74,11 @@ def main():
7574
if os.environ["GITHUB_EVENT_NAME"] != "schedule":
7675
sys.exit(0)
7776

78-
merge()
77+
token = sys.argv[1]
78+
if len(token) == 0:
79+
token = os.environ.get('BOT_TOKEN', None)
80+
81+
merge(token=token)
7982

8083
if __name__ == "__main__":
8184
main()

src/request

+7-5
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ def iterusers(nested_dictionary):
110110
elif type(value) is dict:
111111
yield from iterusers(value)
112112

113-
def auto_request(pr, org, token):
114-
team_name = sys.argv[1]
115-
num_to_request = sys.argv[2]
113+
def auto_request(pr, org, token, team_name="", num_to_request=""):
116114

117115
# Inputs are optional and default to empty strings; check if they're populated
118116
# and fail if not.
@@ -195,9 +193,13 @@ def main():
195193
# Extract relevant data from event/environment.
196194
pr = event['pull_request']['node_id']
197195
org = event['organization']['node_id']
198-
token = os.environ.get('BOT_TOKEN', None)
196+
token = sys.argv[1]
197+
if len(token) == 0:
198+
token = os.environ.get('BOT_TOKEN', None)
199+
team_name = sys.argv[2]
200+
num_to_request = sys.argv[3]
199201

200-
auto_request(pr, org, token)
202+
auto_request(pr=pr, org=org, token=token, team_name=team_name, num_to_request=num_to_request)
201203

202204
if __name__ == "__main__":
203205
main()

0 commit comments

Comments
 (0)