22"""Parses issue and PR comments to dispatch release and backport workflows."""
33
44import argparse
5+ import enum
56import json
67import os
78import re
@@ -101,83 +102,124 @@ def _react_negative(repo: str, comment_id: str) -> None:
101102 _add_comment_reaction (repo = repo , comment_id = comment_id , content = "-1" )
102103
103104
105+ class _Command (enum .StrEnum ):
106+ """Workflow commands dispatched from issue and PR comments."""
107+
108+ NONE = "none"
109+ CREATE_RC = "create-rc"
110+ PREPARE_COMPLETE = "prepare-complete"
111+ CREATE_RELEASE_BRANCH = "create-release-branch"
112+ PREPARE = "prepare"
113+ PROCESS_BACKPORTS = "process-backports"
114+ SYNC_CHANGELOG = "sync-changelog"
115+ ADD_BACKPORTS = "add-backports"
116+ PROMOTE = "promote"
117+ BACKPORT_PREPARE = "backport-prepare"
118+ BACKPORT_CREATE_RELEASES = "backport-create-releases"
119+ PR_BACKPORT = "pr-backport"
120+
121+
122+ _ALLOWED_AUTHOR_ASSOCIATIONS = frozenset ({"OWNER" , "MEMBER" , "COLLABORATOR" })
123+ _BACKPORT_ALLOWED_USERS : frozenset [str ] = frozenset ([])
124+
125+
126+ def _is_command_allowed (
127+ command : _Command ,
128+ * ,
129+ user_login : str ,
130+ author_association : str ,
131+ ) -> bool :
132+ """Returns whether the user is allowed to trigger the given command."""
133+ if command in (_Command .ADD_BACKPORTS , _Command .PR_BACKPORT ) and (
134+ user_login in _BACKPORT_ALLOWED_USERS
135+ ):
136+ return True
137+ if author_association in _ALLOWED_AUTHOR_ASSOCIATIONS :
138+ return True
139+ return False
140+
141+
104142def _process_release_issue_comment (
105143 comment_body : str ,
106144 issue_number : str ,
107145 repo : str = "" ,
108146 comment_id : str = "" ,
109- ) -> None :
147+ ) -> _Command :
110148 """Processes comments on a release tracking issue."""
111149 if _match_command ("create-rc" , comment_body ):
112- _write_github_output ("command" , "create-rc" )
113- return
150+ _write_github_output ("command" , _Command . CREATE_RC )
151+ return _Command . CREATE_RC
114152
115153 if m := _match_command ("prepare-complete" , comment_body ):
116- _write_github_output ("command" , "prepare-complete" )
154+ _write_github_output ("command" , _Command . PREPARE_COMPLETE )
117155 if pr_arg := re .sub (r"[\s#]" , "" , m .group (1 )) if m .group (1 ) else "" :
118156 _write_github_output ("pr_number" , pr_arg )
119- return
157+ return _Command . PREPARE_COMPLETE
120158
121159 if _match_command ("create-release-branch" , comment_body ):
122- _write_github_output ("command" , "create-release-branch" )
123- return
160+ _write_github_output ("command" , _Command . CREATE_RELEASE_BRANCH )
161+ return _Command . CREATE_RELEASE_BRANCH
124162
125163 if _match_command ("prepare" , comment_body ):
126- _write_github_output ("command" , "prepare" )
127- return
164+ _write_github_output ("command" , _Command . PREPARE )
165+ return _Command . PREPARE
128166
129167 if _match_command ("process-backports" , comment_body ):
130- _write_github_output ("command" , "process-backports" )
131- return
168+ _write_github_output ("command" , _Command . PROCESS_BACKPORTS )
169+ return _Command . PROCESS_BACKPORTS
132170
133171 if _match_command ("sync-changelog" , comment_body ):
134- _write_github_output ("command" , "sync-changelog" )
135- return
172+ _write_github_output ("command" , _Command . SYNC_CHANGELOG )
173+ return _Command . SYNC_CHANGELOG
136174
137175 if m := _match_command (("backport" , "backports" ), comment_body ):
138176 raw_args = m .group (1 ) if m .group (1 ) else ""
139177 items = [item for item in re .split (r"[\s,]+" , raw_args ) if item ]
140178 if csv := "," .join (items ):
141- _write_github_output ("command" , "add-backports" )
179+ _write_github_output ("command" , _Command . ADD_BACKPORTS )
142180 _write_github_output ("backports" , csv )
181+ return _Command .ADD_BACKPORTS
143182 else :
144- _write_github_output ("command" , "none" )
183+ _write_github_output ("command" , _Command . NONE )
145184 _react_negative (repo = repo , comment_id = comment_id )
146- return
185+ return _Command . NONE
147186
148187 if _match_command ("promote" , comment_body ):
149- _write_github_output ("command" , "promote" )
150- return
188+ _write_github_output ("command" , _Command . PROMOTE )
189+ return _Command . PROMOTE
151190
152- _write_github_output ("command" , "none" )
191+ _write_github_output ("command" , _Command .NONE )
192+ return _Command .NONE
153193
154194
155- def _process_backport_issue_comment (comment_body : str ) -> None :
195+ def _process_backport_issue_comment (comment_body : str ) -> _Command :
156196 """Processes comments on a backport tracking issue."""
157197 if _match_command ("prepare" , comment_body ):
158- _write_github_output ("command" , "backport-prepare" )
159- return
198+ _write_github_output ("command" , _Command . BACKPORT_PREPARE )
199+ return _Command . BACKPORT_PREPARE
160200
161201 if _match_command ("create-releases" , comment_body ):
162- _write_github_output ("command" , "backport-create-releases" )
163- return
202+ _write_github_output ("command" , _Command . BACKPORT_CREATE_RELEASES )
203+ return _Command . BACKPORT_CREATE_RELEASES
164204
165- _write_github_output ("command" , "none" )
205+ _write_github_output ("command" , _Command .NONE )
206+ return _Command .NONE
166207
167208
168- def _process_pr_comment (comment_body : str , pr_number : str ) -> None :
209+ def _process_pr_comment (comment_body : str , pr_number : str ) -> _Command :
169210 """Processes comments on a pull request."""
170211 if _match_command (("backport" , "backports" ), comment_body ):
171- _write_github_output ("command" , "pr-backport" )
212+ _write_github_output ("command" , _Command . PR_BACKPORT )
172213 _write_github_output ("pr_number" , pr_number )
173- return
214+ return _Command . PR_BACKPORT
174215
175216 if _match_command ("prepare-complete" , comment_body ):
176- _write_github_output ("command" , "prepare-complete" )
217+ _write_github_output ("command" , _Command . PREPARE_COMPLETE )
177218 _write_github_output ("pr_number" , pr_number )
178- return
219+ return _Command . PREPARE_COMPLETE
179220
180- _write_github_output ("command" , "none" )
221+ _write_github_output ("command" , _Command .NONE )
222+ return _Command .NONE
181223
182224
183225def _report_failure () -> int :
@@ -205,7 +247,7 @@ def _report_failure() -> int:
205247 )
206248 return 1
207249
208- if command and command != "none" :
250+ if command and command != _Command . NONE :
209251 if comment_url :
210252 header = (
211253 f"Workflow failed for command `{ command } ` ([comment]({ comment_url } ))."
@@ -234,6 +276,12 @@ def process_comment(*, report_failure: bool = False) -> int:
234276 if report_failure :
235277 return _report_failure ()
236278
279+ event = _load_event_data ()
280+ comment_data = event .get ("comment" ) or {}
281+ author_association = str (comment_data .get ("author_association" ) or "" )
282+ user_data = comment_data .get ("user" ) or {}
283+ user_login = str (user_data .get ("login" ) or "" )
284+
237285 comment_body = os .environ .get ("COMMENT_BODY" , "" )
238286 is_pr = _get_bool ("IS_PR" )
239287 event_number = os .environ .get ("EVENT_NUMBER" , "" )
@@ -243,29 +291,40 @@ def process_comment(*, report_failure: bool = False) -> int:
243291 repo = os .environ .get ("GITHUB_REPOSITORY" , "" )
244292
245293 if is_pr :
246- _process_pr_comment (
294+ command = _process_pr_comment (
247295 comment_body = comment_body ,
248296 pr_number = event_number ,
249297 )
250- return 0
251-
252- issue_number = event_number
253- _write_github_output ("issue_number" , issue_number )
254- _write_github_env ("issue_number" , issue_number )
255-
256- if has_release_label :
257- _process_release_issue_comment (
258- comment_body = comment_body ,
259- issue_number = issue_number ,
260- repo = repo ,
261- comment_id = comment_id ,
262- )
263- elif has_backport_label :
264- _process_backport_issue_comment (
265- comment_body = comment_body ,
266- )
267298 else :
268- _write_github_output ("command" , "none" )
299+ issue_number = event_number
300+ _write_github_output ("issue_number" , issue_number )
301+ _write_github_env ("issue_number" , issue_number )
302+
303+ if has_release_label :
304+ command = _process_release_issue_comment (
305+ comment_body = comment_body ,
306+ issue_number = issue_number ,
307+ repo = repo ,
308+ comment_id = comment_id ,
309+ )
310+ elif has_backport_label :
311+ command = _process_backport_issue_comment (
312+ comment_body = comment_body ,
313+ )
314+ else :
315+ _write_github_output ("command" , _Command .NONE )
316+ command = _Command .NONE
317+
318+ if command != _Command .NONE and not _is_command_allowed (
319+ command ,
320+ user_login = user_login ,
321+ author_association = author_association ,
322+ ):
323+ print (
324+ f"::error::User '{ user_login } ' (association: '{ author_association } ')"
325+ f" is not allowed to trigger command '{ command } '."
326+ )
327+ return 1
269328
270329 return 0
271330
0 commit comments