1use std::error;
2
3use log::{debug, info, warn};
4use outscale_api::{
5 apis::volume_api::read_volumes,
6 models::{FiltersVolume, ReadVolumesRequest, ReadVolumesResponse},
7};
8
9pub type VolumeId = String;
10const RESOURCE_NAME: &str = "Volume";
11
12use crate::{
13 core::{volumes::Volume, Resource, Resources},
14 VERSION,
15};
16
17use super::Input;
18
19impl Input {
20 pub fn fetch_volumes(&mut self) -> Result<(), Box<dyn error::Error>> {
21 if self.skip_fetch(RESOURCE_NAME) {
22 return Ok(());
23 }
24 let result: ReadVolumesResponse = {
25 let filter_volumes: FiltersVolume = match &self.filters {
26 Some(filter) => FiltersVolume {
27 tag_keys: Some(filter.tag_keys.clone()),
28 tag_values: Some(filter.tag_values.clone()),
29 tags: Some(filter.tags.clone()),
30 ..Default::default()
31 },
32 None => FiltersVolume::new(),
33 };
34 let request = ReadVolumesRequest {
35 filters: Some(Box::new(filter_volumes)),
36 ..Default::default()
37 };
38 read_volumes(&self.config, Some(request))?
39 };
40 debug!("{:#?}", result);
41
42 let volumes = match result.volumes {
43 None => {
44 warn!("no volume available");
45 return Ok(());
46 }
47 Some(volumes) => volumes,
48 };
49 self.volumes.clear();
50 for volume in volumes {
51 let volume_id = volume.volume_id.clone().unwrap_or_else(|| String::from(""));
52 self.volumes.insert(volume_id, volume);
53 }
54 info!("fetched {} volumes", self.volumes.len());
55 Ok(())
56 }
57
58 pub fn fill_resource_volume(&self, resources: &mut Resources) {
59 if self.volumes.is_empty() && self.need_default_resource {
60 resources.resources.push(Resource::Volume(Volume {
61 account_id: self.account_id(),
62 read_date_rfc3339: self.fetch_date.map(|date| date.to_rfc3339()),
63 region: self.region.clone(),
64 ..Default::default()
65 }));
66 }
67 for (volume_id, volume) in &self.volumes {
68 let specs = match VolumeSpecs::new(volume, self) {
69 Some(s) => s,
70 None => continue,
71 };
72 let core_volume = Volume {
73 osc_cost_version: Some(String::from(VERSION)),
74 account_id: self.account_id(),
75 read_date_rfc3339: self.fetch_date.map(|date| date.to_rfc3339()),
76 region: self.region.clone(),
77 resource_id: Some(volume_id.clone()),
78 price_per_hour: None,
79 price_per_month: None,
80 volume_type: Some(specs.volume_type.clone()),
81 volume_iops: Some(specs.iops),
82 volume_size: Some(specs.size),
83 price_gb_per_month: specs.price_gb_per_month,
84 price_iops_per_month: specs.price_iops_per_month,
85 };
86 resources.resources.push(Resource::Volume(core_volume));
87 }
88 }
89}
90
91struct VolumeSpecs {
92 volume_type: String,
93 size: i32,
94 iops: i32,
95 price_gb_per_month: f32,
96 price_iops_per_month: f32,
97}
98
99impl VolumeSpecs {
100 fn new(volume: &outscale_api::models::Volume, input: &Input) -> Option<Self> {
101 let volume_type = match &volume.volume_type {
102 Some(volume_type) => volume_type,
103 None => {
104 warn!("warning: cannot get volume type in volume details");
105 return None;
106 }
107 };
108
109 let iops = volume.iops.unwrap_or_else(|| {
110 if volume_type == "io1" {
111 warn!("cannot get iops in volume details");
112 }
113 0
114 });
115
116 let size = match &volume.size {
117 Some(size) => *size,
118 None => {
119 warn!("cannot get size in volume details");
120 return None;
121 }
122 };
123 let out = VolumeSpecs {
124 volume_type: volume_type.clone(),
125 iops,
126 size,
127 price_gb_per_month: 0_f32,
128 price_iops_per_month: 0_f32,
129 };
130
131 out.parse_volume_prices(input)
132 }
133
134 fn parse_volume_prices(mut self, input: &Input) -> Option<VolumeSpecs> {
135 let price_gb_per_month = input.catalog_entry(
136 "TinaOS-FCU",
137 &format!("BSU:VolumeUsage:{}", self.volume_type),
138 "CreateVolume",
139 )?;
140 if self.volume_type == "io1" {
141 self.price_iops_per_month = input.catalog_entry(
142 "TinaOS-FCU",
143 &format!("BSU:VolumeIOPS:{}", self.volume_type),
144 "CreateVolume",
145 )?;
146 }
147 self.price_gb_per_month = price_gb_per_month;
148 Some(self)
149 }
150}