mirror of
https://github.com/containers/podman-compose.git
synced 2025-07-01 13:10:24 +02:00
Compare commits
19 Commits
v1.0.6
...
revert-574
Author | SHA1 | Date | |
---|---|---|---|
1f4a4d2184 | |||
08a453d643 | |||
75de39c239 | |||
874192568f | |||
0b853f29f4 | |||
847f01a6c6 | |||
e511e6420f | |||
a9723ec1cf | |||
1cb608d8a7 | |||
252f1d57a5 | |||
13856d2e9c | |||
8d8df0bc28 | |||
bc5f0123d9 | |||
9a08f85ffd | |||
8625d7a4e8 | |||
016c97fd1e | |||
2df11674c4 | |||
5eff38e743 | |||
7f5ce26b1b |
4
.github/workflows/pylint.yml
vendored
4
.github/workflows/pylint.yml
vendored
@ -11,8 +11,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install psf/black requirements
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y python3 python3-venv
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3 python3-venv
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
options: "--check --verbose"
|
||||
|
@ -7,6 +7,6 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD [ "python", "-m", "App.web" ]
|
||||
CMD [ "python", "-m", "app.web" ]
|
||||
EXPOSE 8080
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# pylint: disable=import-error
|
||||
# pylint: disable=unused-import
|
||||
import os
|
||||
import asyncio
|
||||
import asyncio # noqa: F401
|
||||
|
||||
import aioredis
|
||||
from aiohttp import web
|
||||
@ -14,13 +16,13 @@ routes = web.RouteTableDef()
|
||||
|
||||
|
||||
@routes.get("/")
|
||||
async def hello(request):
|
||||
async def hello(request): # pylint: disable=unused-argument
|
||||
counter = await redis.incr("mycounter")
|
||||
return web.Response(text=f"counter={counter}")
|
||||
|
||||
|
||||
@routes.get("/hello.json")
|
||||
async def hello_json(request):
|
||||
async def hello_json(request): # pylint: disable=unused-argument
|
||||
counter = await redis.incr("mycounter")
|
||||
data = {"counter": counter}
|
||||
return web.json_response(data)
|
@ -30,7 +30,7 @@ import shlex
|
||||
try:
|
||||
from shlex import quote as cmd_quote
|
||||
except ImportError:
|
||||
from pipes import quote as cmd_quote
|
||||
from pipes import quote as cmd_quote # pylint: disable=deprecated-module
|
||||
|
||||
# import fnmatch
|
||||
# fnmatch.fnmatchcase(env, "*_HOST")
|
||||
@ -38,16 +38,32 @@ except ImportError:
|
||||
import yaml
|
||||
from dotenv import dotenv_values
|
||||
|
||||
__version__ = "1.0.6"
|
||||
__version__ = "1.0.7"
|
||||
|
||||
script = os.path.realpath(sys.argv[0])
|
||||
|
||||
# helper functions
|
||||
is_str = lambda s: isinstance(s, str)
|
||||
is_dict = lambda d: isinstance(d, dict)
|
||||
is_list = lambda l: not is_str(l) and not is_dict(l) and hasattr(l, "__iter__")
|
||||
|
||||
|
||||
def is_str(string_object):
|
||||
return isinstance(string_object, str)
|
||||
|
||||
|
||||
def is_dict(dict_object):
|
||||
return isinstance(dict_object, dict)
|
||||
|
||||
|
||||
def is_list(list_object):
|
||||
return (
|
||||
not is_str(list_object)
|
||||
and not is_dict(list_object)
|
||||
and hasattr(list_object, "__iter__")
|
||||
)
|
||||
|
||||
|
||||
# identity filter
|
||||
filteri = lambda a: filter(lambda i: i, a)
|
||||
def filteri(a):
|
||||
return filter(lambda i: i, a)
|
||||
|
||||
|
||||
def try_int(i, fallback=None):
|
||||
@ -730,7 +746,7 @@ def assert_cnt_nets(compose, cnt):
|
||||
"--label",
|
||||
f"com.docker.compose.project={proj_name}",
|
||||
]
|
||||
# TODO: add more options here, like driver, internal, ..etc
|
||||
# TODO: add more options here, like dns, ipv6, etc.
|
||||
labels = net_desc.get("labels", None) or []
|
||||
for item in norm_as_list(labels):
|
||||
args.extend(["--label", item])
|
||||
@ -742,15 +758,17 @@ def assert_cnt_nets(compose, cnt):
|
||||
driver_opts = net_desc.get("driver_opts", None) or {}
|
||||
for key, value in driver_opts.items():
|
||||
args.extend(("--opt", f"{key}={value}"))
|
||||
ipam_config_ls = (net_desc.get("ipam", None) or {}).get(
|
||||
"config", None
|
||||
) or []
|
||||
ipam = (net_desc.get("ipam", None) or {})
|
||||
ipam_driver = ipam.get("driver", None)
|
||||
if ipam_driver:
|
||||
args.extend(("--ipam-driver", ipam_driver))
|
||||
ipam_config_ls = ipam.get("config", None) or []
|
||||
if is_dict(ipam_config_ls):
|
||||
ipam_config_ls = [ipam_config_ls]
|
||||
for ipam in ipam_config_ls:
|
||||
subnet = ipam.get("subnet", None)
|
||||
ip_range = ipam.get("ip_range", None)
|
||||
gateway = ipam.get("gateway", None)
|
||||
for ipam_config in ipam_config_ls:
|
||||
subnet = ipam_config.get("subnet", None)
|
||||
ip_range = ipam_config.get("ip_range", None)
|
||||
gateway = ipam_config.get("gateway", None)
|
||||
if subnet:
|
||||
args.extend(("--subnet", subnet))
|
||||
if ip_range:
|
||||
@ -777,6 +795,8 @@ def get_net_args(compose, cnt):
|
||||
net_args.extend(["--network", net])
|
||||
elif net.startswith("slirp4netns:"):
|
||||
net_args.extend(["--network", net])
|
||||
elif net.startswith("ns:"):
|
||||
net_args.extend(["--network", net])
|
||||
elif net.startswith("service:"):
|
||||
other_srv = net.split(":", 1)[1].strip()
|
||||
other_cnt = compose.container_names_by_service[other_srv][0]
|
||||
@ -797,15 +817,23 @@ def get_net_args(compose, cnt):
|
||||
cnt_nets = cnt.get("networks", None)
|
||||
aliases = [service_name]
|
||||
# NOTE: from podman manpage:
|
||||
# NOTE: A container will only have access to aliases on the first network that it joins. This is a limitation that will be removed in a later release.
|
||||
# NOTE: A container will only have access to aliases on the first network
|
||||
# that it joins. This is a limitation that will be removed in a later
|
||||
# release.
|
||||
ip = None
|
||||
ip6 = None
|
||||
ip_assignments = 0
|
||||
if cnt_nets and is_dict(cnt_nets):
|
||||
prioritized_cnt_nets = []
|
||||
# cnt_nets is {net_key: net_value, ...}
|
||||
for net_key, net_value in cnt_nets.items():
|
||||
net_value = net_value or {}
|
||||
aliases.extend(norm_as_list(net_value.get("aliases", None)))
|
||||
if net_value.get("ipv4_address", None) != None:
|
||||
ip_assignments = ip_assignments + 1
|
||||
if net_value.get("ipv6_address", None) != None:
|
||||
ip_assignments = ip_assignments + 1
|
||||
|
||||
if not ip:
|
||||
ip = net_value.get("ipv4_address", None)
|
||||
if not ip6:
|
||||
@ -832,12 +860,35 @@ def get_net_args(compose, cnt):
|
||||
)
|
||||
net_names.append(net_name)
|
||||
net_names_str = ",".join(net_names)
|
||||
if is_bridge:
|
||||
net_args.extend(["--net", net_names_str, "--network-alias", ",".join(aliases)])
|
||||
if ip:
|
||||
net_args.append(f"--ip={ip}")
|
||||
if ip6:
|
||||
net_args.append(f"--ip6={ip6}")
|
||||
|
||||
if ip_assignments > 1:
|
||||
multipleNets = cnt.get("networks", None)
|
||||
multipleNetNames = multipleNets.keys()
|
||||
|
||||
for net_ in multipleNetNames:
|
||||
net_desc = nets[net_] or {}
|
||||
is_ext = net_desc.get("external", None)
|
||||
ext_desc = is_ext if is_dict(is_ext) else {}
|
||||
default_net_name = net_ if is_ext else f"{proj_name}_{net_}"
|
||||
net_name = (
|
||||
ext_desc.get("name", None) or net_desc.get("name", None) or default_net_name
|
||||
)
|
||||
|
||||
ipv4 = multipleNets[net_].get("ipv4_address",None)
|
||||
ipv6 = multipleNets[net_].get("ipv6_address",None)
|
||||
if ipv4 is not None and ipv6 is not None:
|
||||
net_args.extend(["--network", f"{net_name}:ip={ipv4},ip={ipv6}"])
|
||||
elif ipv4 is None and ipv6 is not None:
|
||||
net_args.extend(["--network", f"{net_name}:ip={ipv6}"])
|
||||
elif ipv6 is None and ipv4 is not None:
|
||||
net_args.extend(["--network", f"{net_name}:ip={ipv4}"])
|
||||
else:
|
||||
if is_bridge:
|
||||
net_args.extend(["--net", net_names_str, "--network-alias", ",".join(aliases)])
|
||||
if ip:
|
||||
net_args.append(f"--ip={ip}")
|
||||
if ip6:
|
||||
net_args.append(f"--ip6={ip6}")
|
||||
return net_args
|
||||
|
||||
|
||||
@ -1019,6 +1070,14 @@ def container_to_args(compose, cnt, detached=True):
|
||||
if "retries" in healthcheck:
|
||||
podman_args.extend(["--healthcheck-retries", str(healthcheck["retries"])])
|
||||
|
||||
# handle podman extension
|
||||
x_podman = cnt.get("x-podman", None)
|
||||
if x_podman is not None:
|
||||
for uidmap in x_podman.get("uidmaps", []):
|
||||
podman_args.extend(["--uidmap", uidmap])
|
||||
for gidmap in x_podman.get("gidmaps", []):
|
||||
podman_args.extend(["--gidmap", gidmap])
|
||||
|
||||
podman_args.append(cnt["image"]) # command, ..etc.
|
||||
command = cnt.get("command", None)
|
||||
if command is not None:
|
||||
@ -1127,7 +1186,11 @@ class Podman:
|
||||
log(" ".join([str(i) for i in cmd_ls]))
|
||||
if self.dry_run:
|
||||
return None
|
||||
# subprocess.Popen(args, bufsize = 0, executable = None, stdin = None, stdout = None, stderr = None, preexec_fn = None, close_fds = False, shell = False, cwd = None, env = None, universal_newlines = False, startupinfo = None, creationflags = 0)
|
||||
# subprocess.Popen(
|
||||
# args, bufsize = 0, executable = None, stdin = None, stdout = None, stderr = None, preexec_fn = None,
|
||||
# close_fds = False, shell = False, cwd = None, env = None, universal_newlines = False, startupinfo = None,
|
||||
# creationflags = 0
|
||||
# )
|
||||
if log_formatter is not None:
|
||||
# Pipe podman process output through log_formatter (which can add colored prefix)
|
||||
p = subprocess.Popen(
|
||||
@ -1252,11 +1315,11 @@ def rec_merge_one(target, source):
|
||||
if is_list(value2):
|
||||
if key == "volumes":
|
||||
# clean duplicate mount targets
|
||||
pts = {v.split(":", 1)[1] for v in value2 if ":" in v}
|
||||
pts = {v.split(":", 2)[1] for v in value2 if ":" in v}
|
||||
del_ls = [
|
||||
ix
|
||||
for (ix, v) in enumerate(value)
|
||||
if ":" in v and v.split(":", 1)[1] in pts
|
||||
if ":" in v and v.split(":", 2)[1] in pts
|
||||
]
|
||||
for ix in reversed(del_ls):
|
||||
del value[ix]
|
||||
@ -1497,6 +1560,10 @@ class PodmanCompose:
|
||||
# log(filename, json.dumps(content, indent = 2))
|
||||
content = rec_subs(content, self.environ)
|
||||
rec_merge(compose, content)
|
||||
resolved_services = self._resolve_profiles(
|
||||
compose.get("services", {}), set(args.profile)
|
||||
)
|
||||
compose["services"] = resolved_services
|
||||
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()
|
||||
@ -1511,7 +1578,7 @@ class PodmanCompose:
|
||||
if project_name is None:
|
||||
# More strict then actually needed for simplicity: podman requires [a-zA-Z0-9][a-zA-Z0-9_.-]*
|
||||
project_name = (
|
||||
os.environ.get("COMPOSE_PROJECT_NAME", None) or dir_basename.lower()
|
||||
self.environ.get("COMPOSE_PROJECT_NAME", None) or dir_basename.lower()
|
||||
)
|
||||
project_name = norm_re.sub("", project_name)
|
||||
if not project_name:
|
||||
@ -1526,6 +1593,8 @@ class PodmanCompose:
|
||||
if services is None:
|
||||
services = {}
|
||||
log("WARNING: No services defined")
|
||||
# include services with no profile defined or the selected profiles
|
||||
services = self._resolve_profiles(services, set(args.profile))
|
||||
|
||||
# NOTE: maybe add "extends.service" to _deps at this stage
|
||||
flat_deps(services, with_extends=True)
|
||||
@ -1640,6 +1709,30 @@ class PodmanCompose:
|
||||
self.containers = containers
|
||||
self.container_by_name = {c["name"]: c for c in containers}
|
||||
|
||||
def _resolve_profiles(self, defined_services, requested_profiles=None):
|
||||
"""
|
||||
Returns a service dictionary (key = service name, value = service config) compatible with the requested_profiles
|
||||
list.
|
||||
|
||||
The returned service dictionary contains all services which do not include/reference a profile in addition to
|
||||
services that match the requested_profiles.
|
||||
|
||||
:param defined_services: The service dictionary
|
||||
:param requested_profiles: The profiles requested using the --profile arg.
|
||||
"""
|
||||
if requested_profiles is None:
|
||||
requested_profiles = set()
|
||||
|
||||
services = {}
|
||||
|
||||
for name, config in defined_services.items():
|
||||
service_profiles = set(config.get("profiles", []))
|
||||
if not service_profiles or requested_profiles.intersection(
|
||||
service_profiles
|
||||
):
|
||||
services[name] = config
|
||||
return services
|
||||
|
||||
def _parse_args(self):
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
|
||||
self._init_global_parser(parser)
|
||||
@ -1667,7 +1760,7 @@ class PodmanCompose:
|
||||
help="pod creation",
|
||||
metavar="in_pod",
|
||||
type=bool,
|
||||
default=False,
|
||||
default=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pod-args",
|
||||
@ -1691,6 +1784,13 @@ class PodmanCompose:
|
||||
action="append",
|
||||
default=[],
|
||||
)
|
||||
parser.add_argument(
|
||||
"--profile",
|
||||
help="Specify a profile to enable",
|
||||
metavar="profile",
|
||||
action="append",
|
||||
default=[],
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--project-name",
|
||||
@ -1799,7 +1899,7 @@ def is_local(container: dict) -> bool:
|
||||
* has a build section and is not prefixed
|
||||
"""
|
||||
return (
|
||||
not "/" in container["image"]
|
||||
"/" not in container["image"]
|
||||
if "build" in container
|
||||
else container["image"].startswith("localhost/")
|
||||
)
|
||||
@ -1978,7 +2078,8 @@ def build_one(compose, args, cnt):
|
||||
)
|
||||
)
|
||||
build_args.append(ctx)
|
||||
compose.podman.run([], "build", build_args, sleep=0)
|
||||
status = compose.podman.run([], "build", build_args, sleep=0)
|
||||
return status
|
||||
|
||||
|
||||
@cmd_run(podman_compose, "build", "build stack images")
|
||||
@ -1988,10 +2089,12 @@ def compose_build(compose, args):
|
||||
compose.assert_services(args.services)
|
||||
for service in args.services:
|
||||
cnt = compose.container_by_name[container_names_by_service[service][0]]
|
||||
build_one(compose, args, cnt)
|
||||
p = build_one(compose, args, cnt)
|
||||
exit(p.returncode)
|
||||
else:
|
||||
for cnt in compose.containers:
|
||||
build_one(compose, args, cnt)
|
||||
p = build_one(compose, args, cnt)
|
||||
exit(p.returncode)
|
||||
|
||||
|
||||
def create_pods(compose, args): # pylint: disable=unused-argument
|
||||
@ -2522,7 +2625,8 @@ def compose_up_parse(parser):
|
||||
"-d",
|
||||
"--detach",
|
||||
action="store_true",
|
||||
help="Detached mode: Run container in the background, print new container name. Incompatible with --abort-on-container-exit.",
|
||||
help="Detached mode: Run container in the background, print new container name. \
|
||||
Incompatible with --abort-on-container-exit.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-color", action="store_true", help="Produce monochrome output."
|
||||
@ -2573,7 +2677,8 @@ def compose_up_parse(parser):
|
||||
"--timeout",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Use this timeout in seconds for container shutdown when attached or when containers are already running. (default: 10)",
|
||||
help="Use this timeout in seconds for container shutdown when attached or when containers are already running. \
|
||||
(default: 10)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-V",
|
||||
|
@ -1,3 +1,4 @@
|
||||
# pylint: disable=redefined-outer-name
|
||||
import pytest
|
||||
|
||||
from podman_compose import parse_short_mount
|
||||
|
@ -3,3 +3,7 @@ universal = 1
|
||||
|
||||
[metadata]
|
||||
version = attr: podman_compose.__version__
|
||||
|
||||
[flake8]
|
||||
# The GitHub editor is 127 chars wide
|
||||
max-line-length=127
|
10
setup.py
10
setup.py
@ -2,14 +2,16 @@ import os
|
||||
from setuptools import setup
|
||||
|
||||
try:
|
||||
readme = open(os.path.join(os.path.dirname(__file__), "README.md")).read()
|
||||
except:
|
||||
readme = ""
|
||||
README = open(
|
||||
os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8"
|
||||
).read()
|
||||
except: # noqa: E722 # pylint: disable=bare-except
|
||||
README = ""
|
||||
|
||||
setup(
|
||||
name="podman-compose",
|
||||
description="A script to run docker-compose.yml using podman",
|
||||
long_description=readme,
|
||||
long_description=README,
|
||||
long_description_content_type="text/markdown",
|
||||
classifiers=[
|
||||
"Programming Language :: Python",
|
||||
|
25
tests/conftest.py
Normal file
25
tests/conftest.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""conftest.py
|
||||
|
||||
Defines global pytest fixtures available to all tests.
|
||||
"""
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def base_path():
|
||||
"""Returns the base path for the project"""
|
||||
return Path(__file__).parent.parent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_path(base_path):
|
||||
"""Returns the path to the tests directory"""
|
||||
return os.path.join(base_path, "tests")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def podman_compose_path(base_path):
|
||||
"""Returns the path to the podman compose script"""
|
||||
return os.path.join(base_path, "podman_compose.py")
|
24
tests/profile/docker-compose.yml
Normal file
24
tests/profile/docker-compose.yml
Normal file
@ -0,0 +1,24 @@
|
||||
version: "3"
|
||||
services:
|
||||
default-service:
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
service-1:
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
profiles:
|
||||
- profile-1
|
||||
service-2:
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
profiles:
|
||||
- profile-2
|
@ -46,16 +46,16 @@ def test_podman_compose_extends_w_file_subdir():
|
||||
"docker.io/library/busybox",
|
||||
]
|
||||
|
||||
out, err, returncode = capture(command_up)
|
||||
out, _, returncode = capture(command_up)
|
||||
assert 0 == returncode
|
||||
# check container was created and exists
|
||||
out, err, returncode = capture(command_check_container)
|
||||
out, _, returncode = capture(command_check_container)
|
||||
assert 0 == returncode
|
||||
assert out == b'"localhost/subdir_test:me"\n'
|
||||
out, err, returncode = capture(command_down)
|
||||
out, _, returncode = capture(command_down)
|
||||
# cleanup test image(tags)
|
||||
assert 0 == returncode
|
||||
# check container did not exists anymore
|
||||
out, err, returncode = capture(command_check_container)
|
||||
out, _, returncode = capture(command_check_container)
|
||||
assert 0 == returncode
|
||||
assert out == b""
|
||||
|
77
tests/test_podman_compose_config.py
Normal file
77
tests/test_podman_compose_config.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""
|
||||
test_podman_compose_config.py
|
||||
|
||||
Tests the podman-compose config command which is used to return defined compose services.
|
||||
"""
|
||||
import pytest
|
||||
import os
|
||||
from test_podman_compose import capture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def profile_compose_file(test_path):
|
||||
""" "Returns the path to the `profile` compose file used for this test module"""
|
||||
return os.path.join(test_path, "profile", "docker-compose.yml")
|
||||
|
||||
|
||||
def test_config_no_profiles(podman_compose_path, profile_compose_file):
|
||||
"""
|
||||
Tests podman-compose config command without profile enablement.
|
||||
|
||||
:param podman_compose_path: The fixture used to specify the path to the podman compose file.
|
||||
:param profile_compose_file: The fixtued used to specify the path to the "profile" compose used in the test.
|
||||
"""
|
||||
config_cmd = ["python3", podman_compose_path, "-f", profile_compose_file, "config"]
|
||||
|
||||
out, err, return_code = capture(config_cmd)
|
||||
assert return_code == 0
|
||||
|
||||
string_output = out.decode("utf-8")
|
||||
assert "default-service" in string_output
|
||||
assert "service-1" not in string_output
|
||||
assert "service-2" not in string_output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"profiles, expected_services",
|
||||
[
|
||||
(
|
||||
["--profile", "profile-1", "config"],
|
||||
{"default-service": True, "service-1": True, "service-2": False},
|
||||
),
|
||||
(
|
||||
["--profile", "profile-2", "config"],
|
||||
{"default-service": True, "service-1": False, "service-2": True},
|
||||
),
|
||||
(
|
||||
["--profile", "profile-1", "--profile", "profile-2", "config"],
|
||||
{"default-service": True, "service-1": True, "service-2": True},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_config_profiles(
|
||||
podman_compose_path, profile_compose_file, profiles, expected_services
|
||||
):
|
||||
"""
|
||||
Tests podman-compose
|
||||
:param podman_compose_path: The fixture used to specify the path to the podman compose file.
|
||||
:param profile_compose_file: The fixtued used to specify the path to the "profile" compose used in the test.
|
||||
:param profiles: The enabled profiles for the parameterized test.
|
||||
:param expected_services: Dictionary used to model the expected "enabled" services in the profile.
|
||||
Key = service name, Value = True if the service is enabled, otherwise False.
|
||||
"""
|
||||
config_cmd = ["python3", podman_compose_path, "-f", profile_compose_file]
|
||||
config_cmd.extend(profiles)
|
||||
|
||||
out, err, return_code = capture(config_cmd)
|
||||
assert return_code == 0
|
||||
|
||||
actual_output = out.decode("utf-8")
|
||||
|
||||
assert len(expected_services) == 3
|
||||
|
||||
actual_services = {}
|
||||
for service, expected_check in expected_services.items():
|
||||
actual_services[service] = service in actual_output
|
||||
|
||||
assert expected_services == actual_services
|
88
tests/test_podman_compose_up_down.py
Normal file
88
tests/test_podman_compose_up_down.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""
|
||||
test_podman_compose_up_down.py
|
||||
|
||||
Tests the podman compose up and down commands used to create and remove services.
|
||||
"""
|
||||
import pytest
|
||||
import os
|
||||
from test_podman_compose import capture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def profile_compose_file(test_path):
|
||||
""" "Returns the path to the `profile` compose file used for this test module"""
|
||||
return os.path.join(test_path, "profile", "docker-compose.yml")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def teardown(podman_compose_path, profile_compose_file):
|
||||
"""
|
||||
Ensures that the services within the "profile compose file" are removed between each test case.
|
||||
|
||||
:param podman_compose_path: The path to the podman compose script.
|
||||
:param profile_compose_file: The path to the compose file used for this test module.
|
||||
"""
|
||||
# run the test case
|
||||
yield
|
||||
|
||||
down_cmd = [
|
||||
"python3",
|
||||
podman_compose_path,
|
||||
"--profile",
|
||||
"profile-1",
|
||||
"--profile",
|
||||
"profile-2",
|
||||
"-f",
|
||||
profile_compose_file,
|
||||
"down",
|
||||
]
|
||||
capture(down_cmd)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"profiles, expected_services",
|
||||
[
|
||||
(
|
||||
["--profile", "profile-1", "up", "-d"],
|
||||
{"default-service": True, "service-1": True, "service-2": False},
|
||||
),
|
||||
(
|
||||
["--profile", "profile-2", "up", "-d"],
|
||||
{"default-service": True, "service-1": False, "service-2": True},
|
||||
),
|
||||
(
|
||||
["--profile", "profile-1", "--profile", "profile-2", "up", "-d"],
|
||||
{"default-service": True, "service-1": True, "service-2": True},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_up(podman_compose_path, profile_compose_file, profiles, expected_services):
|
||||
up_cmd = [
|
||||
"python3",
|
||||
podman_compose_path,
|
||||
"-f",
|
||||
profile_compose_file,
|
||||
]
|
||||
up_cmd.extend(profiles)
|
||||
|
||||
out, err, return_code = capture(up_cmd)
|
||||
assert return_code == 0
|
||||
|
||||
check_cmd = [
|
||||
"podman",
|
||||
"container",
|
||||
"ps",
|
||||
"--format",
|
||||
'"{{.Names}}"',
|
||||
]
|
||||
out, err, return_code = capture(check_cmd)
|
||||
assert return_code == 0
|
||||
|
||||
assert len(expected_services) == 3
|
||||
actual_output = out.decode("utf-8")
|
||||
|
||||
actual_services = {}
|
||||
for service, expected_check in expected_services.items():
|
||||
actual_services[service] = service in actual_output
|
||||
|
||||
assert expected_services == actual_services
|
15
tests/uidmaps/docker-compose.yml
Normal file
15
tests/uidmaps/docker-compose.yml
Normal file
@ -0,0 +1,15 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
touch:
|
||||
image: busybox
|
||||
command: 'touch /mnt/test'
|
||||
volumes:
|
||||
- ./:/mnt
|
||||
user: 999:999
|
||||
x-podman:
|
||||
uidmaps:
|
||||
- "0:1:1"
|
||||
- "999:0:1"
|
||||
gidmaps:
|
||||
- "0:1:1"
|
||||
- "999:0:1"
|
7
tests/volumes_merge/docker-compose.override.yaml
Normal file
7
tests/volumes_merge/docker-compose.override.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
version: "3"
|
||||
services:
|
||||
web:
|
||||
volumes:
|
||||
- ./override.txt:/var/www/html/index.html:ro,z
|
||||
- ./override.txt:/var/www/html/index2.html:z
|
||||
- ./override.txt:/var/www/html/index3.html
|
11
tests/volumes_merge/docker-compose.yaml
Normal file
11
tests/volumes_merge/docker-compose.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
version: "3"
|
||||
services:
|
||||
web:
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8080"]
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./index.txt:/var/www/html/index.html:ro,z
|
||||
- ./index.txt:/var/www/html/index2.html
|
||||
- ./index.txt:/var/www/html/index3.html:ro
|
1
tests/volumes_merge/index.txt
Normal file
1
tests/volumes_merge/index.txt
Normal file
@ -0,0 +1 @@
|
||||
The file from docker-compose.yaml
|
1
tests/volumes_merge/override.txt
Normal file
1
tests/volumes_merge/override.txt
Normal file
@ -0,0 +1 @@
|
||||
The file from docker-compose.override.yaml
|
Reference in New Issue
Block a user