Skip to main content

terraform_wrapper/commands/
refresh.rs

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