Skip to content

Commit f76ce29

Browse files
committed
Build RC Image in github workflow
1 parent ff7f909 commit f76ce29

4 files changed

Lines changed: 411 additions & 40 deletions

File tree

.github/workflows/update-kubernetes-versions.yml

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,37 +42,48 @@ jobs:
4242
- name: Install Python dependencies
4343
run: pip install requests pyyaml
4444

45+
- name: Install kind
46+
uses: helm/kind-action@v1
47+
with:
48+
install_only: true
49+
4550
- name: Fetch latest Kubernetes version
4651
id: fetch-versions
4752
run: |
4853
dda inv k8s-versions.fetch-versions
4954
50-
- name: Update e2e.yml with new version
51-
id: update-yaml
52-
if: steps.fetch-versions.outputs.has_new_versions == 'true'
55+
- name: Build RC Images
56+
if: contains(steps.fetch-versions.outputs.new_versions, '"rc":true')
57+
id: build-rc-images
5358
run: |
54-
dda inv k8s-versions.update-e2e-yaml
55-
56-
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
57-
name: Create pull request
58-
if: steps.update-yaml.outputs.updated == 'true'
59-
with:
60-
commit-message: "chore(e2e): add new Kubernetes version to e2e tests"
61-
branch: update-k8s-versions-automated
62-
token: ${{ steps.octo-sts.outputs.token }}
63-
sign-commits: true
64-
title: "[automated] Add new Kubernetes version to e2e tests"
65-
body: |
66-
### What does this PR do?
67-
Adds the latest Kubernetes version from kindest/node to the e2e test matrix.
68-
69-
### Motivation
70-
Keep e2e tests running against the latest Kubernetes version to ensure compatibility.
59+
dda inv kind-node-image.build-rc-images --versions='${{ steps.fetch-versions.outputs.new_versions }}'
7160
72-
### New version added:
73-
${{ steps.update-yaml.outputs.new_versions }}
61+
# - name: Update e2e.yml with new version
62+
# id: update-yaml
63+
# if: steps.fetch-versions.outputs.has_new_versions == 'true'
64+
# run: |
65+
# dda inv k8s-versions.update-e2e-yaml
7466

75-
### Describe how you validated your changes
76-
CI will validate the new versions work correctly.
77-
team-reviewers: container-integrations
78-
labels: team/container-integrations,qa/done,changelog/no-changelog,ask-review
67+
# - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
68+
# name: Create pull request
69+
# if: steps.update-yaml.outputs.updated == 'true'
70+
# with:
71+
# commit-message: "chore(e2e): add new Kubernetes version to e2e tests"
72+
# branch: update-k8s-versions-automated
73+
# token: ${{ steps.octo-sts.outputs.token }}
74+
# sign-commits: true
75+
# title: "[automated] Add new Kubernetes version to e2e tests"
76+
# body: |
77+
# ### What does this PR do?
78+
# Adds the latest Kubernetes version from kindest/node to the e2e test matrix.
79+
#
80+
# ### Motivation
81+
# Keep e2e tests running against the latest Kubernetes version to ensure compatibility.
82+
#
83+
# ### New version added:
84+
# ${{ steps.update-yaml.outputs.new_versions }}
85+
#
86+
# ### Describe how you validated your changes
87+
# CI will validate the new versions work correctly.
88+
# team-reviewers: container-integrations
89+
# labels: team/container-integrations,qa/done,changelog/no-changelog,ask-review

tasks/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
invoke_unit_tests,
4343
issue,
4444
k8s_versions,
45+
kind_node_image,
4546
kmt,
4647
linter,
4748
loader,
@@ -228,6 +229,7 @@
228229
ns.add_collection(fakeintake)
229230
ns.add_collection(kmt)
230231
ns.add_collection(k8s_versions)
232+
ns.add_collection(kind_node_image)
231233
ns.add_collection(diff)
232234
ns.add_collection(installer)
233235
ns.add_collection(owners)

tasks/k8s_versions.py

Lines changed: 146 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
import os
1111
import re
1212
import sys
13+
from functools import total_ordering
1314

1415
from invoke.exceptions import Exit
1516
from invoke.tasks import task
1617

18+
from tasks.kind_node_image import get_github_rc_releases
19+
1720
try:
1821
import requests
1922
except ImportError:
@@ -30,6 +33,51 @@
3033
E2E_YAML_PATH = ".gitlab/e2e/e2e.yml"
3134

3235

