diff --git a/docker-manager/docker-manager.py b/docker-manager/docker-manager.py index 007dcae..d0a6e26 100755 --- a/docker-manager/docker-manager.py +++ b/docker-manager/docker-manager.py @@ -9,7 +9,10 @@ Usage Examples: 1. List all running containers: $ dm list - 2. Update a specific project (pulls latest images and recreates containers): + 2. List only stacks with updates available: + $ dm list --update + + 3. Update a specific project (pulls latest images and recreates containers): $ dm update media-server 3. Stop all projects (with confirmation prompt): @@ -171,7 +174,7 @@ def run_command(cmd, cwd, capture_output=False): return None -def list_containers(projects): +def list_containers(projects, show_updates_only=False): """List running containers for all projects.""" table = Table(title="Docker Containers", box=box.ROUNDED) table.add_column("Project", style="cyan", no_wrap=True) @@ -186,6 +189,11 @@ def list_containers(projects): found_any = False for name, path in sorted(projects.items()): + + # Buffer for project rows + project_rows = [] + project_has_update = False + # Get running container names cmd = ["docker", "compose", "ps", "--format", "json"] res = run_command(cmd, path, capture_output=True) @@ -204,8 +212,6 @@ def list_containers(projects): if not c_name: continue - found_any = True - # Get version and update status version = "unknown" update_status = "" @@ -214,7 +220,7 @@ def list_containers(projects): norm_image = image tag = "latest" - # Remove tag if present (heuristic: split on last colon, if right side has no slashes) + # Remove tag if present if ":" in norm_image: base, sep, t = norm_image.rpartition(":") if sep and "/" not in t: @@ -231,27 +237,21 @@ def list_containers(projects): if norm_image in diun_info: image_tags = diun_info[norm_image] - # Only proceed if we have info for this specific tag if tag in image_tags: info = image_tags[tag] - # Try to get version from Diun labels first version = get_image_version(info.get("labels", {})) - # Check for updates - # First get the Image ID of the running container inspect_id_cmd = ["docker", "inspect", c_name, "--format", "{{.Image}}"] inspect_id_res = run_command(inspect_id_cmd, path, capture_output=True) if inspect_id_res and inspect_id_res.returncode == 0: image_id = inspect_id_res.stdout.strip() - # Now inspect the Image ID to get RepoDigests inspect_digest_cmd = ["docker", "inspect", image_id, "--format", "{{if .RepoDigests}}{{index .RepoDigests 0}}{{end}}"] inspect_digest_res = run_command(inspect_digest_cmd, path, capture_output=True) if inspect_digest_res and inspect_digest_res.returncode == 0: running_digest_full = inspect_digest_res.stdout.strip() - # running_digest is like name@sha256:hash if "@" in running_digest_full: running_digest = running_digest_full.split("@")[1] latest_digest = info.get("digest", "") @@ -259,6 +259,7 @@ def list_containers(projects): if latest_digest: if running_digest != latest_digest: update_status = "[bold red]Update Available[/bold red]" + project_has_update = True else: update_status = "[green]Up to Date[/green]" else: @@ -266,7 +267,6 @@ def list_containers(projects): else: update_status = "[dim]Not Monitored[/dim]" - # If version is still unknown, try to get from running container labels if version in ("latest", "unknown"): inspect_cmd = ["docker", "inspect", c_name, "--format", "{{json .Config.Labels}}"] inspect_res = run_command(inspect_cmd, path, capture_output=True) @@ -277,15 +277,25 @@ def list_containers(projects): except Exception: pass - table.add_row(name, c_name, state, image, version, update_status) + project_rows.append((name, c_name, state, image, version, update_status)) except json.JSONDecodeError: pass + # If not hiding, OR if update, add to table + if not show_updates_only or project_has_update: + if project_rows: + found_any = True + for row in project_rows: + table.add_row(*row) + if found_any: console.print(table) else: - console.print("[yellow]No running containers found in managed projects.[/yellow]") + if show_updates_only: + console.print("[green]No updates available for running containers.[/green]") + else: + console.print("[yellow]No running containers found in managed projects.[/yellow]") def is_project_running(path): @@ -544,7 +554,8 @@ def main(): dest="command", help="Command to execute") # List command configuration - subparsers.add_parser("list", help="List running containers") + list_parser = subparsers.add_parser("list", help="List running containers") + list_parser.add_argument("--update", action="store_true", help="Show only stacks that have updates available") # Describe command configuration describe_parser = subparsers.add_parser("describe", help="Show details of a project's containers") @@ -596,7 +607,7 @@ def main(): # Dispatch commands if args.command == "list": - list_containers(projects) + list_containers(projects, show_updates_only=args.update) elif args.command == "describe": describe_project(projects, args.project) elif args.command == "volumes":