Skip to content

Infrastructure Engines

Engines are pluggable modules that interface with infrastructure providers to deploy and manage resources.

Overview

ALMA uses a plugin architecture for infrastructure providers. Each engine implements a common interface, allowing the core system to work with any provider.

Available Engines

EngineProviderStatusResources Supported
FakeEngineTestingSupportedAll types (simulated)
ProxmoxEngineProxmox VESupportedcompute, storage
DockerEngineDockerSupportedcompute
KubernetesEngineK8sExperimentalcompute, network (Service)
AnsibleEngineAnsiblePlannedcompute, configure
MikroTikEngineMikroTikPlannednetwork

Engine Interface

All engines must implement the Engine abstract base class:

python
from alma.engines.base import Engine, DeploymentResult, ResourceState

class MyEngine(Engine):
    async def validate_blueprint(self, blueprint: Dict[str, Any]) -> bool:
        """Validate blueprint for this provider."""
        pass

    async def deploy(self, blueprint: Dict[str, Any]) -> DeploymentResult:
        """Deploy infrastructure."""
        pass

    async def get_state(self, resource_id: str) -> ResourceState:
        """Get current resource state."""
        pass

    async def destroy(self, resource_id: str) -> bool:
        """Destroy a resource."""
        pass

    async def rollback(self, deployment_id: str, target_state: Optional[str] = None) -> bool:
        """Rollback deployment."""
        pass

    async def health_check(self) -> bool:
        """Check engine health."""
        pass

    def get_supported_resource_types(self) -> List[str]:
        """Return supported resource types."""
        return ["compute", "network", "storage"]

FakeEngine

Purpose: Testing and development

Location: alma/engines/fake.py

Features:

  • Simulates all infrastructure operations
  • Instant deployment (no actual resources)
  • Configurable failure modes
  • Perfect for testing

Configuration:

python
engine = FakeEngine(config={
    "fail_on_deploy": False,  # Simulate failures
})

Example:

python
from alma.engines.fake import FakeEngine

engine = FakeEngine()

blueprint = {
    "version": "1.0",
    "name": "test",
    "resources": [...]
}

# Deploy (simulated)
result = await engine.deploy(blueprint)
print(f"Status: {result.status}")
print(f"Resources: {result.resources_created}")

ProxmoxEngine

Purpose: Proxmox Virtual Environment integration

Location: alma/engines/proxmox.py

Status: Supported (Primary Engine)

Supported Resources:

  • compute: Virtual machines (supported)
  • storage: Storage volumes (partial)
  • network: Networks and VLANs (planned)

Configuration:

python
engine = ProxmoxEngine(config={
    "host": "https://proxmox.example.com:8006",
    "username": "root@pam",
    "password": "your-password",
    "node": "pve",
    "verify_ssl": True,
})

Example Blueprint:

yaml
version: "1.0"
name: proxmox-vm
resources:
  - type: compute
    name: web-server-01
    provider: proxmox
    specs:
      cpu: 4
      memory: 8GB
      storage: 100GB
      os: ubuntu-22.04
    metadata:
      node: pve
      pool: production

API Documentation:

Creating Custom Engines

1. Create Engine Class

python
# alma/engines/myengine.py
from alma.engines.base import Engine, DeploymentResult, DeploymentStatus

class MyEngine(Engine):
    def __init__(self, config: Optional[Dict[str, Any]] = None):
        super().__init__(config)
        self.api_url = config.get("api_url")
        self.api_key = config.get("api_key")

    async def validate_blueprint(self, blueprint: Dict[str, Any]) -> bool:
        # Validate blueprint structure
        if "resources" not in blueprint:
            raise ValueError("Missing resources")
        return True

    async def deploy(self, blueprint: Dict[str, Any]) -> DeploymentResult:
        # Deploy infrastructure
        resources_created = []

        for resource in blueprint["resources"]:
            # Create resource via provider API
            resource_id = await self._create_resource(resource)
            resources_created.append(resource["name"])

        return DeploymentResult(
            status=DeploymentStatus.COMPLETED,
            message=f"Deployed {len(resources_created)} resources",
            resources_created=resources_created,
        )

