lmrc-hetzner

Crates.iolmrc-hetzner
lib.rslmrc-hetzner
version0.3.16
created_at2025-11-26 18:42:01.077398+00
updated_at2025-12-11 13:26:44.438939+00
descriptionHetzner Cloud API client library for the LMRC Stack - production-ready async client with state management, automatic rollback, and IaC features
homepagehttps://gitlab.com/lemarco/lmrc-stack/tree/main/libs/hetzner-client
repositoryhttps://gitlab.com/lemarco/lmrc-stack
max_upload_size
id1952012
size261,739
Le Marc (lemarco)

documentation

https://docs.rs/lmrc-hetzner

README

lmrc-hetzner

Part of the LMRC Stack - Infrastructure-as-Code toolkit for building production-ready Rust applications

Crates.io Documentation License

A comprehensive, production-ready async Hetzner Cloud API client with builders, validation, state management, and extensive error handling. Perfect for Infrastructure-as-Code (IaC) applications.

Features

Core Functionality

  • 🔒 Type-safe builders with compile-time validation
  • 📝 Comprehensive error handling with detailed error contexts
  • ⚡ Async/await support with Tokio runtime
  • ✅ Input validation for IP addresses, ports, names, and more
  • 🎯 Zero unsafe code - completely safe Rust

Infrastructure-as-Code Features

  • 📦 Full resource coverage - Servers, Networks, Firewalls, Load Balancers, SSH Keys, Volumes, Floating IPs
  • 🔄 State persistence - Save and restore infrastructure state with JSON serialization
  • 📊 Infrastructure diffing - Deep comparison of desired vs actual state
  • 🔁 Automatic rollback - Automatically clean up on provisioning failures
  • ♻️ Retry logic - Exponential backoff with configurable retry policies
  • 🔧 Update operations - In-place updates for server resizing and firewall rule changes
  • 🏗️ Hexagonal architecture - Clean separation of domain logic and infrastructure

Production Ready

  • 🚀 Battle-tested - 57 comprehensive tests (36 unit + 21 doc tests)
  • 📚 Extensive documentation - Detailed API docs and examples
  • 🔐 Security-focused - Proper error handling and input validation
  • ⚡ Performance-optimized - Async I/O with connection pooling

Installation

Add this to your Cargo.toml:

[dependencies]
lmrc-hetzner = "0.1"
tokio = { version = "1.0", features = ["full"] }

Quick Start

use lmrc_hetzner::{HetznerClient, Result};

#[tokio::main]
async fn main() -> Result<()> {
    // Create a client from environment variable HETZNER_API_TOKEN
    let client = HetznerClient::from_env()?;

    // Or build with custom configuration
    let client = HetznerClient::builder()
        .api_token("your-api-token".to_string())
        .build()?;

    Ok(())
}

Examples

Creating a Server with Validation

use lmrc_hetzner::config::ServerSpec;

let server_spec = ServerSpec::builder()
    .name("web-server-01".to_string())
    .server_type("cx11".to_string())
    .image("ubuntu-22.04".to_string())
    .location("nbg1".to_string())
    .label("environment".to_string(), "production".to_string())
    .ssh_key("my-ssh-key".to_string())
    .build()?;

Creating a Network

use lmrc_hetzner::config::NetworkConfig;

let network = NetworkConfig::builder()
    .name("private-network".to_string())
    .ip_range("10.0.0.0/16".to_string())
    .zone("eu-central".to_string())
    .build()?;

Creating Firewall Rules

use lmrc_hetzner::config::{FirewallConfig, FirewallRule};

let rule = FirewallRule::builder()
    .direction("in".to_string())
    .protocol("tcp".to_string())
    .port("80".to_string())
    .source_ip("0.0.0.0/0".to_string())
    .description("Allow HTTP traffic".to_string())
    .build()?;

let firewall = FirewallConfig::builder()
    .name("web-firewall".to_string())
    .rule(rule)
    .build()?;

Creating a Load Balancer

use lmrc_hetzner::config::{LoadBalancerConfig, LbService};

let service = LbService::builder()
    .protocol("http".to_string())
    .listen_port(80)
    .destination_port(8080)
    .build()?;

let lb = LoadBalancerConfig::builder()
    .name("web-lb".to_string())
    .lb_type("lb11".to_string())
    .health_check_protocol("http".to_string())
    .health_check_port(8080)
    .health_check_path("/health".to_string())
    .service(service)
    .build()?;

Error Handling

