# lmrc-hetzner
> Part of the [LMRC Stack](https://gitlab.com/lemarco/lmrc-stack) - Infrastructure-as-Code toolkit for building production-ready Rust applications
[](https://crates.io/crates/lmrc-hetzner)
[](https://docs.rs/lmrc-hetzner)
[](https://gitlab.com/lemarco/lmrc-stack#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`:
```toml
[dependencies]
lmrc-hetzner = "0.1"
tokio = { version = "1.0", features = ["full"] }
```
## Quick Start
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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
```rust
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:
| `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:
```rust
// 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
```rust
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
```rust
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
```rust
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));
}).await?;
```
### Infrastructure Provisioning with Rollback
```rust
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](../../LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](../../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.