1use log::debug;
8
9use crate::{
10 error::{Error, Result},
11 print_duration,
12 terraform::{TerraformResource, TerraformRunner},
13 EnvironmentDetails, TestnetDeployer,
14};
15use std::time::Instant;
16
17const BUILD_VM: &str = "build";
18const CLIENT: &str = "ant_client";
19const EVM_NODE: &str = "evm_node";
20const FULL_CONE_NAT_GATEWAY: &str = "full_cone_nat_gateway";
21const FULL_CONE_PRIVATE_NODE: &str = "full_cone_private_node";
22const FULL_CONE_PRIVATE_NODE_ATTACHED_VOLUME: &str = "full_cone_private_node_attached_volume";
23const GENESIS_NODE: &str = "genesis_bootstrap";
24const GENESIS_NODE_ATTACHED_VOLUME: &str = "genesis_node_attached_volume";
25const NODE: &str = "node";
26const NODE_ATTACHED_VOLUME: &str = "node_attached_volume";
27const PEER_CACHE_NODE: &str = "peer_cache_node";
28const PEER_CACHE_NODE_ATTACHED_VOLUME: &str = "peer_cache_node_attached_volume";
29const PORT_RESTRICTED_CONE_NAT_GATEWAY: &str = "port_restricted_cone_nat_gateway";
30const PORT_RESTRICTED_PRIVATE_NODE: &str = "port_restricted_private_node";
31const PORT_RESTRICTED_PRIVATE_NODE_ATTACHED_VOLUME: &str =
32 "port_restricted_private_node_attached_volume";
33const SYMMETRIC_NAT_GATEWAY: &str = "symmetric_nat_gateway";
34const SYMMETRIC_PRIVATE_NODE: &str = "symmetric_private_node";
35const SYMMETRIC_PRIVATE_NODE_ATTACHED_VOLUME: &str = "symmetric_private_node_attached_volume";
36const UPNP_PRIVATE_NODE: &str = "upnp_private_node";
37const UPNP_PRIVATE_NODE_ATTACHED_VOLUME: &str = "upnp_private_node_attached_volume";
38
39const SIZE: &str = "size";
40const IMAGE: &str = "image";
41
42#[derive(Clone, Debug)]
43pub struct InfraRunOptions {
44 pub client_image_id: Option<String>,
46 pub client_vm_count: Option<u16>,
47 pub client_vm_size: Option<String>,
48 pub enable_build_vm: bool,
49 pub evm_node_count: Option<u16>,
50 pub evm_node_vm_size: Option<String>,
51 pub evm_node_image_id: Option<String>,
53 pub full_cone_vm_size: Option<String>,
54 pub full_cone_private_node_vm_count: Option<u16>,
55 pub full_cone_private_node_volume_size: Option<u16>,
56 pub genesis_vm_count: Option<u16>,
57 pub genesis_node_volume_size: Option<u16>,
58 pub name: String,
59 pub nat_gateway_image_id: Option<String>,
61 pub node_image_id: Option<String>,
63 pub node_vm_count: Option<u16>,
64 pub node_vm_size: Option<String>,
65 pub node_volume_size: Option<u16>,
66 pub peer_cache_image_id: Option<String>,
68 pub peer_cache_node_vm_count: Option<u16>,
69 pub peer_cache_node_vm_size: Option<String>,
70 pub peer_cache_node_volume_size: Option<u16>,
71 pub port_restricted_cone_vm_size: Option<String>,
72 pub port_restricted_private_node_vm_count: Option<u16>,
73 pub port_restricted_private_node_volume_size: Option<u16>,
74 pub region: String,
75 pub symmetric_nat_gateway_vm_size: Option<String>,
76 pub symmetric_private_node_vm_count: Option<u16>,
77 pub symmetric_private_node_volume_size: Option<u16>,
78 pub tfvars_filenames: Option<Vec<String>>,
79 pub upnp_vm_size: Option<String>,
80 pub upnp_private_node_vm_count: Option<u16>,
81 pub upnp_private_node_volume_size: Option<u16>,
82}
83
84impl InfraRunOptions {
85 pub async fn generate_existing(
87 name: &str,
88 region: &str,
89 terraform_runner: &TerraformRunner,
90 environment_details: Option<&EnvironmentDetails>,
91 ) -> Result<Self> {
92 let resources = terraform_runner.show(name)?;
93
94 let resource_count = |resource_name: &str| -> u16 {
95 resources
96 .iter()
97 .filter(|r| r.resource_name == resource_name)
98 .count() as u16
99 };
100
101 let peer_cache_node_vm_count = resource_count(PEER_CACHE_NODE);
102 debug!("Peer cache node count: {peer_cache_node_vm_count}");
103 let (peer_cache_node_volume_size, peer_cache_node_vm_size, peer_cache_image_id) =
104 if peer_cache_node_vm_count > 0 {
105 let volume_size =
106 get_value_for_resource(&resources, PEER_CACHE_NODE_ATTACHED_VOLUME, SIZE)?;
107 debug!("Peer cache node volume size: {volume_size:?}");
108 let vm_size = get_value_for_resource(&resources, PEER_CACHE_NODE, SIZE)?;
109 debug!("Peer cache node size: {vm_size:?}");
110 let image_id = get_value_for_resource(&resources, PEER_CACHE_NODE, IMAGE)?;
111 debug!("Peer cache node image id: {image_id:?}");
112
113 (volume_size, vm_size, image_id)
114 } else {
115 (None, None, None)
116 };
117
118 let genesis_node_vm_count = resource_count(GENESIS_NODE);
119 debug!("Genesis node count: {genesis_node_vm_count}");
120 let genesis_node_volume_size = if genesis_node_vm_count > 0 {
121 get_value_for_resource(&resources, GENESIS_NODE_ATTACHED_VOLUME, SIZE)?
122 } else {
123 None
124 };
125 debug!("Genesis node volume size: {genesis_node_volume_size:?}");
126
127 let node_vm_count = resource_count(NODE);
128 debug!("Node count: {node_vm_count}");
129 let node_volume_size = if node_vm_count > 0 {
130 get_value_for_resource(&resources, NODE_ATTACHED_VOLUME, SIZE)?
131 } else {
132 None
133 };
134 debug!("Node volume size: {node_volume_size:?}");
135
136 let mut nat_gateway_image_id: Option<String> = None;
137 let symmetric_private_node_vm_count = resource_count(SYMMETRIC_PRIVATE_NODE);
138 debug!("Symmetric private node count: {symmetric_private_node_vm_count}");
139 let (symmetric_private_node_volume_size, symmetric_nat_gateway_vm_size) =
140 if symmetric_private_node_vm_count > 0 {
141 let symmetric_private_node_volume_size = get_value_for_resource(
142 &resources,
143 SYMMETRIC_PRIVATE_NODE_ATTACHED_VOLUME,
144 SIZE,
145 )?;
146 debug!(
147 "Symmetric private node volume size: {symmetric_private_node_volume_size:?}"
148 );
149 let symmetric_nat_gateway_vm_size =
151 get_value_for_resource(&resources, SYMMETRIC_NAT_GATEWAY, SIZE)?;
152
153 debug!("Symmetric nat gateway size: {symmetric_nat_gateway_vm_size:?}");
154
155 nat_gateway_image_id =
156 get_value_for_resource(&resources, SYMMETRIC_NAT_GATEWAY, IMAGE)?;
157 debug!("Nat gateway image: {nat_gateway_image_id:?}");
158
159 (
160 symmetric_private_node_volume_size,
161 symmetric_nat_gateway_vm_size,
162 )
163 } else {
164 (None, None)
165 };
166
167 let full_cone_private_node_vm_count = resource_count(FULL_CONE_PRIVATE_NODE);
168 debug!("Full cone private node count: {full_cone_private_node_vm_count}");
169 let (full_cone_private_node_volume_size, full_cone_vm_size) =
170 if full_cone_private_node_vm_count > 0 {
171 let full_cone_private_node_volume_size = get_value_for_resource(
172 &resources,
173 FULL_CONE_PRIVATE_NODE_ATTACHED_VOLUME,
174 SIZE,
175 )?;
176 debug!(
177 "Full cone private node volume size: {full_cone_private_node_volume_size:?}"
178 );
179 let full_cone_vm_size =
181 get_value_for_resource(&resources, FULL_CONE_NAT_GATEWAY, SIZE)?;
182 debug!("Full cone nat gateway size: {full_cone_vm_size:?}");
183
184 nat_gateway_image_id =
185 get_value_for_resource(&resources, FULL_CONE_NAT_GATEWAY, IMAGE)?;
186 debug!("Nat gateway image: {nat_gateway_image_id:?}");
187
188 (full_cone_private_node_volume_size, full_cone_vm_size)
189 } else {
190 (None, None)
191 };
192
193 let port_restricted_private_node_vm_count = resource_count(PORT_RESTRICTED_PRIVATE_NODE);
194 debug!("Port restricted cone private node count: {port_restricted_private_node_vm_count}");
195 let (port_restricted_private_node_volume_size, port_restricted_cone_vm_size) =
196 if port_restricted_private_node_vm_count > 0 {
197 let port_restricted_private_node_volume_size = get_value_for_resource(
198 &resources,
199 PORT_RESTRICTED_PRIVATE_NODE_ATTACHED_VOLUME,
200 SIZE,
201 )?;
202 debug!(
203 "Port restricted cone private node volume size: {port_restricted_private_node_volume_size:?}"
204 );
205 let port_restricted_cone_vm_size =
207 get_value_for_resource(&resources, PORT_RESTRICTED_CONE_NAT_GATEWAY, SIZE)?;
208 debug!("Port restricted cone nat gateway size: {port_restricted_cone_vm_size:?}");
209
210 nat_gateway_image_id =
211 get_value_for_resource(&resources, PORT_RESTRICTED_CONE_NAT_GATEWAY, IMAGE)?;
212 debug!("Nat gateway image: {nat_gateway_image_id:?}");
213
214 (
215 port_restricted_private_node_volume_size,
216 port_restricted_cone_vm_size,
217 )
218 } else {
219 (None, None)
220 };
221
222 let upnp_private_node_vm_count = resource_count(UPNP_PRIVATE_NODE);
223 debug!("UPnP private node count: {upnp_private_node_vm_count}");
224 let (upnp_private_node_volume_size, upnp_vm_size) = if upnp_private_node_vm_count > 0 {
225 let upnp_private_node_volume_size =
226 get_value_for_resource(&resources, UPNP_PRIVATE_NODE_ATTACHED_VOLUME, SIZE)?;
227 debug!("UPnP private node volume size: {upnp_private_node_volume_size:?}");
228 let upnp_vm_size = get_value_for_resource(&resources, UPNP_PRIVATE_NODE, SIZE)?;
229 debug!("UPnP VM size: {upnp_vm_size:?}");
230
231 (upnp_private_node_volume_size, upnp_vm_size)
232 } else {
233 (None, None)
234 };
235
236 let client_vm_count = resource_count(CLIENT);
237 debug!("Client count: {client_vm_count}");
238 let (client_vm_size, client_image_id) = if client_vm_count > 0 {
239 let vm_size = get_value_for_resource(&resources, CLIENT, SIZE)?;
240 debug!("Client size: {vm_size:?}");
241 let image_id = get_value_for_resource(&resources, CLIENT, IMAGE)?;
242 debug!("Client image id: {image_id:?}");
243 (vm_size, image_id)
244 } else {
245 (None, None)
246 };
247
248 let build_vm_count = resource_count(BUILD_VM);
249 debug!("Build VM count: {build_vm_count}");
250 let enable_build_vm = build_vm_count > 0;
251
252 let (node_vm_size, node_image_id) = if node_vm_count > 0 {
254 let vm_size = get_value_for_resource(&resources, NODE, SIZE)?;
255 debug!("Node size obtained from {NODE}: {vm_size:?}");
256 let image_id = get_value_for_resource(&resources, NODE, IMAGE)?;
257 debug!("Node image id obtained from {NODE}: {image_id:?}");
258 (vm_size, image_id)
259 } else if symmetric_private_node_vm_count > 0 {
260 let vm_size = get_value_for_resource(&resources, SYMMETRIC_PRIVATE_NODE, SIZE)?;
261 debug!("Node size obtained from {SYMMETRIC_PRIVATE_NODE}: {vm_size:?}");
262 let image_id = get_value_for_resource(&resources, SYMMETRIC_PRIVATE_NODE, IMAGE)?;
263 debug!("Node image id obtained from {SYMMETRIC_PRIVATE_NODE}: {image_id:?}");
264 (vm_size, image_id)
265 } else if full_cone_private_node_vm_count > 0 {
266 let vm_size = get_value_for_resource(&resources, FULL_CONE_PRIVATE_NODE, SIZE)?;
267 debug!("Node size obtained from {FULL_CONE_PRIVATE_NODE}: {vm_size:?}");
268 let image_id = get_value_for_resource(&resources, FULL_CONE_PRIVATE_NODE, IMAGE)?;
269 debug!("Node image id obtained from {FULL_CONE_PRIVATE_NODE}: {image_id:?}");
270 (vm_size, image_id)
271 } else if upnp_private_node_vm_count > 0 {
272 let vm_size = get_value_for_resource(&resources, UPNP_PRIVATE_NODE, SIZE)?;
273 debug!("Node size obtained from {UPNP_PRIVATE_NODE}: {vm_size:?}");
274 let image_id = get_value_for_resource(&resources, UPNP_PRIVATE_NODE, IMAGE)?;
275 debug!("Node image id obtained from {UPNP_PRIVATE_NODE}: {image_id:?}");
276 (vm_size, image_id)
277 } else {
278 (None, None)
279 };
280
281 let evm_node_count = resource_count(EVM_NODE);
282 debug!("EVM node count: {evm_node_count}");
283 let (evm_node_vm_size, evm_node_image_id) = if evm_node_count > 0 {
284 let emv_node_vm_size = get_value_for_resource(&resources, EVM_NODE, SIZE)?;
285 debug!("EVM node size: {emv_node_vm_size:?}");
286 let evm_node_image_id = get_value_for_resource(&resources, EVM_NODE, IMAGE)?;
287 debug!("EVM node image id: {evm_node_image_id:?}");
288 (emv_node_vm_size, evm_node_image_id)
289 } else {
290 (None, None)
291 };
292
293 let options = Self {
294 client_image_id,
295 client_vm_count: Some(client_vm_count),
296 client_vm_size,
297 enable_build_vm,
298 evm_node_count: Some(evm_node_count),
299 evm_node_vm_size,
300 evm_node_image_id,
301 full_cone_vm_size,
302 full_cone_private_node_vm_count: Some(full_cone_private_node_vm_count),
303 full_cone_private_node_volume_size,
304 genesis_vm_count: Some(genesis_node_vm_count),
305 genesis_node_volume_size,
306 name: name.to_string(),
307 nat_gateway_image_id,
308 node_image_id,
309 node_vm_count: Some(node_vm_count),
310 node_vm_size,
311 node_volume_size,
312 peer_cache_image_id,
313 peer_cache_node_vm_count: Some(peer_cache_node_vm_count),
314 peer_cache_node_vm_size,
315 peer_cache_node_volume_size,
316 port_restricted_cone_vm_size,
317 port_restricted_private_node_vm_count: Some(port_restricted_private_node_vm_count),
318 port_restricted_private_node_volume_size,
319 region: region.to_string(),
320 symmetric_nat_gateway_vm_size,
321 symmetric_private_node_vm_count: Some(symmetric_private_node_vm_count),
322 symmetric_private_node_volume_size,
323 tfvars_filenames: environment_details
324 .map(|details| details.environment_type.get_tfvars_filenames(name, region)),
325 upnp_vm_size,
326 upnp_private_node_vm_count: Some(upnp_private_node_vm_count),
327 upnp_private_node_volume_size,
328 };
329
330 Ok(options)
331 }
332}
333
334impl TestnetDeployer {
335 pub fn create_or_update_infra(&self, options: &InfraRunOptions) -> Result<()> {
337 let start = Instant::now();
338 println!("Selecting {} workspace...", options.name);
339 self.terraform_runner.workspace_select(&options.name)?;
340
341 let args = build_terraform_args(options)?;
342
343 println!("Running terraform apply...");
344 self.terraform_runner
345 .apply(args, options.tfvars_filenames.clone())?;
346 print_duration(start.elapsed());
347 Ok(())
348 }
349}
350
351#[derive(Clone, Debug)]
352pub struct ClientsInfraRunOptions {
353 pub client_image_id: Option<String>,
354 pub client_vm_count: Option<u16>,
355 pub client_vm_size: Option<String>,
356 pub enable_build_vm: bool,
358 pub name: String,
359 pub tfvars_filenames: Vec<String>,
360}
361
362impl ClientsInfraRunOptions {
363 pub async fn generate_existing(
365 name: &str,
366 terraform_runner: &TerraformRunner,
367 environment_details: &EnvironmentDetails,
368 ) -> Result<Self> {
369 let resources = terraform_runner.show(name)?;
370
371 let resource_count = |resource_name: &str| -> u16 {
372 resources
373 .iter()
374 .filter(|r| r.resource_name == resource_name)
375 .count() as u16
376 };
377
378 let client_vm_count = resource_count(CLIENT);
379 debug!("Client count: {client_vm_count}");
380 let (client_vm_size, client_image_id) = if client_vm_count > 0 {
381 let vm_size = get_value_for_resource(&resources, CLIENT, SIZE)?;
382 debug!("Client size: {vm_size:?}");
383 let image_id = get_value_for_resource(&resources, CLIENT, IMAGE)?;
384 debug!("Client image id: {image_id:?}");
385 (vm_size, image_id)
386 } else {
387 (None, None)
388 };
389
390 let build_vm_count = resource_count(BUILD_VM);
391 debug!("Build VM count: {build_vm_count}");
392 let enable_build_vm = build_vm_count > 0;
393
394 let options = Self {
395 client_image_id,
396 client_vm_count: Some(client_vm_count),
397 client_vm_size,
398 enable_build_vm,
399 name: name.to_string(),
400 tfvars_filenames: environment_details
401 .environment_type
402 .get_tfvars_filenames(name, &environment_details.region),
403 };
404
405 Ok(options)
406 }
407
408 pub fn build_terraform_args(&self) -> Result<Vec<(String, String)>> {
409 let mut args = Vec::new();
410
411 if let Some(client_vm_count) = self.client_vm_count {
412 args.push((
413 "ant_client_vm_count".to_string(),
414 client_vm_count.to_string(),
415 ));
416 }
417 if let Some(client_vm_size) = &self.client_vm_size {
418 args.push((
419 "ant_client_droplet_size".to_string(),
420 client_vm_size.clone(),
421 ));
422 }
423 if let Some(client_image_id) = &self.client_image_id {
424 args.push((
425 "ant_client_droplet_image_id".to_string(),
426 client_image_id.clone(),
427 ));
428 }
429
430 args.push((
431 "use_custom_bin".to_string(),
432 self.enable_build_vm.to_string(),
433 ));
434
435 Ok(args)
436 }
437}
438
439pub fn build_terraform_args(options: &InfraRunOptions) -> Result<Vec<(String, String)>> {
441 let mut args = Vec::new();
442
443 args.push(("region".to_string(), options.region.clone()));
444
445 if let Some(client_image_id) = &options.client_image_id {
446 args.push((
447 "ant_client_droplet_image_id".to_string(),
448 client_image_id.clone(),
449 ));
450 }
451
452 if let Some(client_vm_count) = options.client_vm_count {
453 args.push((
454 "ant_client_vm_count".to_string(),
455 client_vm_count.to_string(),
456 ));
457 }
458
459 if let Some(client_vm_size) = &options.client_vm_size {
460 args.push((
461 "ant_client_droplet_size".to_string(),
462 client_vm_size.clone(),
463 ));
464 }
465
466 args.push((
467 "use_custom_bin".to_string(),
468 options.enable_build_vm.to_string(),
469 ));
470
471 if let Some(evm_node_count) = options.evm_node_count {
472 args.push(("evm_node_vm_count".to_string(), evm_node_count.to_string()));
473 }
474
475 if let Some(evm_node_vm_size) = &options.evm_node_vm_size {
476 args.push((
477 "evm_node_droplet_size".to_string(),
478 evm_node_vm_size.clone(),
479 ));
480 }
481
482 if let Some(emv_node_image_id) = &options.evm_node_image_id {
483 args.push((
484 "evm_node_droplet_image_id".to_string(),
485 emv_node_image_id.clone(),
486 ));
487 }
488
489 if let Some(full_cone_vm_size) = &options.full_cone_vm_size {
490 args.push((
491 "full_cone_droplet_size".to_string(),
492 full_cone_vm_size.clone(),
493 ));
494 }
495
496 if let Some(full_cone_private_node_vm_count) = options.full_cone_private_node_vm_count {
497 args.push((
498 "full_cone_private_node_vm_count".to_string(),
499 full_cone_private_node_vm_count.to_string(),
500 ));
501 }
502
503 if let Some(full_cone_private_node_volume_size) = options.full_cone_private_node_volume_size {
504 args.push((
505 "full_cone_private_node_volume_size".to_string(),
506 full_cone_private_node_volume_size.to_string(),
507 ));
508 }
509
510 if let Some(port_restricted_cone_vm_size) = &options.port_restricted_cone_vm_size {
511 args.push((
512 "port_restricted_cone_droplet_size".to_string(),
513 port_restricted_cone_vm_size.clone(),
514 ));
515 args.push((
517 "port_restricted_cone_nat_gateway_droplet_size".to_string(),
518 port_restricted_cone_vm_size.clone(),
519 ));
520 }
521
522 if let Some(port_restricted_private_node_vm_count) =
523 options.port_restricted_private_node_vm_count
524 {
525 args.push((
526 "port_restricted_cone_node_vm_count".to_string(),
527 port_restricted_private_node_vm_count.to_string(),
528 ));
529 }
530
531 if let Some(port_restricted_private_node_vm_count) =
532 options.port_restricted_private_node_vm_count
533 {
534 args.push((
535 "port_restricted_private_node_vm_count".to_string(),
536 port_restricted_private_node_vm_count.to_string(),
537 ));
538 }
539
540 if let Some(port_restricted_private_node_volume_size) =
541 options.port_restricted_private_node_volume_size
542 {
543 args.push((
544 "port_restricted_private_node_volume_size".to_string(),
545 port_restricted_private_node_volume_size.to_string(),
546 ));
547 }
548
549 if let Some(genesis_vm_count) = options.genesis_vm_count {
550 args.push(("genesis_vm_count".to_string(), genesis_vm_count.to_string()));
551 }
552
553 if let Some(genesis_node_volume_size) = options.genesis_node_volume_size {
554 args.push((
555 "genesis_node_volume_size".to_string(),
556 genesis_node_volume_size.to_string(),
557 ));
558 }
559
560 if let Some(nat_gateway_image_id) = &options.nat_gateway_image_id {
561 args.push((
562 "nat_gateway_droplet_image_id".to_string(),
563 nat_gateway_image_id.clone(),
564 ));
565 }
566
567 if let Some(node_image_id) = &options.node_image_id {
568 args.push(("node_droplet_image_id".to_string(), node_image_id.clone()));
569 }
570
571 if let Some(node_vm_count) = options.node_vm_count {
572 args.push(("node_vm_count".to_string(), node_vm_count.to_string()));
573 }
574
575 if let Some(node_vm_size) = &options.node_vm_size {
576 args.push(("node_droplet_size".to_string(), node_vm_size.clone()));
577 }
578
579 if let Some(node_volume_size) = options.node_volume_size {
580 args.push(("node_volume_size".to_string(), node_volume_size.to_string()));
581 }
582
583 if let Some(peer_cache_image_id) = &options.peer_cache_image_id {
584 args.push((
585 "peer_cache_droplet_image_id".to_string(),
586 peer_cache_image_id.clone(),
587 ));
588 }
589
590 if let Some(peer_cache_node_vm_count) = options.peer_cache_node_vm_count {
591 args.push((
592 "peer_cache_node_vm_count".to_string(),
593 peer_cache_node_vm_count.to_string(),
594 ));
595 }
596
597 if let Some(peer_cache_vm_size) = &options.peer_cache_node_vm_size {
598 args.push((
599 "peer_cache_droplet_size".to_string(),
600 peer_cache_vm_size.clone(),
601 ));
602 }
603
604 if let Some(reserved_ips) = crate::reserved_ip::get_reserved_ips_args(&options.name) {
605 args.push(("peer_cache_reserved_ips".to_string(), reserved_ips));
606 }
607
608 if let Some(peer_cache_node_volume_size) = options.peer_cache_node_volume_size {
609 args.push((
610 "peer_cache_node_volume_size".to_string(),
611 peer_cache_node_volume_size.to_string(),
612 ));
613 }
614
615 if let Some(nat_gateway_vm_size) = &options.symmetric_nat_gateway_vm_size {
616 args.push((
617 "symmetric_nat_gateway_droplet_size".to_string(),
618 nat_gateway_vm_size.clone(),
619 ));
620 }
621
622 if let Some(symmetric_private_node_vm_count) = options.symmetric_private_node_vm_count {
623 args.push((
624 "symmetric_private_node_vm_count".to_string(),
625 symmetric_private_node_vm_count.to_string(),
626 ));
627 }
628
629 if let Some(symmetric_private_node_volume_size) = options.symmetric_private_node_volume_size {
630 args.push((
631 "symmetric_private_node_volume_size".to_string(),
632 symmetric_private_node_volume_size.to_string(),
633 ));
634 }
635
636 if let Some(upnp_private_node_vm_count) = options.upnp_private_node_vm_count {
637 args.push((
638 "upnp_private_node_vm_count".to_string(),
639 upnp_private_node_vm_count.to_string(),
640 ));
641 }
642
643 if let Some(upnp_private_node_volume_size) = options.upnp_private_node_volume_size {
644 args.push((
645 "upnp_private_node_volume_size".to_string(),
646 upnp_private_node_volume_size.to_string(),
647 ));
648 }
649
650 Ok(args)
651}
652
653pub fn select_workspace(terraform_runner: &TerraformRunner, name: &str) -> Result<()> {
656 terraform_runner.init()?;
657 let workspaces = terraform_runner.workspace_list()?;
658 if !workspaces.contains(&name.to_string()) {
659 return Err(Error::EnvironmentDoesNotExist(name.to_string()));
660 }
661 terraform_runner.workspace_select(name)?;
662 println!("Selected {name} workspace");
663 Ok(())
664}
665
666pub fn delete_workspace(terraform_runner: &TerraformRunner, name: &str) -> Result<()> {
667 terraform_runner.workspace_select("dev")?;
671 terraform_runner.workspace_delete(name)?;
672 println!("Deleted {name} workspace");
673 Ok(())
674}
675
676fn get_value_for_resource<T>(
678 resources: &[TerraformResource],
679 resource_name: &str,
680 field_name: &str,
681) -> Result<Option<T>, Error>
682where
683 T: From<TerraformValue>,
684{
685 let field_value = resources
686 .iter()
687 .filter(|r| r.resource_name == resource_name)
688 .try_fold(None, |acc_value: Option<serde_json::Value>, r| {
689 if let Some(value) = r.values.get(field_name) {
690 match acc_value {
691 Some(ref existing_value) if existing_value != value => {
692 log::error!("Expected value: {existing_value}, got value: {value}");
693 Err(Error::TerraformResourceValueMismatch {
694 expected: existing_value.to_string(),
695 actual: value.to_string(),
696 })
697 }
698 _ => Ok(Some(value.clone())),
699 }
700 } else {
701 Ok(acc_value)
702 }
703 })?;
704
705 Ok(field_value.map(TerraformValue::from).map(T::from))
706}
707
708#[derive(Debug, Clone)]
710enum TerraformValue {
711 String(String),
712 Number(u64),
713 Bool(bool),
714 Other(serde_json::Value),
715}
716
717impl From<serde_json::Value> for TerraformValue {
718 fn from(value: serde_json::Value) -> Self {
719 if value.is_string() {
720 TerraformValue::String(value.as_str().unwrap().to_string())
723 } else if value.is_u64() {
724 TerraformValue::Number(value.as_u64().unwrap())
726 } else if value.is_boolean() {
727 TerraformValue::Bool(value.as_bool().unwrap())
729 } else {
730 TerraformValue::Other(value)
731 }
732 }
733}
734
735impl From<TerraformValue> for String {
737 fn from(value: TerraformValue) -> Self {
738 match value {
739 TerraformValue::String(s) => s,
740 TerraformValue::Number(n) => n.to_string(),
741 TerraformValue::Bool(b) => b.to_string(),
742 TerraformValue::Other(v) => v.to_string(),
743 }
744 }
745}
746
747impl From<TerraformValue> for u16 {
748 fn from(value: TerraformValue) -> Self {
749 match value {
750 TerraformValue::Number(n) => n as u16,
751 TerraformValue::String(s) => s.parse().unwrap_or(0),
752 _ => 0,
753 }
754 }
755}