Skip to content

Commit 49324d3

Browse files
committed
From review
1 parent c8bf685 commit 49324d3

5 files changed

Lines changed: 393 additions & 45 deletions

File tree

release/github.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import json
2828
import os
29+
import time
2930
import urllib.request
3031

3132
from runtime import fail
@@ -64,6 +65,22 @@ def _api_request(token, method, path, body=None):
6465
fail(f"GitHub API error {e.code} for {method} {path}: {error_body}")
6566

6667

68+
def _get_latest_run_url(token, workflow_file):
69+
"""
70+
Fetch the most recent run URL for a workflow. Returns the HTML URL
71+
of the latest run, or a fallback URL to the workflow's runs page.
72+
"""
73+
fallback = f"https://github.com/{GITHUB_REPO}/actions/workflows/{workflow_file}"
74+
path = f"/repos/{GITHUB_REPO}/actions/workflows/{workflow_file}/runs?per_page=1"
75+
try:
76+
result = _api_request(token, "GET", path)
77+
if result and result.get("workflow_runs"):
78+
return result["workflow_runs"][0]["html_url"]
79+
except Exception:
80+
pass
81+
return fallback
82+
83+
6784
def trigger_workflow(token, workflow_file, ref, inputs):
6885
"""
6986
Trigger a GitHub Actions workflow_dispatch event.
@@ -74,6 +91,11 @@ def trigger_workflow(token, workflow_file, ref, inputs):
7491
print(f"Triggering workflow {workflow_file} on {GITHUB_REPO} with inputs: {json.dumps(inputs)}")
7592
_api_request(token, "POST", path, body)
7693
print(f"Successfully triggered {workflow_file}")
94+
# Brief pause to allow GitHub to register the run before querying
95+
if not DRY_RUN:
96+
time.sleep(2)
97+
run_url = _get_latest_run_url(token, workflow_file)
98+
print(f" View run: {run_url}")
7799

78100

79101
def trigger_docker_build_test(token, ref, image_type, kafka_url):

release/release.py

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,54 @@ def command_release_announcement_email():
218218
## Default 'stage' subcommand implementation isn't isolated to its own function yet for historical reasons
219219

220220

221+
def trigger_docker_workflows(rc_tag, release_version, dev_branch):
222+
"""
223+
Trigger Docker image build/test and RC release workflows via GitHub Actions API.
224+
Prompts the user for confirmation before each step.
225+
"""
226+
print("\n=== Docker Image Workflows ===")
227+
if github.DRY_RUN:
228+
print("NOTE: GITHUB_DRY_RUN is enabled. No actual API calls will be made.")
229+
if github.GITHUB_REPO != "apache/kafka":
230+
print(f"NOTE: Using custom repository: {github.GITHUB_REPO}")
231+
if not confirm("Trigger Docker image build workflows via GitHub Actions?"):
232+
print("Skipping Docker image workflows.")
233+
return
234+
235+
def get_github_token():
236+
print(templates.github_token_instructions())
237+
return prompt("Enter your GitHub personal access token: ")
238+
github_token = preferences.get('github_token', get_github_token)
239+
kafka_url = f"https://dist.apache.org/repos/dist/dev/kafka/{rc_tag}/kafka_2.13-{release_version}.tgz"
240+
241+
# Step 1: Trigger build/test workflows and loop until CVE-free
242+
while True:
243+
print(f"\nStep 1/2: Triggering Docker Build Test workflows for JVM and native images...")
244+
for image_type in ["jvm", "native"]:
245+
github.trigger_docker_build_test(github_token, dev_branch, image_type, kafka_url)
246+
print("\nDocker Build Test workflows triggered successfully for both JVM and native images.")
247+
print(f"\nPlease check the build results and CVE scan reports at:")
248+
print(f" https://github.com/{github.GITHUB_REPO}/actions/workflows/docker_build_and_test.yml")
249+
print("\nVerify that:")
250+
print(" 1. Both JVM and native image builds succeeded")
251+
print(" 2. The CVE scan reports show no CRITICAL or HIGH vulnerabilities")
252+
print(" 3. If CVEs are found, update the Dockerfiles and re-trigger")
253+
print(" Dockerfiles are located at: docker/jvm/Dockerfile and docker/native/Dockerfile")
254+
if confirm("Have the builds passed with no CVEs? (n to re-trigger after fixing Dockerfiles)"):
255+
break
256+
print("\nRe-triggering Docker Build Test workflows after Dockerfile updates...")
257+
258+
# Step 2: Push RC images to DockerHub
259+
print(f"\nStep 2/2: Triggering Docker RC Release workflows for JVM and native images...")
260+
for image_type in ["jvm", "native"]:
261+
docker_image_name = "apache/kafka-native" if image_type == "native" else "apache/kafka"
262+
rc_docker_image = f"{docker_image_name}:{rc_tag}"
263+
github.trigger_docker_rc_release(github_token, dev_branch, image_type, rc_docker_image, kafka_url)
264+
print("\nDocker RC Release workflows triggered successfully for both JVM and native images.")
265+
266+
print(f"\nAll Docker workflow runs can be monitored at: https://github.com/{github.GITHUB_REPO}/actions")
267+
268+
221269
def verify_gpg_key():
222270
if not gpg.key_exists(gpg_key_id):
223271
fail(f"GPG key {gpg_key_id} not found")
@@ -373,29 +421,7 @@ def delete_gitrefs():
373421
git.push_ref(rc_tag)
374422
git.push_ref(starting_branch)
375423

376-
# Trigger Docker image build and test workflows via GitHub Actions
377-
print("\n=== Docker Image Workflows ===")
378-
if github.DRY_RUN:
379-
print("NOTE: GITHUB_DRY_RUN is enabled. No actual API calls will be made.")
380-
if github.GITHUB_REPO != "apache/kafka":
381-
print(f"NOTE: Using custom repository: {github.GITHUB_REPO}")
382-
if confirm("Trigger Docker image build workflows via GitHub Actions?"):
383-
github_token = preferences.get('github_token', lambda: prompt("Enter your GitHub personal access token (with 'actions' scope): "))
384-
kafka_url = f"https://dist.apache.org/repos/dist/dev/kafka/{rc_tag}/kafka_2.13-{release_version}.tgz"
385-
print(f"\nStep 1/2: Triggering Docker Build Test workflows for JVM and native images...")
386-
for image_type in ["jvm", "native"]:
387-
github.trigger_docker_build_test(github_token, dev_branch, image_type, kafka_url)
388-
print("\nDocker Build Test workflows triggered successfully for both JVM and native images.")
389-
if confirm("Also trigger Docker RC release workflows to push RC images to DockerHub?"):
390-
print(f"\nStep 2/2: Triggering Docker RC Release workflows for JVM and native images...")
391-
for image_type in ["jvm", "native"]:
392-
docker_image_name = "apache/kafka-native" if image_type == "native" else "apache/kafka"
393-
rc_docker_image = f"{docker_image_name}:{rc_tag}"
394-
github.trigger_docker_rc_release(github_token, dev_branch, image_type, rc_docker_image, kafka_url)
395-
print("\nDocker RC Release workflows triggered successfully for both JVM and native images.")
396-
print(f"\nAll Docker workflow runs can be monitored at: https://github.com/{github.GITHUB_REPO}/actions")
397-
else:
398-
print("Skipping Docker image workflows.")
424+
trigger_docker_workflows(rc_tag, release_version, dev_branch)
399425

400426
# Move back to starting branch and clean out the temporary release branch (e.g. 1.0.0) we used to generate everything
401427
git.reset_hard_head()

release/templates.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,25 @@ def rc_email_instructions(rc_email_text):
268268
"""
269269

