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}