Skip to main content

terraform_wrapper/commands/
destroy.rs

1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6/// Command for destroying Terraform-managed infrastructure.
7///
8/// This is equivalent to `terraform apply -destroy` but provided as a
9/// separate command for clarity and discoverability.
10///
11/// ```no_run
12/// # async fn example() -> terraform_wrapper::error::Result<()> {
13/// use terraform_wrapper::{Terraform, TerraformCommand};
14/// use terraform_wrapper::commands::destroy::DestroyCommand;
15///
16/// let tf = Terraform::builder().working_dir("/tmp/infra").build()?;
17/// DestroyCommand::new()
18///     .auto_approve()
19///     .execute(&tf)
20///     .await?;
21/// # Ok(())
22/// # }
23/// ```
24#[derive(Debug, Clone, Default)]
25pub struct DestroyCommand {
26    auto_approve: bool,
27    vars: Vec<(String, String)>,
28    var_files: Vec<String>,
29    targets: Vec<String>,
30    lock: Option<bool>,
31    lock_timeout: Option<String>,
32    parallelism: Option<u32>,
33    json: bool,
34    raw_args: Vec<String>,
35}
36
37impl DestroyCommand {
38    /// Create a new destroy command with default options.
39    #[must_use]
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Skip interactive approval (`-auto-approve`).
45    #[must_use]
46    pub fn auto_approve(mut self) -> Self {
47        self.auto_approve = true;
48        self
49    }
50
51    /// Set a variable value (`-var="name=value"`).
52    #[must_use]
53    pub fn var(mut self, name: &str, value: &str) -> Self {
54        self.vars.push((name.to_string(), value.to_string()));
55        self
56    }
57
58    /// Add a variable definitions file (`-var-file`).
59    #[must_use]
60    pub fn var_file(mut self, path: &str) -> Self {
61        self.var_files.push(path.to_string());
62        self
63    }
64
65    /// Target a specific resource or module (`-target`).
66    #[must_use]
67    pub fn target(mut self, resource: &str) -> Self {
68        self.targets.push(resource.to_string());
69        self
70    }
71
72    /// Enable or disable state locking (`-lock`).
73    #[must_use]
74    pub fn lock(mut self, enabled: bool) -> Self {
75        self.lock = Some(enabled);
76        self
77    }
78
79    /// Duration to wait for state lock (`-lock-timeout`).
80    #[must_use]
81    pub fn lock_timeout(mut self, timeout: &str) -> Self {
82        self.lock_timeout = Some(timeout.to_string());
83        self
84    }
85
86    /// Limit the number of concurrent operations (`-parallelism`).
87    #[must_use]
88    pub fn parallelism(mut self, n: u32) -> Self {
89        self.parallelism = Some(n);
90        self
91    }
92
93    /// Enable machine-readable JSON output (`-json`).
94    #[must_use]
95    pub fn json(mut self) -> Self {
96        self.json = true;
97        self
98    }
99
100    /// Add a raw argument (escape hatch for unsupported options).
101    #[must_use]
102    pub fn arg(mut self, arg: impl Into<String>) -> Self {
103        self.raw_args.push(arg.into());
104        self
105    }
106}
107
108impl TerraformCommand for DestroyCommand {
109    type Output = CommandOutput;
110
111    fn args(&self) -> Vec<String> {
112        let mut args = vec!["destroy".to_string()];
113        if self.auto_approve {
114            args.push("-auto-approve".to_string());
115        }
116        for (name, value) in &self.vars {
117            args.push(format!("-var={name}={value}"));
118        }
119        for file in &self.var_files {
120            args.push(format!("-var-file={file}"));
121        }
122        for target in &self.targets {
123            args.push(format!("-target={target}"));
124        }
125        if let Some(lock) = self.lock {
126            args.push(format!("-lock={lock}"));
127        }
128        if let Some(ref timeout) = self.lock_timeout {
129            args.push(format!("-lock-timeout={timeout}"));
130        }
131        if let Some(n) = self.parallelism {
132            args.push(format!("-parallelism={n}"));
133        }
134        if self.json {
135            args.push("-json".to_string());
136        }
137        args.extend(self.raw_args.clone());
138        args
139    }
140
141    fn supports_input(&self) -> bool {
142        true
143    }
144
145    async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
146        exec::run_terraform(tf, self.prepare_args(tf)).await
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn default_args() {
156        let cmd = DestroyCommand::new();
157        assert_eq!(cmd.args(), vec!["destroy"]);
158    }
159
160    #[test]
161    fn auto_approve_with_targets() {
162        let cmd = DestroyCommand::new()
163            .auto_approve()
164            .target("module.vpc")
165            .var("region", "us-west-2")
166            .json();
167        let args = cmd.args();
168        assert_eq!(args[0], "destroy");
169        assert!(args.contains(&"-auto-approve".to_string()));
170        assert!(args.contains(&"-target=module.vpc".to_string()));
171        assert!(args.contains(&"-var=region=us-west-2".to_string()));
172        assert!(args.contains(&"-json".to_string()));
173    }
174}