1
1
#!/usr/bin/env python3
2
2
"""
3
- Post release status comments to GitHub PRs.
3
+ Post release status to GitHub releases and associated PRs.
4
4
5
- This script posts release information as a comment on associated PRs,
6
- including package locations, Docker images, and installation instructions.
5
+ This script updates GitHub release notes with release information and optionally
6
+ posts comments to associated PRs, including package locations, Docker images,
7
+ and installation instructions.
8
+
9
+ PRIMARY: Always update GitHub release notes (failure = workflow failure)
10
+ SECONDARY: Post PR comment when possible (failure = graceful continue)
7
11
"""
8
12
9
13
import argparse
@@ -53,7 +57,59 @@ def generate_release_comment(
53
57
return body
54
58
55
59
56
- def find_pr_for_tag (github_token : str , repo : str , sha : str ) -> Optional [int ]:
60
+ def update_release_notes (
61
+ github_token : str ,
62
+ repo : str ,
63
+ release_id : str ,
64
+ additional_content : str ,
65
+ ) -> bool :
66
+ """
67
+ Update GitHub release notes by appending status information.
68
+
69
+ Args:
70
+ github_token: GitHub API token
71
+ repo: Repository in "owner/repo" format
72
+ release_id: GitHub release ID
73
+ additional_content: Formatted status content to append
74
+
75
+ Returns:
76
+ True if successful, False otherwise
77
+ """
78
+ import requests
79
+
80
+ headers = {
81
+ "Authorization" : f"token { github_token } " ,
82
+ "Accept" : "application/vnd.github.v3+json" ,
83
+ }
84
+
85
+ # First, get the current release to preserve existing body
86
+ get_url = f"https://api.github.com/repos/{ repo } /releases/{ release_id } "
87
+
88
+ try :
89
+ response = requests .get (get_url , headers = headers )
90
+ response .raise_for_status ()
91
+
92
+ release_data = response .json ()
93
+ existing_body = release_data .get ("body" ) or ""
94
+
95
+ # Append the additional content
96
+ updated_body = existing_body + additional_content
97
+
98
+ # Update the release with the new body
99
+ patch_url = f"https://api.github.com/repos/{ repo } /releases/{ release_id } "
100
+ patch_data = {"body" : updated_body }
101
+
102
+ response = requests .patch (patch_url , headers = headers , json = patch_data )
103
+ response .raise_for_status ()
104
+
105
+ return True
106
+
107
+ except requests .RequestException as e :
108
+ print (f"Error updating release notes: { e } " , file = sys .stderr )
109
+ return False
110
+
111
+
112
+ def find_pr_for_sha (github_token : str , repo : str , sha : str ) -> Optional [int ]:
57
113
"""
58
114
Find the PR number associated with a git SHA.
59
115
@@ -89,6 +145,21 @@ def find_pr_for_tag(github_token: str, repo: str, sha: str) -> Optional[int]:
89
145
return None
90
146
91
147
148
+ def find_pr_for_tag (github_token : str , repo : str , sha : str ) -> Optional [int ]:
149
+ """
150
+ Legacy alias for find_pr_for_sha for backward compatibility.
151
+
152
+ Args:
153
+ github_token: GitHub API token
154
+ repo: Repository in format "owner/repo"
155
+ sha: Git commit SHA to search for
156
+
157
+ Returns:
158
+ PR number if found, None otherwise
159
+ """
160
+ return find_pr_for_sha (github_token , repo , sha )
161
+
162
+
92
163
def post_comment_to_pr (
93
164
github_token : str ,
94
165
repo : str ,
@@ -129,7 +200,7 @@ def post_comment_to_pr(
129
200
def main ():
130
201
"""Main entry point."""
131
202
parser = argparse .ArgumentParser (
132
- description = "Post release status to GitHub PRs"
203
+ description = "Post release status to GitHub releases and PRs"
133
204
)
134
205
parser .add_argument (
135
206
"--version" ,
@@ -150,6 +221,10 @@ def main():
150
221
"--docker-image" ,
151
222
help = "Docker image URI" ,
152
223
)
224
+ parser .add_argument (
225
+ "--release-id" ,
226
+ help = "GitHub release ID for updating notes" ,
227
+ )
153
228
parser .add_argument (
154
229
"--pr-number" ,
155
230
type = int ,
@@ -177,8 +252,8 @@ def main():
177
252
178
253
args = parser .parse_args ()
179
254
180
- # Generate the comment body
181
- comment_body = generate_release_comment (
255
+ # Generate the status content
256
+ status_content = generate_release_comment (
182
257
version = args .version ,
183
258
release_url = args .release_url ,
184
259
pypi_url = args .pypi_url ,
@@ -187,48 +262,67 @@ def main():
187
262
188
263
if args .dry_run :
189
264
print ("=== DRY RUN - Comment Body ===" )
190
- print (comment_body )
265
+ print (status_content )
191
266
return 0
192
267
193
- # Determine PR number
268
+ # PRIMARY GOAL: Update release notes if release-id is provided
269
+ if args .release_id :
270
+ if not args .github_token :
271
+ print ("Error: GitHub token required to update release notes" , file = sys .stderr )
272
+ return 1
273
+
274
+ if not args .repo :
275
+ print ("Error: Repository required to update release notes" , file = sys .stderr )
276
+ return 1
277
+
278
+ # Format content for release notes (add separator)
279
+ release_notes_content = f"\n ---\n \n { status_content } "
280
+
281
+ success = update_release_notes (
282
+ github_token = args .github_token ,
283
+ repo = args .repo ,
284
+ release_id = args .release_id ,
285
+ additional_content = release_notes_content ,
286
+ )
287
+
288
+ if not success :
289
+ print ("Failed to update release notes" , file = sys .stderr )
290
+ return 1 # CRITICAL: Release notes update failure should fail the workflow
291
+
292
+ print (f"Updated release notes for release { args .release_id } " )
293
+
294
+ # SECONDARY GOAL: Post PR comment if possible (failure = graceful continue)
194
295
pr_number = args .pr_number
195
296
196
297
if not pr_number and args .sha and args .github_token :
197
298
# Try to find PR from SHA
198
- pr_number = find_pr_for_tag (
299
+ pr_number = find_pr_for_sha (
199
300
github_token = args .github_token ,
200
301
repo = args .repo ,
201
302
sha = args .sha ,
202
303
)
203
304
204
- if not pr_number :
305
+ if pr_number and args .github_token and args .repo :
306
+ # Attempt to post PR comment
307
+ pr_success = post_comment_to_pr (
308
+ github_token = args .github_token ,
309
+ repo = args .repo ,
310
+ pr_number = pr_number ,
311
+ comment_body = status_content ,
312
+ )
313
+
314
+ if pr_success :
315
+ print (f"Posted release status to PR #{ pr_number } " )
316
+ else :
317
+ print (f"Warning: Failed to post comment to PR #{ pr_number } " , file = sys .stderr )
318
+ # Don't fail the workflow for PR comment failures
319
+ elif not args .release_id :
320
+ # Legacy behavior: if no release-id and no PR found, show status
205
321
print ("No PR found to comment on" , file = sys .stderr )
206
322
print ("Release Status:" )
207
- print (comment_body )
208
- return 0 # Not an error - just no PR to comment on
209
-
210
- # Post the comment
211
- if not args .github_token :
212
- print ("Error: GitHub token required to post comment" , file = sys .stderr )
213
- return 1
214
-
215
- if not args .repo :
216
- print ("Error: Repository required to post comment" , file = sys .stderr )
217
- return 1
218
-
219
- success = post_comment_to_pr (
220
- github_token = args .github_token ,
221
- repo = args .repo ,
222
- pr_number = pr_number ,
223
- comment_body = comment_body ,
224
- )
323
+ print (status_content )
225
324
226
- if success :
227
- print (f"Posted release status to PR #{ pr_number } " )
228
- return 0
229
- else :
230
- print (f"Failed to post comment to PR #{ pr_number } " , file = sys .stderr )
231
- return 1
325
+ return 0
232
326
233
327
234
328
if __name__ == "__main__" :
0 commit comments