mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-20 20:29:06 +00:00
feat(cli): phase 1 - local cli with docker integration
This commit is contained in:
314
mcontainer/cli.py
Normal file
314
mcontainer/cli.py
Normal file
@@ -0,0 +1,314 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from .config import ConfigManager
|
||||
from .container import ContainerManager
|
||||
from .models import SessionStatus
|
||||
|
||||
app = typer.Typer(help="Monadical Container Tool")
|
||||
session_app = typer.Typer(help="Manage MC sessions")
|
||||
driver_app = typer.Typer(help="Manage MC drivers", no_args_is_help=True)
|
||||
app.add_typer(session_app, name="session", no_args_is_help=True)
|
||||
app.add_typer(driver_app, name="driver", no_args_is_help=True)
|
||||
|
||||
console = Console()
|
||||
config_manager = ConfigManager()
|
||||
container_manager = ContainerManager(config_manager)
|
||||
|
||||
|
||||
@app.callback(invoke_without_command=True)
|
||||
def main(ctx: typer.Context) -> None:
|
||||
"""Monadical Container Tool"""
|
||||
# If no command is specified, create a session
|
||||
if ctx.invoked_subcommand is None:
|
||||
create_session(driver=None, project=None, env=[], name=None)
|
||||
|
||||
|
||||
@app.command()
|
||||
def version() -> None:
|
||||
"""Show MC version information"""
|
||||
from importlib.metadata import version as get_version
|
||||
|
||||
try:
|
||||
version_str = get_version("mcontainer")
|
||||
console.print(f"MC - Monadical Container Tool v{version_str}")
|
||||
except Exception:
|
||||
console.print("MC - Monadical Container Tool (development version)")
|
||||
|
||||
|
||||
@session_app.command("list")
|
||||
def list_sessions() -> None:
|
||||
"""List active MC sessions"""
|
||||
sessions = container_manager.list_sessions()
|
||||
|
||||
if not sessions:
|
||||
console.print("No active sessions found")
|
||||
return
|
||||
|
||||
table = Table(show_header=True, header_style="bold")
|
||||
table.add_column("ID")
|
||||
table.add_column("Name")
|
||||
table.add_column("Driver")
|
||||
table.add_column("Status")
|
||||
table.add_column("Ports")
|
||||
table.add_column("Project")
|
||||
|
||||
for session in sessions:
|
||||
ports_str = ", ".join(
|
||||
[
|
||||
f"{container_port}:{host_port}"
|
||||
for container_port, host_port in session.ports.items()
|
||||
]
|
||||
)
|
||||
|
||||
status_color = {
|
||||
SessionStatus.RUNNING: "green",
|
||||
SessionStatus.STOPPED: "red",
|
||||
SessionStatus.CREATING: "yellow",
|
||||
SessionStatus.FAILED: "red",
|
||||
}.get(session.status, "white")
|
||||
|
||||
table.add_row(
|
||||
session.id,
|
||||
session.name,
|
||||
session.driver,
|
||||
f"[{status_color}]{session.status}[/{status_color}]",
|
||||
ports_str,
|
||||
session.project or "",
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
@session_app.command("create")
|
||||
def create_session(
|
||||
driver: Optional[str] = typer.Option(None, "--driver", "-d", help="Driver to use"),
|
||||
project: Optional[str] = typer.Option(None, "--project", "-p", help="Project repository URL"),
|
||||
env: List[str] = typer.Option(
|
||||
[], "--env", "-e", help="Environment variables (KEY=VALUE)"
|
||||
),
|
||||
name: Optional[str] = typer.Option(None, "--name", "-n", help="Session name"),
|
||||
) -> None:
|
||||
"""Create a new MC session"""
|
||||
# Use default driver if not specified
|
||||
if not driver:
|
||||
driver = config_manager.config.defaults.get("driver", "goose")
|
||||
|
||||
# Parse environment variables
|
||||
environment = {}
|
||||
for var in env:
|
||||
if "=" in var:
|
||||
key, value = var.split("=", 1)
|
||||
environment[key] = value
|
||||
else:
|
||||
console.print(
|
||||
f"[yellow]Warning: Ignoring invalid environment variable format: {var}[/yellow]"
|
||||
)
|
||||
|
||||
with console.status(f"Creating session with driver '{driver}'..."):
|
||||
session = container_manager.create_session(
|
||||
driver_name=driver,
|
||||
project=project,
|
||||
environment=environment,
|
||||
session_name=name,
|
||||
)
|
||||
|
||||
if session:
|
||||
console.print("[green]Session created successfully![/green]")
|
||||
console.print(f"Session ID: {session.id}")
|
||||
console.print(f"Driver: {session.driver}")
|
||||
|
||||
if session.ports:
|
||||
console.print("Ports:")
|
||||
for container_port, host_port in session.ports.items():
|
||||
console.print(f" {container_port} -> {host_port}")
|
||||
|
||||
console.print(
|
||||
f"\nConnect to the session with:\n mc session connect {session.id}"
|
||||
)
|
||||
else:
|
||||
console.print("[red]Failed to create session[/red]")
|
||||
|
||||
|
||||
@session_app.command("close")
|
||||
def close_session(
|
||||
session_id: str = typer.Argument(..., help="Session ID to close"),
|
||||
) -> None:
|
||||
"""Close a MC session"""
|
||||
with console.status(f"Closing session {session_id}..."):
|
||||
success = container_manager.close_session(session_id)
|
||||
|
||||
if success:
|
||||
console.print(f"[green]Session {session_id} closed successfully[/green]")
|
||||
else:
|
||||
console.print(f"[red]Failed to close session {session_id}[/red]")
|
||||
|
||||
|
||||
@session_app.command("connect")
|
||||
def connect_session(
|
||||
session_id: str = typer.Argument(..., help="Session ID to connect to"),
|
||||
) -> None:
|
||||
"""Connect to a MC session"""
|
||||
console.print(f"Connecting to session {session_id}...")
|
||||
success = container_manager.connect_session(session_id)
|
||||
|
||||
if not success:
|
||||
console.print(f"[red]Failed to connect to session {session_id}[/red]")
|
||||
|
||||
|
||||
@session_app.command("logs")
|
||||
def session_logs(
|
||||
session_id: str = typer.Argument(..., help="Session ID to get logs from"),
|
||||
follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"),
|
||||
) -> None:
|
||||
"""Stream logs from a MC session"""
|
||||
if follow:
|
||||
console.print(f"Streaming logs from session {session_id}... (Ctrl+C to exit)")
|
||||
container_manager.get_session_logs(session_id, follow=True)
|
||||
else:
|
||||
logs = container_manager.get_session_logs(session_id)
|
||||
if logs:
|
||||
console.print(logs)
|
||||
|
||||
|
||||
@app.command()
|
||||
def stop() -> None:
|
||||
"""Stop the current MC session (from inside the container)"""
|
||||
# Check if running inside a container
|
||||
if not os.path.exists("/.dockerenv"):
|
||||
console.print(
|
||||
"[red]This command can only be run from inside a MC container[/red]"
|
||||
)
|
||||
return
|
||||
|
||||
# Stop the container from inside
|
||||
console.print("Stopping the current session...")
|
||||
os.system("kill 1") # Send SIGTERM to PID 1 (container's init process)
|
||||
|
||||
|
||||
# Main CLI entry point that handles project repository URLs
|
||||
@app.command(name="")
|
||||
def quick_create(
|
||||
project: Optional[str] = typer.Argument(..., help="Project repository URL"),
|
||||
driver: Optional[str] = typer.Option(None, "--driver", "-d", help="Driver to use"),
|
||||
env: List[str] = typer.Option(
|
||||
[], "--env", "-e", help="Environment variables (KEY=VALUE)"
|
||||
),
|
||||
name: Optional[str] = typer.Option(None, "--name", "-n", help="Session name"),
|
||||
) -> None:
|
||||
"""Create a new MC session with a project repository"""
|
||||
create_session(driver=driver, project=project, env=env, name=name)
|
||||
|
||||
|
||||
@driver_app.command("list")
|
||||
def list_drivers() -> None:
|
||||
"""List available MC drivers"""
|
||||
drivers = config_manager.list_drivers()
|
||||
|
||||
if not drivers:
|
||||
console.print("No drivers found")
|
||||
return
|
||||
|
||||
table = Table(show_header=True, header_style="bold")
|
||||
table.add_column("Name")
|
||||
table.add_column("Description")
|
||||
table.add_column("Version")
|
||||
table.add_column("Maintainer")
|
||||
table.add_column("Image")
|
||||
|
||||
for name, driver in drivers.items():
|
||||
table.add_row(
|
||||
driver.name,
|
||||
driver.description,
|
||||
driver.version,
|
||||
driver.maintainer,
|
||||
driver.image,
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
@driver_app.command("build")
|
||||
def build_driver(
|
||||
driver_name: str = typer.Argument(..., help="Driver name to build"),
|
||||
tag: str = typer.Option("latest", "--tag", "-t", help="Image tag"),
|
||||
push: bool = typer.Option(False, "--push", "-p", help="Push image to registry after building"),
|
||||
) -> None:
|
||||
"""Build a driver Docker image"""
|
||||
# Get driver path
|
||||
driver_path = config_manager.get_driver_path(driver_name)
|
||||
if not driver_path:
|
||||
console.print(f"[red]Driver '{driver_name}' not found[/red]")
|
||||
return
|
||||
|
||||
# Check if Dockerfile exists
|
||||
dockerfile_path = driver_path / "Dockerfile"
|
||||
if not dockerfile_path.exists():
|
||||
console.print(f"[red]Dockerfile not found in {driver_path}[/red]")
|
||||
return
|
||||
|
||||
# Build image name
|
||||
image_name = f"monadical/mc-{driver_name}:{tag}"
|
||||
|
||||
# Build the image
|
||||
with console.status(f"Building image {image_name}..."):
|
||||
result = os.system(f"cd {driver_path} && docker build -t {image_name} .")
|
||||
|
||||
if result != 0:
|
||||
console.print("[red]Failed to build driver image[/red]")
|
||||
return
|
||||
|
||||
console.print(f"[green]Successfully built image: {image_name}[/green]")
|
||||
|
||||
# Push if requested
|
||||
if push:
|
||||
with console.status(f"Pushing image {image_name}..."):
|
||||
result = os.system(f"docker push {image_name}")
|
||||
|
||||
if result != 0:
|
||||
console.print("[red]Failed to push driver image[/red]")
|
||||
return
|
||||
|
||||
console.print(f"[green]Successfully pushed image: {image_name}[/green]")
|
||||
|
||||
|
||||
@driver_app.command("info")
|
||||
def driver_info(
|
||||
driver_name: str = typer.Argument(..., help="Driver name to get info for"),
|
||||
) -> None:
|
||||
"""Show detailed information about a driver"""
|
||||
driver = config_manager.get_driver(driver_name)
|
||||
if not driver:
|
||||
console.print(f"[red]Driver '{driver_name}' not found[/red]")
|
||||
return
|
||||
|
||||
console.print(f"[bold]Driver: {driver.name}[/bold]")
|
||||
console.print(f"Description: {driver.description}")
|
||||
console.print(f"Version: {driver.version}")
|
||||
console.print(f"Maintainer: {driver.maintainer}")
|
||||
console.print(f"Image: {driver.image}")
|
||||
|
||||
if driver.ports:
|
||||
console.print("\n[bold]Ports:[/bold]")
|
||||
for port in driver.ports:
|
||||
console.print(f" {port}")
|
||||
|
||||
# Get driver path
|
||||
driver_path = config_manager.get_driver_path(driver_name)
|
||||
if driver_path:
|
||||
console.print(f"\n[bold]Path:[/bold] {driver_path}")
|
||||
|
||||
# Check for README
|
||||
readme_path = driver_path / "README.md"
|
||||
if readme_path.exists():
|
||||
console.print("\n[bold]README:[/bold]")
|
||||
with open(readme_path, "r") as f:
|
||||
console.print(f.read())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
Reference in New Issue
Block a user