mirror of
https://github.com/acedanger/shell.git
synced 2026-03-24 19:11:48 -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
|
## Features
|
||||||
|
|
||||||
- **List**: View currently running containers across all your projects.
|
- **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.
|
- **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`).
|
- **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.
|
- **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 os
|
||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@@ -113,6 +114,142 @@ def is_project_running(path):
|
|||||||
return False
|
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):
|
def manage_project(projects, action, target, extra_args=None):
|
||||||
"""
|
"""
|
||||||
Execute the specified action (stop, update, restart, logs) on target project(s).
|
Execute the specified action (stop, update, restart, logs) on target project(s).
|
||||||
@@ -195,6 +332,14 @@ def main():
|
|||||||
# List command configuration
|
# List command configuration
|
||||||
subparsers.add_parser("list", help="List running containers")
|
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 command configuration
|
||||||
stop_parser = subparsers.add_parser("stop", help="Stop containers")
|
stop_parser = subparsers.add_parser("stop", help="Stop containers")
|
||||||
stop_parser.add_argument("project", nargs="?", default=None,
|
stop_parser.add_argument("project", nargs="?", default=None,
|
||||||
@@ -237,6 +382,10 @@ def main():
|
|||||||
# Dispatch commands
|
# Dispatch commands
|
||||||
if args.command == "list":
|
if args.command == "list":
|
||||||
list_containers(projects)
|
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":
|
elif args.command == "logs":
|
||||||
# Prepare extra args for logs
|
# Prepare extra args for logs
|
||||||
extra_args = []
|
extra_args = []
|
||||||
|
|||||||
Reference in New Issue
Block a user