-
Notifications
You must be signed in to change notification settings - Fork 114
220 lines (211 loc) · 9.72 KB
/
github-slack-notifications.yml
File metadata and controls
220 lines (211 loc) · 9.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
name: GitHub Slack Notifications
# Central GitHub -> Slack integration point. Two triggers feed one shared
# Slack workflow (via SLACK_WEBHOOK_URL), which branches on event_type:
# - issues opened -> notify oncall of a new issue
# - comments on closed PRs -> redirect the commenter to open an issue,
# and notify oncall (closed-PR comments are
# otherwise easy to miss)
# Every payload sends the same key set so the Slack workflow can branch
# reliably; fields that don't apply to an event are sent empty.
on:
issues:
types: [opened]
issue_comment:
types: [created]
jobs:
notify-issue-opened:
if: github.event_name == 'issues'
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Send issue details to Slack
# Attacker-controlled fields are passed through env: rather than
# interpolated into the YAML payload, to prevent workflow injection.
# For issue_opened, the issue_* fields carry the data and the
# pr_*/comment_* fields are empty.
env:
REPOSITORY: ${{ github.repository }}
CREATED_AT: ${{ github.event.issue.created_at }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_URL: ${{ github.event.issue.html_url }}
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
ISSUE_BODY: ${{ github.event.issue.body }}
LABELS: ${{ join(github.event.issue.labels.*.name, ', ') }}
uses: slackapi/slack-github-action@v3.0.1
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: webhook-trigger
payload: |
event_type: "issue_opened"
repository: "${{ env.REPOSITORY }}"
created_at: "${{ env.CREATED_AT }}"
issue_number: "${{ env.ISSUE_NUMBER }}"
issue_title: ${{ toJSON(env.ISSUE_TITLE) }}
issue_url: "${{ env.ISSUE_URL }}"
issue_author: "${{ env.ISSUE_AUTHOR }}"
issue_body: ${{ toJSON(env.ISSUE_BODY) }}
labels: ${{ toJSON(env.LABELS) }}
pr_number: ""
pr_title: ""
pr_url: ""
pr_author: ""
pr_state: ""
pr_closed_at: ""
pr_merged_at: ""
comment_id: ""
comment_url: ""
comment_author: ""
comment_body: ""
closed-pr-comment-redirect:
# Only fire on comments left on PRs (issue_comment fires for issues too)
# that are already closed, and skip comments left by bots.
if: >-
github.event_name == 'issue_comment' && github.event.issue.pull_request != null && github.event.issue.state ==
'closed' && github.event.comment.user.type != 'Bot'
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: read
# Serialize per-PR so the marker-comment dedup is race-free (otherwise two
# rapid-fire comments could both see "no marker" and both post a redirect).
# cancel-in-progress is false so the second run still executes after the
# first finishes -- we want to evaluate dedup against the just-posted marker.
concurrency:
group: closed-pr-comment-${{ github.event.issue.number }}
cancel-in-progress: false
steps:
- name: Check commenter permission
id: perm
uses: actions/github-script@v9
with:
script: |
// External users on private repos can 404 here; treat any
// failure as "not a maintainer" so the redirect still fires.
let permission = 'none';
try {
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.payload.comment.user.login,
});
permission = data.permission;
} catch (err) {
core.info(`Permission lookup failed for ${context.payload.comment.user.login}: ${err.message}. Treating as non-maintainer.`);
}
const skip = ['admin', 'maintain', 'write'].includes(permission);
core.setOutput('skip', String(skip));
core.info(`Commenter ${context.payload.comment.user.login} permission=${permission} skip=${skip}`);
- name: Check for existing redirect comment
id: existing
if: steps.perm.outputs.skip != 'true'
uses: actions/github-script@v9
with:
script: |
// Marker we embed in our reply so we don't double-post on the same PR.
const marker = '<!-- closed-pr-comment-redirect -->';
const comments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
per_page: 100,
}
);
const alreadyPosted = comments.some(c => c.body && c.body.includes(marker));
core.setOutput('already_posted', String(alreadyPosted));
- name: Post redirect comment
if: steps.perm.outputs.skip != 'true' && steps.existing.outputs.already_posted != 'true'
# The Slack notification is the load-bearing part of this job. If
# posting the bot reply fails (rate limit, transient error), don't
# block the Slack notification.
continue-on-error: true
uses: actions/github-script@v9
env:
# Repos with issue templates use /issues/new/choose; repos without
# templates should change this to /issues/new.
ISSUES_NEW_URL: https://github.com/${{ github.repository }}/issues/new/choose
with:
script: |
const commenter = context.payload.comment.user.login;
const issuesNewUrl = process.env.ISSUES_NEW_URL;
const body = [
'<!-- closed-pr-comment-redirect -->',
'',
`Thanks for the report, @${commenter} — feedback like this is exactly`,
"how we catch the things we missed. Because this PR is already",
"closed, the team won't see follow-up comments here.",
'',
'Would you mind opening a new issue so we can track it properly?',
issuesNewUrl,
'',
'If this is a security issue, please report it privately via',
'https://aws.amazon.com/security/vulnerability-reporting/ instead',
'of a public issue.',
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body,
});
- name: Compute PR state
id: pr_state
if: steps.perm.outputs.skip != 'true'
uses: actions/github-script@v9
with:
script: |
// PRs surface as `issue` events; merged_at is null when closed-not-merged.
const mergedAt = context.payload.issue.pull_request &&
context.payload.issue.pull_request.merged_at;
core.setOutput('state', mergedAt ? 'merged' : 'closed');
- name: Notify Slack
# Notify oncall only on the FIRST external comment per PR (gated by
# already_posted). Subsequent comments on the same PR don't notify --
# the redirect comment has already directed the commenter to open an
# issue, and issues notify oncall via the issue path. This bounds
# notification volume regardless of how chatty a thread becomes.
if: steps.perm.outputs.skip != 'true' && steps.existing.outputs.already_posted != 'true'
# Attacker-controlled fields are passed through env: rather than
# interpolated into the YAML payload, to prevent workflow injection.
# For closed-PR comments, the issue_* fields are empty (this isn't
# an issue) and the pr_*/comment_* fields carry the real data.
env:
REPOSITORY: ${{ github.repository }}
CREATED_AT: ${{ github.event.comment.created_at }}
PR_NUMBER: ${{ github.event.issue.number }}
PR_TITLE: ${{ github.event.issue.title }}
PR_URL: ${{ github.event.issue.html_url }}
PR_AUTHOR: ${{ github.event.issue.user.login }}
PR_CLOSED_AT: ${{ github.event.issue.closed_at }}
PR_MERGED_AT: ${{ github.event.issue.pull_request.merged_at }}
COMMENT_ID: ${{ github.event.comment.id }}
COMMENT_URL: ${{ github.event.comment.html_url }}
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
COMMENT_BODY: ${{ github.event.comment.body }}
uses: slackapi/slack-github-action@v3.0.1
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: webhook-trigger
payload: |
event_type: "closed_pr_comment"
repository: "${{ env.REPOSITORY }}"
created_at: "${{ env.CREATED_AT }}"
issue_number: ""
issue_title: ""
issue_url: ""
issue_author: ""
issue_body: ""
labels: ""
pr_number: "${{ env.PR_NUMBER }}"
pr_title: ${{ toJSON(env.PR_TITLE) }}
pr_url: "${{ env.PR_URL }}"
pr_author: "${{ env.PR_AUTHOR }}"
pr_state: "${{ steps.pr_state.outputs.state }}"
pr_closed_at: "${{ env.PR_CLOSED_AT }}"
pr_merged_at: "${{ env.PR_MERGED_AT }}"
comment_id: "${{ env.COMMENT_ID }}"
comment_url: "${{ env.COMMENT_URL }}"
comment_author: "${{ env.COMMENT_AUTHOR }}"
comment_body: ${{ toJSON(env.COMMENT_BODY) }}