torrust_tracker_deployer_lib/application/steps/infrastructure/initialize.rs
1//! `OpenTofu` infrastructure initialization step
2//!
3//! This module provides the `InitializeInfrastructureStep` which handles `OpenTofu`
4//! initialization by executing `tofu init`. This step prepares the working directory
5//! for infrastructure operations by downloading providers and initializing state.
6//!
7//! ## Key Features
8//!
9//! - `OpenTofu` working directory initialization
10//! - Provider plugin downloading and installation
11//! - Backend configuration and state initialization
12//! - Integration with `OpenTofuClient` for command execution
13//!
14//! ## Initialization Process
15//!
16//! The step executes `tofu init` which performs:
17//! - Provider plugin resolution and download
18//! - Backend initialization (local or remote state)
19//! - Module downloading if applicable
20//! - Working directory setup for subsequent operations
21//!
22//! This is typically the first step in any infrastructure provisioning workflow.
23
24use std::sync::Arc;
25
26use tracing::{info, instrument};
27
28use crate::adapters::tofu::client::OpenTofuClient;
29use crate::application::traits::CommandProgressListener;
30use crate::shared::command::CommandError;
31
32/// Simple step that initializes `OpenTofu` configuration by executing `tofu init`
33pub struct InitializeInfrastructureStep {
34 opentofu_client: Arc<OpenTofuClient>,
35}
36
37impl InitializeInfrastructureStep {
38 #[must_use]
39 pub fn new(opentofu_client: Arc<OpenTofuClient>) -> Self {
40 Self { opentofu_client }
41 }
42
43 /// Execute the `OpenTofu` initialization step
44 ///
45 /// # Arguments
46 ///
47 /// * `listener` - Optional progress listener for reporting details
48 ///
49 /// # Errors
50 ///
51 /// Returns an error if:
52 /// * The `OpenTofu` initialization fails
53 /// * The working directory does not exist or is not accessible
54 /// * The `OpenTofu` command execution fails
55 #[instrument(
56 name = "initialize_infrastructure",
57 skip_all,
58 fields(step_type = "infrastructure", operation = "init")
59 )]
60 pub fn execute(
61 &self,
62 listener: Option<&dyn CommandProgressListener>,
63 ) -> Result<(), CommandError> {
64 info!(
65 step = "initialize_infrastructure",
66 "Initializing OpenTofu infrastructure"
67 );
68
69 if let Some(l) = listener {
70 l.on_debug(&format!(
71 "Working directory: {}",
72 self.opentofu_client.working_dir().display()
73 ));
74 l.on_debug("Executing: tofu init");
75 }
76
77 // Execute tofu init command
78 let output = self.opentofu_client.init()?;
79
80 if let Some(l) = listener {
81 l.on_debug("Command completed successfully");
82 l.on_detail("Initialized OpenTofu backend");
83 }
84
85 info!(
86 step = "initialize_infrastructure",
87 status = "success",
88 "OpenTofu infrastructure initialized successfully"
89 );
90
91 // Log output for debugging if needed
92 tracing::debug!(output = %output, "OpenTofu init output");
93
94 Ok(())
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use std::sync::Arc;
101
102 use crate::adapters::tofu::client::OpenTofuClient;
103
104 use super::*;
105
106 #[test]
107 fn it_should_create_initialize_infrastructure_step() {
108 let opentofu_client = Arc::new(OpenTofuClient::new("/tmp"));
109
110 let _step = InitializeInfrastructureStep::new(opentofu_client);
111
112 // If we reach this point, the step was created successfully
113 }
114}