36+
@total_ordering
37+
class Version:
38+
"""Kubernetes version with proper semantic version comparison.
39+
40+
Handles both final releases and RC (release candidate) versions:
41+
- v1.35.0-rc.1 < v1.35.0-rc.2 < v1.35.0
42+
"""
43+
44+
def __init__(self, major: int, minor: int, patch: int, rc: int | None = None):
45+
self.major = major
46+
self.minor = minor
47+
self.patch = patch
48+
self.rc = rc # None for final releases, number for RC versions
49+
50+
def __eq__(self, other):
51+
if not isinstance(other, Version):
52+
return NotImplemented
53+
return (self.major, self.minor, self.patch, self.rc) == (
54+
other.major,
55+
other.minor,
56+
other.patch,
57+
other.rc,
58+
)
59+
60+
def __lt__(self, other):
61+
if not isinstance(other, Version):
62+
return NotImplemented
63+
64+
# Compare major, minor, patch first
65+
# If they aren't equal, then return the tuple comparison
66+
if (self.major, self.minor, self.patch) != (other.major, other.minor, other.patch):
67+
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
68+
69+
# If base versions are equal, compare RC status
70+
# RC versions are less than final releases: v1.35.0-rc.1 < v1.35.0
71+
if self.rc is None:
72+
return False # self is final (>= any RC or final with same base version)
73+
return other.rc is None or self.rc < other.rc # self is RC, so less than final OR compare RC numbers
74+
75+
def __repr__(self):
76+
if self.rc is None:
77+
return f"Version({self.major}.{self.minor}.{self.patch})"
78+
return f"Version({self.major}.{self.minor}.{self.patch}-rc.{self.rc})"
79+
80+
3381
def _check_dependencies():
3482
"""Check if required dependencies are installed."""
3583
missing = []
@@ -45,14 +93,27 @@ def _check_dependencies():
4593
)
4694

4795

48-
def _parse_version(version_str: str) -> tuple[int, int, int] | None:
96+
def _parse_version(version_str: str) -> Version | None:
4997
"""
50-
Parse a Kubernetes version string like 'v1.34.0' into a tuple (1, 34, 0).
98+
Parse a Kubernetes version string into a Version object.
99+
100+
Examples:
101+
'v1.34.0' -> Version(1, 34, 0)
102+
'v1.35.0-rc.1' -> Version(1, 35, 0, rc=1)
103+
51104
Returns None if the version string is invalid.
52105
"""
106+
# Match a release version
53107
match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)$', version_str)
54108
if match:
55-
return tuple(map(int, match.groups()))
109+
major, minor, patch = map(int, match.groups())
110+
return Version(major, minor, patch)
111+
# Match an RC version
112+
rc_match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)\-rc\.(\d+)$', version_str)
113+
if rc_match:
114+
major, minor, patch, rc = map(int, rc_match.groups())
115+
return Version(major, minor, patch, rc=rc)
116+
56117
return None
57118

58119

@@ -92,11 +153,13 @@ def _get_latest_k8s_versions() -> dict[str, dict[str, str]]:
92153
Fetch and parse the latest Kubernetes version from Docker Hub.
93154
Returns a dictionary with only the single latest version.
94155
"""
95-
tags = _get_docker_hub_tags()
96156

97157
# Filter for valid Kubernetes version tags
98158
version_tags = []
99-
for tag in tags:
159+
160+
# Final release Kubernetes version tags
161+
_ = _get_docker_hub_tags() # TODO: add this back when testing of RCs is done
162+
for tag in []:
100163
tag_name = tag.get('name', '')
101164
version = _parse_version(tag_name)
102165

@@ -105,13 +168,40 @@ def _get_latest_k8s_versions() -> dict[str, dict[str, str]]:
105168
if digest:
106169
version_tags.append({'version': version, 'tag': tag_name, 'digest': digest})
107170

171+
# RC Kubernetes version tags
172+
for tag in get_github_rc_releases():
173+
tag_name = tag.get('tag_name', '')
174+
tarball = tag.get('tarball_url')
175+
version = _parse_version(tag_name)
176+
if version and tag_name and tarball:
177+
# Hardcode 'rc' to True because get_github_rc_releases() only returns rc releases
178+
version_tags.append({'version': version, 'tag': tag_name, 'rc': True, 'tarball': tarball})
179+
108180
# Sort by version (major, minor, patch)
109181
version_tags.sort(key=lambda x: x['version'], reverse=True)
110182

111183
# Return only the single latest version
112184
if version_tags:
113185
latest = version_tags[0]
114-
return {latest['tag']: {'digest': latest['digest'], 'tag': latest['tag']}}
186+
187+
# Parse out the necessary fields
188+
tag = latest.get('tag')
189+
digest = latest.get('digest')
190+
rc = latest.get('rc')
191+
tarball = latest.get('tarball')
192+
193+
# Build return dictionary
194+
# Structure: {tag_name: {'tag': tag_name, 'digest': digest?, 'rc': bool?, 'tarball': url?}}
195+
# Final releases include 'digest', RC releases include 'rc' and 'tarball'
196+
if tag:
197+
result = {tag: {'tag': tag}}
198+
if digest:
199+
result[tag]['digest'] = digest
200+
if rc:
201+
result[tag]['rc'] = rc
202+
if tarball:
203+
result[tag]['tarball'] = tarball
204+
return result
115205

116206
return {}
117207

@@ -140,7 +230,7 @@ def _find_new_versions(
140230
new_versions = {}
141231

142232
for version, data in current.items():
143-
if version not in previous or previous[version]['digest'] != data['digest']:
233+
if version not in previous or previous[version].get('digest') != data.get('digest'):
144234
new_versions[version] = data
145235

146236
return new_versions
@@ -333,7 +423,7 @@ def fetch_versions(_, output_file=VERSIONS_FILE):
333423
"""
334424
_check_dependencies()
335425