use lmrc_hetzner::{HetznerClient, HetznerError};

match client.get::<serde_json::Value>("/servers").await {
    Ok(servers) => println!("Servers: {:?}", servers),
    Err(HetznerError::Unauthorized) => {
        eprintln!("Invalid API token");
    }
    Err(HetznerError::RateLimited { retry_after }) => {
        eprintln!("Rate limited, retry after: {:?} seconds", retry_after);
    }
    Err(HetznerError::NotFound { resource_type, identifier }) => {
        eprintln!("{} '{}' not found", resource_type, identifier);
    }
    Err(e) => eprintln!("Error: {}", e),
}

Validation

All builders perform validation before construction:

  • Names: 1-63 characters, lowercase letters, numbers, hyphens, underscores
  • IP addresses: Valid IPv4 addresses and CIDR notation
  • Ports: Valid port numbers (1-65535) and port ranges
  • Protocols: Valid network protocols (tcp, udp, icmp, esp, gre)
  • Directions: Valid firewall directions (in, out)

Error Types

Comprehensive error handling with detailed contexts:

Error Description
Http Network or connection issues
Api Hetzner API error response
NotFound Resource not found (404)
AlreadyExists Resource already exists (409)
InvalidParameter Invalid input parameter
Validation Validation failed
Timeout Operation timeout
Unauthorized Authentication failed (401)
Forbidden Permission denied (403)
RateLimited Rate limit exceeded (429)
ServerError Server error (5xx)
InvalidToken Invalid API token format
BuilderError Builder configuration error

Builder Pattern

All configuration structs use the builder pattern:

// All builders have:
- .build() -> Result<T>          // Validates and builds
- Default values where appropriate
- Fluent interface for chaining

API Coverage

  • ✅ Servers (create, list, get, delete, resize, power on/off, attach to networks)
  • ✅ Networks (create, list, get, delete, subnets)
  • ✅ Firewalls (create, list, get, delete, update rules, apply to servers)
  • ✅ Load Balancers (create, list, get, delete, services, targets, health checks)
  • ✅ SSH Keys (create, list, get, delete)
  • ✅ Volumes (create, list, get, delete, attach/detach, resize)
  • ✅ Floating IPs (create, list, get, delete, assign/unassign, update)
  • ✅ Actions (wait for completion with timeout)

Advanced Features

State Management

use lmrc_hetzner::StateManager;

// Save infrastructure state
let state_manager = StateManager::new("./states");
state_manager.save_state("production", persisted_state).await?;

// Load infrastructure state
let state = state_manager.load_state("production").await?;

// List all saved states
let states = state_manager.list_states().await?;

Infrastructure Diffing

use lmrc_hetzner::HetznerDiffer;

let differ = HetznerDiffer::new(client.clone());
let diff = differ.diff(&infrastructure_spec).await?;

// Check for changes
if diff.has_changes() {
    for item in diff.items() {
        match item.diff_type() {
            DiffType::Create => println!("Will create: {}", item.resource_name()),
            DiffType::Update => println!("Will update: {}", item.resource_name()),
            DiffType::Delete => println!("Will delete: {}", item.resource_name()),
            DiffType::NoChange => println!("No change: {}", item.resource_name()),
        }
    }
}

Retry with Exponential Backoff

use lmrc_hetzner::retry::{retry_with_backoff, RetryConfig};

let config = RetryConfig::new()
    .with_max_attempts(5)
    .with_initial_delay(Duration::from_secs(1))
    .with_max_delay(Duration::from_secs(30));

let result = retry_with_backoff(config, || async {
    client.get::<serde_json::Value>("/servers").await
}).await?;

Infrastructure Provisioning with Rollback

use lmrc_hetzner::HetznerProvisioner;
use lmrc_hetzner::ports::InfrastructureProvisioner;

let provisioner = HetznerProvisioner::new(api_token)?;

// Provision infrastructure - automatically rolls back on failure
match provisioner.provision(&infrastructure_spec, false).await {
    Ok(()) => println!("Infrastructure provisioned successfully"),
    Err(e) => {
        // Rollback was automatically performed
        eprintln!("Provisioning failed (rolled back): {}", e);
    }
}

Requirements

  • Rust 1.70 or later
  • Tokio runtime

License

Part of the LMRC Stack project. Licensed under either of:

at your option.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Disclaimer

This is an unofficial client for the Hetzner Cloud API and is not affiliated with or endorsed by Hetzner Online GmbH.

Commit count: 0

cargo fmt