...
 
Commits (7)
    https://gitcode.net/awesome-mirrors/containers/podman-compose/-/commit/280f1770bf10731a842ed15a9cfe50bdbca5d84f Add a test to extend using an empty service (placeholder) 2023-07-26T17:32:14+03:00 Natanael Arndt arndtn@gmail.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg2" style="text-decoration: none">N</a><a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com">Natanael Arndt</a> &lt;<a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com">arndtn@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/containers/podman-compose/-/commit/e5cdce4e7d6b9b73528237346f43b5fe3b8bfd01 default to an empty dict for the from service if the service is None 2023-07-26T17:32:14+03:00 Natanael Arndt arndtn@gmail.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg1" style="text-decoration: none">N</a><a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com">Natanael Arndt</a> &lt;<a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com">arndtn@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/containers/podman-compose/-/commit/0164c1db56f5e30d454451fa0c02156f81c07609 Simplify the fix using `or`. 2023-07-26T17:32:14+03:00 Natanael Arndt arndtn@gmail.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg1" style="text-decoration: none">N</a><a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com">Natanael Arndt</a> &lt;<a href="mailto:arndtn@gmail.com" title="arndtn@gmail.com">arndtn@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/containers/podman-compose/-/commit/d1f5ac9edc8473e45202f0737fb2a05cf04b9edb convert build context path to absolute during final normalisation 2023-08-02T14:19:15+03:00 Sergei Biriukov svbiriukov@gmail.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg4" style="text-decoration: none">N</a><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">Sergei Biriukov</a> &lt;<a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">svbiriukov@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/containers/podman-compose/-/commit/57c527c2c982dfa2fdaa114bc6fe8856ba68db7e add edits from review 2023-08-02T14:19:15+03:00 Sergei Biriukov svbiriukov@gmail.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg6" style="text-decoration: none">N</a><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">Sergei Biriukov</a> &lt;<a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">svbiriukov@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/containers/podman-compose/-/commit/bc9168b03927ff48120fa89cbebf5d1a47f1c74f add no-normalize flag 2023-08-02T14:19:15+03:00 Evedel svbiriukov@gmail.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg3" style="text-decoration: none">N</a><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">Evedel</a> &lt;<a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">svbiriukov@gmail.com</a>&gt;</span> https://gitcode.net/awesome-mirrors/containers/podman-compose/-/commit/06587c1dca031c51932ba1a8673eb93a09a37c5d rm redundant tests 2023-08-02T14:19:15+03:00 Evedel svbiriukov@gmail.com Signed-off-by: <span data-trailer="Signed-off-by:"><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com"></a><a href="javascript:void(0)" class="avatar s16 avatar-inline identicon bg1" style="text-decoration: none">N</a><a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">Evedel</a> &lt;<a href="mailto:svbiriukov@gmail.com" title="svbiriukov@gmail.com">svbiriukov@gmail.com</a>&gt;</span>
