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