1pub mod adapter;
33pub mod client;
34pub mod config;
35pub mod differ;
36pub mod domain;
37pub mod firewalls;
38pub mod floating_ips;
39pub mod loadbalancers;
40pub mod networks;
41pub mod ports;
42pub mod provisioner;
43pub mod retry;
44pub mod servers;
45pub mod ssh_keys;
46pub mod state;
47pub mod validation;
48pub mod volumes;
49
50pub use adapter::HetznerAdapter;
51pub use client::{HetznerClient, HetznerClientBuilder};
52pub use config::NetworkConfig;
53pub use differ::HetznerDiffer;
54pub use firewalls::FirewallManager;
55pub use floating_ips::FloatingIpManager;
56pub use loadbalancers::LoadBalancerManager;
57pub use networks::NetworkManager;
58pub use provisioner::HetznerProvisioner;
59pub use servers::ServerManager;
60pub use ssh_keys::SshKeyManager;
61pub use state::StateManager;
62pub use volumes::VolumeManager;
63
64use serde::{Deserialize, Serialize};
65use thiserror::Error;
66
67#[derive(Error, Debug)]
96pub enum HetznerError {
97 #[error("HTTP request failed: {0}")]
101 Http(#[from] reqwest::Error),
102
103 #[error("API error [{code}]: {message}")]
107 Api {
108 code: String,
110 message: String,
112 },
113
114 #[error("Resource not found: {resource_type} with identifier '{identifier}'")]
118 NotFound {
119 resource_type: String,
121 identifier: String,
123 },
124
125 #[error("Resource already exists: {resource_type} named '{name}'")]
127 AlreadyExists {
128 resource_type: String,
130 name: String,
132 },
133
134 #[error("Invalid parameter '{parameter}': {reason}")]
147 InvalidParameter {
148 parameter: String,
150 reason: String,
152 },
153
154 #[error("Validation failed: {0}")]
156 Validation(String),
157
158 #[error("Operation timeout: {operation} exceeded {timeout_secs}s")]
160 Timeout {
161 operation: String,
163 timeout_secs: u64,
165 },
166
167 #[error("Serialization error: {0}")]
169 Serialization(#[from] serde_json::Error),
170
171 #[error("Authentication failed: invalid or missing API token")]
175 Unauthorized,
176
177 #[error("Permission denied: {details}")]
181 Forbidden {
182 details: String,
184 },
185
186 #[error("Rate limit exceeded, retry after {retry_after:?}")]
190 RateLimited {
191 retry_after: Option<u64>,
193 },
194
195 #[error("Server error (HTTP {status_code}): {details}")]
197 ServerError {
198 status_code: u16,
200 details: String,
202 },
203
204 #[error("Invalid API token format: {0}")]
206 InvalidToken(String),
207
208 #[error("Builder error: {0}")]
210 BuilderError(String),
211}
212
213pub type Result<T> = std::result::Result<T, HetznerError>;
215
216#[allow(dead_code)]
218#[derive(Debug, Serialize, Deserialize)]
219pub struct ApiResponse<T> {
220 #[serde(flatten)]
221 pub data: T,
222}
223
224#[derive(Debug, Serialize, Deserialize)]
226pub struct ApiError {
227 pub error: ErrorDetails,
228}
229
230#[derive(Debug, Serialize, Deserialize)]
231pub struct ErrorDetails {
232 pub code: String,
233 pub message: String,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct Server {
239 pub id: u64,
240 pub name: String,
241 pub status: String,
242 pub public_net: PublicNet,
243 pub private_net: Vec<PrivateNet>,
244 pub server_type: ServerType,
245 pub datacenter: Datacenter,
246 pub image: Option<Image>,
247 #[serde(default)]
248 pub labels: std::collections::HashMap<String, String>,
249 pub created: String,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct PublicNet {
254 pub ipv4: IpInfo,
255 pub ipv6: Option<IpInfo>,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct IpInfo {
260 pub ip: String,
261 #[serde(default)]
262 pub blocked: bool,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct PrivateNet {
267 pub network: u64,
268 pub ip: String,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct ServerType {
273 pub id: u64,
274 pub name: String,
275 pub description: String,
276 pub cores: u32,
277 pub memory: f64,
278 pub disk: u64,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct Datacenter {
283 pub id: u64,
284 pub name: String,
285 pub description: String,
286 pub location: Location,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct Location {
291 pub id: u64,
292 pub name: String,
293 pub description: String,
294 pub country: String,
295 pub city: String,
296 pub latitude: f64,
297 pub longitude: f64,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct Image {
302 pub id: Option<u64>,
303 pub name: Option<String>,
304 pub description: String,
305 #[serde(rename = "type")]
306 pub image_type: String,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct Network {
312 pub id: u64,
313 pub name: String,
314 pub ip_range: String,
315 pub subnets: Vec<Subnet>,
316 #[serde(default)]
317 pub labels: std::collections::HashMap<String, String>,
318 pub created: String,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct Subnet {
323 pub ip_range: String,
324 pub network_zone: String,
325 #[serde(rename = "type")]
326 pub subnet_type: String,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct Firewall {
332 pub id: u64,
333 pub name: String,
334 pub rules: Vec<FirewallRule>,
335 pub applied_to: Vec<FirewallAppliedTo>,
336 #[serde(default)]
337 pub labels: std::collections::HashMap<String, String>,
338 pub created: String,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
342pub struct FirewallRule {
343 pub direction: String,
344 pub protocol: String,
345 #[serde(skip_serializing_if = "Option::is_none")]
346 pub port: Option<String>,
347 pub source_ips: Vec<String>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub destination_ips: Option<Vec<String>>,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
353pub struct FirewallAppliedTo {
354 #[serde(rename = "type")]
355 pub apply_type: String,
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub server: Option<FirewallServerRef>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct FirewallServerRef {
362 pub id: u64,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct LoadBalancer {
368 pub id: u64,
369 pub name: String,
370 pub public_net: LbPublicNet,
371 pub private_net: Vec<LbPrivateNet>,
372 pub load_balancer_type: LoadBalancerType,
373 pub location: Location,
374 pub algorithm: Algorithm,
375 pub services: Vec<LbService>,
376 pub targets: Vec<LbTarget>,
377 #[serde(default)]
378 pub labels: std::collections::HashMap<String, String>,
379 pub created: String,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct LbPublicNet {
384 pub ipv4: IpInfo,
385 pub ipv6: Option<IpInfo>,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct LbPrivateNet {
390 pub network: u64,
391 pub ip: String,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct LoadBalancerType {
396 pub id: u64,
397 pub name: String,
398 pub description: String,
399 pub max_connections: u64,
400 pub max_targets: u64,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct Algorithm {
405 #[serde(rename = "type")]
406 pub algorithm_type: String,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct LbService {
411 pub protocol: String,
412 pub listen_port: u16,
413 pub destination_port: u16,
414 pub proxyprotocol: bool,
415 pub http: Option<HttpConfig>,
416 pub health_check: HealthCheck,
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct HttpConfig {
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub certificates: Option<Vec<u64>>,
423 #[serde(skip_serializing_if = "Option::is_none")]
424 pub sticky_sessions: Option<bool>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct HealthCheck {
429 pub protocol: String,
430 pub port: u16,
431 pub interval: u64,
432 pub timeout: u64,
433 pub retries: u64,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 pub http: Option<HttpHealthCheck>,
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct HttpHealthCheck {
440 pub domain: Option<String>,
441 pub path: String,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 pub response: Option<String>,
444 #[serde(skip_serializing_if = "Option::is_none")]
445 pub status_codes: Option<Vec<String>>,
446 pub tls: bool,
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct LbTarget {
451 #[serde(rename = "type")]
452 pub target_type: String,
453 pub server: Option<LbTargetServer>,
454 pub use_private_ip: bool,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct LbTargetServer {
459 pub id: u64,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize)]
464pub struct SshKey {
465 pub id: u64,
466 pub name: String,
467 pub fingerprint: String,
468 pub public_key: String,
469 #[serde(default)]
470 pub labels: std::collections::HashMap<String, String>,
471 pub created: String,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct Action {
477 pub id: u64,
478 pub command: String,
479 pub status: String,
480 pub progress: u8,
481 #[serde(skip_serializing_if = "Option::is_none")]
482 pub error: Option<ActionError>,
483 pub started: String,
484 #[serde(skip_serializing_if = "Option::is_none")]
485 pub finished: Option<String>,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
489pub struct ActionError {
490 pub code: String,
491 pub message: String,
492}
493
494impl Action {
495 #[allow(dead_code)]
496 pub fn is_finished(&self) -> bool {
497 self.status == "success" || self.status == "error"
498 }
499
500 #[allow(dead_code)]
501 pub fn is_success(&self) -> bool {
502 self.status == "success"
503 }
504
505 #[allow(dead_code)]
506 pub fn is_error(&self) -> bool {
507 self.status == "error"
508 }
509}