mirror of
https://github.com/containers/podman-compose.git
synced 2025-06-19 11:26:41 +02:00
Add tests to make sure all async paths are covered
Not at 100% yet. But upped code coverage significantly and covered major async calls. Signed-off-by: Falmarri <463948+Falmarri@users.noreply.github.com>
This commit is contained in:
parent
3c9628b462
commit
c6a1c4c432
@ -21,6 +21,7 @@ import asyncio.subprocess
|
|||||||
import signal
|
import signal
|
||||||
|
|
||||||
import shlex
|
import shlex
|
||||||
|
from asyncio import Task
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from shlex import quote as cmd_quote
|
from shlex import quote as cmd_quote
|
||||||
@ -82,7 +83,13 @@ def try_float(i, fallback=None):
|
|||||||
|
|
||||||
|
|
||||||
def log(*msgs, sep=" ", end="\n"):
|
def log(*msgs, sep=" ", end="\n"):
|
||||||
|
try:
|
||||||
|
current_task = asyncio.current_task()
|
||||||
|
except RuntimeError:
|
||||||
|
current_task = None
|
||||||
line = (sep.join([str(msg) for msg in msgs])) + end
|
line = (sep.join([str(msg) for msg in msgs])) + end
|
||||||
|
if current_task and not current_task.get_name().startswith("Task"):
|
||||||
|
line = f"[{current_task.get_name()}] " + line
|
||||||
sys.stderr.write(line)
|
sys.stderr.write(line)
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
@ -1151,27 +1158,29 @@ def flat_deps(services, with_extends=False):
|
|||||||
|
|
||||||
|
|
||||||
class Podman:
|
class Podman:
|
||||||
def __init__(self, compose, podman_path="podman", dry_run=False):
|
def __init__(self, compose, podman_path="podman", dry_run=False, semaphore: asyncio.Semaphore = asyncio.Semaphore(sys.maxsize)):
|
||||||
self.compose = compose
|
self.compose = compose
|
||||||
self.podman_path = podman_path
|
self.podman_path = podman_path
|
||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
|
self.semaphore = semaphore
|
||||||
|
|
||||||
async def output(self, podman_args, cmd="", cmd_args=None):
|
async def output(self, podman_args, cmd="", cmd_args=None):
|
||||||
cmd_args = cmd_args or []
|
async with self.semaphore:
|
||||||
xargs = self.compose.get_podman_args(cmd) if cmd else []
|
cmd_args = cmd_args or []
|
||||||
cmd_ls = [self.podman_path, *podman_args, cmd] + xargs + cmd_args
|
xargs = self.compose.get_podman_args(cmd) if cmd else []
|
||||||
log(cmd_ls)
|
cmd_ls = [self.podman_path, *podman_args, cmd] + xargs + cmd_args
|
||||||
p = await asyncio.subprocess.create_subprocess_exec(
|
log(cmd_ls)
|
||||||
*cmd_ls,
|
p = await asyncio.subprocess.create_subprocess_exec(
|
||||||
stdout=asyncio.subprocess.PIPE,
|
*cmd_ls,
|
||||||
stderr=asyncio.subprocess.PIPE
|
stdout=asyncio.subprocess.PIPE,
|
||||||
)
|
stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
|
||||||
stdout_data, stderr_data = await p.communicate()
|
stdout_data, stderr_data = await p.communicate()
|
||||||
if p.returncode == 0:
|
if p.returncode == 0:
|
||||||
return stdout_data
|
return stdout_data
|
||||||
else:
|
else:
|
||||||
raise subprocess.CalledProcessError(p.returncode, " ".join(cmd_ls), stderr_data)
|
raise subprocess.CalledProcessError(p.returncode, " ".join(cmd_ls), stderr_data)
|
||||||
|
|
||||||
def exec(
|
def exec(
|
||||||
self,
|
self,
|
||||||
@ -1190,62 +1199,62 @@ class Podman:
|
|||||||
podman_args,
|
podman_args,
|
||||||
cmd="",
|
cmd="",
|
||||||
cmd_args=None,
|
cmd_args=None,
|
||||||
wait=True,
|
|
||||||
sleep=1,
|
|
||||||
log_formatter=None,
|
log_formatter=None,
|
||||||
*,
|
*,
|
||||||
# Intentionally mutable default argument to hold references to tasks
|
# Intentionally mutable default argument to hold references to tasks
|
||||||
task_reference=set()
|
task_reference=set()
|
||||||
):
|
) -> int:
|
||||||
cmd_args = list(map(str, cmd_args or []))
|
async with self.semaphore:
|
||||||
xargs = self.compose.get_podman_args(cmd) if cmd else []
|
cmd_args = list(map(str, cmd_args or []))
|
||||||
cmd_ls = [self.podman_path, *podman_args, cmd] + xargs + cmd_args
|
xargs = self.compose.get_podman_args(cmd) if cmd else []
|
||||||
log(" ".join([str(i) for i in cmd_ls]))
|
cmd_ls = [self.podman_path, *podman_args, cmd] + xargs + cmd_args
|
||||||
if self.dry_run:
|
log(" ".join([str(i) for i in cmd_ls]))
|
||||||
return None
|
if self.dry_run:
|
||||||
if log_formatter is not None:
|
return None
|
||||||
|
|
||||||
async def format_out(stdout):
|
if log_formatter is not None:
|
||||||
while True:
|
|
||||||
l = await stdout.readline()
|
|
||||||
if l:
|
|
||||||
print(log_formatter, l.decode('utf-8'), end='')
|
|
||||||
if stdout.at_eof():
|
|
||||||
break
|
|
||||||
|
|
||||||
# read, write = os.pipe()
|
async def format_out(stdout):
|
||||||
# Pipe podman process output through log_formatter (which can add colored prefix)
|
while True:
|
||||||
p = await asyncio.subprocess.create_subprocess_exec(
|
l = await stdout.readline()
|
||||||
*cmd_ls, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
if l:
|
||||||
) # pylint: disable=consider-using-with
|
print(log_formatter, l.decode('utf-8'), end='')
|
||||||
|
if stdout.at_eof():
|
||||||
|
break
|
||||||
|
|
||||||
# This is hacky to make the tasks not get garbage collected
|
p = await asyncio.subprocess.create_subprocess_exec(
|
||||||
# https://github.com/python/cpython/issues/91887
|
*cmd_ls, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
out_t = asyncio.create_task(format_out(p.stdout))
|
) # pylint: disable=consider-using-with
|
||||||
task_reference.add(out_t)
|
|
||||||
out_t.add_done_callback(task_reference.discard)
|
|
||||||
|
|
||||||
err_t = asyncio.create_task(format_out(p.stderr))
|
# This is hacky to make the tasks not get garbage collected
|
||||||
task_reference.add(err_t)
|
# https://github.com/python/cpython/issues/91887
|
||||||
err_t.add_done_callback(task_reference.discard)
|
out_t = asyncio.create_task(format_out(p.stdout))
|
||||||
else:
|
task_reference.add(out_t)
|
||||||
p = await asyncio.subprocess.create_subprocess_exec(*cmd_ls) # pylint: disable=consider-using-with
|
out_t.add_done_callback(task_reference.discard)
|
||||||
|
|
||||||
|
err_t = asyncio.create_task(format_out(p.stderr))
|
||||||
|
task_reference.add(err_t)
|
||||||
|
err_t.add_done_callback(task_reference.discard)
|
||||||
|
|
||||||
|
else:
|
||||||
|
p = await asyncio.subprocess.create_subprocess_exec(*cmd_ls) # pylint: disable=consider-using-with
|
||||||
|
|
||||||
if wait:
|
|
||||||
try:
|
try:
|
||||||
exit_code = await p.wait()
|
exit_code = await p.wait()
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError as e:
|
||||||
|
log(f"Sending termination signal")
|
||||||
p.terminate()
|
p.terminate()
|
||||||
exit_code = await p.wait()
|
try:
|
||||||
|
async with asyncio.timeout(10):
|
||||||
|
exit_code = await p.wait()
|
||||||
|
except TimeoutError:
|
||||||
|
log(f"container did not shut down after 10 seconds, killing")
|
||||||
|
p.kill()
|
||||||
|
exit_code = await p.wait()
|
||||||
|
|
||||||
log("exit code:", exit_code)
|
log(f"exit code: {exit_code}")
|
||||||
return exit_code
|
return exit_code
|
||||||
|
|
||||||
if sleep:
|
|
||||||
log(f"Sleep {sleep}")
|
|
||||||
await asyncio.sleep(sleep)
|
|
||||||
return p
|
|
||||||
|
|
||||||
async def volume_ls(self, proj=None):
|
async def volume_ls(self, proj=None):
|
||||||
if not proj:
|
if not proj:
|
||||||
proj = self.compose.project_name
|
proj = self.compose.project_name
|
||||||
@ -1520,7 +1529,8 @@ class PodmanCompose:
|
|||||||
if args.dry_run is False:
|
if args.dry_run is False:
|
||||||
log(f"Binary {podman_path} has not been found.")
|
log(f"Binary {podman_path} has not been found.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
self.podman = Podman(self, podman_path, args.dry_run)
|
self.podman = Podman(self, podman_path, args.dry_run, asyncio.Semaphore(args.parallel))
|
||||||
|
|
||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
# just to make sure podman is running
|
# just to make sure podman is running
|
||||||
try:
|
try:
|
||||||
@ -1921,6 +1931,11 @@ class PodmanCompose:
|
|||||||
help="No action; perform a simulation of commands",
|
help="No action; perform a simulation of commands",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--parallel",
|
||||||
|
type=int,
|
||||||
|
default=os.environ.get("COMPOSE_PARALLEL_LIMIT", sys.maxsize)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
podman_compose = PodmanCompose()
|
podman_compose = PodmanCompose()
|
||||||
@ -1979,7 +1994,7 @@ async def compose_version(compose, args):
|
|||||||
print(json.dumps(res))
|
print(json.dumps(res))
|
||||||
return
|
return
|
||||||
print("podman-compose version", __version__)
|
print("podman-compose version", __version__)
|
||||||
await compose.podman.run(["--version"], "", [], sleep=0)
|
await compose.podman.run(["--version"], "", [])
|
||||||
|
|
||||||
|
|
||||||
def is_local(container: dict) -> bool:
|
def is_local(container: dict) -> bool:
|
||||||
@ -2023,8 +2038,8 @@ async def compose_systemd(compose, args):
|
|||||||
f.write(f"{k}={v}\n")
|
f.write(f"{k}={v}\n")
|
||||||
print(f"writing [{fn}]: done.")
|
print(f"writing [{fn}]: done.")
|
||||||
print("\n\ncreating the pod without starting it: ...\n\n")
|
print("\n\ncreating the pod without starting it: ...\n\n")
|
||||||
process = subprocess.run([script, "up", "--no-start"], check=False)
|
process = await asyncio.subprocess.create_subprocess_exec(script, ["up", "--no-start"])
|
||||||
print("\nfinal exit code is ", process.returncode)
|
print("\nfinal exit code is ", process)
|
||||||
username = getpass.getuser()
|
username = getpass.getuser()
|
||||||
print(
|
print(
|
||||||
f"""
|
f"""
|
||||||
@ -2096,13 +2111,7 @@ async def compose_pull(compose, args):
|
|||||||
local_images = {cnt["image"] for cnt in img_containers if is_local(cnt)}
|
local_images = {cnt["image"] for cnt in img_containers if is_local(cnt)}
|
||||||
images -= local_images
|
images -= local_images
|
||||||
|
|
||||||
sem = asyncio.Semaphore(args.parallel)
|
await asyncio.gather(*[compose.podman.run([], "pull", [image]) for image in images])
|
||||||
|
|
||||||
async def _pull(image: str):
|
|
||||||
async with sem:
|
|
||||||
return await compose.podman.run([], "pull", [image], sleep=0)
|
|
||||||
|
|
||||||
await asyncio.gather(*[_pull(image) for image in images])
|
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(podman_compose, "push", "push stack images")
|
@cmd_run(podman_compose, "push", "push stack images")
|
||||||
@ -2113,7 +2122,7 @@ async def compose_push(compose, args):
|
|||||||
continue
|
continue
|
||||||
if services and cnt["_service"] not in services:
|
if services and cnt["_service"] not in services:
|
||||||
continue
|
continue
|
||||||
await compose.podman.run([], "push", [cnt["image"]], sleep=0)
|
await compose.podman.run([], "push", [cnt["image"]])
|
||||||
|
|
||||||
|
|
||||||
async def build_one(compose, args, cnt):
|
async def build_one(compose, args, cnt):
|
||||||
@ -2177,29 +2186,24 @@ async def build_one(compose, args, cnt):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
build_args.append(ctx)
|
build_args.append(ctx)
|
||||||
status = await compose.podman.run([], "build", build_args, sleep=0)
|
status = await compose.podman.run([], "build", build_args)
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(podman_compose, "build", "build stack images")
|
@cmd_run(podman_compose, "build", "build stack images")
|
||||||
async def compose_build(compose, args):
|
async def compose_build(compose, args):
|
||||||
tasks = []
|
tasks = []
|
||||||
sem = asyncio.Semaphore(args.parallel)
|
|
||||||
|
|
||||||
async def safe_build_task(cnt):
|
|
||||||
async with sem:
|
|
||||||
return await build_one(compose, args, cnt)
|
|
||||||
|
|
||||||
if args.services:
|
if args.services:
|
||||||
container_names_by_service = compose.container_names_by_service
|
container_names_by_service = compose.container_names_by_service
|
||||||
compose.assert_services(args.services)
|
compose.assert_services(args.services)
|
||||||
for service in args.services:
|
for service in args.services:
|
||||||
cnt = compose.container_by_name[container_names_by_service[service][0]]
|
cnt = compose.container_by_name[container_names_by_service[service][0]]
|
||||||
tasks.append(asyncio.create_task(safe_build_task(cnt)))
|
tasks.append(asyncio.create_task(build_one(compose, args, cnt)))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for cnt in compose.containers:
|
for cnt in compose.containers:
|
||||||
tasks.append(asyncio.create_task(safe_build_task(cnt)))
|
tasks.append(asyncio.create_task(build_one(compose, args, cnt)))
|
||||||
|
|
||||||
status = 0
|
status = 0
|
||||||
for t in asyncio.as_completed(tasks):
|
for t in asyncio.as_completed(tasks):
|
||||||
@ -2283,7 +2287,7 @@ async def compose_up(compose: PodmanCompose, args):
|
|||||||
continue
|
continue
|
||||||
podman_args = await container_to_args(compose, cnt, detached=args.detach)
|
podman_args = await container_to_args(compose, cnt, detached=args.detach)
|
||||||
subproc = await compose.podman.run([], podman_command, podman_args)
|
subproc = await compose.podman.run([], podman_command, podman_args)
|
||||||
if podman_command == "run" and subproc:
|
if podman_command == "run" and subproc is not None:
|
||||||
await compose.podman.run([], "start", [cnt["name"]])
|
await compose.podman.run([], "start", [cnt["name"]])
|
||||||
if args.no_start or args.detach or args.dry_run:
|
if args.no_start or args.detach or args.dry_run:
|
||||||
return
|
return
|
||||||
@ -2320,20 +2324,28 @@ async def compose_up(compose: PodmanCompose, args):
|
|||||||
|
|
||||||
tasks.add(
|
tasks.add(
|
||||||
asyncio.create_task(
|
asyncio.create_task(
|
||||||
compose.podman.run([], "start", ["-a", cnt["name"]], sleep=None, log_formatter=log_formatter),
|
compose.podman.run([], "start", ["-a", cnt["name"]], log_formatter=log_formatter),
|
||||||
name=cnt["_service"]
|
name=cnt["_service"]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
exit_code = 0
|
exit_code = 0
|
||||||
for task in asyncio.as_completed(tasks):
|
exiting = False
|
||||||
t = await task
|
while tasks:
|
||||||
|
done, tasks = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||||
if args.abort_on_container_exit:
|
if args.abort_on_container_exit:
|
||||||
[_.cancel() for _ in tasks if not _.cancelling() and not _.cancelled()]
|
if not exiting:
|
||||||
if task.get_name() == exit_code_from:
|
# If 2 containers exit at the exact same time, the cancellation of the other ones cause the status
|
||||||
exit_code = t
|
# to overwrite. Sleeping for 1 seems to fix this and make it match docker-compose
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
[_.cancel() for _ in tasks if not _.cancelling() and not _.cancelled()]
|
||||||
|
t: Task
|
||||||
|
exiting = True
|
||||||
|
for t in done:
|
||||||
|
if t.get_name() == exit_code_from:
|
||||||
|
exit_code = t.result()
|
||||||
|
|
||||||
sys.exit(exit_code)
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
def get_volume_names(compose, cnt):
|
def get_volume_names(compose, cnt):
|
||||||
@ -2360,6 +2372,8 @@ async def compose_down(compose, args):
|
|||||||
timeout_global = getattr(args, "timeout", None)
|
timeout_global = getattr(args, "timeout", None)
|
||||||
containers = list(reversed(compose.containers))
|
containers = list(reversed(compose.containers))
|
||||||
|
|
||||||
|
down_tasks = []
|
||||||
|
|
||||||
for cnt in containers:
|
for cnt in containers:
|
||||||
if cnt["_service"] in excluded:
|
if cnt["_service"] in excluded:
|
||||||
continue
|
continue
|
||||||
@ -2370,11 +2384,12 @@ async def compose_down(compose, args):
|
|||||||
timeout = str_to_seconds(timeout_str)
|
timeout = str_to_seconds(timeout_str)
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
podman_stop_args.extend(["-t", str(timeout)])
|
podman_stop_args.extend(["-t", str(timeout)])
|
||||||
await compose.podman.run([], "stop", [*podman_stop_args, cnt["name"]], sleep=0)
|
down_tasks.append(asyncio.create_task(compose.podman.run([], "stop", [*podman_stop_args, cnt["name"]]), name=cnt["name"]))
|
||||||
|
await asyncio.gather(*down_tasks)
|
||||||
for cnt in containers:
|
for cnt in containers:
|
||||||
if cnt["_service"] in excluded:
|
if cnt["_service"] in excluded:
|
||||||
continue
|
continue
|
||||||
await compose.podman.run([], "rm", [cnt["name"]], sleep=0)
|
await compose.podman.run([], "rm", [cnt["name"]])
|
||||||
if args.remove_orphans:
|
if args.remove_orphans:
|
||||||
names = (
|
names = (
|
||||||
(await compose.podman.output(
|
(await compose.podman.output(
|
||||||
@ -2392,9 +2407,9 @@ async def compose_down(compose, args):
|
|||||||
.splitlines()
|
.splitlines()
|
||||||
)
|
)
|
||||||
for name in names:
|
for name in names:
|
||||||
await compose.podman.run([], "stop", [*podman_args, name], sleep=0)
|
await compose.podman.run([], "stop", [*podman_args, name])
|
||||||
for name in names:
|
for name in names:
|
||||||
await compose.podman.run([], "rm", [name], sleep=0)
|
await compose.podman.run([], "rm", [name])
|
||||||
if args.volumes:
|
if args.volumes:
|
||||||
vol_names_to_keep = set()
|
vol_names_to_keep = set()
|
||||||
for cnt in containers:
|
for cnt in containers:
|
||||||
@ -2402,7 +2417,7 @@ async def compose_down(compose, args):
|
|||||||
continue
|
continue
|
||||||
vol_names_to_keep.update(get_volume_names(compose, cnt))
|
vol_names_to_keep.update(get_volume_names(compose, cnt))
|
||||||
log("keep", vol_names_to_keep)
|
log("keep", vol_names_to_keep)
|
||||||
async for volume_name in compose.podman.volume_ls():
|
for volume_name in await compose.podman.volume_ls():
|
||||||
if volume_name in vol_names_to_keep:
|
if volume_name in vol_names_to_keep:
|
||||||
continue
|
continue
|
||||||
await compose.podman.run([], "volume", ["rm", volume_name])
|
await compose.podman.run([], "volume", ["rm", volume_name])
|
||||||
@ -2410,28 +2425,23 @@ async def compose_down(compose, args):
|
|||||||
if excluded:
|
if excluded:
|
||||||
return
|
return
|
||||||
for pod in compose.pods:
|
for pod in compose.pods:
|
||||||
await compose.podman.run([], "pod", ["rm", pod["name"]], sleep=0)
|
await compose.podman.run([], "pod", ["rm", pod["name"]])
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(podman_compose, "ps", "show status of containers")
|
@cmd_run(podman_compose, "ps", "show status of containers")
|
||||||
async def compose_ps(compose, args):
|
async def compose_ps(compose, args):
|
||||||
proj_name = compose.project_name
|
proj_name = compose.project_name
|
||||||
|
ps_args = ["-a", "--filter", f"label=io.podman.compose.project={proj_name}"]
|
||||||
if args.quiet is True:
|
if args.quiet is True:
|
||||||
await compose.podman.run(
|
ps_args.extend(["--format", "{{.ID}}"])
|
||||||
[],
|
elif args.format:
|
||||||
"ps",
|
ps_args.extend(["--format", args.format])
|
||||||
[
|
|
||||||
"-a",
|
await compose.podman.run(
|
||||||
"--format",
|
[],
|
||||||
"{{.ID}}",
|
"ps",
|
||||||
"--filter",
|
ps_args,
|
||||||
f"label=io.podman.compose.project={proj_name}",
|
)
|
||||||
],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await compose.podman.run(
|
|
||||||
[], "ps", ["-a", "--filter", f"label=io.podman.compose.project={proj_name}"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(
|
@cmd_run(
|
||||||
@ -2459,17 +2469,19 @@ async def compose_run(compose, args):
|
|||||||
no_start=False,
|
no_start=False,
|
||||||
no_cache=False,
|
no_cache=False,
|
||||||
build_arg=[],
|
build_arg=[],
|
||||||
|
parallel=1,
|
||||||
|
remove_orphans=True
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
compose.commands["up"](compose, up_args)
|
await compose.commands["up"](compose, up_args)
|
||||||
|
|
||||||
build_args = argparse.Namespace(
|
build_args = argparse.Namespace(
|
||||||
services=[args.service],
|
services=[args.service],
|
||||||
if_not_exists=(not args.build),
|
if_not_exists=(not args.build),
|
||||||
build_arg=[],
|
build_arg=[],
|
||||||
**args.__dict__,
|
**args.__dict__
|
||||||
)
|
)
|
||||||
compose.commands["build"](compose, build_args)
|
await compose.commands["build"](compose, build_args)
|
||||||
|
|
||||||
# adjust one-off container options
|
# adjust one-off container options
|
||||||
name0 = "{}_{}_tmp{}".format(
|
name0 = "{}_{}_tmp{}".format(
|
||||||
@ -2510,7 +2522,7 @@ async def compose_run(compose, args):
|
|||||||
podman_args.insert(1, "-i")
|
podman_args.insert(1, "-i")
|
||||||
if args.rm:
|
if args.rm:
|
||||||
podman_args.insert(1, "--rm")
|
podman_args.insert(1, "--rm")
|
||||||
p = await compose.podman.run([], "run", podman_args, sleep=0)
|
p = await compose.podman.run([], "run", podman_args)
|
||||||
sys.exit(p)
|
sys.exit(p)
|
||||||
|
|
||||||
|
|
||||||
@ -2540,7 +2552,7 @@ async def compose_exec(compose, args):
|
|||||||
podman_args += [container_name]
|
podman_args += [container_name]
|
||||||
if args.cnt_command is not None and len(args.cnt_command) > 0:
|
if args.cnt_command is not None and len(args.cnt_command) > 0:
|
||||||
podman_args += args.cnt_command
|
podman_args += args.cnt_command
|
||||||
p = await compose.podman.run([], "exec", podman_args, sleep=0)
|
p = await compose.podman.run([], "exec", podman_args)
|
||||||
sys.exit(p)
|
sys.exit(p)
|
||||||
|
|
||||||
|
|
||||||
@ -2571,23 +2583,23 @@ async def transfer_service_status(compose, args, action):
|
|||||||
timeout = str_to_seconds(timeout_str)
|
timeout = str_to_seconds(timeout_str)
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
podman_args.extend(["-t", str(timeout)])
|
podman_args.extend(["-t", str(timeout)])
|
||||||
tasks.append(asyncio.create_task(compose.podman.run([], action, podman_args + [target], sleep=0)))
|
tasks.append(asyncio.create_task(compose.podman.run([], action, podman_args + [target])))
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(podman_compose, "start", "start specific services")
|
@cmd_run(podman_compose, "start", "start specific services")
|
||||||
async def compose_start(compose, args):
|
async def compose_start(compose, args):
|
||||||
transfer_service_status(compose, args, "start")
|
await transfer_service_status(compose, args, "start")
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(podman_compose, "stop", "stop specific services")
|
@cmd_run(podman_compose, "stop", "stop specific services")
|
||||||
async def compose_stop(compose, args):
|
async def compose_stop(compose, args):
|
||||||
transfer_service_status(compose, args, "stop")
|
await transfer_service_status(compose, args, "stop")
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(podman_compose, "restart", "restart specific services")
|
@cmd_run(podman_compose, "restart", "restart specific services")
|
||||||
async def compose_restart(compose, args):
|
async def compose_restart(compose, args):
|
||||||
transfer_service_status(compose, args, "restart")
|
await transfer_service_status(compose, args, "restart")
|
||||||
|
|
||||||
|
|
||||||
@cmd_run(podman_compose, "logs", "show logs from services")
|
@cmd_run(podman_compose, "logs", "show logs from services")
|
||||||
@ -3098,15 +3110,6 @@ def compose_ps_parse(parser):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@cmd_parse(podman_compose, ["build", "pull", "up"])
|
|
||||||
def compose_build_pull_parse(parser):
|
|
||||||
parser.add_argument(
|
|
||||||
"--parallel",
|
|
||||||
type=int,
|
|
||||||
default=os.environ.get("PODMAN_PARALLEL", sys.maxsize)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@cmd_parse(podman_compose, ["build", "up"])
|
@cmd_parse(podman_compose, ["build", "up"])
|
||||||
def compose_build_up_parse(parser):
|
def compose_build_up_parse(parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@ -3215,12 +3218,6 @@ def compose_stats_parse(parser):
|
|||||||
type=int,
|
type=int,
|
||||||
help="Time in seconds between stats reports (default 5)",
|
help="Time in seconds between stats reports (default 5)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"-f",
|
|
||||||
"--format",
|
|
||||||
type=str,
|
|
||||||
help="Pretty-print container statistics to JSON or using a Go template",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--no-reset",
|
"--no-reset",
|
||||||
help="Disable resetting the screen between intervals",
|
help="Disable resetting the screen between intervals",
|
||||||
@ -3233,6 +3230,15 @@ def compose_stats_parse(parser):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@cmd_parse(podman_compose, ["ps", "stats"])
|
||||||
|
def compose_format_parse(parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--format",
|
||||||
|
type=str,
|
||||||
|
help="Pretty-print container statistics to JSON or using a Go template",
|
||||||
|
)
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
await podman_compose.run()
|
await podman_compose.run()
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
podman-compose run --rm sleep /bin/sh -c 'wget -O - http://localhost:8000/hosts'
|
podman-compose run --rm sleep /bin/sh -c 'wget -O - http://web:8000/hosts'
|
||||||
```
|
```
|
||||||
|
@ -9,7 +9,8 @@ services:
|
|||||||
sleep:
|
sleep:
|
||||||
image: busybox
|
image: busybox
|
||||||
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
|
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
|
||||||
depends_on: "web"
|
depends_on:
|
||||||
|
- "web"
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
|
@ -31,14 +31,14 @@ def test_podman_compose_extends_w_file_subdir():
|
|||||||
]
|
]
|
||||||
|
|
||||||
command_check_container = [
|
command_check_container = [
|
||||||
"podman",
|
"coverage",
|
||||||
"container",
|
"run",
|
||||||
|
str(main_path.joinpath("podman_compose.py")),
|
||||||
|
"-f",
|
||||||
|
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
|
||||||
"ps",
|
"ps",
|
||||||
"--sort",
|
|
||||||
"status",
|
|
||||||
"--all",
|
|
||||||
"--format",
|
"--format",
|
||||||
'"{{.Image}}"',
|
'{{.Image}}',
|
||||||
]
|
]
|
||||||
|
|
||||||
command_down = [
|
command_down = [
|
||||||
@ -52,18 +52,17 @@ def test_podman_compose_extends_w_file_subdir():
|
|||||||
out, _, returncode = capture(command_up)
|
out, _, returncode = capture(command_up)
|
||||||
assert 0 == returncode
|
assert 0 == returncode
|
||||||
# check container was created and exists
|
# check container was created and exists
|
||||||
out, _, returncode = capture(command_check_container)
|
out, err, returncode = capture(command_check_container)
|
||||||
assert 0 == returncode
|
assert 0 == returncode
|
||||||
assert b'"localhost/subdir_test:me"\n' in out
|
assert b'localhost/subdir_test:me\n' == out
|
||||||
out, _, returncode = capture(command_down)
|
out, _, returncode = capture(command_down)
|
||||||
# cleanup test image(tags)
|
# cleanup test image(tags)
|
||||||
assert 0 == returncode
|
assert 0 == returncode
|
||||||
print('ok')
|
print('ok')
|
||||||
# check container did not exists anymore
|
# check container did not exists anymore
|
||||||
out, _, returncode = capture(command_check_container)
|
out, _, returncode = capture(command_check_container)
|
||||||
print(out)
|
|
||||||
assert 0 == returncode
|
assert 0 == returncode
|
||||||
assert b'"localhost/subdir_test:me"\n' not in out
|
assert b'' == out
|
||||||
|
|
||||||
|
|
||||||
def test_podman_compose_extends_w_empty_service():
|
def test_podman_compose_extends_w_empty_service():
|
||||||
|
180
tests/test_podman_compose_tests.py
Normal file
180
tests/test_podman_compose_tests.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
"""
|
||||||
|
test_podman_compose_up_down.py
|
||||||
|
|
||||||
|
Tests the podman compose up and down commands used to create and remove services.
|
||||||
|
"""
|
||||||
|
# pylint: disable=redefined-outer-name
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from test_podman_compose import capture
|
||||||
|
|
||||||
|
|
||||||
|
def test_exit_from(podman_compose_path, test_path):
|
||||||
|
up_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "exit-from", "docker-compose.yaml"),
|
||||||
|
"up"
|
||||||
|
]
|
||||||
|
|
||||||
|
out, _, return_code = capture(up_cmd + ["--exit-code-from", "sh1"])
|
||||||
|
assert return_code == 1
|
||||||
|
|
||||||
|
out, _, return_code = capture(up_cmd + ["--exit-code-from", "sh2"])
|
||||||
|
assert return_code == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_run(podman_compose_path, test_path):
|
||||||
|
"""
|
||||||
|
This will test depends_on as well
|
||||||
|
"""
|
||||||
|
run_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "deps", "docker-compose.yaml"),
|
||||||
|
"run",
|
||||||
|
"--rm",
|
||||||
|
"sleep",
|
||||||
|
"/bin/sh",
|
||||||
|
"-c",
|
||||||
|
"wget -q -O - http://web:8000/hosts"
|
||||||
|
]
|
||||||
|
|
||||||
|
out, _, return_code = capture(run_cmd)
|
||||||
|
assert b'127.0.0.1\tlocalhost' in out
|
||||||
|
|
||||||
|
# Run it again to make sure we can run it twice. I saw an issue where a second run, with the container left up,
|
||||||
|
# would fail
|
||||||
|
run_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "deps", "docker-compose.yaml"),
|
||||||
|
"run",
|
||||||
|
"--rm",
|
||||||
|
"sleep",
|
||||||
|
"/bin/sh",
|
||||||
|
"-c",
|
||||||
|
"wget -q -O - http://web:8000/hosts"
|
||||||
|
]
|
||||||
|
|
||||||
|
out, _, return_code = capture(run_cmd)
|
||||||
|
assert b'127.0.0.1\tlocalhost' in out
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
# This leaves a container running. Not sure it's intended, but it matches docker-compose
|
||||||
|
down_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "deps", "docker-compose.yaml"),
|
||||||
|
"down",
|
||||||
|
]
|
||||||
|
|
||||||
|
out, _, return_code = capture(run_cmd)
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_up_with_ports(podman_compose_path, test_path):
|
||||||
|
|
||||||
|
|
||||||
|
up_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "ports", "docker-compose.yml"),
|
||||||
|
"up",
|
||||||
|
"-d",
|
||||||
|
"--force-recreate"
|
||||||
|
]
|
||||||
|
|
||||||
|
down_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "ports", "docker-compose.yml"),
|
||||||
|
"down",
|
||||||
|
"--volumes"
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
out, _, return_code = capture(up_cmd)
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
|
||||||
|
finally:
|
||||||
|
out, _, return_code = capture(down_cmd)
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_down_with_vols(podman_compose_path, test_path):
|
||||||
|
|
||||||
|
up_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "vol", "docker-compose.yaml"),
|
||||||
|
"up",
|
||||||
|
"-d"
|
||||||
|
]
|
||||||
|
|
||||||
|
down_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "vol", "docker-compose.yaml"),
|
||||||
|
"down",
|
||||||
|
"--volumes"
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
out, _, return_code = capture(["podman", "volume", "create", "my-app-data"])
|
||||||
|
assert return_code == 0
|
||||||
|
out, _, return_code = capture(["podman", "volume", "create", "actual-name-of-volume"])
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
out, _, return_code = capture(up_cmd)
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
capture(["podman", "inspect", "volume", ""])
|
||||||
|
|
||||||
|
finally:
|
||||||
|
out, _, return_code = capture(down_cmd)
|
||||||
|
capture(["podman", "volume", "rm", "my-app-data"])
|
||||||
|
capture(["podman", "volume", "rm", "actual-name-of-volume"])
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_down_with_orphans(podman_compose_path, test_path):
|
||||||
|
|
||||||
|
container_id, _ , return_code = capture(["podman", "run", "--rm", "-d", "busybox", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"])
|
||||||
|
|
||||||
|
down_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path,
|
||||||
|
"-f",
|
||||||
|
os.path.join(test_path, "ports", "docker-compose.yml"),
|
||||||
|
"down",
|
||||||
|
"--volumes",
|
||||||
|
"--remove-orphans"
|
||||||
|
]
|
||||||
|
|
||||||
|
out, _, return_code = capture(down_cmd)
|
||||||
|
assert return_code == 0
|
||||||
|
|
||||||
|
_, _, exists = capture(["podman", "container", "exists", container_id.decode("utf-8")])
|
||||||
|
|
||||||
|
assert exists == 1
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user