Skip to main content

terraform_wrapper/commands/
force_unlock.rs

1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6/// Command for manually releasing a stuck state lock.
7///
8/// Requires a lock ID as a positional argument. Use `-force` to skip the
9/// confirmation prompt.
10///
11/// ```no_run
12/// # async fn example() -> terraform_wrapper::error::Result<()> {
13/// use terraform_wrapper::{Terraform, TerraformCommand};
14/// use terraform_wrapper::commands::force_unlock::ForceUnlockCommand;
15///
16/// let tf = Terraform::builder().working_dir("/tmp/infra").build()?;
17/// ForceUnlockCommand::new("lock-id-here")
18///     .force()
19///     .execute(&tf)
20///     .await?;
21/// # Ok(())
22/// # }
23/// ```
24#[derive(Debug, Clone)]
25pub struct ForceUnlockCommand {
26    lock_id: String,
27    force: bool,
28    raw_args: Vec<String>,
29}
30
31impl ForceUnlockCommand {
32    /// Create a new force-unlock command with the given lock ID.
33    #[must_use]
34    pub fn new(lock_id: &str) -> Self {
35        Self {
36            lock_id: lock_id.to_string(),
37            force: false,
38            raw_args: Vec::new(),
39        }
40    }
41
42    /// Skip the confirmation prompt (`-force`).
43    #[must_use]
44    pub fn force(mut self) -> Self {
45        self.force = true;
46        self
47    }
48
49    /// Add a raw argument (escape hatch for unsupported options).
50    #[must_use]
51    pub fn arg(mut self, arg: impl Into<String>) -> Self {
52        self.raw_args.push(arg.into());
53        self
54    }
55}
56
57impl TerraformCommand for ForceUnlockCommand {
58    type Output = CommandOutput;
59
60    fn args(&self) -> Vec<String> {
61        let mut args = vec!["force-unlock".to_string()];
62        if self.force {
63            args.push("-force".to_string());
64        }
65        args.extend(self.raw_args.clone());
66        args.push(self.lock_id.clone());
67        args
68    }
69
70    async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
71        exec::run_terraform(tf, self.args()).await
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn default_args() {
81        let cmd = ForceUnlockCommand::new("abc-123");
82        assert_eq!(cmd.args(), vec!["force-unlock", "abc-123"]);
83    }
84
85    #[test]
86    fn with_force() {
87        let cmd = ForceUnlockCommand::new("abc-123").force();
88        assert_eq!(cmd.args(), vec!["force-unlock", "-force", "abc-123"]);
89    }
90
91    #[test]
92    fn lock_id_at_end() {
93        let cmd = ForceUnlockCommand::new("abc-123").force().arg("-no-color");
94        let args = cmd.args();
95        assert_eq!(args.last().unwrap(), "abc-123");
96    }
97}