#! /usr/bin/python3
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# https://docs.docker.com/compose/compose-file/#service-configuration-reference
......@@ -1297,6 +1297,30 @@ def normalize(compose):
return compose
def normalize_service_final(service: dict, project_dir: str) -> dict:
if "build" in service:
build = service["build"]
context = build if is_str(build) else build.get("context", ".")
context = os.path.normpath(os.path.join(project_dir, context))
dockerfile = (
"Dockerfile"
if is_str(build)
else service["build"].get("dockerfile", "Dockerfile")
)
if not is_dict(service["build"]):
service["build"] = {}
service["build"]["dockerfile"] = dockerfile
service["build"]["context"] = context
return service
def normalize_final(compose: dict, project_dir: str) -> dict:
services = compose.get("services", None) or {}
for service in services.values():
normalize_service_final(service, project_dir)
return compose
def clone(value):
return value.copy() if is_list(value) or is_dict(value) else value
......@@ -1375,7 +1399,7 @@ def resolve_extends(services, service_names, environ):
content = content["services"]
subdirectory = os.path.dirname(filename)
content = rec_subs(content, environ)
from_service = content.get(from_service_name, {})
from_service = content.get(from_service_name, {}) or {}
normalize_service(from_service, subdirectory)
else:
from_service = services.get(from_service_name, {}).copy()
......@@ -1603,6 +1627,8 @@ class PodmanCompose:
compose.get("services", {}), set(args.profile)
)
compose["services"] = resolved_services
if not getattr(args, "no_normalize", None):
compose = normalize_final(compose, self.dirname)
self.merged_yaml = yaml.safe_dump(compose)
merged_json_b = json.dumps(compose, separators=(",", ":")).encode("utf-8")
self.yaml_hash = hashlib.sha256(merged_json_b).hexdigest()
......@@ -3057,6 +3083,9 @@ def compose_build_parse(parser):
@cmd_parse(podman_compose, "config")
def compose_config_parse(parser):
parser.add_argument(
"--no-normalize", help="Don't normalize compose model.", action="store_true"
)
parser.add_argument(
"--services", help="Print the service names, one per line.", action="store_true"
)
......
......@@ -138,7 +138,9 @@ def test__parse_compose_file_when_multiple_composes() -> None:
if actual_compose != expected_result:
print("compose: ", test_input)
print("override: ", test_override)
print("result: ", expected_result)
print("expected: ", expected_result)
print("actual: ", actual_compose)
compose_expected = expected_result
assert compose_expected == actual_compose
......@@ -151,6 +153,7 @@ def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
podman_compose.global_args.env_file = None
podman_compose.global_args.profile = []
podman_compose.global_args.in_pod = True
podman_compose.global_args.no_normalize = True
def dump_yaml(compose: dict, name: str) -> None:
......
......@@ -107,6 +107,7 @@ def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
podman_compose.global_args.env_file = None
podman_compose.global_args.profile = []
podman_compose.global_args.in_pod = True
podman_compose.global_args.no_normalize = None
def dump_yaml(compose: dict, name: str) -> None:
......
# pylint: disable=protected-access
import argparse
import copy
import os
import yaml
from podman_compose import (
normalize_service,
normalize,
normalize_final,
normalize_service_final,
PodmanCompose,
)
cwd = os.path.abspath(".")
test_cases_simple_normalization = [
({"image": "test-image"}, {"image": "test-image"}),
(
{"build": "."},
{
"build": {"context": cwd, "dockerfile": "Dockerfile"},
},
),
(
{"build": "../relative"},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "../relative")),
"dockerfile": "Dockerfile",
},
},
),
(
{"build": "./relative"},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "./relative")),
"dockerfile": "Dockerfile",
},
},
),
(
{"build": "/workspace/absolute"},
{
"build": {
"context": "/workspace/absolute",
"dockerfile": "Dockerfile",
},
},
),
(
{
"build": {
"dockerfile": "Dockerfile",
},
},
{
"build": {
"context": cwd,
"dockerfile": "Dockerfile",
},
},
),
(
{
"build": {
"context": ".",
},
},
{
"build": {
"context": cwd,
"dockerfile": "Dockerfile",
},
},
),
(
{
"build": {"context": "../", "dockerfile": "test-dockerfile"},
},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "../")),
"dockerfile": "test-dockerfile",
},
},
),
(
{
"build": {"context": ".", "dockerfile": "./dev/test-dockerfile"},
},
{
"build": {
"context": cwd,
"dockerfile": "./dev/test-dockerfile",
},
},
),
]
#
# [service.build] is normalised after merges
#
def test_normalize_service_final_returns_absolute_path_in_context() -> None:
project_dir = cwd
for test_input, expected_service in copy.deepcopy(test_cases_simple_normalization):
actual_service = normalize_service_final(test_input, project_dir)
assert expected_service == actual_service
def test_normalize_returns_absolute_path_in_context() -> None:
project_dir = cwd
for test_input, expected_result in copy.deepcopy(test_cases_simple_normalization):
compose_test = {"services": {"test-service": test_input}}
compose_expected = {"services": {"test-service": expected_result}}
actual_compose = normalize_final(compose_test, project_dir)
assert compose_expected == actual_compose
#
# running full parse over single compose files
#
def test__parse_compose_file_when_single_compose() -> None:
for test_input, expected_result in copy.deepcopy(test_cases_simple_normalization):
compose_test = {"services": {"test-service": test_input}}
dump_yaml(compose_test, "test-compose.yaml")
podman_compose = PodmanCompose()
set_args(podman_compose, ["test-compose.yaml"], no_normalize=None)
podman_compose._parse_compose_file()
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("result: ", expected_result)
assert expected_result == actual_compose
test_cases_with_merges = [
(
{},
{"build": "."},
{"build": {"context": cwd, "dockerfile": "Dockerfile"}},
),
(
{"build": "."},
{},
{"build": {"context": cwd, "dockerfile": "Dockerfile"}},
),
(
{"build": "/workspace/absolute"},
{"build": "./relative"},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "./relative")),
"dockerfile": "Dockerfile",
}
},
),
(
{"build": "./relative"},
{"build": "/workspace/absolute"},
{"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}},
),
(
{"build": "./relative"},
{"build": "/workspace/absolute"},
{"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}},
),
(
{"build": {"dockerfile": "test-dockerfile"}},
{},
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
),
(
{},
{"build": {"dockerfile": "test-dockerfile"}},
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
),
(
{},
{"build": {"dockerfile": "test-dockerfile"}},
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
),
(
{"build": {"dockerfile": "test-dockerfile-1"}},
{"build": {"dockerfile": "test-dockerfile-2"}},
{"build": {"context": cwd, "dockerfile": "test-dockerfile-2"}},
),
(
{"build": "/workspace/absolute"},
{"build": {"dockerfile": "test-dockerfile"}},
{"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}},
),
(
{"build": {"dockerfile": "test-dockerfile"}},
{"build": "/workspace/absolute"},
{"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}},
),
(
{"build": {"dockerfile": "./test-dockerfile-1"}},
{"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV1=1"]}},
{
"build": {
"context": cwd,
"dockerfile": "./test-dockerfile-2",
"args": ["ENV1=1"],
}
},
),
(
{"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}},
{"build": {"dockerfile": "./test-dockerfile-2"}},
{
"build": {
"context": cwd,
"dockerfile": "./test-dockerfile-2",
"args": ["ENV1=1"],
}
},
),
(
{"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}},
{"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV2=2"]}},
{
"build": {
"context": cwd,
"dockerfile": "./test-dockerfile-2",
"args": ["ENV1=1", "ENV2=2"],
}
},
),
]
#
# running full parse over merged
#
def test__parse_compose_file_when_multiple_composes() -> None:
for test_input, test_override, expected_result in copy.deepcopy(
test_cases_with_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"],
no_normalize=None,
)
podman_compose._parse_compose_file()
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], no_normalize: bool
) -> 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
podman_compose.global_args.no_normalize = no_normalize
def dump_yaml(compose: dict, name: str) -> None:
# Path(Path.cwd()/"subdirectory").mkdir(parents=True, exist_ok=True)
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", "test-compose.yaml"]
for file in test_files:
if os.path.exists(file):
os.remove(file)
services:
webapp_default:
webapp_special:
image: busybox
volumes:
- "/data"
version: "3"
services:
web:
image: busybox
extends:
file: common-services.yml
service: webapp_default
environment:
- DEBUG=1
cpu_shares: 5
......@@ -59,3 +59,26 @@ def test_podman_compose_extends_w_file_subdir():
out, _, returncode = capture(command_check_container)
assert 0 == returncode
assert out == b""
def test_podman_compose_extends_w_empty_service():
"""
Test that podman-compose can execute podman-compose -f <file> up with extended File which
includes an empty service. (e.g. if the file is used as placeholder for more complex configurations.)
:return:
"""
main_path = Path(__file__).parent.parent
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(
main_path.joinpath("tests", "extends_w_empty_service", "docker-compose.yml")
),
"up",
"-d",
]
_, _, returncode = capture(command_up)
assert 0 == returncode