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