15
15
# Create an EC2 client
16
16
session = boto3 .Session ()
17
17
ec2 = session .client ('ec2' )
18
+ autoscaling = session .client ('autoscaling' )
18
19
19
20
# configure logging
20
21
logging .basicConfig (level = logging .INFO )
21
22
23
+ def _get_autoscaling_groups_to_delete ():
24
+ logging .info ("Start scanning autoscaling group..." )
25
+
26
+ current_time = datetime .now (timezone .utc )
27
+ time_threshold = current_time - timedelta (hours = 3 )
28
+ groups_to_delete = []
29
+
30
+ # Initialize the paginator
31
+ paginator = autoscaling .get_paginator ('describe_auto_scaling_groups' )
32
+
33
+ # Iterate through each page of results
34
+ for page in paginator .paginate ():
35
+ auto_scaling_groups = page ['AutoScalingGroups' ]
36
+ for asg in auto_scaling_groups :
37
+ asg_name = asg ['AutoScalingGroupName' ]
38
+ tags = asg ['Tags' ]
39
+
40
+ eks_tag_present = any (tag ['Key' ] == 'eks:cluster-name' for tag in tags )
41
+ if eks_tag_present :
42
+ logging .info (f"Skipping autoscaling group with 'eks:cluster-name' tag: { asg_name } ." )
43
+ continue
44
+
45
+ if not _is_active (asg ):
46
+ logging .info (f"Skipping autoscaling group { asg_name } with terminating instances." )
47
+ continue
48
+
49
+ logging .info (f"autoscaling group { asg_name } is active." )
50
+
51
+ creation_time = asg ['CreatedTime' ]
52
+ if creation_time < time_threshold :
53
+ print (f"Autoscaling group: { asg_name } will be deleted." )
54
+ groups_to_delete .append (asg )
55
+
56
+ logging .info (f"{ len (groups_to_delete )} autoscaling groups are active for more than 3 hours." )
57
+
58
+ return groups_to_delete
59
+
60
+
61
+ def _delete_autoscaling_groups (auto_scaling_groups ):
62
+ for asg in auto_scaling_groups :
63
+ try :
64
+ asg_name = asg ['AutoScalingGroupName' ]
65
+ response = autoscaling .delete_auto_scaling_group (AutoScalingGroupName = asg_name , ForceDelete = True )
66
+ logging .info ("===== Response for delete autoscaling group request =====" )
67
+ logging .info (response )
68
+ except Exception as e :
69
+ logging .info (f"Error terminating instances: { e } " )
70
+
71
+ def _is_active (asg ):
72
+ for instance in asg ['Instances' ]:
73
+ if instance ['LifecycleState' ] in [
74
+ 'Terminating' , 'Terminating:Wait' , 'Terminating:Proceed'
75
+ ]:
76
+ return False
77
+ return True
78
+
22
79
23
80
def _get_instances_to_terminate ():
24
81
# Get all the running instances
25
- logging .info ("Getting all running instances" )
82
+ logging .info ("Start scanning instances" )
26
83
running_filter = [{'Name' : 'instance-state-name' , 'Values' : [INSTANCE_STATE_RUNNING ]}]
27
84
running_instances = _get_all_instances_by_filter (filters = running_filter )
28
- logging .info (f"Currently { len (running_instances )} are running." )
85
+ logging .info (f"{ len (running_instances )} instances are running." )
29
86
30
87
# Filter instances that have been running for more than 3 hours
31
88
logging .info ("Filtering instances that have been running for more than 3 hours" )
@@ -42,10 +99,13 @@ def _get_instances_to_terminate():
42
99
logging .info ("Filtering instances that should not be terminated based on conditions" )
43
100
instances_to_terminate = []
44
101
for instance in instances_running_more_than_3hrs :
45
- if (not _is_eks_cluster_instance (instance )
46
- and not _is_k8s_cluster_instance (instance )
47
- and not _is_tagged_do_not_delete (instance )):
48
- instances_to_terminate .append (instance )
102
+ if (not _is_k8s_cluster_instance (instance ) and not _is_tagged_do_not_delete (instance )):
103
+ group_name = _get_associated_autoscaling_group_name (instance )
104
+ if group_name != None :
105
+ logging .info (f"Instance { instance ['InstanceId' ]} is associated with autoscaling group { group_name } , skip the termination." )
106
+ else :
107
+ instances_to_terminate .append (instance )
108
+
49
109
logging .info (f"{ len (instances_to_terminate )} instances will be terminated." )
50
110
51
111
return instances_to_terminate
@@ -70,13 +130,6 @@ def _get_all_instances_by_filter(filters: List[dict]):
70
130
return filtered_instances
71
131
72
132
73
- def _is_eks_cluster_instance (instance ):
74
- security_groups = instance .get ('SecurityGroups' , [])
75
- if any (group ['GroupName' ].startswith (EKS_CLUSTER_SECURITY_GROUP_PREFIX ) for group in security_groups ):
76
- return True
77
- return False
78
-
79
-
80
133
def _is_k8s_cluster_instance (instance ):
81
134
tags = instance .get ('Tags' , [])
82
135
if 'Name' in tags and tags ['Name' ].startswith (K8S_INSTANCE_NAME_PREFIX ):
@@ -92,12 +145,21 @@ def _is_tagged_do_not_delete(instance):
92
145
return True
93
146
return False
94
147
95
-
96
- def _prepare_report_and_upload (instances_to_terminate ) -> bool :
97
- json_data = json .dumps (instances_to_terminate , default = str )
148
+ def _get_associated_autoscaling_group_name (instance ):
149
+ tags = instance .get ('Tags' , [])
150
+ asg_tag = next ((tag for tag in tags if tag ['Key' ] == 'aws:autoscaling:groupName' ), None )
151
+ if asg_tag is None :
152
+ return None
153
+ return asg_tag ['Value' ]
154
+
155
+ def _prepare_report_and_upload (groups_to_delete , instances_to_terminate ) -> bool :
156
+ json_data = json .dumps ({
157
+ "autoscalingGroups" : groups_to_delete ,
158
+ "standaloneInstances" : instances_to_terminate
159
+ }, default = str )
98
160
# save as a json file with timestamp
99
161
timestamp = datetime .now ().strftime ("%Y%m%d-%H%M%S" )
100
- filename = f"report-instances -to-terminate -{ timestamp } .json"
162
+ filename = f"report-resources -to-clean -{ timestamp } .json"
101
163
with open (filename , "w" ) as f :
102
164
f .write (json_data )
103
165
@@ -116,24 +178,26 @@ def _prepare_report_and_upload(instances_to_terminate) -> bool:
116
178
def _terminate_instances (instances_to_terminate ):
117
179
# Terminate the instances
118
180
instance_ids = [instance ['InstanceId' ] for instance in instances ]
119
- logging .info ("Number of instances terminating: " + str (len (instance_ids )))
120
181
try :
121
182
response = ec2 .terminate_instances (InstanceIds = instance_ids )
122
- logging .info ("===== Response for terminate request =====" )
183
+ logging .info ("===== Response for terminate instances request =====" )
123
184
logging .info (response )
124
185
except Exception as e :
125
186
logging .info (f"Error terminating instances: { e } " )
126
187
127
188
128
189
if __name__ == '__main__' :
190
+ groups = _get_autoscaling_groups_to_delete ()
129
191
instances = _get_instances_to_terminate ()
130
- if len (instances ) == 0 :
131
- logging .info ("No instances to terminate" )
192
+
193
+ if len (groups ) == 0 and len (instances ) == 0 :
194
+ logging .info ("No resource to terminate" )
132
195
exit (0 )
133
196
134
- report_successful = _prepare_report_and_upload (instances )
197
+ report_successful = _prepare_report_and_upload (groups , instances )
135
198
if not report_successful :
136
199
logging .error ("Failed to prepare report and upload. Aborting termination of instances." )
137
200
exit (1 )
138
201
202
+ _delete_autoscaling_groups (groups )
139
203
_terminate_instances (instances )
0 commit comments