1use crate::ec2::{
4 aws::*,
5 deployer_directory,
6 s3::{
7 create_s3_client, delete_prefix, is_no_such_bucket_error, Region, S3_BUCKET_NAME,
8 S3_DEPLOYMENTS_PREFIX,
9 },
10 Config, Error, DESTROYED_FILE_NAME, LOGS_PORT, MONITORING_REGION, PROFILES_PORT, TRACES_PORT,
11};
12use futures::future::try_join_all;
13use std::{collections::HashSet, fs::File, path::PathBuf};
14use tracing::{info, warn};
15
16pub async fn destroy(config: &PathBuf) -> Result<(), Error> {
18 let config: Config = {
20 let config_file = File::open(config)?;
21 serde_yaml::from_reader(config_file)?
22 };
23 let tag = &config.tag;
24 info!(tag = tag.as_str(), "loaded configuration");
25
26 let tag_directory = deployer_directory(tag);
28 if !tag_directory.exists() {
29 return Err(Error::DeploymentDoesNotExist(tag.clone()));
30 }
31
32 let destroyed_file = tag_directory.join(DESTROYED_FILE_NAME);
34 if destroyed_file.exists() {
35 warn!("infrastructure already destroyed");
36 return Ok(());
37 }
38
39 info!(bucket = S3_BUCKET_NAME, "cleaning up S3 deployment data");
41 let s3_client = create_s3_client(Region::new(MONITORING_REGION)).await;
42 let deployment_prefix = format!("{}/{}/", S3_DEPLOYMENTS_PREFIX, tag);
43 match delete_prefix(&s3_client, S3_BUCKET_NAME, &deployment_prefix).await {
44 Ok(()) => {
45 info!(
46 bucket = S3_BUCKET_NAME,
47 prefix = deployment_prefix.as_str(),
48 "deleted S3 deployment data"
49 );
50 }
51 Err(e) => {
52 if is_no_such_bucket_error(&e) {
53 info!(
54 bucket = S3_BUCKET_NAME,
55 "bucket does not exist, skipping S3 cleanup"
56 );
57 } else {
58 warn!(bucket = S3_BUCKET_NAME, %e, "failed to delete S3 deployment data, continuing with destroy");
59 }
60 }
61 }
62
63 let mut all_regions = HashSet::new();
65 all_regions.insert(MONITORING_REGION.to_string());
66 for instance in &config.instances {
67 all_regions.insert(instance.region.clone());
68 }
69
70 info!(regions=?all_regions, "removing resources");
72 let jobs = all_regions.iter().map(|region| {
73 let region = region.clone();
74 let tag = tag.clone();
75 async move {
76 let ec2_client = create_ec2_client(Region::new(region.clone())).await;
77 info!(region = region.as_str(), "created EC2 client");
78
79 let instance_ids = find_instances_by_tag(&ec2_client, &tag).await?;
80 if !instance_ids.is_empty() {
81 terminate_instances(&ec2_client, &instance_ids).await?;
82 wait_for_instances_terminated(&ec2_client, &instance_ids).await?;
83 info!(
84 region = region.as_str(),
85 ?instance_ids,
86 "terminated instances"
87 );
88 }
89
90 let security_groups = find_security_groups_by_tag(&ec2_client, &tag).await?;
92 let has_monitoring_sg = security_groups
93 .iter()
94 .any(|sg| sg.group_name() == Some(&tag));
95 let has_binary_sg = security_groups
96 .iter()
97 .any(|sg| sg.group_name() == Some(&format!("{tag}-binary")));
98 if region == MONITORING_REGION && has_monitoring_sg && has_binary_sg {
99 let monitoring_sg = security_groups
101 .iter()
102 .find(|sg| sg.group_name() == Some(&tag))
103 .expect("Monitoring security group not found")
104 .group_id()
105 .unwrap();
106
107 let binary_sg = security_groups
109 .iter()
110 .find(|sg| sg.group_name() == Some(&format!("{tag}-binary")))
111 .expect("Regular security group not found")
112 .group_id()
113 .unwrap();
114
115 let logging_permission = IpPermission::builder()
117 .ip_protocol("tcp")
118 .from_port(LOGS_PORT as i32)
119 .to_port(LOGS_PORT as i32)
120 .user_id_group_pairs(UserIdGroupPair::builder().group_id(binary_sg).build())
121 .build();
122 if let Err(e) = ec2_client
123 .revoke_security_group_ingress()
124 .group_id(monitoring_sg)
125 .ip_permissions(logging_permission)
126 .send()
127 .await
128 {
129 warn!(%e, "failed to revoke logs ingress rule between monitoring and binary security groups");
130 } else {
131 info!(
132 monitoring_sg,
133 binary_sg,
134 "revoked logs ingress rule between monitoring and binary security groups"
135 );
136 }
137
138 let profiling_permission = IpPermission::builder()
140 .ip_protocol("tcp")
141 .from_port(PROFILES_PORT as i32)
142 .to_port(PROFILES_PORT as i32)
143 .user_id_group_pairs(UserIdGroupPair::builder().group_id(binary_sg).build())
144 .build();
145 if let Err(e) = ec2_client
146 .revoke_security_group_ingress()
147 .group_id(monitoring_sg)
148 .ip_permissions(profiling_permission)
149 .send()
150 .await
151 {
152 warn!(%e, "failed to revoke profiles ingress rule between monitoring and binary security groups");
153 } else {
154 info!(
155 monitoring_sg,
156 binary_sg,
157 "revoked profiles ingress rule between monitoring and binary security groups"
158 );
159 }
160
161 let tracing_permission = IpPermission::builder()
163 .ip_protocol("tcp")
164 .from_port(TRACES_PORT as i32)
165 .to_port(TRACES_PORT as i32)
166 .user_id_group_pairs(UserIdGroupPair::builder().group_id(binary_sg).build())
167 .build();
168 if let Err(e) = ec2_client
169 .revoke_security_group_ingress()
170 .group_id(monitoring_sg)
171 .ip_permissions(tracing_permission)
172 .send()
173 .await
174 {
175 warn!(%e, "failed to revoke traces ingress rule between monitoring and binary security groups");
176 } else {
177 info!(
178 monitoring_sg,
179 binary_sg,
180 "revoked traces ingress rule between monitoring and binary security groups"
181 );
182 }
183 }
184
185 let sgs = find_security_groups_by_tag(&ec2_client, &tag).await?;
187 for sg in sgs {
188 let sg_id = sg.group_id().unwrap();
189 wait_for_enis_deleted(&ec2_client, sg_id).await?;
190 info!(
191 region = region.as_str(),
192 sg_id, "ENIs deleted from security group"
193 );
194 delete_security_group(&ec2_client, sg_id).await?;
195 info!(region = region.as_str(), sg_id, "deleted security group");
196 }
197
198 let subnet_ids = find_subnets_by_tag(&ec2_client, &tag).await?;
199 for subnet_id in subnet_ids {
200 delete_subnet(&ec2_client, &subnet_id).await?;
201 info!(region = region.as_str(), subnet_id, "deleted subnet");
202 }
203
204 let route_table_ids = find_route_tables_by_tag(&ec2_client, &tag).await?;
205 for rt_id in route_table_ids {
206 delete_route_table(&ec2_client, &rt_id).await?;
207 info!(region = region.as_str(), rt_id, "deleted route table");
208 }
209
210 let peering_ids = find_vpc_peering_by_tag(&ec2_client, &tag).await?;
211 for peering_id in peering_ids {
212 delete_vpc_peering(&ec2_client, &peering_id).await?;
213 wait_for_vpc_peering_deletion(&ec2_client, &peering_id).await?;
214 info!(
215 region = region.as_str(),
216 peering_id, "deleted VPC peering connection"
217 );
218 }
219
220 let igw_ids = find_igws_by_tag(&ec2_client, &tag).await?;
221 for igw_id in igw_ids {
222 let vpc_id = find_vpc_by_igw(&ec2_client, &igw_id).await?;
223 detach_igw(&ec2_client, &igw_id, &vpc_id).await?;
224 info!(
225 region = region.as_str(),
226 igw_id, vpc_id, "detached internet gateway"
227 );
228 delete_igw(&ec2_client, &igw_id).await?;
229 info!(region = region.as_str(), igw_id, "deleted internet gateway");
230 }
231
232 let key_name = format!("deployer-{tag}");
233 delete_key_pair(&ec2_client, &key_name).await?;
234 info!(region = region.as_str(), key_name, "deleted key pair");
235 Ok::<(), Error>(())
236 }
237 });
238 try_join_all(jobs).await?;
239
240 for region in &all_regions {
242 let ec2_client = create_ec2_client(Region::new(region.clone())).await;
243 info!(region = region.as_str(), "created EC2 client");
244 let vpc_ids = find_vpcs_by_tag(&ec2_client, tag).await?;
245 for vpc_id in vpc_ids {
246 delete_vpc(&ec2_client, &vpc_id).await?;
247 info!(region = region.as_str(), vpc_id, "deleted VPC");
248 }
249 }
250 info!(regions = ?all_regions, "resources removed");
251
252 File::create(destroyed_file)?;
254
255 info!(tag = tag.as_str(), "destruction complete");
257 Ok(())
258}