Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions podman_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ def filteri(a: list[str]) -> list[str]:

@overload
def try_int(i: int | str, fallback: int) -> int: ...


@overload
def try_int(i: int | str, fallback: None) -> int | None: ...

Expand Down Expand Up @@ -272,8 +274,12 @@ def fix_mount_dict(

@overload
def rec_subs(value: dict, subs_dict: dict[str, Any]) -> dict: ...


@overload
def rec_subs(value: str, subs_dict: dict[str, Any]) -> str: ...


@overload
def rec_subs(value: Iterable, subs_dict: dict[str, Any]) -> Iterable: ...

Expand Down Expand Up @@ -2734,6 +2740,63 @@ def wrapped(*args: Any, **kw: Any) -> Any:
###################


@cmd_run(podman_compose, "ls", "List running compose projects")
async def list_running_projects(compose: PodmanCompose, args: argparse.Namespace) -> None:
img_containers = [cnt for cnt in compose.containers if "image" in cnt]
parsed_args = vars(args)
_format = parsed_args.get("format", "table")
data: list[Any] = []
if _format == "table":
data.append(["NAME", "STATUS", "CONFIG_FILES"])

for img in img_containers:
try:
name = img["name"]
output = await compose.podman.output(
[],
"inspect",
[
name,
"--format",
'''
{{ .State.Status }}
{{ .State.Running }}
{{ index .Config.Labels "com.docker.compose.project.working_dir" }}
{{ index .Config.Labels "com.docker.compose.project.config_files" }}
''',
],
)
command_output = output.decode().split()
running = bool(json.loads(command_output[1]))
status = "{}({})".format(command_output[0], 1 if running else 0)
path = "{}/{}".format(command_output[2], command_output[3])

if _format == "table":
if isinstance(command_output, list):
data.append([name, status, path])

elif _format == "json":
# Replicate how docker compose returns the list
json_obj = {"Name": name, "Status": status, "ConfigFiles": path}
data.append(json_obj)
except Exception:
break

if _format == "table":
# Determine the maximum length of each column
column_widths = [max(map(len, column)) for column in zip(*data)]

# Print each row
for row in data:
# Format each cell using the maximum column width
formatted_row = [cell.ljust(width) for cell, width in zip(row, column_widths)]
formatted_row[-2:] = ["\t".join(formatted_row[-2:]).strip()]
print("\t".join(formatted_row))

elif _format == "json":
print(data)


@cmd_run(podman_compose, "version", "show version")
async def compose_version(compose: PodmanCompose, args: argparse.Namespace) -> None:
if getattr(args, "short", False):
Expand Down Expand Up @@ -4381,6 +4444,17 @@ def compose_format_parse(parser: argparse.ArgumentParser) -> None:
)


@cmd_parse(podman_compose, "ls")
def compose_ls_parse(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"-f",
"--format",
choices=["table", "json"],
default="table",
help="Format the output",
)


async def async_main() -> None:
await podman_compose.run()

Expand Down
Empty file.
13 changes: 13 additions & 0 deletions tests/integration/list/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3.6'

services:
service_1:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"]
service_2:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"]
service_3:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"]

102 changes: 102 additions & 0 deletions tests/integration/list/test_podman_compose_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import ast
import os
import unittest
from typing import Union

from tests.integration.test_utils import RunSubprocessMixin
from tests.integration.test_utils import podman_compose_path
from tests.integration.test_utils import test_path


class TestPodmanComposeInclude(unittest.TestCase, RunSubprocessMixin):
def test_podman_compose_list(self) -> None:
"""
Test podman compose list (ls) command
"""
command_up = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "list", "docker-compose.yml"),
"up",
"-d",
]

command_list = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "list", "docker-compose.yml"),
"ls",
]

command_check_container = [
"podman",
"ps",
"-a",
"--filter",
"label=io.podman.compose.project=list",
"--format",
'"{{.Image}}"',
]

command_container_id = [
"podman",
"ps",
"-a",
"--filter",
"label=io.podman.compose.project=list",
"--format",
'"{{.ID}}"',
]

command_down = ["podman", "rm", "--force"]
service: Union[dict[str, str], str]

running_containers = []
self.run_subprocess_assert_returncode(command_up)
out, _ = self.run_subprocess_assert_returncode(command_list)
str_out = out.decode()

# Test for table view
services = str_out.strip().split("\n")
headers = [h.strip() for h in services[0].split("\t")]

for service in services[1:]:
values = [val.strip() for val in service.split("\t")]
zipped = dict(zip(headers, values))
self.assertNotEqual(zipped.get("NAME"), None)
self.assertNotEqual(zipped.get("STATUS"), None)
self.assertNotEqual(zipped.get("CONFIG_FILES"), None)
running_containers.append(zipped)
self.assertEqual(len(running_containers), 3)

# Test for json view
command_list.extend(["--format", "json"])
out, _ = self.run_subprocess_assert_returncode(command_list)
str_out = out.decode()
json_services: list[dict] = ast.literal_eval(str_out)
self.assertIsInstance(json_services, list)

for service in json_services:
self.assertIsInstance(service, dict)
self.assertNotEqual(service.get("Name"), None)
self.assertNotEqual(service.get("Status"), None)
self.assertNotEqual(service.get("ConfigFiles"), None)

self.assertEqual(len(json_services), 3)

# Get container ID to remove it
out, _ = self.run_subprocess_assert_returncode(command_container_id)
self.assertNotEqual(out, b"")
container_ids = out.decode().strip().split("\n")
container_ids = [container_id.replace('"', "") for container_id in container_ids]
command_down.extend(container_ids)
out, _ = self.run_subprocess_assert_returncode(command_down)
# cleanup test image(tags)
self.assertNotEqual(out, b"")
# check container did not exists anymore
out, _ = self.run_subprocess_assert_returncode(command_check_container)
self.assertEqual(out, b"")