terraform_wrapper/commands/
state.rs1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6#[derive(Debug, Clone)]
8pub enum StateSubcommand {
9 List,
11 Show(String),
13 Mv {
15 source: String,
17 destination: String,
19 },
20 Rm(Vec<String>),
22 Pull,
24 Push,
26 ReplaceProvider {
28 from: String,
30 to: String,
32 },
33}
34
35#[derive(Debug, Clone)]
55pub struct StateCommand {
56 subcommand: StateSubcommand,
57 auto_approve: bool,
58 dry_run: bool,
59 lock: Option<bool>,
60 lock_timeout: Option<String>,
61 raw_args: Vec<String>,
62}
63
64impl StateCommand {
65 #[must_use]
67 pub fn list() -> Self {
68 Self {
69 subcommand: StateSubcommand::List,
70 auto_approve: false,
71 dry_run: false,
72 lock: None,
73 lock_timeout: None,
74 raw_args: Vec::new(),
75 }
76 }
77
78 #[must_use]
80 pub fn show(address: &str) -> Self {
81 Self {
82 subcommand: StateSubcommand::Show(address.to_string()),
83 auto_approve: false,
84 dry_run: false,
85 lock: None,
86 lock_timeout: None,
87 raw_args: Vec::new(),
88 }
89 }
90
91 #[must_use]
93 pub fn mv(source: &str, destination: &str) -> Self {
94 Self {
95 subcommand: StateSubcommand::Mv {
96 source: source.to_string(),
97 destination: destination.to_string(),
98 },
99 auto_approve: false,
100 dry_run: false,
101 lock: None,
102 lock_timeout: None,
103 raw_args: Vec::new(),
104 }
105 }
106
107 #[must_use]
109 pub fn rm(addresses: Vec<String>) -> Self {
110 Self {
111 subcommand: StateSubcommand::Rm(addresses),
112 auto_approve: false,
113 dry_run: false,
114 lock: None,
115 lock_timeout: None,
116 raw_args: Vec::new(),
117 }
118 }
119
120 #[must_use]
122 pub fn pull() -> Self {
123 Self {
124 subcommand: StateSubcommand::Pull,
125 auto_approve: false,
126 dry_run: false,
127 lock: None,
128 lock_timeout: None,
129 raw_args: Vec::new(),
130 }
131 }
132
133 #[must_use]
135 pub fn push() -> Self {
136 Self {
137 subcommand: StateSubcommand::Push,
138 auto_approve: false,
139 dry_run: false,
140 lock: None,
141 lock_timeout: None,
142 raw_args: Vec::new(),
143 }
144 }
145
146 #[must_use]
167 pub fn replace_provider(from: &str, to: &str) -> Self {
168 Self {
169 subcommand: StateSubcommand::ReplaceProvider {
170 from: from.to_string(),
171 to: to.to_string(),
172 },
173 auto_approve: false,
174 dry_run: false,
175 lock: None,
176 lock_timeout: None,
177 raw_args: Vec::new(),
178 }
179 }
180
181 #[must_use]
185 pub fn auto_approve(mut self) -> Self {
186 self.auto_approve = true;
187 self
188 }
189
190 #[must_use]
194 pub fn dry_run(mut self) -> Self {
195 self.dry_run = true;
196 self
197 }
198
199 #[must_use]
203 pub fn lock(mut self, enabled: bool) -> Self {
204 self.lock = Some(enabled);
205 self
206 }
207
208 #[must_use]
212 pub fn lock_timeout(mut self, timeout: &str) -> Self {
213 self.lock_timeout = Some(timeout.to_string());
214 self
215 }
216
217 #[must_use]
219 pub fn arg(mut self, arg: impl Into<String>) -> Self {
220 self.raw_args.push(arg.into());
221 self
222 }
223
224 fn push_lock_flags(&self, args: &mut Vec<String>) {
226 if self.dry_run {
227 args.push("-dry-run".to_string());
228 }
229 if let Some(lock) = self.lock {
230 args.push(format!("-lock={lock}"));
231 }
232 if let Some(ref timeout) = self.lock_timeout {
233 args.push(format!("-lock-timeout={timeout}"));
234 }
235 }
236}
237
238impl TerraformCommand for StateCommand {
239 type Output = CommandOutput;
240
241 fn args(&self) -> Vec<String> {
242 let mut args = vec!["state".to_string()];
243 match &self.subcommand {
244 StateSubcommand::List => args.push("list".to_string()),
245 StateSubcommand::Show(address) => {
246 args.push("show".to_string());
247 args.push(address.clone());
248 }
249 StateSubcommand::Mv {
250 source,
251 destination,
252 } => {
253 args.push("mv".to_string());
254 self.push_lock_flags(&mut args);
255 args.push(source.clone());
256 args.push(destination.clone());
257 }
258 StateSubcommand::Rm(addresses) => {
259 args.push("rm".to_string());
260 self.push_lock_flags(&mut args);
261 args.extend(addresses.clone());
262 }
263 StateSubcommand::Pull => args.push("pull".to_string()),
264 StateSubcommand::Push => args.push("push".to_string()),
265 StateSubcommand::ReplaceProvider { from, to } => {
266 args.push("replace-provider".to_string());
267 if self.auto_approve {
268 args.push("-auto-approve".to_string());
269 }
270 self.push_lock_flags(&mut args);
271 args.push(from.clone());
272 args.push(to.clone());
273 }
274 }
275 args.extend(self.raw_args.clone());
276 args
277 }
278
279 async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
280 exec::run_terraform(tf, self.args()).await
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn list_args() {
290 let cmd = StateCommand::list();
291 assert_eq!(cmd.args(), vec!["state", "list"]);
292 }
293
294 #[test]
295 fn show_args() {
296 let cmd = StateCommand::show("null_resource.example");
297 assert_eq!(cmd.args(), vec!["state", "show", "null_resource.example"]);
298 }
299
300 #[test]
301 fn mv_args() {
302 let cmd = StateCommand::mv("null_resource.old", "null_resource.new");
303 assert_eq!(
304 cmd.args(),
305 vec!["state", "mv", "null_resource.old", "null_resource.new"]
306 );
307 }
308
309 #[test]
310 fn mv_dry_run_args() {
311 let cmd = StateCommand::mv("null_resource.old", "null_resource.new").dry_run();
312 assert_eq!(
313 cmd.args(),
314 vec![
315 "state",
316 "mv",
317 "-dry-run",
318 "null_resource.old",
319 "null_resource.new"
320 ]
321 );
322 }
323
324 #[test]
325 fn mv_lock_args() {
326 let cmd = StateCommand::mv("null_resource.old", "null_resource.new")
327 .lock(false)
328 .lock_timeout("10s");
329 assert_eq!(
330 cmd.args(),
331 vec![
332 "state",
333 "mv",
334 "-lock=false",
335 "-lock-timeout=10s",
336 "null_resource.old",
337 "null_resource.new"
338 ]
339 );
340 }
341
342 #[test]
343 fn rm_args() {
344 let cmd = StateCommand::rm(vec![
345 "null_resource.a".to_string(),
346 "null_resource.b".to_string(),
347 ]);
348 assert_eq!(
349 cmd.args(),
350 vec!["state", "rm", "null_resource.a", "null_resource.b"]
351 );
352 }
353
354 #[test]
355 fn rm_dry_run_args() {
356 let cmd = StateCommand::rm(vec!["null_resource.a".to_string()]).dry_run();
357 assert_eq!(
358 cmd.args(),
359 vec!["state", "rm", "-dry-run", "null_resource.a"]
360 );
361 }
362
363 #[test]
364 fn pull_args() {
365 let cmd = StateCommand::pull();
366 assert_eq!(cmd.args(), vec!["state", "pull"]);
367 }
368
369 #[test]
370 fn push_args() {
371 let cmd = StateCommand::push();
372 assert_eq!(cmd.args(), vec!["state", "push"]);
373 }
374
375 #[test]
376 fn replace_provider_args() {
377 let cmd = StateCommand::replace_provider(
378 "registry.terraform.io/-/aws",
379 "registry.terraform.io/hashicorp/aws",
380 );
381 assert_eq!(
382 cmd.args(),
383 vec![
384 "state",
385 "replace-provider",
386 "registry.terraform.io/-/aws",
387 "registry.terraform.io/hashicorp/aws"
388 ]
389 );
390 }
391
392 #[test]
393 fn replace_provider_auto_approve_args() {
394 let cmd = StateCommand::replace_provider(
395 "registry.terraform.io/-/aws",
396 "registry.terraform.io/hashicorp/aws",
397 )
398 .auto_approve()
399 .lock(false);
400 assert_eq!(
401 cmd.args(),
402 vec![
403 "state",
404 "replace-provider",
405 "-auto-approve",
406 "-lock=false",
407 "registry.terraform.io/-/aws",
408 "registry.terraform.io/hashicorp/aws"
409 ]
410 );
411 }
412
413 #[test]
414 fn replace_provider_lock_timeout_args() {
415 let cmd = StateCommand::replace_provider(
416 "registry.terraform.io/-/aws",
417 "registry.terraform.io/hashicorp/aws",
418 )
419 .lock_timeout("30s");
420 assert_eq!(
421 cmd.args(),
422 vec![
423 "state",
424 "replace-provider",
425 "-lock-timeout=30s",
426 "registry.terraform.io/-/aws",
427 "registry.terraform.io/hashicorp/aws"
428 ]
429 );
430 }
431
432 #[test]
433 fn list_ignores_mv_rm_flags() {
434 let cmd = StateCommand::list()
435 .dry_run()
436 .lock(false)
437 .lock_timeout("10s");
438 assert_eq!(cmd.args(), vec!["state", "list"]);
439 }
440}