feat(cli): separate session state into its own session.yaml file

This commit is contained in:
2025-03-11 22:58:31 -06:00
parent 133583b941
commit 7736573b84
5 changed files with 103 additions and 30 deletions

View File

@@ -8,6 +8,7 @@ from .config import ConfigManager
from .container import ContainerManager
from .models import SessionStatus
from .user_config import UserConfigManager
from .session import SessionManager
app = typer.Typer(help="Monadical Container Tool")
session_app = typer.Typer(help="Manage MC sessions")
@@ -20,7 +21,8 @@ app.add_typer(config_app, name="config", no_args_is_help=True)
console = Console()
config_manager = ConfigManager()
user_config = UserConfigManager()
container_manager = ContainerManager(config_manager)
session_manager = SessionManager()
container_manager = ContainerManager(config_manager, session_manager)
@app.callback(invoke_without_command=True)

View File

@@ -43,10 +43,6 @@ class ConfigManager:
for driver_name, driver_data in config_data["drivers"].items():
config.drivers[driver_name] = Driver.model_validate(driver_data)
# Add sessions (stored as simple dictionaries)
if "sessions" in config_data:
config.sessions = config_data["sessions"]
return config
except Exception as e:
print(f"Error loading config: {e}")
@@ -106,21 +102,7 @@ class ConfigManager:
return all_drivers
def add_session(self, session_id: str, session_data: dict) -> None:
"""Add a session to the config"""
# Store session data as a dictionary in the config
self.config.sessions[session_id] = session_data
self.save_config()
def remove_session(self, session_id: str) -> None:
"""Remove a session from the config"""
if session_id in self.config.sessions:
del self.config.sessions[session_id]
self.save_config()
def list_sessions(self) -> Dict:
"""List all sessions in the config"""
return self.config.sessions
# Session management has been moved to SessionManager in session.py
def load_driver_from_dir(self, driver_dir: Path) -> Optional[Driver]:
"""Load a driver configuration from a directory"""

View File

@@ -10,11 +10,17 @@ from docker.errors import DockerException, ImageNotFound
from .models import Session, SessionStatus
from .config import ConfigManager
from .session import SessionManager
class ContainerManager:
def __init__(self, config_manager: Optional[ConfigManager] = None):
def __init__(
self,
config_manager: Optional[ConfigManager] = None,
session_manager: Optional[SessionManager] = None,
):
self.config_manager = config_manager or ConfigManager()
self.session_manager = session_manager or SessionManager()
try:
self.client = docker.from_env()
# Test connection
@@ -329,8 +335,10 @@ class ContainerManager:
ports=ports,
)
# Save session to config as JSON-compatible dict
self.config_manager.add_session(session_id, session.model_dump(mode="json"))
# Save session to the session manager as JSON-compatible dict
self.session_manager.add_session(
session_id, session.model_dump(mode="json")
)
return session
@@ -365,7 +373,6 @@ class ContainerManager:
# Execute interactive shell in container
# The init-status.sh script will automatically show logs if needed
print(f"Connecting to session {session_id}...")
os.system(f"docker exec -it {session.container_id} /bin/bash")
return True
@@ -392,7 +399,7 @@ class ContainerManager:
container = self.client.containers.get(session.container_id)
container.stop()
container.remove()
self.config_manager.remove_session(session.id)
self.session_manager.remove_session(session.id)
return True
except DockerException as e:
print(f"Error closing session {session.id}: {e}")
@@ -425,8 +432,8 @@ class ContainerManager:
# Stop and remove container
container.stop()
container.remove()
# Remove from config
self.config_manager.remove_session(session.id)
# Remove from session storage
self.session_manager.remove_session(session.id)
# Notify about completion
if progress_callback:

View File

@@ -63,9 +63,6 @@ class Session(BaseModel):
class Config(BaseModel):
docker: Dict[str, str] = Field(default_factory=dict)
drivers: Dict[str, Driver] = Field(default_factory=dict)
sessions: Dict[str, dict] = Field(
default_factory=dict
) # Store as dict to avoid serialization issues
defaults: Dict[str, object] = Field(
default_factory=dict
) # Can store strings, booleans, or other values

85
mcontainer/session.py Normal file
View File

@@ -0,0 +1,85 @@
"""
Session storage management for Monadical Container Tool.
"""
import os
import yaml
from pathlib import Path
from typing import Dict, Optional
DEFAULT_SESSIONS_FILE = Path.home() / ".config" / "mc" / "sessions.yaml"
class SessionManager:
"""Manager for container sessions."""
def __init__(self, sessions_path: Optional[Path] = None):
"""Initialize the session manager.
Args:
sessions_path: Optional path to the sessions file.
Defaults to ~/.config/mc/sessions.yaml.
"""
self.sessions_path = sessions_path or DEFAULT_SESSIONS_FILE
self.sessions = self._load_sessions()
def _load_sessions(self) -> Dict[str, dict]:
"""Load sessions from file or create an empty sessions file if it doesn't exist."""
if not self.sessions_path.exists():
# Create directory if it doesn't exist
self.sessions_path.parent.mkdir(parents=True, exist_ok=True)
# Create empty sessions file
with open(self.sessions_path, "w") as f:
yaml.safe_dump({}, f)
# Set secure permissions
os.chmod(self.sessions_path, 0o600)
return {}
# Load existing sessions
with open(self.sessions_path, "r") as f:
sessions = yaml.safe_load(f) or {}
return sessions
def save(self) -> None:
"""Save the sessions to file."""
with open(self.sessions_path, "w") as f:
yaml.safe_dump(self.sessions, f)
def add_session(self, session_id: str, session_data: dict) -> None:
"""Add a session to storage.
Args:
session_id: The unique session ID
session_data: The session data (Session model dump as dict)
"""
self.sessions[session_id] = session_data
self.save()
def get_session(self, session_id: str) -> Optional[dict]:
"""Get a session by ID.
Args:
session_id: The session ID
Returns:
The session data or None if not found
"""
return self.sessions.get(session_id)
def list_sessions(self) -> Dict[str, dict]:
"""List all sessions.
Returns:
Dict of session ID to session data
"""
return self.sessions
def remove_session(self, session_id: str) -> None:
"""Remove a session from storage.
Args:
session_id: The session ID to remove
"""
if session_id in self.sessions:
del self.sessions[session_id]
self.save()