diff --git a/podman_compose.py b/podman_compose.py index 46725ffd..8058fa82 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -62,9 +62,13 @@ def filteri(a: list[str]) -> list[str]: @overload -def try_int(i: int | str, fallback: int) -> int: ... +def try_int(i: int | str, fallback: int) -> int: + ... + + @overload -def try_int(i: int | str, fallback: None) -> int | None: ... +def try_int(i: int | str, fallback: None) -> int | None: + ... def try_int(i: int | str, fallback: int | None = None) -> int | None: @@ -271,11 +275,18 @@ def fix_mount_dict( @overload -def rec_subs(value: dict, subs_dict: dict[str, Any]) -> dict: ... +def rec_subs(value: dict, subs_dict: dict[str, Any]) -> dict: + ... + + @overload -def rec_subs(value: str, subs_dict: dict[str, Any]) -> str: ... +def rec_subs(value: str, subs_dict: dict[str, Any]) -> str: + ... + + @overload -def rec_subs(value: Iterable, subs_dict: dict[str, Any]) -> Iterable: ... +def rec_subs(value: Iterable, subs_dict: dict[str, Any]) -> Iterable: + ... def rec_subs(value: dict | str | Iterable, subs_dict: dict[str, Any]) -> dict | str | Iterable: @@ -2569,7 +2580,11 @@ def _parse_args(self, argv: list[str] | None = None) -> argparse.Namespace: subparsers = parser.add_subparsers(title="command", dest="command") _ = subparsers.add_parser("help", help="show help") for cmd_name, cmd in self.commands.items(): - subparser = subparsers.add_parser(cmd_name, help=cmd.help, description=cmd.desc) # pylint: disable=protected-access + subparser = subparsers.add_parser( + cmd_name, + help=cmd.help, + description=cmd.desc + ) # pylint: disable=protected-access for cmd_parser in cmd._parse_args: # pylint: disable=protected-access cmd_parser(subparser) self.global_args = parser.parse_args(argv) @@ -2733,6 +2748,66 @@ def wrapped(*args: Any, **kw: Any) -> Any: # actual commands ################### +@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 = [] + 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" }} + ''' + ], + ) + output = output.decode().split() + running = bool(json.loads(output[1])) + status = "{}({})".format(output[0], 1 if running else 0) + path = "{}/{}".format(output[2], output[3]) + + if _format == "table": + if isinstance(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: @@ -4381,6 +4456,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()