Skip to content

Commit bb0645b

Browse files
Add support to generate container names and tags (#243)
* Add support to generate container names based on a given feature set * Add support to generate container tags based on given version input Signed-off-by: Tobias Wolf <[email protected]> On-behalf-of: SAP <[email protected]>
1 parent df923f9 commit bb0645b

File tree

5 files changed

+201
-41
lines changed

5 files changed

+201
-41
lines changed

src/gardenlinux/features/__main__.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import argparse
99
import logging
1010
import os
11+
import re
1112
from functools import reduce
1213
from os import path
1314
from typing import Any, List, Set
@@ -18,17 +19,25 @@
1819
_ARGS_TYPE_ALLOWED = [
1920
"cname",
2021
"cname_base",
22+
"container_name",
23+
"container_tag",
2124
"commit_id",
2225
"features",
2326
"platforms",
2427
"flags",
28+
"flavor",
2529
"elements",
2630
"arch",
2731
"version",
2832
"version_and_commit_id",
2933
"graph",
3034
]
3135

36+
RE_CAMEL_CASE_SPLITTER = re.compile("([A-Z]+|[a-z0-9])([A-Z])(?!$)")
37+
"""
38+
CamelCase splitter RegExp
39+
"""
40+
3241

3342
def main() -> None:
3443
"""
@@ -102,15 +111,26 @@ def main() -> None:
102111
commit_id_or_hash = cname.commit_id
103112
version = cname.version
104113

105-
if arch is None or arch == "" and (args.type in ("cname", "arch")):
114+
if (arch is None or arch == "") and (
115+
args.type in ("cname", "container_name", "arch")
116+
):
106117
raise RuntimeError(
107118
"Architecture could not be determined and no default architecture set"
108119
)
109120

110-
if (
111-
version is None
112-
or version == ""
113-
and (args.type in ("cname", "commit_id", "version", "version_and_commit_id"))
121+
if (commit_id_or_hash is None or commit_id_or_hash == "") and (
122+
args.type in ("container_tag", "commit_id", "version_and_commit_id")
123+
):
124+
raise RuntimeError("Commit ID not specified")
125+
126+
if (version is None or version == "") and (
127+
args.type
128+
in (
129+
"container_tag",
130+
"commit_id",
131+
"version",
132+
"version_and_commit_id",
133+
)
114134
):
115135
raise RuntimeError("Version not specified and no default version set")
116136

@@ -121,13 +141,15 @@ def main() -> None:
121141
elif args.type in (
122142
"cname_base",
123143
"cname",
144+
"container_name",
124145
"elements",
125146
"features",
126147
"flags",
148+
"flavor",
127149
"graph",
128150
"platforms",
129151
):
130-
if args.type == "graph" or len(args.ignore) > 1:
152+
if args.type == "graph" or len(args.ignore) > 0:
131153
features_parser = Parser(gardenlinux_root, feature_dir_name)
132154

133155
print_output_from_features_parser(
@@ -137,13 +159,15 @@ def main() -> None:
137159
print_output_from_cname(args.type, cname)
138160
elif args.type == "commit_id":
139161
print(commit_id_or_hash[:8])
162+
elif args.type == "container_tag":
163+
print(re.sub("\\W+", "-", f"{version}-{commit_id_or_hash[:8]}"))
140164
elif args.type == "version":
141165
print(version)
142166
elif args.type == "version_and_commit_id":
143167
print(f"{version}-{commit_id_or_hash[:8]}")
144168

145169

146-
def get_cname_base(sorted_features: List[str]):
170+
def get_flavor(sorted_features: List[str]):
147171
"""
148172
Get the base cname for the feature set given.
149173
@@ -265,7 +289,7 @@ def print_output_from_features_parser(
265289

266290
sorted_minimal_features = sort_subset(minimal_feature_set, sorted_features)
267291

268-
cname_base = get_cname_base(sorted_minimal_features)
292+
cname_base = get_flavor(sorted_minimal_features)
269293

270294
if output_type == "cname_base":
271295
print(cname_base)
@@ -279,7 +303,9 @@ def print_output_from_features_parser(
279303
cname += f"-{version}-{commit_id_or_hash[:8]}"
280304

281305
print(cname)
282-
if output_type == "platforms":
306+
elif output_type == "container_name":
307+
print(RE_CAMEL_CASE_SPLITTER.sub("\\1_\\2", cname_base).lower())
308+
elif output_type == "platforms":
283309
print(",".join(features_by_type["platform"]))
284310
elif output_type == "elements":
285311
print(",".join(features_by_type["element"]))
@@ -303,6 +329,8 @@ def print_output_from_cname(output_type: str, cname_instance: CName) -> None:
303329
print(cname_instance.flavor)
304330
elif output_type == "cname":
305331
print(cname_instance.cname)
332+
elif output_type == "container_name":
333+
print(RE_CAMEL_CASE_SPLITTER.sub("\\1-\\2", cname_instance.flavor).lower())
306334
elif output_type == "platforms":
307335
print(cname_instance.feature_set_platform)
308336
elif output_type == "elements":

src/gardenlinux/features/cname_main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from os.path import basename, dirname
1212

1313
from .__main__ import (
14-
get_cname_base,
14+
get_flavor,
1515
get_minimal_feature_set,
1616
get_version_and_commit_id_from_files,
1717
sort_subset,
@@ -80,7 +80,7 @@ def main():
8080

8181
sorted_minimal_features = sort_subset(minimal_feature_set, sorted_features)
8282

83-
generated_cname = get_cname_base(sorted_minimal_features)
83+
generated_cname = get_flavor(sorted_minimal_features)
8484

8585
generated_cname += f"-{cname.arch}"
8686

src/gardenlinux/features/parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ class Parser(object):
3030
Apache License, Version 2.0
3131
"""
3232

33-
_GARDENLINUX_ROOT: str = "."
33+
_GARDENLINUX_ROOT: str = os.getenv("GL_ROOT_DIR", ".")
3434
"""
3535
Default GardenLinux root directory
3636
"""
3737

3838
def __init__(
3939
self,
40-
gardenlinux_root: str | None = None,
41-
feature_dir_name: str = "features",
40+
gardenlinux_root: Optional[str] = None,
41+
feature_dir_name: Optional[str] = "features",
4242
logger: Optional[logging.Logger] = None,
4343
):
4444
"""

tests/features/test_main.py

Lines changed: 154 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,30 @@
44
import pytest
55

66
import gardenlinux.features.__main__ as fema
7-
from gardenlinux.features import CName
7+
from gardenlinux.features import CName, Parser
8+
9+
from ..constants import GL_ROOT_DIR
810

911
# -------------------------------
1012
# Helper function tests
1113
# -------------------------------
1214

1315

14-
def test_get_cname_base():
16+
def test_get_flavor():
1517
# Arrange
1618
sorted_features = ["base", "_hidden", "extra"]
1719

1820
# Act
19-
result = fema.get_cname_base(sorted_features)
21+
result = fema.get_flavor(sorted_features)
2022

2123
# Assert
2224
assert result == "base_hidden-extra"
2325

2426

25-
def test_get_cname_base_empty_raises():
26-
# get_cname_base with empty iterable raises TypeError
27+
def test_get_flavor_empty_raises():
28+
# get_flavor with empty iterable raises TypeError
2729
with pytest.raises(TypeError):
28-
fema.get_cname_base([])
30+
fema.get_flavor([])
2931

3032

3133
def test_sort_return_intersection_subset():
@@ -146,6 +148,54 @@ def test_main_prints_arch(monkeypatch, capsys):
146148
assert "amd64" in out
147149

148150

151+
def test_main_prints_container_name(monkeypatch, capsys):
152+
# Arrange
153+
argv = [
154+
"prog",
155+
"--arch",
156+
"amd64",
157+
"--cname",
158+
"container-pythonDev",
159+
"--version",
160+
"1.0",
161+
"container_name",
162+
]
163+
monkeypatch.setattr(sys, "argv", argv)
164+
monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None)
165+
166+
# Act
167+
fema.main()
168+
169+
# Assert
170+
out = capsys.readouterr().out
171+
assert "container-python-dev" in out
172+
173+
174+
def test_main_prints_container_tag(monkeypatch, capsys):
175+
# Arrange
176+
argv = [
177+
"prog",
178+
"--arch",
179+
"amd64",
180+
"--cname",
181+
"flav",
182+
"--version",
183+
"1.0",
184+
"--commit",
185+
"~post1",
186+
"container_tag",
187+
]
188+
monkeypatch.setattr(sys, "argv", argv)
189+
monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None)
190+
191+
# Act
192+
fema.main()
193+
194+
# Assert
195+
out = capsys.readouterr().out.strip()
196+
assert "1-0-post1" == out
197+
198+
149199
def test_main_prints_commit_id(monkeypatch, capsys):
150200
# Arrange
151201
argv = ["prog", "--arch", "amd64", "--cname", "flav", "commit_id"]
@@ -238,14 +288,49 @@ def test_main_prints_version_and_commit_id(monkeypatch, capsys):
238288
assert "1.2.3-abcdef12" == captured.out.strip()
239289

240290

241-
def test_main_arch_raises_missing_verison(monkeypatch, capsys):
291+
def test_main_requires_cname(monkeypatch):
242292
# Arrange
243-
argv = ["prog", "--arch", "amd64", "--cname", "flav", "arch"]
293+
monkeypatch.setattr(sys, "argv", ["prog", "arch"])
294+
monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None)
295+
296+
# Act / Assert
297+
with pytest.raises(SystemExit):
298+
fema.main()
299+
300+
301+
def test_main_raises_no_arch_no_default(monkeypatch):
302+
# Arrange
303+
# args.type == 'cname, arch is None and no default_arch set
304+
argv = ["prog", "--cname", "flav", "cname"]
305+
monkeypatch.setattr(sys, "argv", argv)
306+
monkeypatch.setattr(
307+
fema,
308+
"Parser",
309+
lambda *a, **kw: types.SimpleNamespace(filter=lambda *a, **k: None),
310+
)
311+
312+
# Act / Assert
313+
with pytest.raises(RuntimeError, match="Architecture could not be determined"):
314+
fema.main()
315+
316+
317+
def test_main_raises_missing_commit_id(monkeypatch, capsys):
318+
# Arrange
319+
argv = [
320+
"prog",
321+
"--arch",
322+
"amd64",
323+
"--cname",
324+
"flav",
325+
"--version",
326+
"1.0",
327+
"version_and_commit_id",
328+
]
244329
monkeypatch.setattr(sys, "argv", argv)
245330
monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None)
246331

