Skip to main content

rusticity_core/
ec2.rs

1use crate::config::AwsConfig;
2use anyhow::Result;
3
4#[derive(Clone, Debug)]
5pub struct Instance {
6    pub instance_id: String,
7    pub name: String,
8    pub state: String,
9    pub instance_type: String,
10    pub availability_zone: String,
11    pub public_ipv4_dns: String,
12    pub public_ipv4_address: String,
13    pub elastic_ip: String,
14    pub ipv6_ips: String,
15    pub monitoring: String,
16    pub security_groups: String,
17    pub key_name: String,
18    pub launch_time: String,
19    pub platform_details: String,
20    pub status_checks: String,
21    pub alarm_status: String,
22    pub private_ip_address: String,
23    pub private_dns_name: String,
24}
25
26#[derive(Clone, Debug)]
27pub struct InstanceTag {
28    pub key: String,
29    pub value: String,
30}
31
32pub struct Ec2Client {
33    config: AwsConfig,
34}
35
36impl Ec2Client {
37    pub fn new(config: AwsConfig) -> Self {
38        Self { config }
39    }
40
41    pub async fn list_instances(&self) -> Result<Vec<Instance>> {
42        let client = self.config.ec2_client();
43        let mut instances = Vec::new();
44        let mut next_token: Option<String> = None;
45
46        loop {
47            let mut request = client.describe_instances();
48            if let Some(token) = next_token {
49                request = request.next_token(token);
50            }
51
52            let response = request.send().await?;
53
54            if let Some(reservations) = response.reservations {
55                for reservation in reservations {
56                    if let Some(insts) = reservation.instances {
57                        for inst in insts {
58                            let tags: std::collections::HashMap<String, String> = inst
59                                .tags()
60                                .iter()
61                                .filter_map(|t| {
62                                    Some((t.key()?.to_string(), t.value()?.to_string()))
63                                })
64                                .collect();
65
66                            let name = tags.get("Name").cloned().unwrap_or_default();
67
68                            let state = inst
69                                .state()
70                                .and_then(|s| s.name())
71                                .map(|n| n.as_str().to_string())
72                                .unwrap_or_default();
73
74                            let security_groups = inst
75                                .security_groups()
76                                .iter()
77                                .filter_map(|sg| sg.group_name())
78                                .collect::<Vec<_>>()
79                                .join(", ");
80
81                            let ipv6_ips = inst
82                                .network_interfaces()
83                                .iter()
84                                .flat_map(|ni| ni.ipv6_addresses())
85                                .filter_map(|ip| ip.ipv6_address())
86                                .collect::<Vec<_>>()
87                                .join(", ");
88
89                            let launch_time = inst
90                                .launch_time()
91                                .map(|dt| {
92                                    let timestamp = dt.secs();
93                                    chrono::DateTime::from_timestamp(timestamp, 0)
94                                        .map(|dt| dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string())
95                                        .unwrap_or_default()
96                                })
97                                .unwrap_or_default();
98
99                            instances.push(Instance {
100                                instance_id: inst.instance_id().unwrap_or("").to_string(),
101                                name,
102                                state,
103                                instance_type: inst
104                                    .instance_type()
105                                    .map(|t| t.as_str().to_string())
106                                    .unwrap_or_default(),
107                                availability_zone: inst
108                                    .placement()
109                                    .and_then(|p| p.availability_zone())
110                                    .unwrap_or("")
111                                    .to_string(),
112                                public_ipv4_dns: inst.public_dns_name().unwrap_or("").to_string(),
113                                public_ipv4_address: inst
114                                    .public_ip_address()
115                                    .unwrap_or("")
116                                    .to_string(),
117                                elastic_ip: String::new(),
118                                ipv6_ips,
119                                monitoring: inst
120                                    .monitoring()
121                                    .and_then(|m| m.state())
122                                    .map(|s| s.as_str().to_string())
123                                    .unwrap_or_default(),
124                                security_groups,
125                                key_name: inst.key_name().unwrap_or("").to_string(),
126                                launch_time,
127                                platform_details: inst.platform_details().unwrap_or("").to_string(),
128                                status_checks: String::new(),
129                                alarm_status: String::new(),
130                                private_ip_address: inst
131                                    .private_ip_address()
132                                    .unwrap_or("")
133                                    .to_string(),
134                                private_dns_name: inst.private_dns_name().unwrap_or("").to_string(),
135                            });
136                        }
137                    }
138                }
139            }
140
141            next_token = response.next_token;
142            if next_token.is_none() {
143                break;
144            }
145        }
146
147        Ok(instances)
148    }
149
150    pub async fn list_tags(&self, instance_id: &str) -> Result<Vec<InstanceTag>> {
151        let client = self.config.ec2_client();
152
153        let response = client
154            .describe_tags()
155            .filters(
156                aws_sdk_ec2::types::Filter::builder()
157                    .name("resource-id")
158                    .values(instance_id)
159                    .build(),
160            )
161            .send()
162            .await?;
163
164        let mut tags = Vec::new();
165        if let Some(tag_list) = response.tags {
166            for tag in tag_list {
167                if let (Some(key), Some(value)) = (tag.key, tag.value) {
168                    tags.push(InstanceTag { key, value });
169                }
170            }
171        }
172
173        Ok(tags)
174    }
175
176    pub async fn get_cpu_metrics(&self, instance_id: &str) -> Result<Vec<(i64, f64)>> {
177        let client = self.config.cloudwatch_client();
178        let now = chrono::Utc::now();
179        let start_time = now - chrono::Duration::hours(3);
180
181        let response = client
182            .get_metric_statistics()
183            .namespace("AWS/EC2")
184            .metric_name("CPUUtilization")
185            .dimensions(
186                aws_sdk_cloudwatch::types::Dimension::builder()
187                    .name("InstanceId")
188                    .value(instance_id)
189                    .build(),
190            )
191            .start_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
192                start_time.timestamp_millis(),
193            ))
194            .end_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
195                now.timestamp_millis(),
196            ))
197            .period(300)
198            .statistics(aws_sdk_cloudwatch::types::Statistic::Average)
199            .send()
200            .await?;
201
202        let mut data_points = Vec::new();
203        if let Some(datapoints) = response.datapoints {
204            for dp in datapoints {
205                if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.average) {
206                    data_points.push((timestamp.secs(), value));
207                }
208            }
209        }
210
211        data_points.sort_by_key(|(ts, _)| *ts);
212        Ok(data_points)
213    }
214
215    pub async fn get_network_in_metrics(&self, instance_id: &str) -> Result<Vec<(i64, f64)>> {
216        let client = self.config.cloudwatch_client();
217        let now = chrono::Utc::now();
218        let start_time = now - chrono::Duration::hours(3);
219
220        let response = client
221            .get_metric_statistics()
222            .namespace("AWS/EC2")
223            .metric_name("NetworkIn")
224            .dimensions(
225                aws_sdk_cloudwatch::types::Dimension::builder()
226                    .name("InstanceId")
227                    .value(instance_id)
228                    .build(),
229            )
230            .start_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
231                start_time.timestamp_millis(),
232            ))
233            .end_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
234                now.timestamp_millis(),
235            ))
236            .period(300)
237            .statistics(aws_sdk_cloudwatch::types::Statistic::Average)
238            .send()
239            .await?;
240
241        let mut data_points = Vec::new();
242        if let Some(datapoints) = response.datapoints {
243            for dp in datapoints {
244                if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.average) {
245                    data_points.push((timestamp.secs(), value));
246                }
247            }
248        }
249
250        data_points.sort_by_key(|(ts, _)| *ts);
251        Ok(data_points)
252    }
253
254    pub async fn get_network_out_metrics(&self, instance_id: &str) -> Result<Vec<(i64, f64)>> {
255        let client = self.config.cloudwatch_client();
256        let now = chrono::Utc::now();
257        let start_time = now - chrono::Duration::hours(3);
258
259        let response = client
260            .get_metric_statistics()
261            .namespace("AWS/EC2")
262            .metric_name("NetworkOut")
263            .dimensions(
264                aws_sdk_cloudwatch::types::Dimension::builder()
265                    .name("InstanceId")
266                    .value(instance_id)
267                    .build(),
268            )
269            .start_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
270                start_time.timestamp_millis(),
271            ))
272            .end_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
273                now.timestamp_millis(),
274            ))
275            .period(300)
276            .statistics(aws_sdk_cloudwatch::types::Statistic::Average)
277            .send()
278            .await?;
279
280        let mut data_points = Vec::new();
281        if let Some(datapoints) = response.datapoints {
282            for dp in datapoints {
283                if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.average) {
284                    data_points.push((timestamp.secs(), value));
285                }
286            }
287        }
288
289        data_points.sort_by_key(|(ts, _)| *ts);
290        Ok(data_points)
291    }
292
293    pub async fn get_network_packets_in_metrics(
294        &self,
295        instance_id: &str,
296    ) -> Result<Vec<(i64, f64)>> {
297        let client = self.config.cloudwatch_client();
298        let now = chrono::Utc::now();
299        let start_time = now - chrono::Duration::hours(3);
300
301        let response = client
302            .get_metric_statistics()
303            .namespace("AWS/EC2")
304            .metric_name("NetworkPacketsIn")
305            .dimensions(
306                aws_sdk_cloudwatch::types::Dimension::builder()
307                    .name("InstanceId")
308                    .value(instance_id)
309                    .build(),
310            )
311            .start_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
312                start_time.timestamp_millis(),
313            ))
314            .end_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
315                now.timestamp_millis(),
316            ))
317            .period(300)
318            .statistics(aws_sdk_cloudwatch::types::Statistic::Average)
319            .send()
320            .await?;
321
322        let mut data_points = Vec::new();
323        if let Some(datapoints) = response.datapoints {
324            for dp in datapoints {
325                if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.average) {
326                    data_points.push((timestamp.secs(), value));
327                }
328            }
329        }
330
331        data_points.sort_by_key(|(ts, _)| *ts);
332        Ok(data_points)
333    }
334
335    pub async fn get_network_packets_out_metrics(
336        &self,
337        instance_id: &str,
338    ) -> Result<Vec<(i64, f64)>> {
339        let client = self.config.cloudwatch_client();
340        let now = chrono::Utc::now();
341        let start_time = now - chrono::Duration::hours(3);
342
343        let response = client
344            .get_metric_statistics()
345            .namespace("AWS/EC2")
346            .metric_name("NetworkPacketsOut")
347            .dimensions(
348                aws_sdk_cloudwatch::types::Dimension::builder()
349                    .name("InstanceId")
350                    .value(instance_id)
351                    .build(),
352            )
353            .start_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
354                start_time.timestamp_millis(),
355            ))
356            .end_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
357                now.timestamp_millis(),
358            ))
359            .period(300)
360            .statistics(aws_sdk_cloudwatch::types::Statistic::Average)
361            .send()
362            .await?;
363
364        let mut data_points = Vec::new();
365        if let Some(datapoints) = response.datapoints {
366            for dp in datapoints {
367                if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.average) {
368                    data_points.push((timestamp.secs(), value));
369                }
370            }
371        }
372
373        data_points.sort_by_key(|(ts, _)| *ts);
374        Ok(data_points)
375    }
376
377    pub async fn get_metadata_no_token_metrics(
378        &self,
379        instance_id: &str,
380    ) -> Result<Vec<(i64, f64)>> {
381        let client = self.config.cloudwatch_client();
382        let now = chrono::Utc::now();
383        let start_time = now - chrono::Duration::hours(3);
384
385        let response = client
386            .get_metric_statistics()
387            .namespace("AWS/EC2")
388            .metric_name("MetadataNoToken")
389            .dimensions(
390                aws_sdk_cloudwatch::types::Dimension::builder()
391                    .name("InstanceId")
392                    .value(instance_id)
393                    .build(),
394            )
395            .start_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
396                start_time.timestamp_millis(),
397            ))
398            .end_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
399                now.timestamp_millis(),
400            ))
401            .period(300)
402            .statistics(aws_sdk_cloudwatch::types::Statistic::Average)
403            .send()
404            .await?;
405
406        let mut data_points = Vec::new();
407        if let Some(datapoints) = response.datapoints {
408            for dp in datapoints {
409                if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.average) {
410                    data_points.push((timestamp.secs(), value));
411                }
412            }
413        }
414
415        data_points.sort_by_key(|(ts, _)| *ts);
416        Ok(data_points)
417    }
418}