336-
print("Fetching latest Kubernetes version from Docker Hub...")
426+
print("Fetching latest Kubernetes version from Docker Hub and GitHub...")
337427
current_versions = _get_latest_k8s_versions()
338428

339429
if not current_versions:
@@ -344,20 +434,24 @@ def fetch_versions(_, output_file=VERSIONS_FILE):
344434
# Show the latest version
345435
latest_version = list(current_versions.keys())[0]
346436
latest_data = current_versions[latest_version]
437+
347438
print(f"Latest Kubernetes version: {latest_version}")
348-
print(f" Digest: {latest_data['digest']}")
439+
if latest_data.get('tarball'):
440+
print(f" Tarball: {latest_data['tarball']}")
441+
else:
442+
print(f" Digest: {latest_data['digest']}")
349443

350444
# Load previous versions and compare
351445
previous_versions = _load_existing_versions(output_file)
352446
new_versions = _find_new_versions(current_versions, previous_versions)
353447

354448
if new_versions:
355-
print("\nNew version found!")
449+
print("\nNew version(s) found!")
356450
for version, data in new_versions.items():
357-
print(f" {version}: {data['digest']}")
358-
359-
# Save current versions for next run
360-
_save_versions(current_versions, output_file)
451+
if data.get('tarball'):
452+
print(f" {version}: {data['tarball']}")
453+
else:
454+
print(f" {version}: {data['digest']}")
361455

362456
# Set GitHub Actions outputs
363457
_set_github_output('has_new_versions', 'true')
@@ -408,3 +502,41 @@ def update_e2e_yaml(_, versions_file=VERSIONS_FILE):
408502
else:
409503
_set_github_output('updated', 'false')
410504
print("\nNo updates made")
505+
506+
507+
@task
508+
def save_versions(_, versions, versions_file=VERSIONS_FILE):
509+
"""
510+
Save multiple Kubernetes versions to the versions file.
511+
512+
This task merges the provided versions with existing versions in the file,
513+
preserving existing entries and adding new ones.
514+
515+
Args:
516+
versions: JSON string or dict mapping version tags to version data
517+
(e.g., '{"v1.35.0": {"tag": "v1.35.0", "digest": "sha256:..."}}')
518+
versions_file: Path to the JSON file to store versions (default: k8s_versions.json)
519+
"""
520+
521+
# Parse if it's a JSON string
522+
if isinstance(versions, str):
523+
try:
524+
versions = json.loads(versions)
525+
except json.JSONDecodeError as e:
526+
raise Exit(f"Invalid JSON in versions argument: {e}", code=1) from e
527+
528+
# Load existing versions
529+
existing_versions = _load_existing_versions(versions_file)
530+
531+
# Safely append the passed in dictionary items to the version list
532+
for outer_tag, version in versions.items():
533+
inner_tag = version.get('tag')
534+
digest = version.get('digest')
535+
if not inner_tag or not digest:
536+
print(f"Version {outer_tag} is missing required field tag or digest, skipping...")
537+
continue
538+
539+
existing_versions[outer_tag] = {'tag': inner_tag, 'digest': digest}
540+
541+
# Save to file
542+
_save_versions(existing_versions, versions_file)

0 commit comments

Comments
 (0)