mirror of
https://github.com/Monadical-SAS/cubbi.git
synced 2025-12-20 20:29:06 +00:00
feat(cli): support to join external network
This commit is contained in:
48
README.md
48
README.md
@@ -50,6 +50,9 @@ mc session create -e VAR1=value1 -e VAR2=value2
|
|||||||
mc session create -v /local/path:/container/path
|
mc session create -v /local/path:/container/path
|
||||||
mc session create -v ~/data:/data -v ./configs:/etc/app/config
|
mc session create -v ~/data:/data -v ./configs:/etc/app/config
|
||||||
|
|
||||||
|
# Connect to external Docker networks
|
||||||
|
mc session create --network teamnet --network dbnet
|
||||||
|
|
||||||
# Shorthand for creating a session with a project repository
|
# Shorthand for creating a session with a project repository
|
||||||
mc github.com/username/repo
|
mc github.com/username/repo
|
||||||
```
|
```
|
||||||
@@ -124,6 +127,51 @@ mc config set anthropic.api_key "sk-ant-..."
|
|||||||
mc config reset
|
mc config reset
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Default Networks Configuration
|
||||||
|
|
||||||
|
You can configure default networks that will be applied to every new session:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List default networks
|
||||||
|
mc config network list
|
||||||
|
|
||||||
|
# Add a network to defaults
|
||||||
|
mc config network add teamnet
|
||||||
|
|
||||||
|
# Remove a network from defaults
|
||||||
|
mc config network remove teamnet
|
||||||
|
```
|
||||||
|
|
||||||
|
### External Network Connectivity
|
||||||
|
|
||||||
|
MC containers can connect to external Docker networks, allowing them to communicate with other services in those networks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a session connected to external networks
|
||||||
|
mc session create --network teamnet --network dbnet
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Networks must be "attachable" to be joined by MC containers. Here's how to create attachable networks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create an attachable network with Docker
|
||||||
|
docker network create --driver bridge --attachable teamnet
|
||||||
|
|
||||||
|
# Example docker-compose.yml with attachable network
|
||||||
|
# docker-compose.yml
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: nginx
|
||||||
|
networks:
|
||||||
|
- teamnet
|
||||||
|
|
||||||
|
networks:
|
||||||
|
teamnet:
|
||||||
|
driver: bridge
|
||||||
|
attachable: true # This is required for MC containers to connect
|
||||||
|
```
|
||||||
|
|
||||||
### Service Credentials
|
### Service Credentials
|
||||||
|
|
||||||
Service credentials like API keys configured in `~/.config/mc/config.yaml` are automatically passed to containers as environment variables:
|
Service credentials like API keys configured in `~/.config/mc/config.yaml` are automatically passed to containers as environment variables:
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ defaults:
|
|||||||
driver: "goose" # Default driver to use
|
driver: "goose" # Default driver to use
|
||||||
connect: true # Automatically connect after creating session
|
connect: true # Automatically connect after creating session
|
||||||
mount_local: true # Mount local directory by default
|
mount_local: true # Mount local directory by default
|
||||||
|
networks: [] # Default networks to connect to (besides mc-network)
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# Service credentials with simplified naming
|
# Service credentials with simplified naming
|
||||||
@@ -96,7 +97,7 @@ services:
|
|||||||
api_key: "sk-or-..."
|
api_key: "sk-or-..."
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
network: "mc-network" # Docker network to use
|
network: "mc-network" # Default Docker network to use
|
||||||
socket: "/var/run/docker.sock" # Docker socket path
|
socket: "/var/run/docker.sock" # Docker socket path
|
||||||
|
|
||||||
remote:
|
remote:
|
||||||
@@ -153,6 +154,11 @@ mc config get defaults.driver
|
|||||||
mc config set langfuse.url "https://cloud.langfuse.com"
|
mc config set langfuse.url "https://cloud.langfuse.com"
|
||||||
mc config set openai.api_key "sk-..."
|
mc config set openai.api_key "sk-..."
|
||||||
|
|
||||||
|
# Network configuration
|
||||||
|
mc config network list # List default networks
|
||||||
|
mc config network add example-network # Add a network to defaults
|
||||||
|
mc config network remove example-network # Remove a network from defaults
|
||||||
|
|
||||||
# Reset configuration to defaults
|
# Reset configuration to defaults
|
||||||
mc config reset
|
mc config reset
|
||||||
```
|
```
|
||||||
@@ -177,6 +183,9 @@ mc session create --driver goose
|
|||||||
# Create a session with a specific project repository
|
# Create a session with a specific project repository
|
||||||
mc session create --driver goose --project github.com/hello/private
|
mc session create --driver goose --project github.com/hello/private
|
||||||
|
|
||||||
|
# Create a session with external networks
|
||||||
|
mc session create --network teamnet --network othernetwork
|
||||||
|
|
||||||
# Create a session with a project (shorthand)
|
# Create a session with a project (shorthand)
|
||||||
mc git@github.com:hello/private
|
mc git@github.com:hello/private
|
||||||
|
|
||||||
@@ -558,12 +567,44 @@ persistent_configs:
|
|||||||
3. **claude-code**: Claude Code environment
|
3. **claude-code**: Claude Code environment
|
||||||
4. **custom**: Custom Dockerfile support
|
4. **custom**: Custom Dockerfile support
|
||||||
|
|
||||||
|
## Network Management
|
||||||
|
|
||||||
|
### Docker Network Integration
|
||||||
|
|
||||||
|
MC provides flexible network management for containers:
|
||||||
|
|
||||||
|
1. **Default MC Network**:
|
||||||
|
- Each container is automatically connected to the MC network (`mc-network` by default)
|
||||||
|
- This ensures containers can communicate with each other
|
||||||
|
|
||||||
|
2. **External Network Connection**:
|
||||||
|
- Containers can be connected to one or more external Docker networks
|
||||||
|
- This allows integration with existing infrastructure (e.g., databases, web servers)
|
||||||
|
- Networks can be specified at session creation time: `mc session create --network mynetwork`
|
||||||
|
|
||||||
|
3. **Default Networks Configuration**:
|
||||||
|
- Users can configure default networks in their configuration
|
||||||
|
- These networks will be used for all new sessions unless overridden
|
||||||
|
- Managed with `mc config network` commands
|
||||||
|
|
||||||
|
4. **Network Command Examples**:
|
||||||
|
```bash
|
||||||
|
# Use with session creation
|
||||||
|
mc session create --network teamnet
|
||||||
|
|
||||||
|
# Use with multiple networks
|
||||||
|
mc session create --network teamnet --network dbnet
|
||||||
|
|
||||||
|
# Configure default networks
|
||||||
|
mc config network add teamnet
|
||||||
|
```
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
1. **Container Isolation**: Each session runs in an isolated container
|
1. **Container Isolation**: Each session runs in an isolated container
|
||||||
2. **Authentication**: Integration with Authentik for secure authentication
|
2. **Authentication**: Integration with Authentik for secure authentication
|
||||||
3. **Resource Limits**: Configurable CPU, memory, and storage limits
|
3. **Resource Limits**: Configurable CPU, memory, and storage limits
|
||||||
4. **Network Isolation**: Internal Docker network for container-to-container communication
|
4. **Network Isolation**: Internal Docker network for container-to-container communication with optional external network connections
|
||||||
5. **Encrypted Connections**: TLS for API connections and SSH for terminal access
|
5. **Encrypted Connections**: TLS for API connections and SSH for terminal access
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ def main(ctx: typer.Context) -> None:
|
|||||||
project=None,
|
project=None,
|
||||||
env=[],
|
env=[],
|
||||||
volume=[],
|
volume=[],
|
||||||
|
network=[],
|
||||||
name=None,
|
name=None,
|
||||||
no_connect=False,
|
no_connect=False,
|
||||||
no_mount=False,
|
no_mount=False,
|
||||||
@@ -113,6 +114,9 @@ def create_session(
|
|||||||
volume: List[str] = typer.Option(
|
volume: List[str] = typer.Option(
|
||||||
[], "--volume", "-v", help="Mount volumes (LOCAL_PATH:CONTAINER_PATH)"
|
[], "--volume", "-v", help="Mount volumes (LOCAL_PATH:CONTAINER_PATH)"
|
||||||
),
|
),
|
||||||
|
network: List[str] = typer.Option(
|
||||||
|
[], "--network", "-N", help="Connect to additional Docker networks"
|
||||||
|
),
|
||||||
name: Optional[str] = typer.Option(None, "--name", "-n", help="Session name"),
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Session name"),
|
||||||
no_connect: bool = typer.Option(
|
no_connect: bool = typer.Option(
|
||||||
False, "--no-connect", help="Don't automatically connect to the session"
|
False, "--no-connect", help="Don't automatically connect to the session"
|
||||||
@@ -166,6 +170,15 @@ def create_session(
|
|||||||
f"[yellow]Warning: Ignoring invalid volume format: {vol}. Use LOCAL_PATH:CONTAINER_PATH.[/yellow]"
|
f"[yellow]Warning: Ignoring invalid volume format: {vol}. Use LOCAL_PATH:CONTAINER_PATH.[/yellow]"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Get default networks from user config
|
||||||
|
default_networks = user_config.get("defaults.networks", [])
|
||||||
|
|
||||||
|
# Combine default networks with user-specified networks, removing duplicates
|
||||||
|
all_networks = list(set(default_networks + network))
|
||||||
|
|
||||||
|
if all_networks:
|
||||||
|
console.print(f"Networks: {', '.join(all_networks)}")
|
||||||
|
|
||||||
with console.status(f"Creating session with driver '{driver}'..."):
|
with console.status(f"Creating session with driver '{driver}'..."):
|
||||||
session = container_manager.create_session(
|
session = container_manager.create_session(
|
||||||
driver_name=driver,
|
driver_name=driver,
|
||||||
@@ -174,6 +187,7 @@ def create_session(
|
|||||||
session_name=name,
|
session_name=name,
|
||||||
mount_local=not no_mount and user_config.get("defaults.mount_local", True),
|
mount_local=not no_mount and user_config.get("defaults.mount_local", True),
|
||||||
volumes=volume_mounts,
|
volumes=volume_mounts,
|
||||||
|
networks=all_networks,
|
||||||
)
|
)
|
||||||
|
|
||||||
if session:
|
if session:
|
||||||
@@ -315,6 +329,9 @@ def quick_create(
|
|||||||
volume: List[str] = typer.Option(
|
volume: List[str] = typer.Option(
|
||||||
[], "--volume", "-v", help="Mount volumes (LOCAL_PATH:CONTAINER_PATH)"
|
[], "--volume", "-v", help="Mount volumes (LOCAL_PATH:CONTAINER_PATH)"
|
||||||
),
|
),
|
||||||
|
network: List[str] = typer.Option(
|
||||||
|
[], "--network", "-N", help="Connect to additional Docker networks"
|
||||||
|
),
|
||||||
name: Optional[str] = typer.Option(None, "--name", "-n", help="Session name"),
|
name: Optional[str] = typer.Option(None, "--name", "-n", help="Session name"),
|
||||||
no_connect: bool = typer.Option(
|
no_connect: bool = typer.Option(
|
||||||
False, "--no-connect", help="Don't automatically connect to the session"
|
False, "--no-connect", help="Don't automatically connect to the session"
|
||||||
@@ -335,6 +352,7 @@ def quick_create(
|
|||||||
project=project,
|
project=project,
|
||||||
env=env,
|
env=env,
|
||||||
volume=volume,
|
volume=volume,
|
||||||
|
network=network,
|
||||||
name=name,
|
name=name,
|
||||||
no_connect=no_connect,
|
no_connect=no_connect,
|
||||||
no_mount=no_mount,
|
no_mount=no_mount,
|
||||||
@@ -449,6 +467,11 @@ def driver_info(
|
|||||||
console.print(f.read())
|
console.print(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
# Create a network subcommand for config
|
||||||
|
network_app = typer.Typer(help="Manage default networks")
|
||||||
|
config_app.add_typer(network_app, name="network", no_args_is_help=True)
|
||||||
|
|
||||||
|
|
||||||
# Configuration commands
|
# Configuration commands
|
||||||
@config_app.command("list")
|
@config_app.command("list")
|
||||||
def list_config() -> None:
|
def list_config() -> None:
|
||||||
@@ -544,5 +567,56 @@ def reset_config(
|
|||||||
console.print("[green]Configuration reset to defaults[/green]")
|
console.print("[green]Configuration reset to defaults[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
# Network configuration commands
|
||||||
|
@network_app.command("list")
|
||||||
|
def list_networks() -> None:
|
||||||
|
"""List all default networks"""
|
||||||
|
networks = user_config.get("defaults.networks", [])
|
||||||
|
|
||||||
|
if not networks:
|
||||||
|
console.print("No default networks configured")
|
||||||
|
return
|
||||||
|
|
||||||
|
table = Table(show_header=True, header_style="bold")
|
||||||
|
table.add_column("Network")
|
||||||
|
|
||||||
|
for network in networks:
|
||||||
|
table.add_row(network)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
@network_app.command("add")
|
||||||
|
def add_network(
|
||||||
|
network: str = typer.Argument(..., help="Network name to add to defaults"),
|
||||||
|
) -> None:
|
||||||
|
"""Add a network to default networks"""
|
||||||
|
networks = user_config.get("defaults.networks", [])
|
||||||
|
|
||||||
|
if network in networks:
|
||||||
|
console.print(f"Network '{network}' is already in defaults")
|
||||||
|
return
|
||||||
|
|
||||||
|
networks.append(network)
|
||||||
|
user_config.set("defaults.networks", networks)
|
||||||
|
console.print(f"[green]Added network '{network}' to defaults[/green]")
|
||||||
|
|
||||||
|
|
||||||
|
@network_app.command("remove")
|
||||||
|
def remove_network(
|
||||||
|
network: str = typer.Argument(..., help="Network name to remove from defaults"),
|
||||||
|
) -> None:
|
||||||
|
"""Remove a network from default networks"""
|
||||||
|
networks = user_config.get("defaults.networks", [])
|
||||||
|
|
||||||
|
if network not in networks:
|
||||||
|
console.print(f"Network '{network}' is not in defaults")
|
||||||
|
return
|
||||||
|
|
||||||
|
networks.remove(network)
|
||||||
|
user_config.set("defaults.networks", networks)
|
||||||
|
console.print(f"[green]Removed network '{network}' from defaults[/green]")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app()
|
app()
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ class ContainerManager:
|
|||||||
session_name: Optional[str] = None,
|
session_name: Optional[str] = None,
|
||||||
mount_local: bool = True,
|
mount_local: bool = True,
|
||||||
volumes: Optional[Dict[str, Dict[str, str]]] = None,
|
volumes: Optional[Dict[str, Dict[str, str]]] = None,
|
||||||
|
networks: Optional[List[str]] = None,
|
||||||
) -> Optional[Session]:
|
) -> Optional[Session]:
|
||||||
"""Create a new MC session
|
"""Create a new MC session
|
||||||
|
|
||||||
@@ -136,6 +137,7 @@ class ContainerManager:
|
|||||||
session_name: Optional session name
|
session_name: Optional session name
|
||||||
mount_local: Whether to mount the current directory to /app
|
mount_local: Whether to mount the current directory to /app
|
||||||
volumes: Optional additional volumes to mount (dict of {host_path: {"bind": container_path, "mode": mode}})
|
volumes: Optional additional volumes to mount (dict of {host_path: {"bind": container_path, "mode": mode}})
|
||||||
|
networks: Optional list of additional Docker networks to connect to
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Validate driver exists
|
# Validate driver exists
|
||||||
@@ -254,7 +256,12 @@ class ContainerManager:
|
|||||||
f" - Created direct volume mount: {target_dir} -> {config.source}"
|
f" - Created direct volume mount: {target_dir} -> {config.source}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create container
|
# Default MC network
|
||||||
|
default_network = self.config_manager.config.docker.get(
|
||||||
|
"network", "mc-network"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create container with default MC network
|
||||||
container = self.client.containers.create(
|
container = self.client.containers.create(
|
||||||
image=driver.image,
|
image=driver.image,
|
||||||
name=session_name,
|
name=session_name,
|
||||||
@@ -271,13 +278,32 @@ class ContainerManager:
|
|||||||
"mc.driver": driver_name,
|
"mc.driver": driver_name,
|
||||||
"mc.project": project or "",
|
"mc.project": project or "",
|
||||||
},
|
},
|
||||||
network=self.config_manager.config.docker.get("network", "mc-network"),
|
network=default_network,
|
||||||
ports={f"{port}/tcp": None for port in driver.ports},
|
ports={f"{port}/tcp": None for port in driver.ports},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Start container
|
# Start container
|
||||||
container.start()
|
container.start()
|
||||||
|
|
||||||
|
# Connect to additional networks if specified
|
||||||
|
if networks:
|
||||||
|
for network_name in networks:
|
||||||
|
try:
|
||||||
|
# Get or create the network
|
||||||
|
try:
|
||||||
|
network = self.client.networks.get(network_name)
|
||||||
|
except DockerException:
|
||||||
|
print(f"Network '{network_name}' not found, creating it...")
|
||||||
|
network = self.client.networks.create(
|
||||||
|
network_name, driver="bridge"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Connect the container to the network
|
||||||
|
network.connect(container)
|
||||||
|
print(f"Connected to network: {network_name}")
|
||||||
|
except DockerException as e:
|
||||||
|
print(f"Error connecting to network {network_name}: {e}")
|
||||||
|
|
||||||
# Get updated port information
|
# Get updated port information
|
||||||
container.reload()
|
container.reload()
|
||||||
ports = {}
|
ports = {}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class UserConfigManager:
|
|||||||
"driver": "goose",
|
"driver": "goose",
|
||||||
"connect": True,
|
"connect": True,
|
||||||
"mount_local": True,
|
"mount_local": True,
|
||||||
|
"networks": [], # Default networks to connect to (besides mc-network)
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"langfuse": {},
|
"langfuse": {},
|
||||||
|
|||||||
Reference in New Issue
Block a user