Skip to main content

rusticity_core/
ec2.rs

1use crate::config::AwsConfig;
2use crate::metrics::{MetricQuery, MetricReader};
3use anyhow::Result;
4use aws_sdk_cloudwatch::types::Statistic;
5
6#[derive(Clone, Debug)]
7pub struct Instance {
8    pub instance_id: String,
9    pub name: String,
10    pub state: String,
11    pub instance_type: String,
12    pub availability_zone: String,
13    pub public_ipv4_dns: String,
14    pub public_ipv4_address: String,
15    pub elastic_ip: String,
16    pub ipv6_ips: String,
17    pub monitoring: String,
18    pub security_groups: String,
19    pub key_name: String,
20    pub launch_time: String,
21    pub platform_details: String,
22    pub status_checks: String,
23    pub alarm_status: String,
24    pub private_ip_address: String,
25    pub private_dns_name: String,
26}
27
28#[derive(Clone, Debug)]
29pub struct InstanceTag {
30    pub key: String,
31    pub value: String,
32}
33
34pub struct Ec2Client {
35    config: AwsConfig,
36}
37
38impl Ec2Client {
39    pub fn new(config: AwsConfig) -> Self {
40        Self { config }
41    }
42
43    pub async fn list_instances(&self) -> Result<Vec<Instance>> {
44        let client = self.config.ec2_client();
45        let mut instances = Vec::new();
46        let mut next_token: Option<String> = None;
47
48        loop {
49            let mut request = client.describe_instances();
50            if let Some(token) = next_token {
51                request = request.next_token(token);
52            }
53
54            let response = request.send().await?;
55
56            if let Some(reservations) = response.reservations {
57                for reservation in reservations {
58                    if let Some(insts) = reservation.instances {
59                        for inst in insts {
60                            let tags: std::collections::HashMap<String, String> = inst
61                                .tags()
62                                .iter()
63                                .filter_map(|t| {
64                                    Some((t.key()?.to_string(), t.value()?.to_string()))
65                                })
66                                .collect();
67
68                            let name = tags.get("Name").cloned().unwrap_or_default();
69
70                            let state = inst
71                                .state()
72                                .and_then(|s| s.name())
73                                .map(|n| n.as_str().to_string())
74                                .unwrap_or_default();
75
76                            let security_groups = inst
77                                .security_groups()
78                                .iter()
79                                .filter_map(|sg| sg.group_name())
80                                .collect::<Vec<_>>()
81                                .join(", ");
82
83                            let ipv6_ips = inst
84                                .network_interfaces()
85                                .iter()
86                                .flat_map(|ni| ni.ipv6_addresses())
87                                .filter_map(|ip| ip.ipv6_address())
88                                .collect::<Vec<_>>()
89                                .join(", ");
90
91                            let launch_time = inst
92                                .launch_time()
93                                .map(|dt| {
94                                    let timestamp = dt.secs();
95                                    chrono::DateTime::from_timestamp(timestamp, 0)
96                                        .map(|dt| dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string())
97                                        .unwrap_or_default()
98                                })
99                                .unwrap_or_default();
100
101                            instances.push(Instance {
102                                instance_id: inst.instance_id().unwrap_or("").to_string(),
103                                name,
104                                state,
105                                instance_type: inst
106                                    .instance_type()
107                                    .map(|t| t.as_str().to_string())
108                                    .unwrap_or_default(),
109                                availability_zone: inst
110                                    .placement()
111                                    .and_then(|p| p.availability_zone())
112                                    .unwrap_or("")
113                                    .to_string(),
114                                public_ipv4_dns: inst.public_dns_name().unwrap_or("").to_string(),
115                                public_ipv4_address: inst
116                                    .public_ip_address()
117                                    .unwrap_or("")
118                                    .to_string(),
119                                elastic_ip: String::new(),
120                                ipv6_ips,
121                                monitoring: inst
122                                    .monitoring()
123                                    .and_then(|m| m.state())
124                                    .map(|s| s.as_str().to_string())
125                                    .unwrap_or_default(),
126                                security_groups,
127                                key_name: inst.key_name().unwrap_or("").to_string(),
128                                launch_time,
129                                platform_details: inst.platform_details().unwrap_or("").to_string(),
130                                status_checks: String::new(),
131                                alarm_status: String::new(),
132                                private_ip_address: inst
133                                    .private_ip_address()
134                                    .unwrap_or("")
135                                    .to_string(),
136                                private_dns_name: inst.private_dns_name().unwrap_or("").to_string(),
137                            });
138                        }
139                    }
140                }
141            }
142
143            next_token = response.next_token;
144            if next_token.is_none() {
145                break;
146            }
147        }
148
149        Ok(instances)
150    }
151
152    pub async fn list_tags(&self, instance_id: &str) -> Result<Vec<InstanceTag>> {
153        let client = self.config.ec2_client();
154
155        let response = client
156            .describe_tags()
157            .filters(
158                aws_sdk_ec2::types::Filter::builder()
159                    .name("resource-id")
160                    .values(instance_id)
161                    .build(),
162            )
163            .send()
164            .await?;
165
166        let mut tags = Vec::new();
167        if let Some(tag_list) = response.tags {
168            for tag in tag_list {
169                if let (Some(key), Some(value)) = (tag.key, tag.value) {
170                    tags.push(InstanceTag { key, value });
171                }
172            }
173        }
174
175        Ok(tags)
176    }
177
178    pub async fn get_cpu_metrics(&self, instance_id: &str) -> Result<Vec<(i64, f64)>> {
179        let query = MetricQuery::new("AWS/EC2", "CPUUtilization", Statistic::Average)
180            .dimension("InstanceId", instance_id);
181        MetricReader::new(self.config.cloudwatch_client())
182            .recent(query, 300)
183            .await
184    }
185
186    pub async fn get_network_in_metrics(&self, instance_id: &str) -> Result<Vec<(i64, f64)>> {
187        let query = MetricQuery::new("AWS/EC2", "NetworkIn", Statistic::Average)
188            .dimension("InstanceId", instance_id);
189        MetricReader::new(self.config.cloudwatch_client())
190            .recent(query, 300)
191            .await
192    }
193
194    pub async fn get_network_out_metrics(&self, instance_id: &str) -> Result<Vec<(i64, f64)>> {
195        let query = MetricQuery::new("AWS/EC2", "NetworkOut", Statistic::Average)
196            .dimension("InstanceId", instance_id);
197        MetricReader::new(self.config.cloudwatch_client())
198            .recent(query, 300)
199            .await
200    }
201
202    pub async fn get_network_packets_in_metrics(
203        &self,
204        instance_id: &str,
205    ) -> Result<Vec<(i64, f64)>> {
206        let query = MetricQuery::new("AWS/EC2", "NetworkPacketsIn", Statistic::Average)
207            .dimension("InstanceId", instance_id);
208        MetricReader::new(self.config.cloudwatch_client())
209            .recent(query, 300)
210            .await
211    }
212
213    pub async fn get_network_packets_out_metrics(
214        &self,
215        instance_id: &str,
216    ) -> Result<Vec<(i64, f64)>> {
217        let query = MetricQuery::new("AWS/EC2", "NetworkPacketsOut", Statistic::Average)
218            .dimension("InstanceId", instance_id);
219        MetricReader::new(self.config.cloudwatch_client())
220            .recent(query, 300)
221            .await
222    }
223
224    pub async fn get_metadata_no_token_metrics(
225        &self,
226        instance_id: &str,
227    ) -> Result<Vec<(i64, f64)>> {
228        let query = MetricQuery::new("AWS/EC2", "MetadataNoToken", Statistic::Average)
229            .dimension("InstanceId", instance_id);
230        MetricReader::new(self.config.cloudwatch_client())
231            .recent(query, 300)
232            .await
233    }
234}