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}