Skip to content

Commit 0717508

Browse files
committed
Add workaround to accept GL canonical names without a commit ID
Fixes: #302 Signed-off-by: Tobias Wolf <wolf@b1-systems.de> On-behalf-of: SAP <tobias.wolf@sap.com>
1 parent 7535722 commit 0717508

10 files changed

Lines changed: 120 additions & 81 deletions

File tree

.github/actions/features_parse/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ outputs:
1111
runs:
1212
using: composite
1313
steps:
14-
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.10.10
14+
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.10.11
1515
- id: result
1616
shell: bash
1717
run: |

.github/actions/flavors_parse/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ outputs:
1313
runs:
1414
using: composite
1515
steps:
16-
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.10.10
16+
- uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@0.10.11
1717
- id: matrix
1818
shell: bash
1919
run: |

.github/actions/setup/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: Installs the given GardenLinux Python library
44
inputs:
55
version:
66
description: GardenLinux Python library version
7-
default: "0.10.10"
7+
default: "0.10.11"
88
python_version:
99
description: Python version to setup
1010
default: "3.13"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "gardenlinux"
3-
version = "0.10.10"
3+
version = "0.10.11"
44
description = "Contains tools to work with the features directory of gardenlinux, for example deducting dependencies from feature sets or validating cnames"
55
authors = ["Garden Linux Maintainers <contact@gardenlinux.io>"]
66
license = "Apache-2.0"

