mirror of
https://github.com/acedanger/shell.git
synced 2026-03-24 18:01:49 -07:00
feat: Add describe and volumes commands to Docker Manager for detailed project insights
This commit is contained in:
@@ -5,6 +5,8 @@ A Python command-line application to manage Docker containers defined in subdire
|
||||
## Features
|
||||
|
||||
- **List**: View currently running containers across all your projects.
|
||||
- **Describe**: Show detailed information about containers in a specific project, including descriptions.
|
||||
- **Volumes**: List volumes used by a specific project.
|
||||
- **Stop**: Stop containers for a specific project or all projects.
|
||||
- **Update**: Pull the latest images and recreate containers (equivalent to `docker compose pull && docker compose up -d`).
|
||||
- **Restart**: Restart containers for a specific project or all projects.
|
||||
|
||||
@@ -35,6 +35,7 @@ To make the command available as dm from any directory, run:
|
||||
import os
|
||||
import argparse
|
||||
import subprocess
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Configuration
|
||||
@@ -113,6 +114,142 @@ def is_project_running(path):
|
||||
return False
|
||||
|
||||
|
||||
def describe_project(projects, target):
|
||||
"""Show detailed information about containers in a project."""
|
||||
if target not in projects:
|
||||
print(f"Error: Project '{target}' not found.")
|
||||
return
|
||||
|
||||
path = projects[target]
|
||||
print(f"Describing project: {target}")
|
||||
|
||||
cmd = ["docker", "compose", "ps", "--format", "json"]
|
||||
res = run_command(cmd, path, capture_output=True)
|
||||
|
||||
if res and res.returncode == 0:
|
||||
lines = res.stdout.strip().split('\n')
|
||||
found_any = False
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
container = json.loads(line)
|
||||
found_any = True
|
||||
name = container.get('Name', 'N/A')
|
||||
print("-" * 60)
|
||||
print(f"Service: {container.get('Service', 'N/A')}")
|
||||
print(f"Container: {name}")
|
||||
print(f"Image: {container.get('Image', 'N/A')}")
|
||||
print(f"State: {container.get('State', 'N/A')}")
|
||||
print(f"Status: {container.get('Status', 'N/A')}")
|
||||
|
||||
ports = container.get('Ports', '')
|
||||
if ports:
|
||||
print(f"Ports: {ports}")
|
||||
|
||||
# Get details via inspect for better parsing (Labels and Mounts)
|
||||
inspect_cmd = ["docker", "inspect", name, "--format", "{{json .}}"]
|
||||
inspect_res = run_command(inspect_cmd, path, capture_output=True)
|
||||
if inspect_res and inspect_res.returncode == 0:
|
||||
try:
|
||||
details = json.loads(inspect_res.stdout.strip())
|
||||
|
||||
# Description from labels
|
||||
labels = details.get("Config", {}).get("Labels", {})
|
||||
description = labels.get("org.opencontainers.image.description")
|
||||
if description:
|
||||
print(f"Description: {description}")
|
||||
|
||||
# Volumes/Mounts
|
||||
mounts = details.get("Mounts", [])
|
||||
if mounts:
|
||||
print("Volumes:")
|
||||
print(f" {'Name/Type':<20} | {'Source (Local Path)':<50} | {'Destination'}")
|
||||
print(f" {'-'*20} | {'-'*50} | {'-'*20}")
|
||||
for mount in mounts:
|
||||
m_type = mount.get("Type")
|
||||
vol_name = mount.get("Name", "")
|
||||
source = mount.get("Source", "")
|
||||
dest = mount.get("Destination", "")
|
||||
|
||||
# If it's a bind mount, use "Bind" as name, otherwise use volume name
|
||||
display_name = vol_name if vol_name else f"[{m_type}]"
|
||||
|
||||
# Truncate source if too long for cleaner display, or just let it wrap?
|
||||
# Let's just print it.
|
||||
print(f" {display_name:<20} | {source:<50} | {dest}")
|
||||
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
if not found_any:
|
||||
print("No containers found for this project.")
|
||||
else:
|
||||
print("Failed to get container info.")
|
||||
|
||||
|
||||
def list_project_volumes(projects, target):
|
||||
"""List volumes used by containers in a project."""
|
||||
if target not in projects:
|
||||
print(f"Error: Project '{target}' not found.")
|
||||
return
|
||||
|
||||
path = projects[target]
|
||||
print(f"Volumes for project: {target}")
|
||||
print("-" * 100)
|
||||
print(f"{'Service':<20} | {'Type':<10} | {'Name/Source':<40} | {'Destination'}")
|
||||
print("-" * 100)
|
||||
|
||||
cmd = ["docker", "compose", "ps", "--format", "json"]
|
||||
res = run_command(cmd, path, capture_output=True)
|
||||
|
||||
if res and res.returncode == 0:
|
||||
lines = res.stdout.strip().split('\n')
|
||||
found_any = False
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
container = json.loads(line)
|
||||
name = container.get('Name', 'N/A')
|
||||
service = container.get('Service', 'N/A')
|
||||
|
||||
# Get details via inspect
|
||||
inspect_cmd = ["docker", "inspect", name, "--format", "{{json .Mounts}}"]
|
||||
inspect_res = run_command(inspect_cmd, path, capture_output=True)
|
||||
|
||||
if inspect_res and inspect_res.returncode == 0:
|
||||
try:
|
||||
mounts = json.loads(inspect_res.stdout.strip())
|
||||
if mounts:
|
||||
found_any = True
|
||||
for mount in mounts:
|
||||
m_type = mount.get("Type", "unknown")
|
||||
source = mount.get("Source", "")
|
||||
dest = mount.get("Destination", "")
|
||||
name_or_source = mount.get("Name", "")
|
||||
|
||||
if m_type == "bind":
|
||||
name_or_source = source
|
||||
elif not name_or_source:
|
||||
name_or_source = source
|
||||
|
||||
print(f"{service:<20} | {m_type:<10} | {name_or_source:<40} | {dest}")
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
if not found_any:
|
||||
print("No volumes found for this project.")
|
||||
else:
|
||||
print("Failed to get container info.")
|
||||
|
||||
|
||||
def manage_project(projects, action, target, extra_args=None):
|
||||
"""
|
||||
Execute the specified action (stop, update, restart, logs) on target project(s).
|
||||
@@ -195,6 +332,14 @@ def main():
|
||||
# List command configuration
|
||||
subparsers.add_parser("list", help="List running containers")
|
||||
|
||||
# Describe command configuration
|
||||
describe_parser = subparsers.add_parser("describe", help="Show details of a project's containers")
|
||||
describe_parser.add_argument("project", help="Project name to describe")
|
||||
|
||||
# Volumes command configuration
|
||||
volumes_parser = subparsers.add_parser("volumes", help="List volumes used by a project")
|
||||
volumes_parser.add_argument("project", help="Project name to list volumes for")
|
||||
|
||||
# Stop command configuration
|
||||
stop_parser = subparsers.add_parser("stop", help="Stop containers")
|
||||
stop_parser.add_argument("project", nargs="?", default=None,
|
||||
@@ -237,6 +382,10 @@ def main():
|
||||
# Dispatch commands
|
||||
if args.command == "list":
|
||||
list_containers(projects)
|
||||
elif args.command == "describe":
|
||||
describe_project(projects, args.project)
|
||||
elif args.command == "volumes":
|
||||
list_project_volumes(projects, args.project)
|
||||
elif args.command == "logs":
|
||||
# Prepare extra args for logs
|
||||
extra_args = []
|
||||
|
||||
Reference in New Issue
Block a user