Skip to main content

terraform_wrapper/commands/
state.rs

1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6/// The state subcommand to execute.
7#[derive(Debug, Clone)]
8pub enum StateSubcommand {
9    /// List resources in the state.
10    List,
11    /// Show a single resource in the state.
12    Show(String),
13    /// Move a resource to a different address.
14    Mv {
15        /// Source address.
16        source: String,
17        /// Destination address.
18        destination: String,
19    },
20    /// Remove a resource from the state (without destroying it).
21    Rm(Vec<String>),
22    /// Pull remote state and output to stdout.
23    Pull,
24    /// Push local state to remote backend.
25    Push,
26}
27
28/// Command for managing Terraform state.
29///
30/// ```no_run
31/// # async fn example() -> terraform_wrapper::error::Result<()> {
32/// use terraform_wrapper::{Terraform, TerraformCommand};
33/// use terraform_wrapper::commands::state::StateCommand;
34///
35/// let tf = Terraform::builder().working_dir("/tmp/infra").build()?;
36///
37/// // List resources in state
38/// let output = StateCommand::list().execute(&tf).await?;
39///
40/// // Show a specific resource
41/// let output = StateCommand::show("null_resource.example")
42///     .execute(&tf)
43///     .await?;
44/// # Ok(())
45/// # }
46/// ```
47#[derive(Debug, Clone)]
48pub struct StateCommand {
49    subcommand: StateSubcommand,
50    raw_args: Vec<String>,
51}
52
53impl StateCommand {
54    /// List resources in the state.
55    #[must_use]
56    pub fn list() -> Self {
57        Self {
58            subcommand: StateSubcommand::List,
59            raw_args: Vec::new(),
60        }
61    }
62
63    /// Show a single resource in the state.
64    #[must_use]
65    pub fn show(address: &str) -> Self {
66        Self {
67            subcommand: StateSubcommand::Show(address.to_string()),
68            raw_args: Vec::new(),
69        }
70    }
71
72    /// Move a resource to a different address.
73    #[must_use]
74    pub fn mv(source: &str, destination: &str) -> Self {
75        Self {
76            subcommand: StateSubcommand::Mv {
77                source: source.to_string(),
78                destination: destination.to_string(),
79            },
80            raw_args: Vec::new(),
81        }
82    }
83
84    /// Remove resources from the state (without destroying them).
85    #[must_use]
86    pub fn rm(addresses: Vec<String>) -> Self {
87        Self {
88            subcommand: StateSubcommand::Rm(addresses),
89            raw_args: Vec::new(),
90        }
91    }
92
93    /// Pull remote state and output to stdout.
94    #[must_use]
95    pub fn pull() -> Self {
96        Self {
97            subcommand: StateSubcommand::Pull,
98            raw_args: Vec::new(),
99        }
100    }
101
102    /// Push local state to remote backend.
103    #[must_use]
104    pub fn push() -> Self {
105        Self {
106            subcommand: StateSubcommand::Push,
107            raw_args: Vec::new(),
108        }
109    }
110
111    /// Add a raw argument (escape hatch for unsupported options).
112    #[must_use]
113    pub fn arg(mut self, arg: impl Into<String>) -> Self {
114        self.raw_args.push(arg.into());
115        self
116    }
117}
118
119impl TerraformCommand for StateCommand {
120    type Output = CommandOutput;
121
122    fn args(&self) -> Vec<String> {
123        let mut args = vec!["state".to_string()];
124        match &self.subcommand {
125            StateSubcommand::List => args.push("list".to_string()),
126            StateSubcommand::Show(address) => {
127                args.push("show".to_string());
128                args.push(address.clone());
129            }
130            StateSubcommand::Mv {
131                source,
132                destination,
133            } => {
134                args.push("mv".to_string());
135                args.push(source.clone());
136                args.push(destination.clone());
137            }
138            StateSubcommand::Rm(addresses) => {
139                args.push("rm".to_string());
140                args.extend(addresses.clone());
141            }
142            StateSubcommand::Pull => args.push("pull".to_string()),
143            StateSubcommand::Push => args.push("push".to_string()),
144        }
145        args.extend(self.raw_args.clone());
146        args
147    }
148
149    async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
150        exec::run_terraform(tf, self.args()).await
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn list_args() {
160        let cmd = StateCommand::list();
161        assert_eq!(cmd.args(), vec!["state", "list"]);
162    }
163
164    #[test]
165    fn show_args() {
166        let cmd = StateCommand::show("null_resource.example");
167        assert_eq!(cmd.args(), vec!["state", "show", "null_resource.example"]);
168    }
169
170    #[test]
171    fn mv_args() {
172        let cmd = StateCommand::mv("null_resource.old", "null_resource.new");
173        assert_eq!(
174            cmd.args(),
175            vec!["state", "mv", "null_resource.old", "null_resource.new"]
176        );
177    }
178
179    #[test]
180    fn rm_args() {
181        let cmd = StateCommand::rm(vec![
182            "null_resource.a".to_string(),
183            "null_resource.b".to_string(),
184        ]);
185        assert_eq!(
186            cmd.args(),
187            vec!["state", "rm", "null_resource.a", "null_resource.b"]
188        );
189    }
190
191    #[test]
192    fn pull_args() {
193        let cmd = StateCommand::pull();
194        assert_eq!(cmd.args(), vec!["state", "pull"]);
195    }
196
197    #[test]
198    fn push_args() {
199        let cmd = StateCommand::push();
200        assert_eq!(cmd.args(), vec!["state", "push"]);
201    }
202}