terraform_wrapper/commands/
plan.rs1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6#[derive(Debug, Clone, Default)]
26pub struct PlanCommand {
27 vars: Vec<(String, String)>,
28 var_files: Vec<String>,
29 out: Option<String>,
30 targets: Vec<String>,
31 replace: Vec<String>,
32 destroy: bool,
33 refresh_only: bool,
34 lock: Option<bool>,
35 lock_timeout: Option<String>,
36 parallelism: Option<u32>,
37 detailed_exitcode: bool,
38 json: bool,
39 raw_args: Vec<String>,
40}
41
42impl PlanCommand {
43 #[must_use]
45 pub fn new() -> Self {
46 Self::default()
47 }
48
49 #[must_use]
51 pub fn var(mut self, name: &str, value: &str) -> Self {
52 self.vars.push((name.to_string(), value.to_string()));
53 self
54 }
55
56 #[must_use]
58 pub fn var_file(mut self, path: &str) -> Self {
59 self.var_files.push(path.to_string());
60 self
61 }
62
63 #[must_use]
65 pub fn out(mut self, path: &str) -> Self {
66 self.out = Some(path.to_string());
67 self
68 }
69
70 #[must_use]
72 pub fn target(mut self, resource: &str) -> Self {
73 self.targets.push(resource.to_string());
74 self
75 }
76
77 #[must_use]
79 pub fn replace(mut self, resource: &str) -> Self {
80 self.replace.push(resource.to_string());
81 self
82 }
83
84 #[must_use]
86 pub fn destroy(mut self) -> Self {
87 self.destroy = true;
88 self
89 }
90
91 #[must_use]
93 pub fn refresh_only(mut self) -> Self {
94 self.refresh_only = true;
95 self
96 }
97
98 #[must_use]
100 pub fn lock(mut self, enabled: bool) -> Self {
101 self.lock = Some(enabled);
102 self
103 }
104
105 #[must_use]
107 pub fn lock_timeout(mut self, timeout: &str) -> Self {
108 self.lock_timeout = Some(timeout.to_string());
109 self
110 }
111
112 #[must_use]
114 pub fn parallelism(mut self, n: u32) -> Self {
115 self.parallelism = Some(n);
116 self
117 }
118
119 #[must_use]
125 pub fn detailed_exitcode(mut self) -> Self {
126 self.detailed_exitcode = true;
127 self
128 }
129
130 #[must_use]
135 pub fn json(mut self) -> Self {
136 self.json = true;
137 self
138 }
139
140 #[must_use]
142 pub fn arg(mut self, arg: impl Into<String>) -> Self {
143 self.raw_args.push(arg.into());
144 self
145 }
146}
147
148impl TerraformCommand for PlanCommand {
149 type Output = CommandOutput;
150
151 fn args(&self) -> Vec<String> {
152 let mut args = vec!["plan".to_string()];
153 for (name, value) in &self.vars {
154 args.push(format!("-var={name}={value}"));
155 }
156 for file in &self.var_files {
157 args.push(format!("-var-file={file}"));
158 }
159 if let Some(ref out) = self.out {
160 args.push(format!("-out={out}"));
161 }
162 for target in &self.targets {
163 args.push(format!("-target={target}"));
164 }
165 for resource in &self.replace {
166 args.push(format!("-replace={resource}"));
167 }
168 if self.destroy {
169 args.push("-destroy".to_string());
170 }
171 if self.refresh_only {
172 args.push("-refresh-only".to_string());
173 }
174 if let Some(lock) = self.lock {
175 args.push(format!("-lock={lock}"));
176 }
177 if let Some(ref timeout) = self.lock_timeout {
178 args.push(format!("-lock-timeout={timeout}"));
179 }
180 if let Some(n) = self.parallelism {
181 args.push(format!("-parallelism={n}"));
182 }
183 if self.detailed_exitcode {
184 args.push("-detailed-exitcode".to_string());
185 }
186 if self.json {
187 args.push("-json".to_string());
188 }
189 args.extend(self.raw_args.clone());
190 args
191 }
192
193 async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
194 let mut args = self.args();
195 if tf.no_input {
196 args.insert(1, "-input=false".to_string());
197 }
198 exec::run_terraform_allow_exit_codes(tf, args, &[0, 2]).await
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn default_args() {
208 let cmd = PlanCommand::new();
209 assert_eq!(cmd.args(), vec!["plan"]);
210 }
211
212 #[test]
213 fn full_options() {
214 let cmd = PlanCommand::new()
215 .var("region", "us-west-2")
216 .var_file("prod.tfvars")
217 .out("tfplan")
218 .target("module.vpc")
219 .replace("aws_instance.web")
220 .destroy()
221 .parallelism(10)
222 .json();
223 let args = cmd.args();
224 assert_eq!(args[0], "plan");
225 assert!(args.contains(&"-var=region=us-west-2".to_string()));
226 assert!(args.contains(&"-var-file=prod.tfvars".to_string()));
227 assert!(args.contains(&"-out=tfplan".to_string()));
228 assert!(args.contains(&"-target=module.vpc".to_string()));
229 assert!(args.contains(&"-replace=aws_instance.web".to_string()));
230 assert!(args.contains(&"-destroy".to_string()));
231 assert!(args.contains(&"-parallelism=10".to_string()));
232 assert!(args.contains(&"-json".to_string()));
233 }
234
235 #[test]
236 fn multiple_targets() {
237 let cmd = PlanCommand::new().target("module.vpc").target("module.rds");
238 let args = cmd.args();
239 assert!(args.contains(&"-target=module.vpc".to_string()));
240 assert!(args.contains(&"-target=module.rds".to_string()));
241 }
242}