270270

271+
def github_token_instructions():
272+
return """
273+
To trigger Docker image builds, you need a GitHub Personal Access Token.
274+
275+
How to generate one:
276+
1. Go to https://github.com/settings/tokens
277+
2. Click "Generate new token" -> "Generate new token (classic)"
278+
3. Set a name (e.g. "kafka-release")
279+
4. Set an expiration (e.g. 7 days is sufficient for a release cycle)
280+
5. Select the scope: "repo" (Full control of private repositories)
281+
- This includes the required "actions" write permission
282+
6. Click "Generate token"
283+
7. Copy the token (starts with "ghp_...")
284+
285+
Note: The token will be saved in your release preferences file for reuse.
286+
You only need to generate it once per release cycle.
287+
"""
288+
289+
271290
def cmd_failed():
272291
return """
273292
*************************************************
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
"""
19+
Interactive test script for the Docker workflow trigger flow.
20+
21+
This invokes the actual trigger_docker_workflows() function from release.py
22+
without needing GPG, SVN, Maven, or committer access. The function is
23+
extracted from release.py without executing its top-level interactive code.
24+
25+
Usage:
26+
# Dry-run (no API calls, no token needed — recommended for first test):
27+
GITHUB_DRY_RUN=true python test_docker_trigger_interactive.py
28+
29+
# Against your fork (real API calls, needs a GitHub token):
30+
GITHUB_REPO=yourusername/kafka python test_docker_trigger_interactive.py
31+
32+
# Combine both:
33+
GITHUB_DRY_RUN=true GITHUB_REPO=yourusername/kafka python test_docker_trigger_interactive.py
34+
"""
35+
36+
import os
37+
import sys
38+
39+
# Ensure release/ is on the path
40+
sys.path.insert(0, os.path.dirname(__file__))
41+
42+
from runtime import confirm, confirm_or_fail, prompt
43+
import github
44+
import preferences
45+
import templates
46+
47+
48+
def _load_trigger_docker_workflows():
49+
"""
50+
Extract trigger_docker_workflows from release.py without executing the
51+
module's top-level interactive code. We parse the source and compile just
52+
the function definition, then bind it to real (not mocked) dependencies.
53+
"""
54+
release_path = os.path.join(os.path.dirname(__file__), "release.py")
55+
with open(release_path) as f:
56+
source = f.read()
57+
58+
lines = source.split('\n')
59+
func_lines = []
60+
capturing = False
61+
for line in lines:
62+
if line.startswith('def trigger_docker_workflows('):
63+
capturing = True
64+
elif capturing and line and not line[0].isspace() and not line.startswith('#'):
65+
break
66+
if capturing:
67+
func_lines.append(line)
68+
69+
func_source = '\n'.join(func_lines)
70+
71+
ns = {
72+
'github': github,
73+
'confirm': confirm,
74+
'confirm_or_fail': confirm_or_fail,
75+
'preferences': preferences,
76+
'templates': templates,
77+
'prompt': prompt,
78+
}
79+
exec(compile(func_source, release_path, 'exec'), ns)
80+
return ns['trigger_docker_workflows']
81+
82+
83+
if __name__ == "__main__":
84+
trigger_docker_workflows = _load_trigger_docker_workflows()
85+
86+
print("=" * 70)
87+
print(" Docker Workflow Trigger - Interactive Test")
88+
print("=" * 70)
89+
print(f"\n Target repo : {github.GITHUB_REPO}")
90+
print(f" Dry-run mode : {github.DRY_RUN}")
91+
print()
92+
93+
release_version = prompt("Enter release version (e.g. 4.3.0): ")
94+
rc = prompt("Enter RC number (e.g. 0): ")
95+
rc_tag = f"{release_version}-rc{rc}"
96+
dev_branch = '.'.join(release_version.split('.')[:2])
97+
98+
print(f"\n Release version : {release_version}")
99+
print(f" RC tag : {rc_tag}")
100+
print(f" Dev branch : {dev_branch}")
101+
102+
trigger_docker_workflows(rc_tag, release_version, dev_branch)
103+
104+
print("\nDone.")

0 commit comments

Comments
 (0)