2. Register Engine

Add to alma/engines/__init__.py:

python
from alma.engines.myengine import MyEngine

__all__ = ["Engine", "FakeEngine", "ProxmoxEngine", "MyEngine"]

3. Add Tests

python
# tests/unit/test_myengine.py
import pytest
from alma.engines.myengine import MyEngine

@pytest.fixture
def engine():
    return MyEngine(config={"api_url": "https://api.example.com"})

async def test_deploy(engine):
    blueprint = {...}
    result = await engine.deploy(blueprint)
    assert result.status == DeploymentStatus.COMPLETED

4. Document Provider

Add provider documentation with:

  • Supported resource types
  • Configuration options
  • Example blueprints
  • API requirements

Best Practices

Error Handling

python
async def deploy(self, blueprint):
    try:
        # Deployment logic
        pass
    except ProviderAPIError as e:
        return DeploymentResult(
            status=DeploymentStatus.FAILED,
            message=f"API error: {e}",
            resources_failed=[...],
        )
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        raise

Idempotency

python
async def deploy(self, blueprint):
    for resource in blueprint["resources"]:
        existing = await self._check_exists(resource["name"])

        if existing:
            # Update existing resource
            await self._update_resource(existing.id, resource)
        else:
            # Create new resource
            await self._create_resource(resource)

State Management

python
async def get_state(self, resource_id):
    # Query provider API
    provider_state = await self.api.get_resource(resource_id)

    # Map to standard state
    return ResourceState(
        resource_id=resource_id,
        resource_type=provider_state.type,
        status=self._map_status(provider_state.status),
        properties=provider_state.config,
    )

Resource Cleanup

python
async def rollback(self, deployment_id):
    # Get resources for deployment
    resources = await self._get_deployment_resources(deployment_id)

    # Destroy in reverse order (handle dependencies)
    for resource in reversed(resources):
        try:
            await self.destroy(resource.id)
        except Exception as e:
            logger.error(f"Failed to destroy {resource.id}: {e}")

    return True

Testing Engines

Unit Tests

python
@pytest.mark.asyncio
async def test_validate_blueprint():
    engine = MyEngine()

    valid_blueprint = {...}
    assert await engine.validate_blueprint(valid_blueprint)

    invalid_blueprint = {}
    with pytest.raises(ValueError):
        await engine.validate_blueprint(invalid_blueprint)

Integration Tests

python
@pytest.mark.integration
async def test_full_deployment():
    # Requires actual provider access
    engine = MyEngine(config=test_config)

    blueprint = load_test_blueprint()
    result = await engine.deploy(blueprint)

    assert result.status == DeploymentStatus.COMPLETED

    # Cleanup
    for resource in result.resources_created:
        await engine.destroy(resource)

Mock Tests

python
@patch("alma.engines.myengine.ProviderAPI")
async def test_deploy_mocked(mock_api):
    mock_api.create_vm.return_value = {"id": "vm-123"}

    engine = MyEngine()
    result = await engine.deploy(blueprint)

    assert "vm-123" in result.resources_created
    mock_api.create_vm.assert_called_once()

Engine Configuration

Engines can be configured via:

1. Environment Variables

bash
# .env
PROXMOX_HOST=https://proxmox.example.com:8006
PROXMOX_USERNAME=root@pam
PROXMOX_PASSWORD=secret

2. Config Files

yaml
# config/engines.yaml
engines:
  proxmox:
    host: https://proxmox.example.com:8006
    username: root@pam
    node: pve

3. Blueprint Metadata

yaml
version: "1.0"
name: my-infra
metadata:
  engine_config:
    proxmox:
      node: pve-node-02
resources: [...]

Provider-Specific Guides


Next: API Reference

Released under the Apache 2.0 License.