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
24pub struct Ec2Client {
25 config: AwsConfig,
26}
27
28impl Ec2Client {
29 pub fn new(config: AwsConfig) -> Self {
30 Self { config }
31 }
32
33 pub async fn list_instances(&self) -> Result<Vec<Instance>> {
34 let client = self.config.ec2_client().await;
35 let mut instances = Vec::new();
36 let mut next_token: Option<String> = None;
37
38 loop {
39 let mut request = client.describe_instances();
40 if let Some(token) = next_token {
41 request = request.next_token(token);
42 }
43
44 let response = request.send().await?;
45
46 if let Some(reservations) = response.reservations {
47 for reservation in reservations {
48 if let Some(insts) = reservation.instances {
49 for inst in insts {
50 let tags: std::collections::HashMap<String, String> = inst
51 .tags()
52 .iter()
53 .filter_map(|t| {
54 Some((t.key()?.to_string(), t.value()?.to_string()))
55 })
56 .collect();
57
58 let name = tags.get("Name").cloned().unwrap_or_default();
59
60 let state = inst
61 .state()
62 .and_then(|s| s.name())
63 .map(|n| n.as_str().to_string())
64 .unwrap_or_default();
65
66 let security_groups = inst
67 .security_groups()
68 .iter()
69 .filter_map(|sg| sg.group_name())
70 .collect::<Vec<_>>()
71 .join(", ");
72
73 let ipv6_ips = inst
74 .network_interfaces()
75 .iter()
76 .flat_map(|ni| ni.ipv6_addresses())
77 .filter_map(|ip| ip.ipv6_address())
78 .collect::<Vec<_>>()
79 .join(", ");
80
81 let launch_time = inst
82 .launch_time()
83 .map(|dt| {
84 let timestamp = dt.secs();
85 chrono::DateTime::from_timestamp(timestamp, 0)
86 .map(|dt| dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string())
87 .unwrap_or_default()
88 })
89 .unwrap_or_default();
90
91 instances.push(Instance {
92 instance_id: inst.instance_id().unwrap_or("").to_string(),
93 name,
94 state,
95 instance_type: inst
96 .instance_type()
97 .map(|t| t.as_str().to_string())
98 .unwrap_or_default(),
99 availability_zone: inst
100 .placement()
101 .and_then(|p| p.availability_zone())
102 .unwrap_or("")
103 .to_string(),
104 public_ipv4_dns: inst.public_dns_name().unwrap_or("").to_string(),
105 public_ipv4_address: inst
106 .public_ip_address()
107 .unwrap_or("")
108 .to_string(),
109 elastic_ip: String::new(),
110 ipv6_ips,
111 monitoring: inst
112 .monitoring()
113 .and_then(|m| m.state())
114 .map(|s| s.as_str().to_string())
115 .unwrap_or_default(),
116 security_groups,
117 key_name: inst.key_name().unwrap_or("").to_string(),
118 launch_time,
119 platform_details: inst.platform_details().unwrap_or("").to_string(),
120 status_checks: String::new(),
121 alarm_status: String::new(),
122 });
123 }
124 }
125 }
126 }
127
128 next_token = response.next_token;
129 if next_token.is_none() {
130 break;
131 }
132 }
133
134 Ok(instances)
135 }
136}