247332
# Act / Assert
248-
with pytest.raises(RuntimeError):
333+
with pytest.raises(RuntimeError, match="Commit ID not specified"):
249334
fema.main()
250335

251336

@@ -288,27 +373,71 @@ def sort_subset(subset, length):
288373
assert "flav" in captured.out
289374

290375

291-
def test_main_requires_cname(monkeypatch):
376+
def test_main_with_exclude_cname_print_elements(monkeypatch, capsys):
292377
# Arrange
293-
monkeypatch.setattr(sys, "argv", ["prog", "arch"])
294-
monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None)
378+
monkeypatch.setattr(
379+
sys,
380+
"argv",
381+
[
382+
"prog",
383+
"--feature-dir",
384+
f"{GL_ROOT_DIR}/features",
385+
"--cname",
386+
"kvm-gardener_prod",
387+
"--ignore",
388+
"cloud",
389+
"--arch",
390+
"amd64",
391+
"--version",
392+
"local",
393+
"--commit",
394+
"today",
395+
"elements",
396+
],
397+
)
295398

296-
# Act / Assert
297-
with pytest.raises(SystemExit):
298-
fema.main()
399+
# Act
400+
fema.main()
299401

402+
# Assert
403+
captured = capsys.readouterr().out.strip()
300404

