16 releases
Uses new Rust 2024
| 0.3.16 | Dec 11, 2025 |
|---|---|
| 0.3.15 | Dec 11, 2025 |
| 0.3.9 | Nov 30, 2025 |
#629 in Network programming
54 downloads per month
Used in 9 crates
(8 directly)
49KB
316 lines
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 serverlist_servers()- List all serversget_server()- Get server by IDget_server_by_name()- Find server by namedelete_server()- Permanently delete a serverpower_on()- Start a stopped serverpower_off()- Stop a running serverreboot()- 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 recordlist_records()- List all DNS records in a zoneget_record()- Get a specific DNS recordupdate_record()- Update an existing DNS recorddelete_record()- Delete a DNS recordfind_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 clusteruninstall_cluster()- Remove a clusterget_cluster_status()- Get cluster health statusget_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 databasedrop_database()- Delete a databaselist_databases()- List all databasesdatabase_exists()- Check if database existscreate_user()- Create a database userdrop_user()- Delete a database usergrant_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 informationcreate_ci_variable()- Create a CI/CD variableupdate_ci_variable()- Update a CI/CD variablelist_ci_variables()- List all CI/CD variablesdelete_ci_variable()- Delete a CI/CD variabletrigger_pipeline()- Trigger a new pipeline runget_pipeline()- Get pipeline status
Domain Models
Provider-agnostic domain models that work across all implementations:
ProvisionRequest/ProvisionedServerDnsRecordRequest/DnsRecordClusterInstallRequest/InstalledClusterDatabaseCreateRequest/CreatedDatabaseCiVariableRequest/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:
-
Create a new adapter crate:
cargo new --lib libs/lmrc-mycloud-adapter -
Add dependencies:
[dependencies] lmrc-ports = { workspace = true } async-trait = { workspace = true } # Your cloud provider SDK -
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... } -
Add to provider factory: Update
lmrc-pipeline/src/factory.rsto 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
Dependencies
~0.3–0.9MB
~19K SLoC