# lmrc-hetzner

> Part of the [LMRC Stack](https://gitlab.com/lemarco/lmrc-stack) - Infrastructure-as-Code toolkit for building production-ready Rust applications

[![Crates.io](https://img.shields.io/crates/v/lmrc-hetzner.svg)](https://crates.io/crates/lmrc-hetzner)
[![Documentation](https://docs.rs/lmrc-hetzner/badge.svg)](https://docs.rs/lmrc-hetzner)
[![License](https://img.shields.io/crates/l/lmrc-hetzner.svg)](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:

| 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:

```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));

let result = retry_with_backoff(config, || async {
    client.get::<serde_json::Value>("/servers").await
}).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.
