Skip to main content

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}