From 85d5d5dcc9cb2f41e278f9991012bc915144e36f Mon Sep 17 00:00:00 2001 From: Sergei Biriukov Date: Sat, 6 May 2023 15:00:46 +1000 Subject: [PATCH] move logic from rec_merge to normalize_service Signed-off-by: Sergei Biriukov --- podman_compose.py | 37 +---- pytests/test_build_can_merge_str_and_dict.py | 165 +++++++++++++++++++ pytests/test_rec_merge_one_build.py | 91 ---------- 3 files changed, 172 insertions(+), 121 deletions(-) create mode 100644 pytests/test_build_can_merge_str_and_dict.py delete mode 100644 pytests/test_rec_merge_one_build.py diff --git a/podman_compose.py b/podman_compose.py index d7f549c..e53a7cd 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -1237,12 +1237,13 @@ class Podman: def normalize_service(service, sub_dir=""): - # make `build.context` relative to sub_dir - # TODO: should we make volume and secret relative too? + if "build" in service: + build = service["build"] + if is_str(build): + service["build"] = {"context": build} if sub_dir and "build" in service: build = service["build"] - context = build if is_str(build) else build.get("context", None) - context = context or "" + context = build.get("context", None) or "" if context or sub_dir: if context.startswith("./"): context = context[2:] @@ -1251,10 +1252,7 @@ def normalize_service(service, sub_dir=""): context = context.rstrip("/") if not context: context = "." - if is_str(build): - service["build"] = context - else: - service["build"]["context"] = context + service["build"]["context"] = context for key in ("env_file", "security_opt", "volumes"): if key not in service: continue @@ -1291,17 +1289,6 @@ def clone(value): return value.copy() if is_list(value) or is_dict(value) else value -def parse_build(build): - build_parsed = {} - if is_str(build): - build_parsed["context"] = build - elif is_dict(build): - build_parsed = build - else: - raise ValueError(f"invalid type of build value [{type(build)}]") - return build_parsed - - def rec_merge_one(target, source): """ update target from source recursively @@ -1310,21 +1297,11 @@ def rec_merge_one(target, source): for key, value in source.items(): if key in target: continue - if key == "build": - target[key] = parse_build(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 == "build": - target_parsed = parse_build(value) - source_parsed = {} - if key in source: - source_parsed = parse_build(source[key]) - target["build"] = rec_merge_one(target_parsed, source_parsed) - continue if key not in source: continue value2 = source[key] diff --git a/pytests/test_build_can_merge_str_and_dict.py b/pytests/test_build_can_merge_str_and_dict.py new file mode 100644 index 0000000..a020cb5 --- /dev/null +++ b/pytests/test_build_can_merge_str_and_dict.py @@ -0,0 +1,165 @@ +import copy +import os +import argparse +import yaml +from podman_compose import normalize_service, PodmanCompose + + +test_cases_simple = [ + ({"test": "test"}, {"test": "test"}), + ({"build": "."}, {"build": {"context": "."}}), + ({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}), + ({"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}), + ( + {"build": {"dockerfile": "dockerfile-1"}}, + {"build": {"dockerfile": "dockerfile-1"}}, + ), + ( + {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, + {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, + ), +] + + +def test_normalize_service_simple(): + for test_case, expected in copy.deepcopy(test_cases_simple): + test_original = copy.deepcopy(test_case) + test_case = normalize_service(test_case) + test_result = expected == test_case + if not test_result: + print("test: ", test_original) + print("expected: ", expected) + print("actual: ", test_case) + assert test_result + + +test_cases_sub_dir = [ + ({"test": "test"}, {"test": "test"}), + ({"build": "."}, {"build": {"context": "./sub_dir/."}}), + ({"build": "./dir-1"}, {"build": {"context": "./sub_dir/dir-1"}}), + ({"build": {"context": "./dir-1"}}, {"build": {"context": "./sub_dir/dir-1"}}), + ( + {"build": {"dockerfile": "dockerfile-1"}}, + {"build": {"context": "./sub_dir", "dockerfile": "dockerfile-1"}}, + ), + ( + {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, + {"build": {"context": "./sub_dir/dir-1", "dockerfile": "dockerfile-1"}}, + ), +] + + +def test_normalize_service_with_sub_dir(): + for test_case, expected in copy.deepcopy(test_cases_sub_dir): + test_original = copy.deepcopy(test_case) + test_case = normalize_service(test_case, sub_dir="./sub_dir") + test_result = expected == test_case + if not test_result: + print("test: ", test_original) + print("expected: ", expected) + print("actual: ", test_case) + assert test_result + + +test_cases_merges = [ + ({}, {}, {}), + ({}, {"test": "test"}, {"test": "test"}), + ({"test": "test"}, {}, {"test": "test"}), + ({"test": "test-1"}, {"test": "test-2"}, {"test": "test-2"}), + ({}, {"build": "."}, {"build": {"context": "."}}), + ({"build": "."}, {}, {"build": {"context": "."}}), + ({"build": "./dir-1"}, {"build": "./dir-2"}, {"build": {"context": "./dir-2"}}), + ({}, {"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}), + ({"build": {"context": "./dir-1"}}, {}, {"build": {"context": "./dir-1"}}), + ( + {"build": {"context": "./dir-1"}}, + {"build": {"context": "./dir-2"}}, + {"build": {"context": "./dir-2"}}, + ), + ( + {}, + {"build": {"dockerfile": "dockerfile-1"}}, + {"build": {"dockerfile": "dockerfile-1"}}, + ), + ( + {"build": {"dockerfile": "dockerfile-1"}}, + {}, + {"build": {"dockerfile": "dockerfile-1"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"dockerfile": "./dockerfile-2"}}, + {"build": {"dockerfile": "./dockerfile-2"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"context": "./dir-2"}}, + {"build": {"dockerfile": "./dockerfile-1", "context": "./dir-2"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1", "context": "./dir-1"}}, + {"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}}, + {"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1"]}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}}, + {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1", "ENV2=2"]}}, + ), +] + + +def test__parse_compose_file_when_multiple_composes() -> None: + for test_input, test_override, expected_result in copy.deepcopy(test_cases_merges): + compose_test_1 = {"services": {"test-service": test_input}} + compose_test_2 = {"services": {"test-service": test_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_compose = {} + if podman_compose.services: + podman_compose.services["test-service"].pop("_deps") + actual_compose = podman_compose.services["test-service"] + if actual_compose != expected_result: + print("compose: ", test_input) + print("override: ", test_override) + print("result: ", expected_result) + compose_expected = expected_result + + assert compose_expected == actual_compose + + +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_rec_merge_one_build.py b/pytests/test_rec_merge_one_build.py deleted file mode 100644 index 3d25ec7..0000000 --- a/pytests/test_rec_merge_one_build.py +++ /dev/null @@ -1,91 +0,0 @@ -import copy - -import pytest - -from podman_compose import rec_merge_one - -test_cases = [ - ({}, {"build": "."}, {"build": {"context": "."}}), - ({"build": "."}, {}, {"build": {"context": "."}}), - ({"build": "./dir-1"}, {"build": "./dir-2"}, {"build": {"context": "./dir-2"}}), - ({}, {"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}), - ({"build": {"context": "./dir-1"}}, {}, {"build": {"context": "./dir-1"}}), - ( - {"build": {"context": "./dir-1"}}, - {"build": {"context": "./dir-2"}}, - {"build": {"context": "./dir-2"}}, - ), - ( - {}, - {"build": {"dockerfile": "./compose.yaml"}}, - {"build": {"dockerfile": "./compose.yaml"}}, - ), - ( - {"build": {"dockerfile": "./compose.yaml"}}, - {}, - {"build": {"dockerfile": "./compose.yaml"}}, - ), - ( - {"build": {"dockerfile": "./compose-1.yaml"}}, - {"build": {"dockerfile": "./compose-2.yaml"}}, - {"build": {"dockerfile": "./compose-2.yaml"}}, - ), - ( - {"build": {"dockerfile": "./compose-1.yaml"}}, - {"build": {"context": "./dir-2"}}, - {"build": {"dockerfile": "./compose-1.yaml", "context": "./dir-2"}}, - ), - ( - {"build": {"dockerfile": "./compose-1.yaml", "context": "./dir-1"}}, - {"build": {"dockerfile": "./compose-2.yaml", "context": "./dir-2"}}, - {"build": {"dockerfile": "./compose-2.yaml", "context": "./dir-2"}}, - ), - ( - {"build": {"dockerfile": "./compose-1.yaml"}}, - {"build": {"dockerfile": "./compose-2.yaml", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./compose-2.yaml", "args": ["ENV1=1"]}}, - ), - ( - {"build": {"dockerfile": "./compose-2.yaml", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./compose-1.yaml"}}, - {"build": {"dockerfile": "./compose-1.yaml", "args": ["ENV1=1"]}}, - ), - ( - {"build": {"dockerfile": "./compose-2.yaml", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./compose-1.yaml", "args": ["ENV2=2"]}}, - {"build": {"dockerfile": "./compose-1.yaml", "args": ["ENV1=1", "ENV2=2"]}}, - ), -] - - -def test_rec_merge_one_for_build(): - for base, override, expected in copy.deepcopy(test_cases): - base_original = copy.deepcopy(base) - base = rec_merge_one(base, override) - test_result = expected == base - if not test_result: - print("base: ", base_original) - print("override: ", override) - print("expected: ", expected) - print("actual: ", base) - assert test_result - - -test_cases_with_exceptions = [ - ({}, {"build": 1234}, ValueError), - ({"build": 1234}, {}, ValueError), - ({"build": 1234}, {"build": 1234}, ValueError), - ({"build": []}, {}, ValueError), - ({}, {"build": []}, ValueError), - ({"build": []}, {"build": []}, ValueError), -] - - -def test_rec_merge_one_for_build_eception(): - for base, override, expected_exception in copy.deepcopy(test_cases_with_exceptions): - base_original = copy.deepcopy(base) - with pytest.raises(expected_exception): - base = rec_merge_one(base, override) - print("base: ", base_original) - print("override: ", override) - print("expected: ", expected_exception)