torrust_tracker_deployer_lib/domain/environment/params.rs
1//! Environment Creation Parameters
2//!
3//! This module provides `EnvironmentParams`, a domain value object that holds
4//! all validated parameters needed to create an `Environment` aggregate.
5//!
6//! # DDD Pattern: Factory Input
7//!
8//! This is a value object that groups all the inputs required by the
9//! `Environment::create()` factory method. It provides:
10//!
11//! - **Named fields**: Self-documenting, no positional confusion
12//! - **Type safety**: All fields are validated domain types
13//! - **Clean API**: Single parameter instead of 10+ arguments
14//!
15//! # Architecture
16//!
17//! ```text
18//! EnvironmentCreationConfig (DTO - Application Layer)
19//! │
20//! │ TryFrom (in Application Layer)
21//! ▼
22//! EnvironmentParams (Domain Value Object)
23//! │
24//! │ Environment::create(params, working_dir, timestamp)
25//! ▼
26//! Environment<Created> (Domain Aggregate)
27//! ```
28//!
29//! The `TryFrom` implementation lives in the Application layer since it needs
30//! to reference the DTO, but `EnvironmentParams` itself is a pure domain type.
31//!
32//! # Usage
33//!
34//! ```rust,no_run
35//! use torrust_tracker_deployer_lib::domain::environment::EnvironmentParams;
36//! use torrust_tracker_deployer_lib::domain::{EnvironmentName, InstanceName};
37//!
38//! // EnvironmentParams is typically constructed via TryFrom in application layer
39//! // or directly in domain tests
40//! ```
41
42use crate::adapters::ssh::SshCredentials;
43use crate::domain::backup::BackupConfig;
44use crate::domain::grafana::GrafanaConfig;
45use crate::domain::https::HttpsConfig;
46use crate::domain::prometheus::PrometheusConfig;
47use crate::domain::provider::ProviderConfig;
48use crate::domain::tracker::TrackerConfig;
49use crate::domain::{EnvironmentName, InstanceName};
50
51/// Parameters for creating a new Environment aggregate
52///
53/// This value object contains all validated domain objects needed to construct
54/// an `Environment<Created>` aggregate. It serves as a "factory input" pattern,
55/// grouping related parameters into a single, self-documenting type.
56///
57/// # Field Categories
58///
59/// - **Identity**: `environment_name`, `instance_name`
60/// - **Infrastructure**: `provider_config`, `ssh_credentials`, `ssh_port`
61/// - **Application**: `tracker_config`
62/// - **Observability**: `prometheus_config`, `grafana_config`
63/// - **Security**: `https_config`
64///
65/// # Invariants
66///
67/// All fields are pre-validated domain types. Cross-field validation
68/// (e.g., Grafana requires Prometheus) happens in `Environment::create()`.
69#[derive(Debug, Clone)]
70pub struct EnvironmentParams {
71 /// Validated environment name (e.g., "production", "staging")
72 pub environment_name: EnvironmentName,
73
74 /// Validated instance name for the VM/container
75 ///
76 /// Either user-provided or auto-generated as `torrust-tracker-vm-{env_name}`
77 pub instance_name: InstanceName,
78
79 /// Provider-specific configuration (LXD, Hetzner, etc.)
80 pub provider_config: ProviderConfig,
81
82 /// SSH credentials for remote access to the deployed instance
83 pub ssh_credentials: SshCredentials,
84
85 /// SSH port for remote connections (typically 22)
86 pub ssh_port: u16,
87
88 /// Tracker application configuration
89 pub tracker_config: TrackerConfig,
90
91 /// Optional Prometheus monitoring configuration
92 pub prometheus_config: Option<PrometheusConfig>,
93
94 /// Optional Grafana dashboard configuration
95 ///
96 /// Note: Requires `prometheus_config` to be set (validated in `Environment::create()`)
97 pub grafana_config: Option<GrafanaConfig>,
98
99 /// Optional HTTPS/TLS configuration for secure endpoints
100 pub https_config: Option<HttpsConfig>,
101
102 /// Optional backup service configuration
103 pub backup_config: Option<BackupConfig>,
104}
105
106impl EnvironmentParams {
107 /// Creates a new `EnvironmentParams` instance
108 ///
109 /// This constructor is primarily used in domain tests. In production,
110 /// `EnvironmentParams` is typically constructed via `TryFrom` conversion
111 /// from a configuration DTO in the application layer.
112 ///
113 /// # Arguments
114 ///
115 /// * `environment_name` - Validated environment name
116 /// * `instance_name` - Validated instance name
117 /// * `provider_config` - Provider configuration
118 /// * `ssh_credentials` - SSH access credentials
119 /// * `ssh_port` - SSH port number
120 /// * `tracker_config` - Tracker application configuration
121 /// * `prometheus_config` - Optional Prometheus configuration
122 /// * `grafana_config` - Optional Grafana configuration
123 /// * `https_config` - Optional HTTPS configuration
124 /// * `backup_config` - Optional backup configuration
125 #[must_use]
126 #[allow(clippy::too_many_arguments)]
127 pub fn new(
128 environment_name: EnvironmentName,
129 instance_name: InstanceName,
130 provider_config: ProviderConfig,
131 ssh_credentials: SshCredentials,
132 ssh_port: u16,
133 tracker_config: TrackerConfig,
134 prometheus_config: Option<PrometheusConfig>,
135 grafana_config: Option<GrafanaConfig>,
136 https_config: Option<HttpsConfig>,
137 backup_config: Option<BackupConfig>,
138 ) -> Self {
139 Self {
140 environment_name,
141 instance_name,
142 provider_config,
143 ssh_credentials,
144 ssh_port,
145 tracker_config,
146 prometheus_config,
147 grafana_config,
148 https_config,
149 backup_config,
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::domain::provider::LxdConfig;
158 use crate::domain::ProfileName;
159 use crate::shared::Username;
160 use std::path::PathBuf;
161
162 fn sample_ssh_credentials() -> SshCredentials {
163 let project_root = env!("CARGO_MANIFEST_DIR");
164 SshCredentials::new(
165 PathBuf::from(format!("{project_root}/fixtures/testing_rsa")),
166 PathBuf::from(format!("{project_root}/fixtures/testing_rsa.pub")),
167 Username::new("torrust").unwrap(),
168 )
169 }
170
171 fn sample_tracker_config() -> TrackerConfig {
172 TrackerConfig::default()
173 }
174
175 #[test]
176 fn it_should_create_environment_params_with_all_fields() {
177 let params = EnvironmentParams::new(
178 EnvironmentName::new("test-env").unwrap(),
179 InstanceName::new("test-instance".to_string()).unwrap(),
180 ProviderConfig::Lxd(LxdConfig {
181 profile_name: ProfileName::new("lxd-test").unwrap(),
182 }),
183 sample_ssh_credentials(),
184 22,
185 sample_tracker_config(),
186 None,
187 None,
188 None,
189 None,
190 );
191
192 assert_eq!(params.environment_name.as_str(), "test-env");
193 assert_eq!(params.instance_name.as_str(), "test-instance");
194 assert_eq!(params.ssh_port, 22);
195 }
196
197 #[test]
198 fn it_should_provide_named_field_access() {
199 let params = EnvironmentParams::new(
200 EnvironmentName::new("prod").unwrap(),
201 InstanceName::new("prod-vm".to_string()).unwrap(),
202 ProviderConfig::Lxd(LxdConfig {
203 profile_name: ProfileName::new("lxd-prod").unwrap(),
204 }),
205 sample_ssh_credentials(),
206 2222,
207 sample_tracker_config(),
208 None,
209 None,
210 None,
211 None,
212 );
213
214 // All fields accessible by name
215 let _name: &EnvironmentName = ¶ms.environment_name;
216 let _instance: &InstanceName = ¶ms.instance_name;
217 let _provider: &ProviderConfig = ¶ms.provider_config;
218 let _ssh: &SshCredentials = ¶ms.ssh_credentials;
219 let _port: u16 = params.ssh_port;
220 let _tracker: &TrackerConfig = ¶ms.tracker_config;
221 let _prometheus: &Option<PrometheusConfig> = ¶ms.prometheus_config;
222 let _grafana: &Option<GrafanaConfig> = ¶ms.grafana_config;
223 let _https: &Option<HttpsConfig> = ¶ms.https_config;
224 }
225}