lmrc-ports

Crates.iolmrc-ports
lib.rslmrc-ports
version0.3.16
created_at2025-11-28 21:05:57.543963+00
updated_at2025-12-11 13:25:31.138997+00
descriptionPort trait definitions for LMRC Stack hexagonal architecture
homepage
repositoryhttps://gitlab.com/lemarco/lmrc-stack
max_upload_size
id1956036
size51,045
Le Marc (lemarco)

documentation

README

lmrc-ports

Port trait definitions for LMRC Stack hexagonal architecture.

Overview

This library defines the core abstractions (ports) that separate business logic from infrastructure implementation details. Provider-specific implementations (adapters) implement these traits to enable easy swapping of cloud providers, DNS services, and other infrastructure components.

Architecture

┌─────────────────────────────────────────┐
│  Business Logic (Pipeline Steps)       │
└─────────────────┬───────────────────────┘
                  │ depends on
                  ▼
┌─────────────────────────────────────────┐
│  Ports (This Library)                   │
│  - ServerProvider trait                 │
│  - DnsProvider trait                    │
│  - K8sInstaller trait                   │
│  - DatabaseProvider trait               │
│  - GitProvider trait                    │
└─────────────────▲───────────────────────┘
                  │ implements
                  │
┌─────────────────┴───────────────────────┐
│  Adapters (Provider-Specific)           │
│  - lmrc-hetzner-adapter                 │
│  - lmrc-aws-adapter (planned)           │
│  - lmrc-cloudflare-adapter (planned)    │
│  - etc.                                 │
└─────────────────────────────────────────┘

Benefits

  • Provider Independence: Business logic doesn't depend on specific providers
  • Easy Testing: Mock implementations for unit tests
  • Flexibility: Swap providers without changing core logic
  • Extensibility: Add new providers by implementing traits

Port Traits

ServerProvider

Abstraction for server provisioning operations across cloud providers (Hetzner, AWS, GCP, etc.).

use lmrc_ports::{ServerProvider, ProvisionRequest, ProvisionedServer};

async fn provision_server(provider: &dyn ServerProvider) -> Result<ProvisionedServer> {
    let request = ProvisionRequest {
        name: "web-server".to_string(),
        server_type: "cx11".to_string(),
        image: "ubuntu-22.04".to_string(),
        location: "nbg1".to_string(),
        // ...
    };

    provider.provision_server(request).await
}

Methods:

  • provision_server() - Create and start a new server
  • list_servers() - List all servers
  • get_server() - Get server by ID
  • get_server_by_name() - Find server by name
  • delete_server() - Permanently delete a server
  • power_on() - Start a stopped server
  • power_off() - Stop a running server
  • reboot() - Restart a server

DnsProvider

Abstraction for DNS management across providers (Cloudflare, Route53, etc.).

use lmrc_ports::{DnsProvider, DnsRecordRequest, DnsRecordType};

async fn create_dns_record(provider: &dyn DnsProvider, zone_id: &str) {
    let request = DnsRecordRequest {
        record_type: DnsRecordType::A,
        name: "www".to_string(),
        content: "192.0.2.1".to_string(),
        ttl: Some(3600),
        proxied: false,
        priority: None,
    };

    provider.create_record(zone_id, request).await?;
}

Methods:

  • create_record() - Create a new DNS record
  • list_records() - List all DNS records in a zone
  • get_record() - Get a specific DNS record
  • update_record() - Update an existing DNS record
  • delete_record() - Delete a DNS record
  • find_record_by_name() - Find record by name

K8sInstaller

Abstraction for Kubernetes cluster installation (K3s, EKS, AKS, GKE, etc.).

use lmrc_ports::{K8sInstaller, ClusterInstallRequest};

async fn install_cluster(installer: &dyn K8sInstaller) {
    let request = ClusterInstallRequest {
        name: "production".to_string(),
        version: Some("1.28".to_string()),
        master_nodes: vec!["10.0.1.10".to_string()],
        worker_nodes: vec!["10.0.1.11".to_string(), "10.0.1.12".to_string()],
        options: HashMap::new(),
    };

    let cluster = installer.install_cluster(request).await?;
    println!("Kubeconfig: {}", cluster.kubeconfig);
}

Methods:

  • install_cluster() - Install and configure a new cluster
  • uninstall_cluster() - Remove a cluster
  • get_cluster_status() - Get cluster health status
  • get_kubeconfig() - Retrieve kubeconfig content

DatabaseProvider

Abstraction for database management (PostgreSQL, MySQL, RDS, etc.).

