Skip to main content

torrust_tracker_deployer_lib/application/steps/infrastructure/
plan.rs

1//! `OpenTofu` infrastructure planning step
2//!
3//! This module provides the `PlanInfrastructureStep` which handles `OpenTofu`
4//! planning by executing `tofu plan`. This step creates an execution plan
5//! showing what changes will be made to the infrastructure.
6//!
7//! ## Key Features
8//!
9//! - Infrastructure change planning and preview
10//! - Resource dependency analysis and ordering
11//! - Plan validation and error detection
12//! - Integration with `OpenTofuClient` for command execution
13//!
14//! ## Planning Process
15//!
16//! The step executes `tofu plan` which:
17//! - Analyzes current state vs desired configuration
18//! - Determines what resources need to be created, modified, or destroyed
19//! - Validates configuration and dependencies
20//! - Provides a preview of changes before application
21//!
22//! This step is crucial for validating infrastructure changes before applying them.
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 plans `OpenTofu` configuration by executing `tofu plan`
33pub struct PlanInfrastructureStep {
34    opentofu_client: Arc<OpenTofuClient>,
35}
36
37impl PlanInfrastructureStep {
38    #[must_use]
39    pub fn new(opentofu_client: Arc<OpenTofuClient>) -> Self {
40        Self { opentofu_client }
41    }
42
43    /// Execute the `OpenTofu` plan 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` plan fails
53    /// * The working directory does not exist or is not accessible
54    /// * The `OpenTofu` command execution fails
55    #[instrument(
56        name = "plan_infrastructure",
57        skip_all,
58        fields(step_type = "infrastructure", operation = "plan")
59    )]
60    pub fn execute(
61        &self,
62        listener: Option<&dyn CommandProgressListener>,
63    ) -> Result<(), CommandError> {
64        info!(
65            step = "plan_infrastructure",
66            "Planning 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 plan -var-file=variables.tfvars");
75        }
76
77        // Execute tofu plan command with variables file
78        let output = self.opentofu_client.plan(&["-var-file=variables.tfvars"])?;
79
80        // Extract and report plan summary if listener is provided
81        if let Some(l) = listener {
82            // Parse output to extract resource change counts
83            if let Some(plan_line) = output.lines().find(|line| line.contains("Plan:")) {
84                l.on_detail(plan_line.trim());
85            } else if output.contains("No changes") {
86                l.on_detail("Plan: No changes. Infrastructure is up-to-date.");
87            } else {
88                l.on_detail("Plan created successfully");
89            }
90        }
91
92        info!(
93            step = "plan_infrastructure",
94            status = "success",
95            "OpenTofu infrastructure planned successfully"
96        );
97
98        // Log output for debugging if needed
99        tracing::debug!(output = %output, "OpenTofu plan output");
100
101        Ok(())
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use std::sync::Arc;
108
109    use crate::adapters::tofu::client::OpenTofuClient;
110
111    use super::*;
112
113    #[test]
114    fn it_should_create_plan_infrastructure_step() {
115        let opentofu_client = Arc::new(OpenTofuClient::new("/tmp"));
116
117        let _step = PlanInfrastructureStep::new(opentofu_client);
118
119        // If we reach this point, the step was created successfully
120    }
121}