301-
def test_main_raises_no_arch_no_default(monkeypatch):
405+
assert (
406+
"log,sap,ssh,base,server,gardener"
407+
== captured
408+
)
409+
410+
411+
def test_main_with_exclude_cname_print_features(monkeypatch, capsys):
302412
# Arrange
303-
# args.type == 'cname, arch is None and no default_arch set
304-
argv = ["prog", "--cname", "flav", "cname"]
305-
monkeypatch.setattr(sys, "argv", argv)
306413
monkeypatch.setattr(
307-
fema,
308-
"Parser",
309-
lambda *a, **kw: types.SimpleNamespace(filter=lambda *a, **k: None),
414+
sys,
415+
"argv",
416+
[
417+
"prog",
418+
"--feature-dir",
419+
f"{GL_ROOT_DIR}/features",
420+
"--cname",
421+
"kvm-gardener_prod",
422+
"--ignore",
423+
"cloud",
424+
"--arch",
425+
"amd64",
426+
"--version",
427+
"local",
428+
"--commit",
429+
"today",
430+
"features",
431+
],
310432
)
311433

312-
# Act / Assert
313-
with pytest.raises(RuntimeError, match="Architecture could not be determined"):
314-
fema.main()
434+
# Act
435+
fema.main()
436+
437+
# Assert
438+
captured = capsys.readouterr().out.strip()
439+
440+
assert (
441+
"log,sap,ssh,_boot,_ignite,kvm,_nopkg,_prod,_slim,base,server,gardener"
442+
== captured
443+
)

0 commit comments

Comments
 (0)