src/gardenlinux/features/cname.py

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,46 @@ def __init__(
6969

7070
commit_id_or_hash = None
7171

72-
re_match = re.match(
73-
"([a-zA-Z0-9]+([\\_\\-][a-zA-Z0-9]+)*?)(-([a-z0-9]+)(-([a-z0-9.]+)-([a-z0-9]+))*)?$",
74-
cname,
72+
if version is not None:
73+
# Support version values formatted as <version>-<commit_hash>
74+
if commit_hash is None:
75+
re_match = re.match("([a-z0-9.]+)(-([a-z0-9]+))?$", version)
76+
assert re_match, f"Not a valid version {version}"
77+
78+
commit_id_or_hash = re_match[3]
79+
version = re_match[1]
80+
else:
81+
commit_id_or_hash = commit_hash
82+
83+
re_object = re.compile(
84+
"([a-zA-Z0-9]+([\\_\\-][a-zA-Z0-9]+)*?)(-([a-z0-9]+)(-([a-z0-9.]+)-([a-z0-9]+))*)?$"
7585
)
7686

87+
re_match = re_object.match(cname)
88+
89+
# Workaround Garden Linux canonical names without mandatory final commit hash
90+
if (
91+
not re_match
92+
and commit_id_or_hash
93+
and re.match(
94+
"([a-zA-Z0-9]+([\\_\\-][a-zA-Z0-9]+)*?)(-([a-z0-9]+)(-([a-z0-9.]+))*)?$",
95+
cname,
96+
)
97+
):
98+
re_match = re_object.match(f"{cname}-{commit_id_or_hash}")
99+
77100
assert re_match, f"Not a valid Garden Linux canonical name {cname}"
78101

79102
if re_match.lastindex == 1:
80103
self._flavor = re_match[1]
81104
else:
82-
commit_id_or_hash = re_match[7]
105+
if commit_id_or_hash is None:
106+
commit_id_or_hash = re_match[7]
107+
elif re_match.group(7) is not None:
108+
assert commit_id_or_hash.startswith(re_match[7]), (
109+
f"Mismatch between Garden Linux canonical name '{cname}' and given commit ID '{commit_id_or_hash}' detected"
110+
)
111+
83112
self._flavor = re_match[1]
84113
self._version = re_match[6]
85114

@@ -91,17 +120,13 @@ def __init__(
91120
if self._arch is None and arch is not None:
92121
self._arch = arch
93122

94-
if self._version is None and version is not None:
95-
# Support version values formatted as <version>-<commit_hash>
96-
if commit_hash is None:
97-
re_match = re.match("([a-z0-9.]+)(-([a-z0-9]+))?$", version)
98-
assert re_match, f"Not a valid version {version}"
99-
100-
commit_id_or_hash = re_match[3]
101-
self._version = re_match[1]
102-
else:
103-
commit_id_or_hash = commit_hash
123+
if version is not None:
124+
if self._version is None:
104125
self._version = version
126+
else:
127+
assert version == self._version, (
128+
f"Mismatch between Garden Linux canonical name '{cname}' and given version '{version}' detected"
129+
)
105130

106131
if commit_id_or_hash is not None:
107132
self._commit_id = commit_id_or_hash[:8]
@@ -312,21 +337,36 @@ def release_metadata_string(self) -> str:
312337
assert len(features["platform"]) < 2
313338
"Only one platform is supported"
314339

340+
commit_hash = self.commit_hash
341+
commit_id = self.commit_id
315342
elements = ",".join(features["element"])
316343
flags = ",".join(features["flag"])
317344
platform = features["platform"][0]
318345
platforms = ",".join(features["platform"])
319346
platform_variant = self.platform_variant
347+
version = self.version
348+
349+
if commit_id is None:
350+
commit_id = ""
351+
352+
if commit_hash is None:
353+
commit_hash = commit_id
320354

321355
if platform_variant is None:
322356
platform_variant = ""
323357

358+
if version is None:
359+
pretty_name = f"{GL_DISTRIBUTION_NAME} unsupported version"
360+
version = ""
361+
else:
362+
pretty_name = f"{GL_DISTRIBUTION_NAME} {version}"
363+
324364
metadata = f"""
325365
ID={GL_RELEASE_ID}
326366
ID_LIKE=debian
327367
NAME="{GL_DISTRIBUTION_NAME}"
328-
PRETTY_NAME="{GL_DISTRIBUTION_NAME} {self.version}"
329-
IMAGE_VERSION={self.version}
368+
PRETTY_NAME="{pretty_name}"
369+
IMAGE_VERSION={version}
330370
VARIANT_ID="{self.flavor}-{self.arch}"
331371
HOME_URL="{GL_HOME_URL}"
332372
SUPPORT_URL="{GL_SUPPORT_URL}"
@@ -338,9 +378,9 @@ def release_metadata_string(self) -> str:
338378
GARDENLINUX_FEATURES_FLAGS="{flags}"
339379
GARDENLINUX_PLATFORM="{platform}"
340380
GARDENLINUX_PLATFORM_VARIANT="{platform_variant}"
341-
GARDENLINUX_VERSION="{self.version}"
342-
GARDENLINUX_COMMIT_ID="{self.commit_id}"
343-
GARDENLINUX_COMMIT_ID_LONG="{self.commit_hash}"
381+
GARDENLINUX_VERSION="{version}"
382+
GARDENLINUX_COMMIT_ID="{commit_id}"
383+
GARDENLINUX_COMMIT_ID_LONG="{commit_hash}"
344384
""".strip()
345385

346386
return metadata
@@ -365,7 +405,7 @@ def version_and_commit_id(self) -> Optional[str]:
365405
:since: 0.7.0
366406
"""
367407

368-
if self._commit_id is None:
408+
if self._version is None or self._commit_id is None:
369409
return None
370410

371411
return f"{self._version}-{self._commit_id}"

src/gardenlinux/s3/__main__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@
99

1010
from .s3_artifacts import S3Artifacts
1111

12-
_ARGS_ACTION_ALLOWED = [
13-
"download-artifacts-from-bucket",
14-
"upload-artifacts-to-bucket",
15-
]
16-
1712

1813
def main() -> None:
1914
"""
@@ -25,17 +20,22 @@ def main() -> None:
2520
parser = argparse.ArgumentParser()
2621

2722
parser.add_argument("--bucket", dest="bucket")
28-
parser.add_argument("--cname", required=False, dest="cname")
2923
parser.add_argument("--path", required=False, dest="path")
3024
parser.add_argument("--dry-run", action="store_true")
3125

32-
parser.add_argument("action", nargs="?", choices=_ARGS_ACTION_ALLOWED)
26+
subparsers = parser.add_subparsers(dest="action")
27+
28+
download_parser = subparsers.add_parser("download-artifacts-from-bucket")
29+
download_parser.add_argument("--cname", required=False, dest="cname")
30+
31+
upload_parser = subparsers.add_parser("upload-artifacts-to-bucket")
32+
upload_parser.add_argument("--artifact-name", required=False, dest="artifact_name")
3333

3434
args = parser.parse_args()
3535

3636
if args.action == "download-artifacts-from-bucket":
3737
S3Artifacts(args.bucket).download_to_directory(args.cname, args.path)
3838
elif args.action == "upload-artifacts-to-bucket":
3939
S3Artifacts(args.bucket).upload_from_directory(
40-
args.cname, args.path, dry_run=args.dry_run
40+
args.artifact_name, args.path, dry_run=args.dry_run
4141
)

src/gardenlinux/s3/s3_artifacts.py

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ def download_to_directory(
9898

9999
def upload_from_directory(
100100
self,
101-
cname: str,
101+
artifact_base_name: str,
102102
artifacts_dir: PathLike[str] | str,
103103
delete_before_push: bool = False,
104104
dry_run: bool = False,
105105
) -> None:
106106
"""
107107
Pushes S3 artifacts to the underlying bucket.
108108
109-
:param cname: Canonical name of the GardenLinux S3 artifacts
109+
:param artifact_base_name: Base name of the GardenLinux S3 artifacts
110110
:param artifacts_dir: Path of the image artifacts
111111
:param delete_before_push: True to delete objects before upload
112112
@@ -115,35 +115,32 @@ def upload_from_directory(
115115

116116
artifacts_dir = Path(artifacts_dir)
117117

118-
cname_object = CName(cname)
119-
120118
if not artifacts_dir.is_dir():
121119
raise RuntimeError(f"Artifacts directory given is invalid: {artifacts_dir}")
122120

123-
release_file = artifacts_dir.joinpath(f"{cname}.release")
124-
release_timestamp = stat(release_file).st_ctime
125-
126-
cname_object.load_from_release_file(release_file)
121+
release_file = artifacts_dir.joinpath(f"{artifact_base_name}.release")
127122

128-
if cname_object.arch is None:
129-
raise RuntimeError(
130-
"Architecture could not be determined from GardenLinux canonical name or release file"
131-
)
123+
cname_object = CName.new_from_release_file(release_file)
132124

133125
if cname_object.version_and_commit_id is None:
134126
raise RuntimeError(
135-
"Version information could not be determined from GardenLinux canonical name or release file"
127+
"Version information could not be determined from release file"
136128
)
137129

130+
arch = cname_object.arch
138131
feature_list = cname_object.feature_set
139-
requirements_file = artifacts_dir.joinpath(f"{cname}.requirements")
132+
release_timestamp = stat(release_file).st_ctime
133+
requirements_file = artifacts_dir.joinpath(f"{artifact_base_name}.requirements")
140134
require_uefi = None
141135
secureboot = None
142136

143137
if requirements_file.exists():
144138
requirements_config = ConfigParser(allow_unnamed_section=True)
145139
requirements_config.read(requirements_file)
146140

141+
if requirements_config.has_option(UNNAMED_SECTION, "arch"):
142+
arch = requirements_config.get(UNNAMED_SECTION, "arch")
143+
147144
if requirements_config.has_option(UNNAMED_SECTION, "uefi"):
148145
require_uefi = requirements_config.getboolean(UNNAMED_SECTION, "uefi")
149146

@@ -152,16 +149,25 @@ def upload_from_directory(
152149
UNNAMED_SECTION, "secureboot"
153150
)
154151

152+
if arch is None:
153+
raise RuntimeError(
154+
"Architecture could not be determined from release or requirements file"
155+
)
156+
155157
if require_uefi is None:
156158
require_uefi = "_usi" in feature_list
157159

158160
if secureboot is None:
159161
secureboot = "_trustedboot" in feature_list
160162

161-
commit_hash = cname_object.commit_hash
163+
# RegEx for S3 supported characters
164+
re_object = re.compile("[^a-zA-Z0-9\\s+\\-=.\\_:/@]")
165+
166+
arch = re_object.sub("+", arch)
167+
commit_id_or_hash = cname_object.commit_hash
162168

163-
if commit_hash is None:
164-
commit_hash = ""
169+
if commit_id_or_hash is None:
170+
commit_id_or_hash = cname_object.commit_id
165171

166172
version_epoch = str(cname_object.version_epoch)
167173

@@ -170,9 +176,9 @@ def upload_from_directory(
170176

171177
metadata = {
172178
"platform": cname_object.feature_set_platform,
173-
"architecture": cname_object.arch,
179+
"architecture": arch,
174180
"base_image": None,
175-
"build_committish": commit_hash,
181+
"build_committish": commit_id_or_hash,
176182
"build_timestamp": datetime.fromtimestamp(release_timestamp).isoformat(),
177183
"gardenlinux_epoch": {version_epoch},
178184
"logs": None,
@@ -181,7 +187,7 @@ def upload_from_directory(
181187
"secureboot": secureboot,
182188
"published_image_metadata": None,
183189
"s3_bucket": self._bucket.name,
184-
"s3_key": f"meta/singles/{cname}",
190+
"s3_key": f"meta/singles/{artifact_base_name}",
185191
"test_result": None,
186192
"version": cname_object.version,
187193
"paths": [],
@@ -192,24 +198,17 @@ def upload_from_directory(
192198
if platform_variant is not None:
193199
metadata["platform_variant"] = platform_variant
194200

195-
re_object = re.compile("[^a-zA-Z0-9\\s+\\-=.\\_:/@]")
196-
197201
for artifact in artifacts_dir.iterdir():
198-
if not artifact.match(f"{cname}*"):
202+
if not artifact.match(f"{artifact_base_name}*"):
199203
continue
200204

201-
if not artifact.name.startswith(cname):
202-
raise RuntimeError(
203-
f"Artifact name '{artifact.name}' does not start with cname '{cname}'"
204-
)
205-
206-
s3_key = f"objects/{cname}/{artifact.name}"
205+
s3_key = f"objects/{artifact_base_name}/{artifact.name}"
207206

208207
with artifact.open("rb") as fp:
209208
md5sum = file_digest(fp, "md5").hexdigest()
210209
sha256sum = file_digest(fp, "sha256").hexdigest()
211210

212-
suffix = artifact.name[len(cname) :]
211+
suffix = artifact.stem
213212

214213
artifact_metadata = {
215214
"name": artifact.name,
@@ -221,10 +220,10 @@ def upload_from_directory(
221220
}
222221

223222
s3_tags = {
224-
"architecture": re_object.sub("+", cname_object.arch),
223+
"architecture": arch,
225224
"platform": re_object.sub("+", cname_object.platform),
226225
"version": re_object.sub("+", cname_object.version), # type: ignore[arg-type]
227-
"committish": commit_hash,
226+
"committish": commit_id_or_hash,
228227
"md5sum": md5sum,
229228
"sha256sum": sha256sum,
230229
}
@@ -246,13 +245,13 @@ def upload_from_directory(
246245
else:
247246
if delete_before_push:
248247
self._bucket.delete_objects(
249-
Delete={"Objects": [{"Key": f"meta/singles/{cname}"}]}
248+
Delete={"Objects": [{"Key": f"meta/singles/{artifact_base_name}"}]}
250249
)
251250

252251
with TemporaryFile(mode="wb+") as fp:
253252
fp.write(yaml.dump(metadata).encode("utf-8"))
254253
fp.seek(0)
255254

256255
self._bucket.upload_fileobj(
257-
fp, f"meta/singles/{cname}", ExtraArgs={"ContentType": "text/yaml"}
256+
fp, f"meta/singles/{artifact_base_name}", ExtraArgs={"ContentType": "text/yaml"}
258257
)

tests/s3/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ def make_cname(
2727
flavor: str = "container",
2828
arch: str = "amd64",
2929
version: str = "1234.1",
30-
commit: str = "abc123",
30+
commit: str = "abc123long",
3131
) -> str:
3232
"""
3333
Helper function to build cname. Can be used to customized the cname.
3434
"""
35-
return f"{flavor}-{arch}-{version}-{commit}"
35+
return f"{flavor}-{arch}-{version}-{commit[:8]}"
3636

3737

3838
# Helpers to compute digests for fake files

0 commit comments

Comments
 (0)