terraform_wrapper/commands/
apply.rs1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6#[derive(Debug, Clone, Default)]
26pub struct ApplyCommand {
27 plan_file: Option<String>,
28 auto_approve: bool,
29 vars: Vec<(String, String)>,
30 var_files: Vec<String>,
31 targets: Vec<String>,
32 replace: 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 ApplyCommand {
41 #[must_use]
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 #[must_use]
49 pub fn plan_file(mut self, path: &str) -> Self {
50 self.plan_file = Some(path.to_string());
51 self
52 }
53
54 #[must_use]
56 pub fn auto_approve(mut self) -> Self {
57 self.auto_approve = true;
58 self
59 }
60
61 #[must_use]
63 pub fn var(mut self, name: &str, value: &str) -> Self {
64 self.vars.push((name.to_string(), value.to_string()));
65 self
66 }
67
68 #[must_use]
70 pub fn var_file(mut self, path: &str) -> Self {
71 self.var_files.push(path.to_string());
72 self
73 }
74
75 #[must_use]
77 pub fn target(mut self, resource: &str) -> Self {
78 self.targets.push(resource.to_string());
79 self
80 }
81
82 #[must_use]
84 pub fn replace(mut self, resource: &str) -> Self {
85 self.replace.push(resource.to_string());
86 self
87 }
88
89 #[must_use]
91 pub fn lock(mut self, enabled: bool) -> Self {
92 self.lock = Some(enabled);
93 self
94 }
95
96 #[must_use]
98 pub fn lock_timeout(mut self, timeout: &str) -> Self {
99 self.lock_timeout = Some(timeout.to_string());
100 self
101 }
102
103 #[must_use]
105 pub fn parallelism(mut self, n: u32) -> Self {
106 self.parallelism = Some(n);
107 self
108 }
109
110 #[must_use]
112 pub fn json(mut self) -> Self {
113 self.json = true;
114 self
115 }
116
117 #[must_use]
119 pub fn arg(mut self, arg: impl Into<String>) -> Self {
120 self.raw_args.push(arg.into());
121 self
122 }
123}
124
125impl TerraformCommand for ApplyCommand {
126 type Output = CommandOutput;
127
128 fn args(&self) -> Vec<String> {
129 let mut args = vec!["apply".to_string()];
130 if self.auto_approve {
131 args.push("-auto-approve".to_string());
132 }
133 for (name, value) in &self.vars {
134 args.push(format!("-var={name}={value}"));
135 }
136 for file in &self.var_files {
137 args.push(format!("-var-file={file}"));
138 }
139 for target in &self.targets {
140 args.push(format!("-target={target}"));
141 }
142 for resource in &self.replace {
143 args.push(format!("-replace={resource}"));
144 }
145 if let Some(lock) = self.lock {
146 args.push(format!("-lock={lock}"));
147 }
148 if let Some(ref timeout) = self.lock_timeout {
149 args.push(format!("-lock-timeout={timeout}"));
150 }
151 if let Some(n) = self.parallelism {
152 args.push(format!("-parallelism={n}"));
153 }
154 if self.json {
155 args.push("-json".to_string());
156 }
157 args.extend(self.raw_args.clone());
158 if let Some(ref plan) = self.plan_file {
160 args.push(plan.clone());
161 }
162 args
163 }
164
165 fn supports_input(&self) -> bool {
166 true
167 }
168
169 async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
170 exec::run_terraform(tf, self.prepare_args(tf)).await
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn default_args() {
180 let cmd = ApplyCommand::new();
181 assert_eq!(cmd.args(), vec!["apply"]);
182 }
183
184 #[test]
185 fn auto_approve_with_vars() {
186 let cmd = ApplyCommand::new()
187 .auto_approve()
188 .var("region", "us-west-2")
189 .var_file("prod.tfvars");
190 let args = cmd.args();
191 assert_eq!(args[0], "apply");
192 assert!(args.contains(&"-auto-approve".to_string()));
193 assert!(args.contains(&"-var=region=us-west-2".to_string()));
194 assert!(args.contains(&"-var-file=prod.tfvars".to_string()));
195 }
196
197 #[test]
198 fn plan_file_at_end() {
199 let cmd = ApplyCommand::new().auto_approve().plan_file("tfplan");
200 let args = cmd.args();
201 assert_eq!(args.last().unwrap(), "tfplan");
202 }
203
204 #[test]
205 fn parallelism_and_json() {
206 let cmd = ApplyCommand::new().parallelism(10).json();
207 let args = cmd.args();
208 assert!(args.contains(&"-parallelism=10".to_string()));
209 assert!(args.contains(&"-json".to_string()));
210 }
211}