use lmrc_ports::{DatabaseProvider, DatabaseCreateRequest};

async fn create_database(provider: &dyn DatabaseProvider) {
    let request = DatabaseCreateRequest {
        name: "myapp_production".to_string(),
        owner: "myapp_user".to_string(),
        encoding: Some("UTF8".to_string()),
        locale: Some("en_US.UTF-8".to_string()),
    };

    let db = provider.create_database(request).await?;
    println!("Connection string: {}", db.connection_string);
}

Methods:

  • create_database() - Create a new database
  • drop_database() - Delete a database
  • list_databases() - List all databases
  • database_exists() - Check if database exists
  • create_user() - Create a database user
  • drop_user() - Delete a database user
  • grant_privileges() - Grant user permissions

GitProvider

Abstraction for Git platform operations (GitLab, GitHub, etc.).

use lmrc_ports::{GitProvider, CiVariableRequest};

async fn setup_ci_variables(provider: &dyn GitProvider, project_id: &str) {
    let variable = CiVariableRequest {
        key: "DATABASE_URL".to_string(),
        value: "postgres://...".to_string(),
        protected: true,
        masked: true,
    };

    provider.create_ci_variable(project_id, variable).await?;
}

Methods:

  • get_repository() - Get repository information
  • create_ci_variable() - Create a CI/CD variable
  • update_ci_variable() - Update a CI/CD variable
  • list_ci_variables() - List all CI/CD variables
  • delete_ci_variable() - Delete a CI/CD variable
  • trigger_pipeline() - Trigger a new pipeline run
  • get_pipeline() - Get pipeline status

Domain Models

Provider-agnostic domain models that work across all implementations:

  • ProvisionRequest / ProvisionedServer
  • DnsRecordRequest / DnsRecord
  • ClusterInstallRequest / InstalledCluster
  • DatabaseCreateRequest / CreatedDatabase
  • CiVariableRequest / CiVariable

Error Handling

All ports use a unified error type:

pub enum PortError {
    NotFound { resource_type: String, resource_id: String },
    AlreadyExists { resource_type: String, resource_id: String },
    AuthenticationFailed(String),
    InvalidConfiguration(String),
    NetworkError(String),
    Timeout { operation: String, seconds: u64 },
    ProviderError(String),
    OperationFailed(String),
    InvalidState(String),
    RateLimitExceeded { retry_after_seconds: Option<u64> },
}

Implementing an Adapter

To add support for a new provider:

  1. Create a new adapter crate:

    cargo new --lib libs/lmrc-mycloud-adapter
    
  2. Add dependencies:

    [dependencies]
    lmrc-ports = { workspace = true }
    async-trait = { workspace = true }
    # Your cloud provider SDK
    
  3. Implement the port trait:

    use async_trait::async_trait;
    use lmrc_ports::{ServerProvider, ProvisionRequest, ProvisionedServer, PortResult};
    
    pub struct MyCloudAdapter {
        client: MyCloudClient,
    }
    
    #[async_trait]
    impl ServerProvider for MyCloudAdapter {
        async fn provision_server(&self, request: ProvisionRequest)
            -> PortResult<ProvisionedServer> {
            // Convert request to provider-specific format
            // Call provider API
            // Convert response back to ProvisionedServer
            todo!()
        }
    
        // Implement other methods...
    }
    
  4. Add to provider factory: Update lmrc-pipeline/src/factory.rs to include your adapter.

Example Adapters

lmrc-hetzner-adapter

Implements ServerProvider for Hetzner Cloud:

use lmrc_hetzner_adapter::HetznerAdapter;

let adapter = HetznerAdapter::from_env()?;
let servers = adapter.list_servers().await?;

Testing

Ports make it easy to test business logic with mock implementations:

use async_trait::async_trait;
use lmrc_ports::{ServerProvider, ProvisionRequest, ProvisionedServer, PortResult};

struct MockServerProvider {
    servers: Vec<ProvisionedServer>,
}

#[async_trait]
impl ServerProvider for MockServerProvider {
    async fn provision_server(&self, request: ProvisionRequest)
        -> PortResult<ProvisionedServer> {
        Ok(ProvisionedServer {
            id: "mock-123".to_string(),
            name: request.name,
            // ...
        })
    }

    // Mock other methods...
}

// Use in tests
#[tokio::test]
async fn test_provisioning_logic() {
    let mock_provider = MockServerProvider { servers: vec![] };
    // Test your business logic with the mock
}

License

MIT OR Apache-2.0

Commit count: 0

cargo fmt