| Crates.io | lmrc-hetzner |
| lib.rs | lmrc-hetzner |
| version | 0.3.16 |
| created_at | 2025-11-26 18:42:01.077398+00 |
| updated_at | 2025-12-11 13:26:44.438939+00 |
| description | Hetzner Cloud API client library for the LMRC Stack - production-ready async client with state management, automatic rollback, and IaC features |
| homepage | https://gitlab.com/lemarco/lmrc-stack/tree/main/libs/hetzner-client |
| repository | https://gitlab.com/lemarco/lmrc-stack |
| max_upload_size | |
| id | 1952012 |
| size | 261,739 |
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.
Add this to your Cargo.toml:
[dependencies]
lmrc-hetzner = "0.1"
tokio = { version = "1.0", features = ["full"] }
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(())
}
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()?;
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()?;
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()?;
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()?;
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),
}
All builders perform validation before construction:
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 |
All configuration structs use the builder pattern:
// All builders have:
- .build() -> Result<T> // Validates and builds
- Default values where appropriate
- Fluent interface for chaining
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?;
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()),
}
}
}
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?;
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);
}
}
Part of the LMRC Stack project. Licensed under either of:
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.
This is an unofficial client for the Hetzner Cloud API and is not affiliated with or endorsed by Hetzner Online GmbH.