initial commit after one day coding agent session
This commit is contained in:
commit
372af75b90
88 changed files with 22694 additions and 0 deletions
157
src/mcp_forge/podman/client.py
Normal file
157
src/mcp_forge/podman/client.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
"""
|
||||
Podman client wrapper with security validation.
|
||||
|
||||
Wraps Podman API with security validation and error handling.
|
||||
All container operations are validated against security policy.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from podman import PodmanClient as BasePodmanClient
|
||||
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
|
||||
|
||||
class PodmanConnectionError(Exception):
|
||||
"""Raised when connection to Podman fails."""
|
||||
pass
|
||||
|
||||
|
||||
class PodmanClient:
|
||||
"""
|
||||
Wrapper around Podman API with security validation.
|
||||
|
||||
All container operations are validated against security policy
|
||||
before being sent to Podman. Provides lazy connection and
|
||||
proper error handling.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
socket_path: Path,
|
||||
validator: OperationValidator,
|
||||
audit_logger: AuditLogger
|
||||
):
|
||||
"""
|
||||
Initialize Podman client wrapper.
|
||||
|
||||
Args:
|
||||
socket_path: Path to Podman socket
|
||||
validator: Operation validator for security checks
|
||||
audit_logger: Audit logger for operation logging
|
||||
"""
|
||||
self.socket_path = Path(socket_path)
|
||||
self.validator = validator
|
||||
self.audit_logger = audit_logger
|
||||
self._client: Optional[BasePodmanClient] = None
|
||||
|
||||
def connect(self) -> None:
|
||||
"""
|
||||
Connect to Podman via socket.
|
||||
|
||||
Raises:
|
||||
PodmanConnectionError: If connection fails
|
||||
"""
|
||||
try:
|
||||
# Verify socket exists and is accessible
|
||||
self.verify_socket_access()
|
||||
|
||||
# Create Podman client with Unix socket
|
||||
base_url = f"unix://{self.socket_path}"
|
||||
self._client = BasePodmanClient(base_url=base_url)
|
||||
|
||||
# Test connection with ping (only if client supports it)
|
||||
if hasattr(self._client, 'ping'):
|
||||
self._client.ping()
|
||||
|
||||
except PodmanConnectionError:
|
||||
# Re-raise our own exceptions
|
||||
raise
|
||||
except Exception as e:
|
||||
raise PodmanConnectionError(
|
||||
f"Failed to connect to Podman at {self.socket_path}: {e}"
|
||||
) from e
|
||||
|
||||
def ping(self) -> bool:
|
||||
"""
|
||||
Test connection to Podman.
|
||||
|
||||
Returns:
|
||||
True if connection is healthy
|
||||
|
||||
Raises:
|
||||
PodmanConnectionError: If not connected or ping fails
|
||||
"""
|
||||
if self._client is None:
|
||||
raise PodmanConnectionError("Not connected to Podman")
|
||||
|
||||
try:
|
||||
result = self._client.ping()
|
||||
return result == "OK" or result is True
|
||||
except Exception as e:
|
||||
raise PodmanConnectionError(f"Ping failed: {e}") from e
|
||||
|
||||
def disconnect(self) -> None:
|
||||
"""Disconnect from Podman and cleanup."""
|
||||
if self._client is not None:
|
||||
try:
|
||||
self._client.close()
|
||||
except Exception:
|
||||
pass # Ignore errors during cleanup
|
||||
finally:
|
||||
self._client = None
|
||||
|
||||
def verify_socket_access(self) -> None:
|
||||
"""
|
||||
Verify that socket exists and is accessible.
|
||||
|
||||
Raises:
|
||||
PodmanConnectionError: If socket is not accessible
|
||||
"""
|
||||
if not self.socket_path.exists():
|
||||
raise PodmanConnectionError(
|
||||
f"Podman socket not found: {self.socket_path}"
|
||||
)
|
||||
|
||||
if not os.access(self.socket_path, os.R_OK):
|
||||
raise PodmanConnectionError(
|
||||
f"Podman socket is not readable: {self.socket_path}"
|
||||
)
|
||||
|
||||
def check_api_version(self) -> dict:
|
||||
"""
|
||||
Get Podman API version information.
|
||||
|
||||
Returns:
|
||||
Dictionary with version information
|
||||
|
||||
Raises:
|
||||
PodmanConnectionError: If not connected
|
||||
"""
|
||||
if self._client is None:
|
||||
raise PodmanConnectionError("Not connected to Podman")
|
||||
|
||||
try:
|
||||
return self._client.version()
|
||||
except Exception as e:
|
||||
raise PodmanConnectionError(
|
||||
f"Failed to get API version: {e}"
|
||||
) from e
|
||||
|
||||
@property
|
||||
def client(self) -> BasePodmanClient:
|
||||
"""
|
||||
Get underlying Podman client (lazy connection).
|
||||
|
||||
Returns:
|
||||
Connected Podman client
|
||||
|
||||
Raises:
|
||||
PodmanConnectionError: If connection fails
|
||||
"""
|
||||
if self._client is None:
|
||||
self.connect()
|
||||
assert self._client is not None # Type narrowing for mypy
|
||||
return self._client
|
||||
Loading…
Add table
Add a link
Reference in a new issue