Skip to content

Commit 6a5edeb

Browse files
committed
Remove tarball requirement for image build, parse image digests
1 parent f8625c6 commit 6a5edeb

2 files changed

Lines changed: 175 additions & 166 deletions

File tree

tasks/k8s_versions.py

Lines changed: 46 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import os
1111
import re
1212
import sys
13-
from functools import total_ordering
1413

1514
from invoke.exceptions import Exit
1615
from invoke.tasks import task
@@ -27,64 +26,25 @@
2726
except ImportError:
2827
yaml = None
2928

29+
try:
30+
import semver
31+
except ImportError:
32+
semver = None
3033

3134
DOCKER_HUB_API_URL = "https://hub.docker.com/v2/repositories/kindest/node/tags"
3235
VERSIONS_FILE = "k8s_versions.json"
3336
E2E_YAML_PATH = ".gitlab/e2e/e2e.yml"
3437

3538

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-
8139
def _check_dependencies():
8240
"""Check if required dependencies are installed."""
8341
missing = []
8442
if requests is None:
8543
missing.append('requests')
8644
if yaml is None:
8745
missing.append('pyyaml')
46+
if semver is None:
47+
missing.append('semver')
8848

8949
if missing:
9050
raise Exit(
@@ -93,28 +53,26 @@ def _check_dependencies():
9353
)
9454

9555

96-
def _parse_version(version_str: str) -> Version | None:
56+
def _parse_version(version_str: str) -> semver.VersionInfo | None:
9757
"""
98-
Parse a Kubernetes version string into a Version object.
58+
Parse a Kubernetes version string into a semver VersionInfo object.
59+
60+
Semver naturally handles RC versions correctly:
61+
- v1.35.0-rc.1 < v1.35.0-rc.2 < v1.35.0
9962
10063
Examples:
101-
'v1.34.0' -> Version(1, 34, 0)
102-
'v1.35.0-rc.1' -> Version(1, 35, 0, rc=1)
64+
'v1.34.0' -> VersionInfo(1, 34, 0)
65+
'v1.35.0-rc.1' -> VersionInfo(1, 35, 0, prerelease='rc.1')
10366
10467
Returns None if the version string is invalid.
10568
"""
106-
# Match a release version
107-
match = re.match(r'^v?(\d+)\.(\d+)\.(\d+)$', version_str)
108-
if match:
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)
69+
# Remove leading 'v' if present
70+
clean_version = version_str.lstrip('v')
11671

117-
return None
72+
try:
73+
return semver.VersionInfo.parse(clean_version)
74+
except (ValueError, AttributeError):
75+
return None
11876

11977

12078
def _get_docker_hub_tags() -> list[dict]:
@@ -158,8 +116,7 @@ def _get_latest_k8s_versions() -> dict[str, dict[str, str]]:
158116
version_tags = []
159117

160118
# Final release Kubernetes version tags
161-
_ = _get_docker_hub_tags() # TODO: add this back when testing of RCs is done
162-
for tag in []:
119+
for tag in _get_docker_hub_tags():
163120
tag_name = tag.get('name', '')
164121
version = _parse_version(tag_name)
165122

@@ -171,11 +128,10 @@ def _get_latest_k8s_versions() -> dict[str, dict[str, str]]:
171128
# RC Kubernetes version tags
172129
for tag in get_github_rc_releases():
173130
tag_name = tag.get('tag_name', '')
174-
tarball = tag.get('tarball_url')
175131
version = _parse_version(tag_name)
176-
if version and tag_name and tarball:
132+
if version and tag_name:
177133
# 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})
134+
version_tags.append({'version': version, 'tag': tag_name, 'rc': True})
179135

180136
# Sort by version (major, minor, patch)
181137
version_tags.sort(key=lambda x: x['version'], reverse=True)
@@ -188,19 +144,16 @@ def _get_latest_k8s_versions() -> dict[str, dict[str, str]]:
188144
tag = latest.get('tag')
189145
digest = latest.get('digest')
190146
rc = latest.get('rc')
191-
tarball = latest.get('tarball')
192147

193148
# 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'
149+
# Structure: {tag_name: {'tag': tag_name, 'digest': digest?, 'rc': bool?}}
150+
# Final releases include 'digest', RC releases include 'rc'
196151
if tag:
197152
result = {tag: {'tag': tag}}
198153
if digest:
199154
result[tag]['digest'] = digest
200155
if rc:
201156
result[tag]['rc'] = rc
202-
if tarball:
203-
result[tag]['tarball'] = tarball
204157
return result
205158

206159
return {}
@@ -226,11 +179,29 @@ def _save_versions(versions: dict[str, dict[str, str]], versions_file: str) -> N
226179
def _find_new_versions(
227180
current: dict[str, dict[str, str]], previous: dict[str, dict[str, str]]
228181
) -> dict[str, dict[str, str]]:
229-
"""Find versions that are new or have different digests."""
182+
"""Find versions that are new or have different digests.
183+
184+
Notes:
185+
- RC versions from GitHub won't have digests initially
186+
- Only compare digests if BOTH current and previous have them
187+
- If current has no digest (RC from GitHub) and version exists, don't mark as new
188+
"""
230189
new_versions = {}
231190

232191
for version, data in current.items():
233-
if version not in previous or previous[version].get('digest') != data.get('digest'):
192+
# Version doesn't exist in previous - it's new
193+
if version not in previous:
194+
new_versions[version] = data
195+
continue
196+
197+
# Version exists - check if digest changed
198+
current_digest = data.get('digest')
199+
previous_digest = previous[version].get('digest')
200+
201+
# Only compare digests if BOTH have them
202+
# This prevents RC versions (no digest from GitHub) from being marked as new
203+
# when they already exist in the saved file (with digest from build)
204+
if current_digest and previous_digest and current_digest != previous_digest:
234205
new_versions[version] = data
235206

236207
return new_versions
@@ -436,10 +407,7 @@ def fetch_versions(_, output_file=VERSIONS_FILE):
436407
latest_data = current_versions[latest_version]
437408

438409
print(f"Latest Kubernetes version: {latest_version}")
439-
if latest_data.get('tarball'):
440-
print(f" Tarball: {latest_data['tarball']}")
441-
else:
442-
print(f" Digest: {latest_data['digest']}")
410+
print(f" Digest: {latest_data.get('digest', 'Digest unknown')}")
443411

444412
# Load previous versions and compare
445413
previous_versions = _load_existing_versions(output_file)
@@ -448,10 +416,7 @@ def fetch_versions(_, output_file=VERSIONS_FILE):
448416
if new_versions:
449417
print("\nNew version(s) found!")
450418
for version, data in new_versions.items():
451-
if data.get('tarball'):
452-
print(f" {version}: {data['tarball']}")
453-
else:
454-
print(f" {version}: {data['digest']}")
419+
print(f" {version}: {data.get('digest', 'Digest unknown')}")
455420

456421
# Set GitHub Actions outputs
457422
_set_github_output('has_new_versions', 'true')

0 commit comments

Comments
 (0)