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}