torrust_tracker_deployer_lib/domain/environment/state/
provisioning.rs1use std::net::IpAddr;
13
14use serde::{Deserialize, Serialize};
15
16use crate::domain::environment::runtime_outputs::ProvisionMethod;
17use crate::domain::environment::state::{
18 AnyEnvironmentState, ProvisionFailed, Provisioned, StateTypeError,
19};
20use crate::domain::environment::Environment;
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct Provisioning;
32
33impl Environment<Provisioning> {
35 #[must_use]
50 pub fn provisioned(
51 self,
52 instance_ip: IpAddr,
53 provision_method: ProvisionMethod,
54 ) -> Environment<Provisioned> {
55 self.with_instance_ip(instance_ip)
56 .with_provision_method(provision_method)
57 .with_state(Provisioned)
58 }
59
60 #[must_use]
66 pub fn provision_failed(
67 self,
68 context: crate::domain::environment::state::ProvisionFailureContext,
69 ) -> Environment<ProvisionFailed> {
70 self.with_state(ProvisionFailed { context })
71 }
72}
73
74impl Environment<Provisioning> {
76 #[must_use]
78 pub fn into_any(self) -> AnyEnvironmentState {
79 AnyEnvironmentState::Provisioning(self)
80 }
81}
82
83impl AnyEnvironmentState {
85 pub fn try_into_provisioning(self) -> Result<Environment<Provisioning>, StateTypeError> {
91 match self {
92 Self::Provisioning(env) => Ok(env),
93 other => Err(StateTypeError::UnexpectedState {
94 expected: "provisioning",
95 actual: other.state_name().to_string(),
96 }),
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn it_should_create_provisioning_state() {
107 let state = Provisioning;
108 assert_eq!(state, Provisioning);
109 }
110
111 mod conversion_tests {
112 use super::*;
113 use crate::adapters::ssh::SshCredentials;
114 use crate::domain::environment::name::EnvironmentName;
115 use crate::domain::provider::{LxdConfig, ProviderConfig};
116 use crate::domain::ProfileName;
117 use crate::shared::Username;
118 use std::path::PathBuf;
119
120 fn default_lxd_provider_config(env_name: &EnvironmentName) -> ProviderConfig {
121 ProviderConfig::Lxd(LxdConfig {
122 profile_name: ProfileName::new(format!("lxd-{}", env_name.as_str())).unwrap(),
123 })
124 }
125
126 fn create_test_ssh_credentials() -> SshCredentials {
127 let username = Username::new("test-user".to_string()).unwrap();
128 SshCredentials::new(
129 PathBuf::from("/tmp/test_key"),
130 PathBuf::from("/tmp/test_key.pub"),
131 username,
132 )
133 }
134
135 fn create_test_environment_provisioning() -> Environment<Provisioning> {
136 let name = EnvironmentName::new("test-env".to_string()).unwrap();
137 let ssh_creds = create_test_ssh_credentials();
138 Environment::new(
139 name.clone(),
140 default_lxd_provider_config(&name),
141 ssh_creds,
142 22,
143 chrono::Utc::now(),
144 )
145 .start_provisioning()
146 }
147
148 #[test]
149 fn it_should_convert_provisioning_environment_into_any() {
150 let env = create_test_environment_provisioning();
151 let any_env = env.into_any();
152 assert!(matches!(any_env, AnyEnvironmentState::Provisioning(_)));
153 }
154
155 #[test]
156 fn it_should_convert_any_to_provisioning_successfully() {
157 let env = create_test_environment_provisioning();
158 let any_env = env.into_any();
159 let result = any_env.try_into_provisioning();
160 assert!(result.is_ok());
161 }
162
163 #[test]
164 fn it_should_fail_converting_created_to_provisioning() {
165 let name = EnvironmentName::new("test-env".to_string()).unwrap();
166 let ssh_creds = create_test_ssh_credentials();
167 let env = Environment::new(
168 name.clone(),
169 default_lxd_provider_config(&name),
170 ssh_creds,
171 22,
172 chrono::Utc::now(),
173 );
174 let any_env = env.into_any();
175 let result = any_env.try_into_provisioning();
176 assert!(result.is_err());
177 let err = result.unwrap_err();
178 assert!(err.to_string().contains("provisioning"));
179 assert!(err.to_string().contains("created"));
180 }
181 }
182
183 mod state_transitions {
184 use super::super::*;
185 use crate::adapters::ssh::SshCredentials;
186 use crate::domain::environment::name::EnvironmentName;
187 use crate::domain::environment::runtime_outputs::ProvisionMethod;
188 use crate::domain::environment::state::Provisioned;
189 use crate::domain::provider::{LxdConfig, ProviderConfig};
190 use crate::domain::ProfileName;
191 use crate::shared::Username;
192 use std::net::{IpAddr, Ipv4Addr};
193 use std::path::PathBuf;
194
195 fn default_lxd_provider_config(env_name: &EnvironmentName) -> ProviderConfig {
196 ProviderConfig::Lxd(LxdConfig {
197 profile_name: ProfileName::new(format!("lxd-{}", env_name.as_str())).unwrap(),
198 })
199 }
200
201 fn create_test_ssh_credentials() -> SshCredentials {
202 let username = Username::new("test-user".to_string()).unwrap();
203 SshCredentials::new(
204 PathBuf::from("/tmp/test_key"),
205 PathBuf::from("/tmp/test_key.pub"),
206 username,
207 )
208 }
209
210 fn create_test_environment_provisioning() -> Environment<Provisioning> {
211 let name = EnvironmentName::new("test-env".to_string()).unwrap();
212 let ssh_creds = create_test_ssh_credentials();
213 Environment::new(
214 name.clone(),
215 default_lxd_provider_config(&name),
216 ssh_creds,
217 22,
218 chrono::Utc::now(),
219 )
220 .start_provisioning()
221 }
222
223 #[test]
224 fn it_should_transition_from_provisioning_to_provisioned() {
225 let env = create_test_environment_provisioning();
226 let env = env.provisioned(
227 IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)),
228 ProvisionMethod::Provisioned,
229 );
230
231 assert_eq!(*env.state(), Provisioned);
232 assert_eq!(env.name().as_str(), "test-env");
233 }
234
235 #[test]
236 fn it_should_transition_from_provisioning_to_provision_failed() {
237 use crate::domain::environment::state::{
238 BaseFailureContext, ProvisionFailureContext, ProvisionStep,
239 };
240 use crate::domain::environment::TraceId;
241 use crate::shared::ErrorKind;
242 use chrono::Utc;
243 use std::time::Duration;
244
245 let env = create_test_environment_provisioning();
246 let context = ProvisionFailureContext {
247 failed_step: ProvisionStep::CloudInitWait,
248 error_kind: ErrorKind::Timeout,
249 base: BaseFailureContext {
250 error_summary: "cloud_init_timeout".to_string(),
251 failed_at: Utc::now(),
252 execution_started_at: Utc::now(),
253 execution_duration: Duration::from_secs(30),
254 trace_id: TraceId::new(),
255 trace_file_path: None,
256 },
257 };
258 let env = env.provision_failed(context.clone());
259
260 assert_eq!(
261 env.state().context.failed_step,
262 ProvisionStep::CloudInitWait
263 );
264 assert_eq!(env.name().as_str(), "test-env");
265 }
266 }
267}