mirror of
https://github.com/codeflash-ai/codeflash-agent.git
synced 2026-05-04 18:25:19 +00:00
432 lines
No EOL
12 KiB
Markdown
432 lines
No EOL
12 KiB
Markdown
# Docker Compose Orchestration
|
|
|
|
Complete Docker Compose integration for managing multi-container environments, service discovery, and complex application stacks during testing. Enables full orchestration of interconnected services with configuration management and lifecycle control.
|
|
|
|
## Capabilities
|
|
|
|
### Compose Environment Management
|
|
|
|
Manage entire Docker Compose environments with automatic service startup, configuration loading, and coordinated shutdown.
|
|
|
|
```python { .api }
|
|
@dataclass
|
|
class DockerCompose:
|
|
context: Union[str, PathLike[str]]
|
|
compose_file_name: Optional[Union[str, list[str]]] = None
|
|
pull: bool = False
|
|
build: bool = False
|
|
wait: bool = True
|
|
keep_volumes: bool = False
|
|
env_file: Optional[str] = None
|
|
services: Optional[list[str]] = None
|
|
docker_command_path: Optional[str] = None
|
|
profiles: Optional[list[str]] = None
|
|
"""
|
|
Initialize Docker Compose environment.
|
|
|
|
Args:
|
|
context: Path to compose context directory
|
|
compose_file_name: Compose file name (default: docker-compose.yml)
|
|
pull: Pull images before starting
|
|
build: Build images before starting
|
|
wait: Wait for services to be ready
|
|
keep_volumes: Preserve volumes on shutdown
|
|
env_file: Environment file path
|
|
services: Specific services to run
|
|
docker_command_path: Custom docker-compose command path
|
|
profiles: Compose profiles to activate
|
|
**kwargs: Additional compose options
|
|
"""
|
|
|
|
def start(self) -> "DockerCompose":
|
|
"""
|
|
Start the compose environment.
|
|
|
|
Returns:
|
|
Self for method chaining
|
|
"""
|
|
|
|
def stop(self, down: bool = True) -> None:
|
|
"""
|
|
Stop the compose environment.
|
|
|
|
Args:
|
|
down: Use 'docker-compose down' instead of 'stop'
|
|
"""
|
|
|
|
def __enter__(self) -> "DockerCompose":
|
|
"""Context manager entry - starts compose environment."""
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
"""Context manager exit - stops compose environment."""
|
|
```
|
|
|
|
### Service Discovery and Access
|
|
|
|
Access individual services within the compose environment, retrieve connection information, and interact with running containers.
|
|
|
|
```python { .api }
|
|
def get_container(self, service_name: Optional[str] = None, include_all: bool = False) -> ComposeContainer:
|
|
"""
|
|
Get container for specific service.
|
|
|
|
Args:
|
|
service_name: Service name (first service if None)
|
|
include_all: Include stopped containers
|
|
|
|
Returns:
|
|
ComposeContainer instance
|
|
"""
|
|
|
|
def get_containers(self, include_all: bool = False) -> list[ComposeContainer]:
|
|
"""
|
|
Get all containers in the compose environment.
|
|
|
|
Args:
|
|
include_all: Include stopped containers
|
|
|
|
Returns:
|
|
List of ComposeContainer instances
|
|
"""
|
|
|
|
def get_service_host(self, service_name: Optional[str] = None, port: Optional[int] = None) -> str:
|
|
"""
|
|
Get host address for service.
|
|
|
|
Args:
|
|
service_name: Service name
|
|
port: Service port
|
|
|
|
Returns:
|
|
Host address string
|
|
"""
|
|
|
|
def get_service_port(self, service_name: Optional[str] = None, port: Optional[int] = None) -> int:
|
|
"""
|
|
Get mapped port for service.
|
|
|
|
Args:
|
|
service_name: Service name
|
|
port: Internal service port
|
|
|
|
Returns:
|
|
Mapped host port number
|
|
"""
|
|
|
|
def get_service_host_and_port(self, service_name: Optional[str] = None, port: Optional[int] = None) -> tuple[str, int]:
|
|
"""
|
|
Get host and port for service.
|
|
|
|
Args:
|
|
service_name: Service name
|
|
port: Internal service port
|
|
|
|
Returns:
|
|
Tuple of (host, port)
|
|
"""
|
|
```
|
|
|
|
### Container Operations
|
|
|
|
Execute commands in running services, retrieve logs, and interact with the compose environment.
|
|
|
|
```python { .api }
|
|
def exec_in_container(self, command: str, service_name: Optional[str] = None) -> str:
|
|
"""
|
|
Execute command in service container.
|
|
|
|
Args:
|
|
command: Command to execute
|
|
service_name: Target service name
|
|
|
|
Returns:
|
|
Command output string
|
|
"""
|
|
|
|
def get_logs(self, *services: str) -> str:
|
|
"""
|
|
Get logs from services.
|
|
|
|
Args:
|
|
*services: Service names (all services if none specified)
|
|
|
|
Returns:
|
|
Combined log output string
|
|
"""
|
|
|
|
def get_config(
|
|
self,
|
|
path_resolution: bool = True,
|
|
normalize: bool = True,
|
|
interpolate: bool = True
|
|
) -> dict:
|
|
"""
|
|
Get compose configuration.
|
|
|
|
Args:
|
|
path_resolution: Resolve file paths
|
|
normalize: Normalize configuration format
|
|
interpolate: Interpolate environment variables
|
|
|
|
Returns:
|
|
Compose configuration dictionary
|
|
"""
|
|
```
|
|
|
|
### Service Health Checking
|
|
|
|
Wait for services to become available and ready for connections.
|
|
|
|
```python { .api }
|
|
def wait_for(self, url: str) -> None:
|
|
"""
|
|
Wait for URL to become available.
|
|
|
|
Args:
|
|
url: URL to check for availability
|
|
"""
|
|
```
|
|
|
|
### Container Information
|
|
|
|
Access detailed information about individual containers within the compose environment.
|
|
|
|
```python { .api }
|
|
class ComposeContainer:
|
|
ID: str # Container ID
|
|
Name: str # Container name
|
|
Command: str # Container command
|
|
Project: str # Compose project name
|
|
Service: str # Service name
|
|
State: str # Container state
|
|
Health: str # Health status
|
|
ExitCode: int # Exit code
|
|
Publishers: list[PublishedPortModel] # Published ports
|
|
|
|
def get_publisher(
|
|
self,
|
|
by_port: Optional[int] = None,
|
|
by_host: Optional[str] = None,
|
|
prefer_ip_version: str = "IPv4"
|
|
) -> PublishedPortModel:
|
|
"""
|
|
Get port publisher information.
|
|
|
|
Args:
|
|
by_port: Filter by port number
|
|
by_host: Filter by host address
|
|
prefer_ip_version: Preferred IP version ("IPv4" or "IPv6")
|
|
|
|
Returns:
|
|
PublishedPortModel instance
|
|
"""
|
|
|
|
class PublishedPortModel:
|
|
URL: str # Published URL
|
|
TargetPort: int # Target container port
|
|
PublishedPort: int # Published host port
|
|
Protocol: str # Protocol (tcp/udp)
|
|
|
|
def normalize(self) -> "PublishedPortModel":
|
|
"""
|
|
Normalize for Windows compatibility.
|
|
|
|
Returns:
|
|
Normalized PublishedPortModel
|
|
"""
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### Basic Compose Usage
|
|
|
|
```python
|
|
from testcontainers.compose import DockerCompose
|
|
import requests
|
|
|
|
# docker-compose.yml in current directory with web and db services
|
|
with DockerCompose(".") as compose:
|
|
# Get service endpoints
|
|
web_host = compose.get_service_host("web", 80)
|
|
web_port = compose.get_service_port("web", 80)
|
|
|
|
# Make request to web service
|
|
response = requests.get(f"http://{web_host}:{web_port}/health")
|
|
assert response.status_code == 200
|
|
|
|
# Get database connection info
|
|
db_host = compose.get_service_host("db", 5432)
|
|
db_port = compose.get_service_port("db", 5432)
|
|
print(f"Database available at {db_host}:{db_port}")
|
|
```
|
|
|
|
### Custom Compose File
|
|
|
|
```python
|
|
from testcontainers.compose import DockerCompose
|
|
|
|
# Use specific compose file and environment
|
|
compose = DockerCompose(
|
|
context="./docker",
|
|
compose_file_name="docker-compose.test.yml",
|
|
pull=True, # Pull latest images
|
|
build=True, # Build custom images
|
|
env_file="test.env"
|
|
)
|
|
|
|
with compose:
|
|
# Execute command in service
|
|
result = compose.exec_in_container("ls -la", service_name="app")
|
|
print(f"Container contents: {result}")
|
|
|
|
# Get logs from specific services
|
|
logs = compose.get_logs("app", "worker")
|
|
print(f"Service logs: {logs}")
|
|
```
|
|
|
|
### Service-Specific Operations
|
|
|
|
```python
|
|
from testcontainers.compose import DockerCompose
|
|
|
|
with DockerCompose(".", compose_file_name="microservices.yml") as compose:
|
|
# Get all running containers
|
|
containers = compose.get_containers()
|
|
|
|
for container in containers:
|
|
print(f"Service: {container.Service}")
|
|
print(f"State: {container.State}")
|
|
print(f"Health: {container.Health}")
|
|
|
|
# Get port information
|
|
for publisher in container.Publishers:
|
|
print(f"Port {publisher.TargetPort} -> {publisher.PublishedPort}")
|
|
|
|
# Access specific service container
|
|
api_container = compose.get_container("api")
|
|
print(f"API container ID: {api_container.ID}")
|
|
```
|
|
|
|
### Profile-Based Deployment
|
|
|
|
```python
|
|
from testcontainers.compose import DockerCompose
|
|
|
|
# Use compose profiles for different environments
|
|
test_compose = DockerCompose(
|
|
context=".",
|
|
profiles=["test", "monitoring"],
|
|
services=["app", "db", "redis"] # Only start specific services
|
|
)
|
|
|
|
with test_compose:
|
|
# Only services in 'test' and 'monitoring' profiles are started
|
|
app_url = f"http://{test_compose.get_service_host('app', 8080)}:{test_compose.get_service_port('app', 8080)}"
|
|
print(f"Test app available at: {app_url}")
|
|
```
|
|
|
|
### Integration Testing Setup
|
|
|
|
```python
|
|
from testcontainers.compose import DockerCompose
|
|
import pytest
|
|
import requests
|
|
|
|
@pytest.fixture(scope="session")
|
|
def app_stack():
|
|
"""Pytest fixture for full application stack."""
|
|
with DockerCompose(".", compose_file_name="test-stack.yml") as compose:
|
|
# Wait for services to be ready
|
|
compose.wait_for(f"http://{compose.get_service_host('app', 8080)}:{compose.get_service_port('app', 8080)}/health")
|
|
|
|
yield compose
|
|
|
|
def test_api_endpoints(app_stack):
|
|
"""Test API endpoints with full stack."""
|
|
compose = app_stack
|
|
|
|
# Get API endpoint
|
|
api_host = compose.get_service_host("api", 3000)
|
|
api_port = compose.get_service_port("api", 3000)
|
|
base_url = f"http://{api_host}:{api_port}"
|
|
|
|
# Test endpoints
|
|
response = requests.get(f"{base_url}/users")
|
|
assert response.status_code == 200
|
|
|
|
response = requests.post(f"{base_url}/users", json={"name": "Test User"})
|
|
assert response.status_code == 201
|
|
|
|
def test_database_integration(app_stack):
|
|
"""Test database operations."""
|
|
compose = app_stack
|
|
|
|
# Execute database command
|
|
result = compose.exec_in_container("psql -U postgres -c 'SELECT version();'", "db")
|
|
assert "PostgreSQL" in result
|
|
```
|
|
|
|
### Complex Multi-Service Architecture
|
|
|
|
```python
|
|
from testcontainers.compose import DockerCompose
|
|
import time
|
|
|
|
# docker-compose.yml with web, api, worker, db, redis, elasticsearch
|
|
with DockerCompose(".", compose_file_name="full-stack.yml") as compose:
|
|
# Get all service endpoints
|
|
services = {
|
|
"web": compose.get_service_host_and_port("web", 80),
|
|
"api": compose.get_service_host_and_port("api", 3000),
|
|
"db": compose.get_service_host_and_port("db", 5432),
|
|
"redis": compose.get_service_host_and_port("redis", 6379),
|
|
"elasticsearch": compose.get_service_host_and_port("elasticsearch", 9200)
|
|
}
|
|
|
|
print("Service endpoints:")
|
|
for service, (host, port) in services.items():
|
|
print(f" {service}: {host}:{port}")
|
|
|
|
# Wait for all services to be healthy
|
|
for container in compose.get_containers():
|
|
while container.Health not in ["healthy", ""]:
|
|
print(f"Waiting for {container.Service} to be healthy...")
|
|
time.sleep(2)
|
|
# Refresh container info
|
|
container = compose.get_container(container.Service)
|
|
|
|
print("All services are ready!")
|
|
|
|
# Run integration tests
|
|
web_host, web_port = services["web"]
|
|
response = requests.get(f"http://{web_host}:{web_port}")
|
|
print(f"Web response status: {response.status_code}")
|
|
```
|
|
|
|
### Environment Configuration
|
|
|
|
```python
|
|
from testcontainers.compose import DockerCompose
|
|
import os
|
|
|
|
# Set environment variables for compose
|
|
os.environ["DATABASE_URL"] = "postgres://test:test@db:5432/testdb"
|
|
os.environ["REDIS_URL"] = "redis://redis:6379"
|
|
os.environ["DEBUG"] = "true"
|
|
|
|
# Compose with environment file and variable interpolation
|
|
compose = DockerCompose(
|
|
context="./infrastructure",
|
|
env_file="test.env",
|
|
keep_volumes=False # Clean up volumes after testing
|
|
)
|
|
|
|
with compose:
|
|
# Environment variables are available in compose services
|
|
config = compose.get_config()
|
|
print("Compose configuration:", config)
|
|
|
|
# Services use interpolated environment variables
|
|
app_logs = compose.get_logs("app")
|
|
print("Application logs:", app_logs)
|
|
``` |