29 releases
Uses new Rust 2024
| 0.3.16 | Dec 11, 2025 |
|---|---|
| 0.3.15 | Dec 11, 2025 |
| 0.3.9 | Nov 30, 2025 |
| 0.2.12 | Nov 27, 2025 |
| 0.1.0 | Nov 26, 2025 |
#102 in HTTP client
Used in 2 crates
260KB
5K
SLoC
lmrc-hetzner
Part of the LMRC Stack - Infrastructure-as-Code toolkit for building production-ready Rust applications
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:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
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.
Dependencies
~13–31MB
~333K SLoC