Skip to main content

torrust_tracker_deployer_lib/application/steps/infrastructure/
validate.rs

1//! `OpenTofu` infrastructure validation step
2//!
3//! This module provides the `ValidateInfrastructureStep` which handles `OpenTofu`
4//! validation by executing `tofu validate`. This step validates the syntax and
5//! internal consistency of configuration files without creating a plan or applying changes.
6//!
7//! ## Key Features
8//!
9//! - Configuration syntax validation and error detection
10//! - Internal consistency checks for resource definitions
11//! - Provider schema validation against installed providers
12//! - Integration with `OpenTofuClient` for command execution
13//!
14//! ## Validation Process
15//!
16//! The step executes `tofu validate` which:
17//! - Validates syntax of all `.tf` configuration files
18//! - Checks for missing required arguments and invalid attribute names
19//! - Validates resource and data source configurations against provider schemas
20//! - Ensures internal consistency of variable references and expressions
21//!
22//! This step should be run after initialization but before planning to catch
23//! configuration errors early in the workflow.
24
25use std::sync::Arc;
26
27use tracing::{info, instrument};
28
29use crate::adapters::tofu::client::OpenTofuClient;
30use crate::application::traits::CommandProgressListener;
31use crate::shared::command::CommandError;
32
33/// Simple step that validates `OpenTofu` configuration by executing `tofu validate`
34pub struct ValidateInfrastructureStep {
35    opentofu_client: Arc<OpenTofuClient>,
36}
37
38impl ValidateInfrastructureStep {
39    #[must_use]
40    pub fn new(opentofu_client: Arc<OpenTofuClient>) -> Self {
41        Self { opentofu_client }
42    }
43
44    /// Execute the `OpenTofu` validation step
45    ///
46    /// # Arguments
47    ///
48    /// * `listener` - Optional progress listener for reporting details
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if:
53    /// * The `OpenTofu` validation fails due to syntax or consistency errors
54    /// * The working directory does not exist or is not accessible
55    /// * The `OpenTofu` command execution fails
56    /// * The configuration is not initialized (providers not installed)
57    #[instrument(
58        name = "validate_infrastructure",
59        skip_all,
60        fields(step_type = "infrastructure", operation = "validate")
61    )]
62    pub fn execute(
63        &self,
64        listener: Option<&dyn CommandProgressListener>,
65    ) -> Result<(), CommandError> {
66        info!(
67            step = "validate_infrastructure",
68            "Validating OpenTofu configuration"
69        );
70
71        if let Some(l) = listener {
72            l.on_debug(&format!(
73                "Working directory: {}",
74                self.opentofu_client.working_dir().display()
75            ));
76            l.on_debug("Executing: tofu validate");
77        }
78
79        // Execute tofu validate command
80        let output = self.opentofu_client.validate()?;
81
82        if let Some(l) = listener {
83            l.on_debug(&format!("Validation output: {}", output.trim()));
84            l.on_detail("Configuration is valid ✓");
85        }
86
87        info!(
88            step = "validate_infrastructure",
89            status = "success",
90            "OpenTofu configuration validated successfully"
91        );
92
93        // Log output for debugging if needed
94        tracing::debug!(output = %output, "OpenTofu validate output");
95
96        Ok(())
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use std::sync::Arc;
103
104    use crate::adapters::tofu::client::OpenTofuClient;
105
106    use super::*;
107
108    #[test]
109    fn it_should_create_validate_infrastructure_step() {
110        let opentofu_client = Arc::new(OpenTofuClient::new("/tmp"));
111
112        let _step = ValidateInfrastructureStep::new(opentofu_client);
113
114        // If we reach this point, the step was created successfully
115    }
116}