lmrc_hetzner/
domain.rs

1//! Domain entities for infrastructure management
2//!
3//! This module provides domain-driven design entities for managing infrastructure.
4//! These types represent the business domain concepts, separate from API details.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Unique identifier for a server
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct ServerId(String);
12
13impl ServerId {
14    pub fn new(id: String) -> Self {
15        Self(id)
16    }
17
18    pub fn as_str(&self) -> &str {
19        &self.0
20    }
21}
22
23impl fmt::Display for ServerId {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29/// Server type/role
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub enum ServerType {
32    Master,
33    Worker,
34    Database,
35}
36
37impl fmt::Display for ServerType {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self {
40            ServerType::Master => write!(f, "master"),
41            ServerType::Worker => write!(f, "worker"),
42            ServerType::Database => write!(f, "database"),
43        }
44    }
45}
46
47/// Server status
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub enum ServerStatus {
50    Running,
51    Stopped,
52    Starting,
53    Stopping,
54    Unknown,
55}
56
57/// Server specification (desired state)
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ServerSpec {
60    name: String,
61    server_type: ServerType,
62    size: String,
63    location: String,
64    image: String,
65    ssh_keys: Vec<String>,
66    labels: Vec<(String, String)>,
67    attach_to_network: Option<String>, // Network name to attach to
68}
69
70impl ServerSpec {
71    pub fn new(
72        name: String,
73        server_type: ServerType,
74        size: String,
75        location: String,
76        image: String,
77    ) -> Self {
78        Self {
79            name,
80            server_type,
81            size,
82            location,
83            image,
84            ssh_keys: Vec::new(),
85            labels: Vec::new(),
86            attach_to_network: None,
87        }
88    }
89
90    pub fn with_ssh_keys(mut self, ssh_keys: Vec<String>) -> Self {
91        self.ssh_keys = ssh_keys;
92        self
93    }
94
95    pub fn with_labels(mut self, labels: Vec<(String, String)>) -> Self {
96        self.labels = labels;
97        self
98    }
99
100    pub fn with_network(mut self, network_name: String) -> Self {
101        self.attach_to_network = Some(network_name);
102        self
103    }
104
105    pub fn name(&self) -> &str {
106        &self.name
107    }
108
109    pub fn server_type(&self) -> &ServerType {
110        &self.server_type
111    }
112
113    pub fn size(&self) -> &str {
114        &self.size
115    }
116
117    pub fn location(&self) -> &str {
118        &self.location
119    }
120
121    pub fn image(&self) -> &str {
122        &self.image
123    }
124
125    pub fn ssh_keys(&self) -> &[String] {
126        &self.ssh_keys
127    }
128
129    pub fn labels(&self) -> &[(String, String)] {
130        &self.labels
131    }
132
133    pub fn network(&self) -> Option<&str> {
134        self.attach_to_network.as_deref()
135    }
136}
137
138/// Server entity (actual state)
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct Server {
141    id: ServerId,
142    spec: ServerSpec,
143    status: ServerStatus,
144    public_ipv4: Option<String>,
145    private_ipv4: Option<String>,
146}
147
148impl Server {
149    pub fn new(id: ServerId, spec: ServerSpec, status: ServerStatus) -> Self {
150        Self {
151            id,
152            spec,
153            status,
154            public_ipv4: None,
155            private_ipv4: None,
156        }
157    }
158
159    pub fn with_public_ipv4(mut self, ip: String) -> Self {
160        self.public_ipv4 = Some(ip);
161        self
162    }
163
164    pub fn with_private_ipv4(mut self, ip: String) -> Self {
165        self.private_ipv4 = Some(ip);
166        self
167    }
168
169    pub fn id(&self) -> &ServerId {
170        &self.id
171    }
172
173    pub fn spec(&self) -> &ServerSpec {
174        &self.spec
175    }
176
177    pub fn status(&self) -> &ServerStatus {
178        &self.status
179    }
180
181    pub fn public_ipv4(&self) -> Option<&str> {
182        self.public_ipv4.as_deref()
183    }
184
185    pub fn private_ipv4(&self) -> Option<&str> {
186        self.private_ipv4.as_deref()
187    }
188
189    pub fn name(&self) -> &str {
190        self.spec.name()
191    }
192}
193
194/// Unique identifier for a network
195#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
196pub struct NetworkId(String);
197
198impl NetworkId {
199    pub fn new(id: String) -> Self {
200        Self(id)
201    }
202
203    pub fn as_str(&self) -> &str {
204        &self.0
205    }
206}
207
208impl fmt::Display for NetworkId {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        write!(f, "{}", self.0)
211    }
212}
213
214/// Subnet specification
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct SubnetSpec {
217    pub network_zone: String,
218    pub ip_range: String,
219}
220
221/// Network specification
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct NetworkSpec {
224    name: String,
225    ip_range: String,
226    subnets: Vec<SubnetSpec>,
227}
228
229impl NetworkSpec {
230    pub fn new(name: String, ip_range: String) -> Self {
231        Self {
232            name,
233            ip_range,
234            subnets: Vec::new(),
235        }
236    }
237
238    pub fn with_subnets(mut self, subnets: Vec<SubnetSpec>) -> Self {
239        self.subnets = subnets;
240        self
241    }
242
243    pub fn name(&self) -> &str {
244        &self.name
245    }
246
247    pub fn ip_range(&self) -> &str {
248        &self.ip_range
249    }
250
251    pub fn subnets(&self) -> &[SubnetSpec] {
252        &self.subnets
253    }
254}
255
256/// Network entity
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct Network {
259    id: NetworkId,
260    spec: NetworkSpec,
261}
262
263impl Network {
264    pub fn new(id: NetworkId, spec: NetworkSpec) -> Self {
265        Self { id, spec }
266    }
267
268    pub fn id(&self) -> &NetworkId {
269        &self.id
270    }
271
272    pub fn spec(&self) -> &NetworkSpec {
273        &self.spec
274    }
275
276    pub fn name(&self) -> &str {
277        self.spec.name()
278    }
279}
280
281/// Unique identifier for a firewall
282#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
283pub struct FirewallId(String);
284
285impl FirewallId {
286    pub fn new(id: String) -> Self {
287        Self(id)
288    }
289
290    pub fn as_str(&self) -> &str {
291        &self.0
292    }
293}
294
295impl fmt::Display for FirewallId {
296    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297        write!(f, "{}", self.0)
298    }
299}
300
301/// Firewall rule
302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
303pub struct FirewallRule {
304    pub direction: String,
305    pub source_ips: Vec<String>,
306    pub destination_ips: Vec<String>,
307    pub protocol: String,
308    pub port: Option<String>,
309    pub description: Option<String>,
310}
311
312impl FirewallRule {
313    pub fn new(direction: String, protocol: String) -> Self {
314        Self {
315            direction,
316            source_ips: Vec::new(),
317            destination_ips: Vec::new(),
318            protocol,
319            port: None,
320            description: None,
321        }
322    }
323
324    pub fn with_source_ips(mut self, ips: Vec<String>) -> Self {
325        self.source_ips = ips;
326        self
327    }
328
329    pub fn with_port(mut self, port: String) -> Self {
330        self.port = Some(port);
331        self
332    }
333
334    pub fn with_description(mut self, desc: String) -> Self {
335        self.description = Some(desc);
336        self
337    }
338}
339
340/// Firewall specification
341#[derive(Debug, Clone, Serialize, Deserialize)]
342pub struct FirewallSpec {
343    name: String,
344    rules: Vec<FirewallRule>,
345    apply_to: Vec<String>,
346}
347
348impl FirewallSpec {
349    pub fn new(name: String) -> Self {
350        Self {
351            name,
352            rules: Vec::new(),
353            apply_to: Vec::new(),
354        }
355    }
356
357    pub fn with_rules(mut self, rules: Vec<FirewallRule>) -> Self {
358        self.rules = rules;
359        self
360    }
361
362    pub fn with_apply_to(mut self, apply_to: Vec<String>) -> Self {
363        self.apply_to = apply_to;
364        self
365    }
366
367    pub fn name(&self) -> &str {
368        &self.name
369    }
370
371    pub fn rules(&self) -> &[FirewallRule] {
372        &self.rules
373    }
374
375    pub fn apply_to(&self) -> &[String] {
376        &self.apply_to
377    }
378}
379
380/// Firewall entity
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct Firewall {
383    id: FirewallId,
384    spec: FirewallSpec,
385}
386
387impl Firewall {
388    pub fn new(id: FirewallId, spec: FirewallSpec) -> Self {
389        Self { id, spec }
390    }
391
392    pub fn id(&self) -> &FirewallId {
393        &self.id
394    }
395
396    pub fn spec(&self) -> &FirewallSpec {
397        &self.spec
398    }
399
400    pub fn name(&self) -> &str {
401        self.spec.name()
402    }
403}
404
405/// Unique identifier for a load balancer
406#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
407pub struct LoadBalancerId(String);
408
409impl LoadBalancerId {
410    pub fn new(id: String) -> Self {
411        Self(id)
412    }
413
414    pub fn as_str(&self) -> &str {
415        &self.0
416    }
417}
418
419impl fmt::Display for LoadBalancerId {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        write!(f, "{}", self.0)
422    }
423}
424
425/// Load balancer specification
426#[derive(Debug, Clone, Serialize, Deserialize)]
427pub struct LoadBalancerSpec {
428    name: String,
429    load_balancer_type: String,
430    location: String,
431    algorithm: String,
432}
433
434impl LoadBalancerSpec {
435    pub fn new(name: String, load_balancer_type: String, location: String) -> Self {
436        Self {
437            name,
438            load_balancer_type,
439            location,
440            algorithm: "round_robin".to_string(),
441        }
442    }
443
444    pub fn with_algorithm(mut self, algorithm: String) -> Self {
445        self.algorithm = algorithm;
446        self
447    }
448
449    pub fn name(&self) -> &str {
450        &self.name
451    }
452
453    pub fn load_balancer_type(&self) -> &str {
454        &self.load_balancer_type
455    }
456
457    pub fn location(&self) -> &str {
458        &self.location
459    }
460
461    pub fn algorithm(&self) -> &str {
462        &self.algorithm
463    }
464}
465
466/// Load balancer entity
467#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct LoadBalancer {
469    id: LoadBalancerId,
470    spec: LoadBalancerSpec,
471    public_ipv4: Option<String>,
472}
473
474impl LoadBalancer {
475    pub fn new(id: LoadBalancerId, spec: LoadBalancerSpec) -> Self {
476        Self {
477            id,
478            spec,
479            public_ipv4: None,
480        }
481    }
482
483    pub fn with_public_ipv4(mut self, ip: String) -> Self {
484        self.public_ipv4 = Some(ip);
485        self
486    }
487
488    pub fn id(&self) -> &LoadBalancerId {
489        &self.id
490    }
491
492    pub fn spec(&self) -> &LoadBalancerSpec {
493        &self.spec
494    }
495
496    pub fn public_ipv4(&self) -> Option<&str> {
497        self.public_ipv4.as_deref()
498    }
499
500    pub fn name(&self) -> &str {
501        self.spec.name()
502    }
503}
504
505/// Complete infrastructure specification
506#[derive(Debug, Clone)]
507pub struct InfrastructureSpec {
508    pub network: NetworkSpec,
509    pub firewall: FirewallSpec,
510    pub loadbalancer: LoadBalancerSpec,
511    pub servers: Vec<ServerSpec>,
512}
513
514impl InfrastructureSpec {
515    pub fn new(
516        network: NetworkSpec,
517        firewall: FirewallSpec,
518        loadbalancer: LoadBalancerSpec,
519        servers: Vec<ServerSpec>,
520    ) -> Self {
521        Self {
522            network,
523            firewall,
524            loadbalancer,
525            servers,
526        }
527    }
528
529    pub fn network(&self) -> &NetworkSpec {
530        &self.network
531    }
532
533    pub fn firewall(&self) -> &FirewallSpec {
534        &self.firewall
535    }
536
537    pub fn loadbalancer(&self) -> &LoadBalancerSpec {
538        &self.loadbalancer
539    }
540
541    pub fn servers(&self) -> &[ServerSpec] {
542        &self.servers
543    }
544}
545
546/// Type of difference
547#[derive(Debug, Clone, PartialEq, Eq)]
548pub enum DiffType {
549    Create,
550    Update,
551    Delete,
552    NoChange,
553}
554
555/// Individual diff item
556#[derive(Debug, Clone)]
557pub struct DiffItem {
558    resource_type: String,
559    resource_name: String,
560    diff_type: DiffType,
561    details: Vec<String>,
562}
563
564impl DiffItem {
565    pub fn new(resource_type: String, resource_name: String, diff_type: DiffType) -> Self {
566        Self {
567            resource_type,
568            resource_name,
569            diff_type,
570            details: Vec::new(),
571        }
572    }
573
574    pub fn with_details(mut self, details: Vec<String>) -> Self {
575        self.details = details;
576        self
577    }
578
579    pub fn resource_type(&self) -> &str {
580        &self.resource_type
581    }
582
583    pub fn resource_name(&self) -> &str {
584        &self.resource_name
585    }
586
587    pub fn diff_type(&self) -> &DiffType {
588        &self.diff_type
589    }
590
591    pub fn details(&self) -> &[String] {
592        &self.details
593    }
594}
595
596/// Infrastructure diff result
597#[derive(Debug, Clone)]
598pub struct InfrastructureDiff {
599    items: Vec<DiffItem>,
600}
601
602impl InfrastructureDiff {
603    pub fn new(items: Vec<DiffItem>) -> Self {
604        Self { items }
605    }
606
607    pub fn items(&self) -> &[DiffItem] {
608        &self.items
609    }
610
611    pub fn has_changes(&self) -> bool {
612        self.items
613            .iter()
614            .any(|item| item.diff_type != DiffType::NoChange)
615    }
616
617    pub fn creates(&self) -> Vec<&DiffItem> {
618        self.items
619            .iter()
620            .filter(|item| item.diff_type == DiffType::Create)
621            .collect()
622    }
623
624    pub fn updates(&self) -> Vec<&DiffItem> {
625        self.items
626            .iter()
627            .filter(|item| item.diff_type == DiffType::Update)
628            .collect()
629    }
630
631    pub fn deletes(&self) -> Vec<&DiffItem> {
632        self.items
633            .iter()
634            .filter(|item| item.diff_type == DiffType::Delete)
635            .collect()
636    }
637}
638
639/// Current infrastructure state
640#[derive(Debug, Clone, Serialize, Deserialize)]
641pub struct InfrastructureState {
642    pub servers: Vec<Server>,
643    pub networks: Vec<Network>,
644    pub firewalls: Vec<Firewall>,
645    pub load_balancers: Vec<LoadBalancer>,
646}
647
648impl InfrastructureState {
649    pub fn new() -> Self {
650        Self {
651            servers: Vec::new(),
652            networks: Vec::new(),
653            firewalls: Vec::new(),
654            load_balancers: Vec::new(),
655        }
656    }
657
658    pub fn is_empty(&self) -> bool {
659        self.servers.is_empty()
660            && self.networks.is_empty()
661            && self.firewalls.is_empty()
662            && self.load_balancers.is_empty()
663    }
664}
665
666impl Default for InfrastructureState {
667    fn default() -> Self {
668        Self::new()
669    }
670}