diff --git a/src/gardenlinux/features/__main__.py b/src/gardenlinux/features/__main__.py index 01253d2..45d4bde 100644 --- a/src/gardenlinux/features/__main__.py +++ b/src/gardenlinux/features/__main__.py @@ -8,6 +8,7 @@ import argparse import logging import os +import re from functools import reduce from os import path from typing import Any, List, Set @@ -18,10 +19,13 @@ _ARGS_TYPE_ALLOWED = [ "cname", "cname_base", + "container_name", + "container_tag", "commit_id", "features", "platforms", "flags", + "flavor", "elements", "arch", "version", @@ -29,6 +33,11 @@ "graph", ] +RE_CAMEL_CASE_SPLITTER = re.compile("([A-Z]+|[a-z0-9])([A-Z])(?!$)") +""" +CamelCase splitter RegExp +""" + def main() -> None: """ @@ -102,15 +111,26 @@ def main() -> None: commit_id_or_hash = cname.commit_id version = cname.version - if arch is None or arch == "" and (args.type in ("cname", "arch")): + if (arch is None or arch == "") and ( + args.type in ("cname", "container_name", "arch") + ): raise RuntimeError( "Architecture could not be determined and no default architecture set" ) - if ( - version is None - or version == "" - and (args.type in ("cname", "commit_id", "version", "version_and_commit_id")) + if (commit_id_or_hash is None or commit_id_or_hash == "") and ( + args.type in ("container_tag", "commit_id", "version_and_commit_id") + ): + raise RuntimeError("Commit ID not specified") + + if (version is None or version == "") and ( + args.type + in ( + "container_tag", + "commit_id", + "version", + "version_and_commit_id", + ) ): raise RuntimeError("Version not specified and no default version set") @@ -121,13 +141,15 @@ def main() -> None: elif args.type in ( "cname_base", "cname", + "container_name", "elements", "features", "flags", + "flavor", "graph", "platforms", ): - if args.type == "graph" or len(args.ignore) > 1: + if args.type == "graph" or len(args.ignore) > 0: features_parser = Parser(gardenlinux_root, feature_dir_name) print_output_from_features_parser( @@ -137,13 +159,15 @@ def main() -> None: print_output_from_cname(args.type, cname) elif args.type == "commit_id": print(commit_id_or_hash[:8]) + elif args.type == "container_tag": + print(re.sub("\\W+", "-", f"{version}-{commit_id_or_hash[:8]}")) elif args.type == "version": print(version) elif args.type == "version_and_commit_id": print(f"{version}-{commit_id_or_hash[:8]}") -def get_cname_base(sorted_features: List[str]): +def get_flavor(sorted_features: List[str]): """ Get the base cname for the feature set given. @@ -265,7 +289,7 @@ def print_output_from_features_parser( sorted_minimal_features = sort_subset(minimal_feature_set, sorted_features) - cname_base = get_cname_base(sorted_minimal_features) + cname_base = get_flavor(sorted_minimal_features) if output_type == "cname_base": print(cname_base) @@ -279,7 +303,9 @@ def print_output_from_features_parser( cname += f"-{version}-{commit_id_or_hash[:8]}" print(cname) - if output_type == "platforms": + elif output_type == "container_name": + print(RE_CAMEL_CASE_SPLITTER.sub("\\1_\\2", cname_base).lower()) + elif output_type == "platforms": print(",".join(features_by_type["platform"])) elif output_type == "elements": print(",".join(features_by_type["element"])) @@ -303,6 +329,8 @@ def print_output_from_cname(output_type: str, cname_instance: CName) -> None: print(cname_instance.flavor) elif output_type == "cname": print(cname_instance.cname) + elif output_type == "container_name": + print(RE_CAMEL_CASE_SPLITTER.sub("\\1-\\2", cname_instance.flavor).lower()) elif output_type == "platforms": print(cname_instance.feature_set_platform) elif output_type == "elements": diff --git a/src/gardenlinux/features/cname_main.py b/src/gardenlinux/features/cname_main.py index 9413b90..9ddc1e3 100644 --- a/src/gardenlinux/features/cname_main.py +++ b/src/gardenlinux/features/cname_main.py @@ -11,7 +11,7 @@ from os.path import basename, dirname from .__main__ import ( - get_cname_base, + get_flavor, get_minimal_feature_set, get_version_and_commit_id_from_files, sort_subset, @@ -80,7 +80,7 @@ def main(): sorted_minimal_features = sort_subset(minimal_feature_set, sorted_features) - generated_cname = get_cname_base(sorted_minimal_features) + generated_cname = get_flavor(sorted_minimal_features) generated_cname += f"-{cname.arch}" diff --git a/src/gardenlinux/features/parser.py b/src/gardenlinux/features/parser.py index 73c94db..7f00374 100644 --- a/src/gardenlinux/features/parser.py +++ b/src/gardenlinux/features/parser.py @@ -30,15 +30,15 @@ class Parser(object): Apache License, Version 2.0 """ - _GARDENLINUX_ROOT: str = "." + _GARDENLINUX_ROOT: str = os.getenv("GL_ROOT_DIR", ".") """ Default GardenLinux root directory """ def __init__( self, - gardenlinux_root: str | None = None, - feature_dir_name: str = "features", + gardenlinux_root: Optional[str] = None, + feature_dir_name: Optional[str] = "features", logger: Optional[logging.Logger] = None, ): """ diff --git a/tests/features/test_main.py b/tests/features/test_main.py index 850690c..6b302ec 100644 --- a/tests/features/test_main.py +++ b/tests/features/test_main.py @@ -4,28 +4,30 @@ import pytest import gardenlinux.features.__main__ as fema -from gardenlinux.features import CName +from gardenlinux.features import CName, Parser + +from ..constants import GL_ROOT_DIR # ------------------------------- # Helper function tests # ------------------------------- -def test_get_cname_base(): +def test_get_flavor(): # Arrange sorted_features = ["base", "_hidden", "extra"] # Act - result = fema.get_cname_base(sorted_features) + result = fema.get_flavor(sorted_features) # Assert assert result == "base_hidden-extra" -def test_get_cname_base_empty_raises(): - # get_cname_base with empty iterable raises TypeError +def test_get_flavor_empty_raises(): + # get_flavor with empty iterable raises TypeError with pytest.raises(TypeError): - fema.get_cname_base([]) + fema.get_flavor([]) def test_sort_return_intersection_subset(): @@ -146,6 +148,54 @@ def test_main_prints_arch(monkeypatch, capsys): assert "amd64" in out +def test_main_prints_container_name(monkeypatch, capsys): + # Arrange + argv = [ + "prog", + "--arch", + "amd64", + "--cname", + "container-pythonDev", + "--version", + "1.0", + "container_name", + ] + monkeypatch.setattr(sys, "argv", argv) + monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None) + + # Act + fema.main() + + # Assert + out = capsys.readouterr().out + assert "container-python-dev" in out + + +def test_main_prints_container_tag(monkeypatch, capsys): + # Arrange + argv = [ + "prog", + "--arch", + "amd64", + "--cname", + "flav", + "--version", + "1.0", + "--commit", + "~post1", + "container_tag", + ] + monkeypatch.setattr(sys, "argv", argv) + monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None) + + # Act + fema.main() + + # Assert + out = capsys.readouterr().out.strip() + assert "1-0-post1" == out + + def test_main_prints_commit_id(monkeypatch, capsys): # Arrange argv = ["prog", "--arch", "amd64", "--cname", "flav", "commit_id"] @@ -238,14 +288,49 @@ def test_main_prints_version_and_commit_id(monkeypatch, capsys): assert "1.2.3-abcdef12" == captured.out.strip() -def test_main_arch_raises_missing_verison(monkeypatch, capsys): +def test_main_requires_cname(monkeypatch): # Arrange - argv = ["prog", "--arch", "amd64", "--cname", "flav", "arch"] + monkeypatch.setattr(sys, "argv", ["prog", "arch"]) + monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None) + + # Act / Assert + with pytest.raises(SystemExit): + fema.main() + + +def test_main_raises_no_arch_no_default(monkeypatch): + # Arrange + # args.type == 'cname, arch is None and no default_arch set + argv = ["prog", "--cname", "flav", "cname"] + monkeypatch.setattr(sys, "argv", argv) + monkeypatch.setattr( + fema, + "Parser", + lambda *a, **kw: types.SimpleNamespace(filter=lambda *a, **k: None), + ) + + # Act / Assert + with pytest.raises(RuntimeError, match="Architecture could not be determined"): + fema.main() + + +def test_main_raises_missing_commit_id(monkeypatch, capsys): + # Arrange + argv = [ + "prog", + "--arch", + "amd64", + "--cname", + "flav", + "--version", + "1.0", + "version_and_commit_id", + ] monkeypatch.setattr(sys, "argv", argv) monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None) # Act / Assert - with pytest.raises(RuntimeError): + with pytest.raises(RuntimeError, match="Commit ID not specified"): fema.main() @@ -288,27 +373,71 @@ def sort_subset(subset, length): assert "flav" in captured.out -def test_main_requires_cname(monkeypatch): +def test_main_with_exclude_cname_print_elements(monkeypatch, capsys): # Arrange - monkeypatch.setattr(sys, "argv", ["prog", "arch"]) - monkeypatch.setattr(fema, "Parser", lambda *a, **kw: None) + monkeypatch.setattr( + sys, + "argv", + [ + "prog", + "--feature-dir", + f"{GL_ROOT_DIR}/features", + "--cname", + "kvm-gardener_prod", + "--ignore", + "cloud", + "--arch", + "amd64", + "--version", + "local", + "--commit", + "today", + "elements", + ], + ) - # Act / Assert - with pytest.raises(SystemExit): - fema.main() + # Act + fema.main() + # Assert + captured = capsys.readouterr().out.strip() -def test_main_raises_no_arch_no_default(monkeypatch): + assert ( + "log,sap,ssh,base,server,gardener" + == captured + ) + + +def test_main_with_exclude_cname_print_features(monkeypatch, capsys): # Arrange - # args.type == 'cname, arch is None and no default_arch set - argv = ["prog", "--cname", "flav", "cname"] - monkeypatch.setattr(sys, "argv", argv) monkeypatch.setattr( - fema, - "Parser", - lambda *a, **kw: types.SimpleNamespace(filter=lambda *a, **k: None), + sys, + "argv", + [ + "prog", + "--feature-dir", + f"{GL_ROOT_DIR}/features", + "--cname", + "kvm-gardener_prod", + "--ignore", + "cloud", + "--arch", + "amd64", + "--version", + "local", + "--commit", + "today", + "features", + ], ) - # Act / Assert - with pytest.raises(RuntimeError, match="Architecture could not be determined"): - fema.main() + # Act + fema.main() + + # Assert + captured = capsys.readouterr().out.strip() + + assert ( + "log,sap,ssh,_boot,_ignite,kvm,_nopkg,_prod,_slim,base,server,gardener" + == captured + ) diff --git a/tests/s3/test_s3_artifacts.py b/tests/s3/test_s3_artifacts.py index 309cfae..0b1cfda 100644 --- a/tests/s3/test_s3_artifacts.py +++ b/tests/s3/test_s3_artifacts.py @@ -120,8 +120,10 @@ def test_upload_from_directory_success(s3_setup): assert metadata["require_uefi"] is True assert metadata["secureboot"] is True - raw_tags_response = env.s3.meta.client.get_object_tagging(Bucket=env.bucket_name, Key=f"objects/{env.cname}/{env.cname}-file1") - tags = { tag['Key']: tag['Value'] for tag in raw_tags_response["TagSet"] } + raw_tags_response = env.s3.meta.client.get_object_tagging( + Bucket=env.bucket_name, Key=f"objects/{env.cname}/{env.cname}-file1" + ) + tags = {tag["Key"]: tag["Value"] for tag in raw_tags_response["TagSet"]} assert tags["platform"] == "container+kvm" @@ -260,6 +262,7 @@ def test_upload_from_directory_invalid_artifact_name(s3_setup): bucket = env.s3.Bucket(env.bucket_name) assert len(list(bucket.objects.filter(Prefix=f"meta/singles/{env.cname}"))) == 1 + def test_upload_from_directory_commit_mismatch_raises(s3_setup): """Raise RuntimeError when commit ID is not matching with cname.""" # Arrange