diff --git a/podman_compose.py b/podman_compose.py index 017acbd..f7f24ee 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -61,15 +61,6 @@ def is_list(list_object): ) -def is_list_of_str(list_of_str_object): - if is_list(list_of_str_object): - for element in list_of_str_object: - if not is_str(element): - return False - return True - return False - - # identity filter def filteri(a): return filter(lambda i: i, a) @@ -1264,6 +1255,10 @@ def normalize_service(service, sub_dir=""): service["build"] = context else: service["build"]["context"] = context + for key in ("command", "entrypoint"): + if key in service: + if is_str(service[key]): + service[key] = shlex.split(service[key]) for key in ("env_file", "security_opt", "volumes"): if key not in service: continue @@ -1300,20 +1295,6 @@ def clone(value): return value.copy() if is_list(value) or is_dict(value) else value -def clone_shell_value(target, key, value): - if is_str(value): - target[key] = shlex.split(value) - else: - target[key] = clone(value) - - -def check_shell_value_type(key, value): - if not is_str(value) and not is_list_of_str(value): - raise ValueError( - f"can't merge value of [{key}]: must be a string or a list of strings" - ) - - def rec_merge_one(target, source): """ update target from source recursively @@ -1322,26 +1303,17 @@ def rec_merge_one(target, source): for key, value in source.items(): if key in target: continue - if key in ("command", "entrypoint"): - check_shell_value_type(key, value) - clone_shell_value(target, key, value) - else: - target[key] = clone(value) + target[key] = clone(value) done.add(key) for key, value in target.items(): if key in done: continue - if key in ("command", "entrypoint"): - if key not in source: - check_shell_value_type(key, value) - clone_shell_value(target, key, value) - else: - check_shell_value_type(key, source[key]) - clone_shell_value(target, key, source[key]) - continue if key not in source: continue value2 = source[key] + if key in ("command", "entrypoint"): + target[key] = clone(value2) + continue if not isinstance(value2, type(value)): value_type = type(value) value2_type = type(value2) diff --git a/pytests/test_can_merge_cmd_ent.py b/pytests/test_can_merge_cmd_ent.py new file mode 100644 index 0000000..5175673 --- /dev/null +++ b/pytests/test_can_merge_cmd_ent.py @@ -0,0 +1,121 @@ +import copy +import os +import argparse +import yaml +from podman_compose import normalize_service, PodmanCompose + +test_keys = ["command", "entrypoint"] + +test_cases_normalise_pre_merge = [ + ({"$$$": []}, {"$$$": []}), + ({"$$$": ["sh"]}, {"$$$": ["sh"]}), + ({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}), + ({"$$$": "sh"}, {"$$$": ["sh"]}), + ({"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}), + ( + {"$$$": "bash -c 'sleep infinity'"}, + {"$$$": ["bash", "-c", "sleep infinity"]}, + ), +] + +test_cases_merges = [ + ({}, {"$$$": []}, {"$$$": []}), + ({"$$$": []}, {}, {"$$$": []}), + ({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), + ({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}), + ({}, {"$$$": "sh"}, {"$$$": ["sh"]}), + ({"$$$": "sh"}, {}, {"$$$": ["sh"]}), + ({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), + ({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), + ({"$$$": "sh-1"}, {"$$$": ["sh-2"]}, {"$$$": ["sh-2"]}), + ({"$$$": "sh-1"}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), + ({"$$$": ["sh-1"]}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), + ({"$$$": ["sh-1", "sh-2"]}, {"$$$": ["sh-3", "sh-4"]}, {"$$$": ["sh-3", "sh-4"]}), + ({}, {"$$$": ["sh-3", "sh 4"]}, {"$$$": ["sh-3", "sh 4"]}), + ({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": ["sh"]}), + ({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}), + ( + {}, + {"$$$": "bash -c 'sleep infinity'"}, + {"$$$": ["bash", "-c", "sleep infinity"]}, + ), +] + + +def template_to_expression(base, override, expected, key): + base_copy = copy.deepcopy(base) + override_copy = copy.deepcopy(override) + expected_copy = copy.deepcopy(expected) + + expected_copy[key] = expected_copy.pop("$$$") + if "$$$" in base: + base_copy[key] = base_copy.pop("$$$") + if "$$$" in override: + override_copy[key] = override_copy.pop("$$$") + return base_copy, override_copy, expected_copy + + +def test_normalize_service(): + for test_input_template, expected_template in test_cases_normalise_pre_merge: + for key in test_keys: + test_input, _, expected = template_to_expression( + test_input_template, {}, expected_template, key + ) + test_input = normalize_service(test_input) + test_result = expected == test_input + if not test_result: + print("base_template: ", test_input_template) + print("expected: ", expected) + print("actual: ", test_input) + assert test_result + + +def test__parse_compose_file_when_multiple_composes() -> None: + for base_template, override_template, expected_template in copy.deepcopy( + test_cases_merges + ): + for key in test_keys: + base, override, expected = template_to_expression( + base_template, override_template, expected_template, key + ) + compose_test_1 = {"services": {"test-service": base}} + compose_test_2 = {"services": {"test-service": override}} + dump_yaml(compose_test_1, "test-compose-1.yaml") + dump_yaml(compose_test_2, "test-compose-2.yaml") + + podman_compose = PodmanCompose() + set_args(podman_compose, ["test-compose-1.yaml", "test-compose-2.yaml"]) + + podman_compose._parse_compose_file() # pylint: disable=protected-access + + actual = {} + if podman_compose.services: + podman_compose.services["test-service"].pop("_deps") + actual = podman_compose.services["test-service"] + if actual != expected: + print("compose: ", base) + print("override: ", override) + print("result: ", expected) + + assert expected == actual + + +def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None: + podman_compose.global_args = argparse.Namespace() + podman_compose.global_args.file = file_names + podman_compose.global_args.project_name = None + podman_compose.global_args.env_file = None + podman_compose.global_args.profile = [] + podman_compose.global_args.in_pod = True + + +def dump_yaml(compose: dict, name: str) -> None: + with open(name, "w", encoding="utf-8") as outfile: + yaml.safe_dump(compose, outfile, default_flow_style=False) + + +def test_clean_test_yamls() -> None: + test_files = ["test-compose-1.yaml", "test-compose-2.yaml"] + for file in test_files: + if os.path.exists(file): + os.remove(file) diff --git a/pytests/test_is_list_of_str.py b/pytests/test_is_list_of_str.py deleted file mode 100644 index 5e5766a..0000000 --- a/pytests/test_is_list_of_str.py +++ /dev/null @@ -1,10 +0,0 @@ -from podman_compose import is_list_of_str - - -def test_is_list_of_str(): - assert is_list_of_str([]) - assert is_list_of_str(["foo", "bar"]) - assert not is_list_of_str(["foo", 1]) - assert not is_list_of_str("foo") - assert not is_list_of_str(1) - assert not is_list_of_str(None) diff --git a/pytests/test_rec_merge_one_cmd_ent.py b/pytests/test_rec_merge_one_cmd_ent.py deleted file mode 100644 index 722606d..0000000 --- a/pytests/test_rec_merge_one_cmd_ent.py +++ /dev/null @@ -1,83 +0,0 @@ -import copy - -import pytest -from podman_compose import rec_merge_one - - -test_keys = ["command", "entrypoint"] -test_cases = [ - ({}, {"$$$": []}, {"$$$": []}), - ({"$$$": []}, {}, {"$$$": []}), - ({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), - ({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}), - ({}, {"$$$": "sh"}, {"$$$": ["sh"]}), - ({"$$$": "sh"}, {}, {"$$$": ["sh"]}), - ({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), - ({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), - ({"$$$": "sh-1"}, {"$$$": ["sh-2"]}, {"$$$": ["sh-2"]}), - ({"$$$": "sh-1"}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), - ({"$$$": ["sh-1"]}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), - ({"$$$": ["sh-1", "sh-2"]}, {"$$$": ["sh-3", "sh-4"]}, {"$$$": ["sh-3", "sh-4"]}), - ({}, {"$$$": ["sh-3", "sh 4"]}, {"$$$": ["sh-3", "sh 4"]}), - ({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": ["sh"]}), - ({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}), - ( - {}, - {"$$$": "bash -c 'sleep infinity'"}, - {"$$$": ["bash", "-c", "sleep infinity"]}, - ), -] -test_cases_with_exceptions = [ - ({}, {"$$$": 1234}, ValueError), - ({"$$$": 1234}, {}, ValueError), - ({"$$$": 1234}, {"$$$": 1234}, ValueError), - ({"$$$": {}}, {}, ValueError), - ({}, {"$$$": {}}, ValueError), - ({"$$$": {}}, {"$$$": {}}, ValueError), -] - - -def template_to_expression(base, override, expected, key): - base_copy = copy.deepcopy(base) - override_copy = copy.deepcopy(override) - expected_copy = copy.deepcopy(expected) - - expected_copy[key] = expected_copy.pop("$$$") - if "$$$" in base: - base_copy[key] = base_copy.pop("$$$") - if "$$$" in override: - override_copy[key] = override_copy.pop("$$$") - return base_copy, override_copy, expected_copy - - -def test_rec_merge_one_for_command_and_entrypoint(): - for base_template, override_template, expected_template in test_cases: - for key in test_keys: - base, override, expected = template_to_expression( - base_template, override_template, expected_template, key - ) - - base = rec_merge_one(base, override) - test_result = expected == base - if not test_result: - print("base_template: ", base_template) - print("override_template: ", override_template) - print("expected: ", expected) - print("actual: ", base) - assert test_result - - for ( - base_template, - override_template, - expected_exception, - ) in test_cases_with_exceptions: - for key in test_keys: - base, override, expected = template_to_expression( - base_template, override_template, {"$$$": ""}, key - ) - - with pytest.raises(expected_exception): - base = rec_merge_one(base, override) - print("base_template: ", base_template) - print("override_template: ", override_template) - print("expected: ", expected_exception)