From 7736573b84c7a51eaa60b932f835726b411ca742 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Tue, 11 Mar 2025 22:58:31 -0600 Subject: [PATCH] feat(cli): separate session state into its own session.yaml file --- mcontainer/cli.py | 4 +- mcontainer/config.py | 20 +--------- mcontainer/container.py | 21 ++++++---- mcontainer/models.py | 3 -- mcontainer/session.py | 85 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 mcontainer/session.py diff --git a/mcontainer/cli.py b/mcontainer/cli.py index 7ca6859..9cd7e4f 100644 --- a/mcontainer/cli.py +++ b/mcontainer/cli.py @@ -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) diff --git a/mcontainer/config.py b/mcontainer/config.py index b62455d..72997c9 100644 --- a/mcontainer/config.py +++ b/mcontainer/config.py @@ -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""" diff --git a/mcontainer/container.py b/mcontainer/container.py index 5f246cc..4f5a372 100644 --- a/mcontainer/container.py +++ b/mcontainer/container.py @@ -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: diff --git a/mcontainer/models.py b/mcontainer/models.py index c103eab..d09d706 100644 --- a/mcontainer/models.py +++ b/mcontainer/models.py @@ -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 diff --git a/mcontainer/session.py b/mcontainer/session.py new file mode 100644 index 0000000..d2c92ab --- /dev/null +++ b/mcontainer/session.py @@ -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()