Skip to content

Commit 133a236

Browse files
Semver support (#219)
* images info for release notes * working deployment platform classes * removing unused code * gitignore update * cleanup; tests for github cli * gitignore update * semver support for release notes * black-formatting applied + fixes as per PR review * setting minor and patch versions to None to please type checker * updated failing test * output separators for "make lint" * better lint output separators * DistroVersion class * class-based DistroVersion * making pyright happier * Fix remaining type issues due to implicit None return in Version Factory method --------- Co-authored-by: Tiara Lena Hock <hock@b1-systems.de>
1 parent 2c47ca5 commit 133a236

File tree

5 files changed

+261
-69
lines changed

5 files changed

+261
-69
lines changed

Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,20 @@ format: install-dev
6262
$(POETRY) run black --extend-exclude test-data/gardenlinux .
6363

6464
lint: install-dev
65+
@echo
66+
@echo "------------------------------------------------------------------------------------------------------------------------"
67+
@echo "--// BLACK //-----------------------------------------------------------------------------------------------------------"
68+
@echo "------------------------------------------------------------------------------------------------------------------------"
6569
$(POETRY) run black --diff --extend-exclude test-data/gardenlinux .
70+
@echo
71+
@echo "------------------------------------------------------------------------------------------------------------------------"
72+
@echo "--// ISORT //-----------------------------------------------------------------------------------------------------------"
73+
@echo "------------------------------------------------------------------------------------------------------------------------"
6674
$(POETRY) run isort --check-only .
75+
@echo
76+
@echo "------------------------------------------------------------------------------------------------------------------------"
77+
@echo "--// PYRIGHT //---------------------------------------------------------------------------------------------------------"
78+
@echo "------------------------------------------------------------------------------------------------------------------------"
6779
$(POETRY) run pyright
6880

6981
security: install-dev
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
class UnsupportedDistroVersion(Exception):
2+
pass
3+
4+
5+
class NotAPatchRelease(Exception):
6+
pass
7+
8+
9+
def DistroVersion(maybe_distro_version):
10+
version_components = maybe_distro_version.split(".")
11+
if len(version_components) > 3 or len(version_components) < 2:
12+
raise UnsupportedDistroVersion(
13+
f"Unexpected version number format {maybe_distro_version}"
14+
)
15+
16+
if not all(map(lambda x: x.isdigit(), version_components)):
17+
raise UnsupportedDistroVersion(
18+
f"Unexpected version number format {maybe_distro_version}"
19+
)
20+
21+
if len(version_components) == 2:
22+
return LegacyDistroVersion(*(int(c) for c in version_components))
23+
elif len(version_components) == 3:
24+
return SemverDistroVersion(*(int(c) for c in version_components))
25+
else:
26+
raise UnsupportedDistroVersion(
27+
f"Unexpected number of version components: {maybe_distro_version}"
28+
)
29+
30+
31+
class BaseDistroVersion:
32+
major = None
33+
minor = None
34+
patch = None
35+
36+
def is_patch_release(self):
37+
return self.patch and self.patch > 0
38+
39+
40+
class LegacyDistroVersion(BaseDistroVersion):
41+
def __init__(self, major, patch):
42+
self.major = major
43+
self.patch = patch
44+
45+
def __str__(self):
46+
return f"{self.major}.{self.patch}"
47+
48+
def previous_patch_release(self):
49+
if not self.is_patch_release():
50+
raise NotAPatchRelease(f"{self} is not a patch release")
51+
52+
return LegacyDistroVersion(self.major, self.patch - 1)
53+
54+
55+
class SemverDistroVersion(BaseDistroVersion):
56+
def __init__(self, major, minor, patch):
57+
self.major = major
58+
self.minor = minor
59+
self.patch = patch
60+
61+
def __str__(self):
62+
return f"{self.major}.{self.minor}.{self.patch}"
63+
64+
def previous_patch_release(self):
65+
if not self.is_patch_release():
66+
raise NotAPatchRelease(f"{self} is not a patch release")
67+
68+
return SemverDistroVersion(self.major, self.minor, self.patch - 1)

src/gardenlinux/github/release_notes/sections.py

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from yaml import SafeLoader
77

88
from gardenlinux.constants import GLVD_BASE_URL, REQUESTS_TIMEOUTS
9+
from gardenlinux.distro_version import DistroVersion
910
from gardenlinux.logger import LoggerSetup
1011

1112
from .deployment_platform.ali_cloud import AliCloud
@@ -19,7 +20,11 @@
1920
LOGGER = LoggerSetup.get_logger("gardenlinux.github.release_notes", "INFO")
2021

2122
IMAGE_IDS_VARIANT_ORDER = ["legacy", "usi", "tpm2_trustedboot"]
22-
IMAGE_IDS_VARIANT_TABLE_NAMES = {"legacy": "Default", "usi": "USI", "tpm2_trustedboot": "TPM2"}
23+
IMAGE_IDS_VARIANT_TABLE_NAMES = {
24+
"legacy": "Default",
25+
"usi": "USI",
26+
"tpm2_trustedboot": "TPM2",
27+
}
2328
IMAGE_IDS_VARIANT_NAMES = {
2429
"legacy": "Default",
2530
"usi": "USI (Unified System Image)",
@@ -98,41 +103,26 @@ def release_notes_software_components_section(package_list):
98103

99104

100105
def release_notes_compare_package_versions_section(gardenlinux_version, package_list):
106+
version = DistroVersion(gardenlinux_version)
101107
output = ""
102-
version_components = gardenlinux_version.split(".")
103-
# Assumes we always have version numbers like 1443.2
104-
if len(version_components) == 2:
105-
try:
106-
major = int(version_components[0])
107-
patch = int(version_components[1])
108-
109-
if patch > 0:
110-
previous_version = f"{major}.{patch - 1}"
111-
112-
output += (
113-
f"## Changes in Package Versions Compared to {previous_version}\n"
114-
)
115-
output += compare_apt_repo_versions(
116-
previous_version, gardenlinux_version
117-
)
118-
elif patch == 0:
119-
output += f"## Full List of Packages in Garden Linux version {major}\n"
120-
output += "<details><summary>Expand to see full list</summary>\n"
121-
output += "<pre>"
122-
output += "\n"
123-
for entry in package_list.values():
124-
output += f"{entry!r}\n"
125-
output += "</pre>"
126-
output += "\n</details>\n\n"
127-
128-
except ValueError:
129-
LOGGER.error(
130-
f"Could not parse {gardenlinux_version} as the Garden Linux version, skipping version compare section"
131-
)
108+
109+
if version.is_patch_release():
110+
previous_version = f"{version.previous_patch_release()}"
111+
112+
output += f"## Changes in Package Versions Compared to {previous_version}\n"
113+
output += compare_apt_repo_versions(previous_version, gardenlinux_version)
132114
else:
133-
LOGGER.error(
134-
f"Unexpected version number format {gardenlinux_version}, expected format (major is int).(patch is int)"
115+
output += (
116+
f"## Full List of Packages in Garden Linux version {gardenlinux_version}\n"
135117
)
118+
output += "<details><summary>Expand to see full list</summary>\n"
119+
output += "<pre>"
120+
output += "\n"
121+
for entry in package_list.values():
122+
output += f"{entry!r}\n"
123+
output += "</pre>"
124+
output += "\n</details>\n\n"
125+
136126
return output
137127

138128

@@ -157,16 +147,20 @@ def generate_table_format(grouped_data):
157147
details_content = PLATFORMS[platform].region_details(metadata)
158148
summary_text = PLATFORMS[platform].summary_text(metadata)
159149

160-
download_link = PLATFORMS[platform].artifact_for_flavor(data['flavor'])
150+
download_link = PLATFORMS[platform].artifact_for_flavor(
151+
data["flavor"]
152+
)
161153

162154
variant_display = IMAGE_IDS_VARIANT_TABLE_NAMES[variant]
163-
output += (f"| {variant_display} "
164-
f"| {PLATFORMS[platform].full_name()} "
165-
f"| {arch} "
166-
f"| `{data['flavor']}` "
167-
f"| <details><summary>{summary_text}</summary><br>{details_content}</details> "
168-
f"| <details><summary>Download</summary><br>{download_link}</details> "
169-
"|\n")
155+
output += (
156+
f"| {variant_display} "
157+
f"| {PLATFORMS[platform].full_name()} "
158+
f"| {arch} "
159+
f"| `{data['flavor']}` "
160+
f"| <details><summary>{summary_text}</summary><br>{details_content}</details> "
161+
f"| <details><summary>Download</summary><br>{download_link}</details> "
162+
"|\n"
163+
)
170164

171165
return output
172166

@@ -181,9 +175,7 @@ def generate_detailed_format(grouped_data):
181175
if variant not in grouped_data:
182176
continue
183177

184-
output += (
185-
f"<details>\n<summary>Variant - {IMAGE_IDS_VARIANT_NAMES[variant]}</summary>\n\n"
186-
)
178+
output += f"<details>\n<summary>Variant - {IMAGE_IDS_VARIANT_NAMES[variant]}</summary>\n\n"
187179
output += f"### Variant - {IMAGE_IDS_VARIANT_NAMES[variant]}\n\n"
188180

189181
for platform in sorted(grouped_data[variant].keys()):
@@ -205,7 +197,9 @@ def generate_detailed_format(grouped_data):
205197

206198
output += f"- flavor: {data['flavor']}\n"
207199

208-
download_url = PLATFORMS[platform].artifact_for_flavor(data['flavor'], markdown_format=False)
200+
download_url = PLATFORMS[platform].artifact_for_flavor(
201+
data["flavor"], markdown_format=False
202+
)
209203
output += f" download_url: {download_url}\n"
210204

211205
if "regions" in data:
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import pytest
2+
3+
from gardenlinux.distro_version import (
4+
DistroVersion,
5+
LegacyDistroVersion,
6+
NotAPatchRelease,
7+
SemverDistroVersion,
8+
UnsupportedDistroVersion,
9+
)
10+
11+
12+
def test_distro_version_unrecognizable_non_numeric_version():
13+
with pytest.raises(UnsupportedDistroVersion):
14+
DistroVersion("garden.linux")
15+
16+
17+
def test_distro_version_unrecognizable_numeric_version():
18+
with pytest.raises(UnsupportedDistroVersion):
19+
DistroVersion("1.2.3.4")
20+
with pytest.raises(UnsupportedDistroVersion):
21+
DistroVersion("1.100.-10")
22+
23+
24+
def test_distro_version_unrecognizable_too_short_version():
25+
with pytest.raises(UnsupportedDistroVersion):
26+
DistroVersion("1")
27+
28+
29+
def test_distro_version_legacy_version_is_parsable():
30+
assert isinstance(DistroVersion("1.2"), LegacyDistroVersion)
31+
32+
33+
def test_distro_version_semver_version_is_parsable():
34+
assert isinstance(DistroVersion("1.2.3"), SemverDistroVersion)
35+
36+
37+
def test_distro_version_patch_release_is_recognized():
38+
assert DistroVersion("1.1").is_patch_release()
39+
assert DistroVersion("1.1.100").is_patch_release()
40+
assert not DistroVersion("1.0").is_patch_release()
41+
assert not DistroVersion("1.0.0").is_patch_release()
42+
43+
44+
def test_distro_version_previous_patch_release_is_recognized():
45+
assert DistroVersion("1.1").previous_patch_release().__str__() == "1.0"
46+
assert DistroVersion("1.1.100").previous_patch_release().__str__() == "1.1.99"
47+
with pytest.raises(NotAPatchRelease):
48+
DistroVersion("1.0").previous_patch_release()
49+
with pytest.raises(NotAPatchRelease):
50+
DistroVersion("1.100.0").previous_patch_release()

0 commit comments

Comments
 (0)