Skip to content

Commit b706f60

Browse files
authored
Improve kernelCTF auto releaser to handle Android releases (#284)
- The auto-releaser would wok in a following logic: 1. Access https://androidbuildinternal.googleapis.com/android/internal/build/v3/builds?branches=aosp-android-latest-release&buildAttemptStatus=complete&buildType=submitted&maxResults=1&successful=true&target=aosp_cf_x86_64_only_phone-userdebug to get an information about latest Android build available in JSON format. 2. We take the build number and using fetch_artifact (https://android.googlesource.com/tools/fetch_artifact/) attempt to download "kernel_version.txt" artefact from Android build to get all the necessary build details. 3. Using build details check if kernelCTF GCS bucket contains already the release. If not add it to releases that should be processed by kernelctf-release-build action. - The logic of kernelctf-release-build action updated to handle new naming style of Android releases.
1 parent a1a0b27 commit b706f60

File tree

4 files changed

+215
-17
lines changed

4 files changed

+215
-17
lines changed

.github/workflows/kernelctf-auto-releaser.yaml

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: kernelCTF auto releaser
22
on:
33
workflow_dispatch:
44
schedule:
5-
- cron: '0 12 * * *' # every day at 12:00 UTC
5+
- cron: '0 12 * * *' # every day at 12:00 UTC
66
permissions: {}
77
defaults:
88
run:
@@ -11,25 +11,57 @@ defaults:
1111
jobs:
1212
get_new_builds:
1313
runs-on: ubuntu-latest
14+
timeout-minutes: 15
1415
steps:
1516
- name: Test GCS SA key to fail quick before trying to make a build
1617
uses: google-github-actions/auth@v2
1718
with:
18-
credentials_json: '${{secrets.KERNELCTF_GCS_SA_KEY}}'
19+
credentials_json: '${{ secrets.KERNELCTF_GCS_SA_KEY }}'
1920
create_credentials_file: false
20-
21+
2122
- name: Checkout repo
2223
uses: actions/checkout@v4
23-
24+
25+
- name: Setup Go
26+
uses: actions/setup-go@v5
27+
with:
28+
go-version: 'stable'
29+
2430
- name: Install prerequisites
25-
run: sudo apt install -yq --no-install-recommends python3-lxml
31+
run: sudo apt-get install -yq --no-install-recommends jq python3-lxml
32+
33+
- name: Build fetch_artifact tool
34+
run: |
35+
git clone https://android.googlesource.com/tools/fetch_artifact
36+
cd fetch_artifact
37+
go build -o fetch_artifact .
38+
sudo mv fetch_artifact /usr/local/bin/
39+
cd ..
40+
rm -rf fetch_artifact
41+
42+
- name: Check latest Linux kernel versions
43+
id: check_linux
44+
run: python3 get_latest_lts_cos_versions.py
2645

27-
- id: check
28-
name: Check latest kernel versions
29-
run: ./get_latest_kernel_versions.py
46+
- name: Check for new Android builds
47+
id: check_android
48+
run: python3 get_latest_android_versions.py
49+
50+
- name: Merge all releases
51+
id: merge
52+
run: |
53+
LINUX_RELEASES='${{ steps.check_linux.outputs.releases }}'
54+
ANDROID_RELEASES='${{ steps.check_android.outputs.releases }}'
55+
56+
# Merge both release arrays
57+
MERGED=$(jq -sc 'add' <(echo "$LINUX_RELEASES") <(echo "$ANDROID_RELEASES"))
58+
59+
echo "releases=$MERGED" >> $GITHUB_OUTPUT
60+
echo "Merged releases: $MERGED"
61+
3062
outputs:
31-
releases: ${{ steps.check.outputs.releases }}
32-
63+
releases: ${{ steps.merge.outputs.releases }}
64+
3365
build_release:
3466
needs: get_new_builds
3567
if: fromJSON(needs.get_new_builds.outputs.releases)[0] != null

.github/workflows/kernelctf-release-build.yaml

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
id: detect
3434
run: |
3535
# Detect Android builds by prefix, assume Linux for everything else
36-
if [[ "${{inputs.releaseId}}" =~ ^android- ]]; then
36+
if [[ "${{inputs.releaseId}}" =~ ^android ]]; then
3737
echo "type=android" >> $GITHUB_OUTPUT
3838
echo "Detected Android build from releaseId prefix"
3939
else
@@ -43,10 +43,23 @@ jobs:
4343
4444
- name: Check release does not exist yet
4545
run: |
46-
if curl --fail -I https://storage.googleapis.com/kernelctf-build/releases/${{inputs.releaseId}}/bzImage 2>/dev/null; then
47-
echo "Error: Release ${{inputs.releaseId}} already exists"
46+
RELEASE_ID="${{inputs.releaseId}}"
47+
48+
# Detect build type
49+
if [[ "$RELEASE_ID" =~ ^android ]]; then
50+
# Check for Android release artifact
51+
CHECK_FILE="cvd-host_package.tar.gz"
52+
else
53+
# Check for Linux release artifact
54+
CHECK_FILE="bzImage"
55+
fi
56+
57+
if curl --fail -I "https://storage.googleapis.com/kernelctf-build/releases/${RELEASE_ID}/${CHECK_FILE}" 2>/dev/null; then
58+
echo "Error: Release ${RELEASE_ID} already exists (found ${CHECK_FILE})"
4859
exit 1
4960
fi
61+
62+
echo "Release ${RELEASE_ID} does not exist yet, proceeding with build"
5063
5164
- name: Install Linux build prerequisites
5265
if: steps.detect.outputs.type == 'linux'
@@ -81,13 +94,24 @@ jobs:
8194
if: steps.detect.outputs.type == 'android'
8295
run: |
8396
RELEASE_ID="${{inputs.releaseId}}"
84-
ARCH=$(echo $RELEASE_ID | cut -d'-' -f3)
85-
BUILD_ID=$(echo $RELEASE_ID | cut -d'-' -f4)
97+
98+
echo "Release ID: $RELEASE_ID"
99+
100+
# Parse release ID format: android16-6.12.23-x86_64-14421689
101+
# Use awk to split and extract fields
102+
ARCH=$(echo "$RELEASE_ID" | awk -F'-' '{print $(NF-1)}')
103+
BUILD_ID=$(echo "$RELEASE_ID" | awk -F'-' '{print $NF}')
86104
87105
echo "Architecture: $ARCH"
88106
echo "Build ID: $BUILD_ID"
89107
90-
if [[ "$ARCH" == "x64" ]]; then
108+
if [[ -z "$ARCH" || -z "$BUILD_ID" ]]; then
109+
echo "Error: Failed to parse release ID"
110+
echo "Expected format: androidXX-X.XX.XX-ARCH-BUILDID"
111+
exit 1
112+
fi
113+
114+
if [[ "$ARCH" == "x86_64" ]]; then
91115
TARGET="aosp_cf_x86_64_only_phone-userdebug"
92116
TARGET_BASE="aosp_cf_x86_64_only_phone"
93117
elif [[ "$ARCH" == "arm64" ]]; then
@@ -118,7 +142,7 @@ jobs:
118142
-artifact=$IMG_ARTIFACT
119143
mv "$IMG_ARTIFACT" "$RELEASE_DIR/"
120144
121-
echo "Android artifacts downloaded to $RELEASE_DIR"
145+
echo "Android artifacts downloaded to $RELEASE_DIR"
122146
123147
- name: Show releases
124148
run: find releases -type f|xargs ls -al
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env -S python3 -u
2+
import json
3+
import subprocess
4+
import os
5+
import re
6+
from utils import *
7+
8+
def validate_build_id(build_id):
9+
if not re.match(r'^\d+$', str(build_id)):
10+
fail(f"Invalid build_id format: {build_id}")
11+
return build_id
12+
13+
def validate_version_component(component, name):
14+
if not re.match(r'^[a-z0-9.-]+$', component, re.IGNORECASE):
15+
fail(f"Invalid {name} format: {component}")
16+
return component
17+
18+
def release_exists(release_id):
19+
url = f"https://storage.googleapis.com/kernelctf-build/releases/{release_id}/cvd-host_package.tar.gz"
20+
status_code = requests.head(url).status_code
21+
22+
if status_code == 200:
23+
return True
24+
if status_code == 403:
25+
return False
26+
27+
fail(f"Unexpected HTTP status code for release check: {status_code} (url = {url})")
28+
29+
def fetch_android_build():
30+
API_URL = "https://androidbuildinternal.googleapis.com/android/internal/build/v3/builds"
31+
TARGET = "aosp_cf_x86_64_only_phone-userdebug"
32+
BRANCH = "aosp-android-latest-release"
33+
34+
print(f"Fetching latest Android build for {TARGET}...")
35+
36+
response = fetch(f"{API_URL}?branches={BRANCH}&buildAttemptStatus=complete&buildType=submitted&maxResults=1&successful=true&target={TARGET}")
37+
38+
try:
39+
data = json.loads(response)
40+
builds = data.get('builds', [])
41+
42+
if not builds:
43+
fail("No builds found in API response")
44+
45+
build_id = builds[0].get('buildId')
46+
if not build_id:
47+
fail("No buildId found in response")
48+
49+
# Validate build_id is numeric only
50+
build_id = validate_build_id(build_id)
51+
52+
print(f"Latest build ID: {build_id}")
53+
return {
54+
'build_id': build_id,
55+
'target': TARGET
56+
}
57+
58+
except (json.JSONDecodeError, KeyError) as e:
59+
fail(f"Error parsing API response: {e}")
60+
61+
def download_kernel_version(build_info):
62+
build_id = build_info['build_id']
63+
target = build_info['target']
64+
65+
print(f"Fetching kernel_version.txt for build {build_id}...")
66+
67+
try:
68+
result = subprocess.run(
69+
[
70+
'fetch_artifact',
71+
f'-target={target}',
72+
f'-build_id={build_id}',
73+
'-artifact=kernel_version.txt'
74+
],
75+
capture_output=True,
76+
text=True,
77+
check=True
78+
)
79+
80+
if not os.path.exists('kernel_version.txt'):
81+
fail("kernel_version.txt not found after download")
82+
83+
with open('kernel_version.txt', 'r') as f:
84+
kernel_version_string = f.read().strip()
85+
86+
print(f"Kernel version string: {kernel_version_string}")
87+
88+
os.remove('kernel_version.txt')
89+
90+
return kernel_version_string
91+
92+
except subprocess.CalledProcessError as e:
93+
fail(f"fetch_artifact failed: {e.stderr}")
94+
95+
def parse_kernel_version(kernel_version_string):
96+
# Example: "6.12.23-android16-5-g2cc84bbe1269-ab13603476"
97+
parts = kernel_version_string.split('-')
98+
99+
if len(parts) < 2:
100+
fail(f"Invalid kernel version format: {kernel_version_string}")
101+
102+
kernel_version = validate_version_component(parts[0], "kernel_version")
103+
android_version = validate_version_component(parts[1], "android_version")
104+
105+
print(f"Kernel version: {kernel_version}")
106+
print(f"Android version: {android_version}")
107+
108+
return {
109+
'kernel_version': kernel_version,
110+
'android_version': android_version
111+
}
112+
113+
def get_latest_android_release():
114+
# Fetch latest build info
115+
build_info = fetch_android_build()
116+
117+
# Download kernel version info
118+
kernel_version_string = download_kernel_version(build_info)
119+
120+
# Parse kernel version
121+
version_info = parse_kernel_version(kernel_version_string)
122+
123+
# Example build release ID: android16-6.12.23-x86_64-14421689
124+
release_id = f"{version_info['android_version']}-{version_info['kernel_version']}-x86_64-{build_info['build_id']}"
125+
126+
print(f"Release ID: {release_id}")
127+
128+
# Check if release already exists
129+
if release_exists(release_id):
130+
print("Release already exists in GCS, skipping")
131+
return []
132+
133+
print("Release does not exist in GCS, will proceed with build")
134+
return [{
135+
"releaseId": release_id,
136+
"branch": None
137+
}]
138+
139+
releases = get_latest_android_release()
140+
141+
ghSet("OUTPUT", "releases=" + json.dumps(releases))
142+
print(f"Generated releases output: {json.dumps(releases)}")

0 commit comments

Comments
 (0)