└── unused.py
/unused.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import boto3
3 |
4 | region_list = ['eu-west-1', 'eu-central-1', 'us-east-1', 'us-west-1', 'us-west-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1']
5 | owner_id = 'your_account_id'
6 | filename = 'report.html'
7 |
8 | def send_report():
9 |
10 | with open (filename, "r") as myfile:
11 | data=myfile.read().replace('\n', '')
12 |
13 | client = boto3.client('ses', region_name='eu-west-1') #Choose which region you want to use SES
14 | response = client.send_email(
15 | Source='me@example.com',
16 | Destination={
17 | 'ToAddresses': ['you@example.com'
18 | ]
19 | },
20 | Message={
21 | 'Subject': {
22 | 'Data': 'Unused AWS EC2 Resources'
23 | },
24 | 'Body': {
25 | 'Html': {
26 | 'Data': data
27 | }
28 | }
29 |
30 | }
31 | )
32 |
33 |
34 | def append(text):
35 | with open(filename, "a") as f:
36 | f.write(text+'\n')
37 | f.close()
38 |
39 |
40 | def save_cost():
41 | html = """\
42 |
43 |
44 |
75 |
76 |
77 | """
78 | with open(filename, "w") as f:
79 | f.write(html+'\n')
80 | f.close()
81 |
82 | for region in region_list:
83 | append(''+region+'
')
84 |
85 | client = boto3.client('ec2', region_name=region)
86 |
87 | #EIP
88 | response = client.describe_addresses()
89 | eips=[]
90 | for address in response['Addresses']:
91 | if 'InstanceId' not in address:
92 | eips.append(address['PublicIp'])
93 | if len(eips) > 0:
94 | append('
Disassociated EIPSResource | Ip Address | ')
95 | for eip in eips:
96 | append('
---|
EIP | '+eip+' |
')
97 | append('
')
98 |
99 | #Volumes
100 | response=client.describe_volumes()
101 |
102 | volumes = []
103 | for volume in response['Volumes']:
104 | if len(volume['Attachments']) == 0:
105 | volume_dict = {}
106 | volume_dict['VolumeId'] = volume['VolumeId']
107 | volume_dict['VolumeType'] = volume['VolumeType']
108 | volume_dict['VolumeSize'] = volume['Size']
109 | volumes.append(volume_dict)
110 | if len(volumes) > 0:
111 | append('
Unattached VolumesResource | Volume ID | Volume Type | Volume Size | ')
112 | for vol in volumes:
113 | append('Volume | '+vol['VolumeId']+' | '+vol['VolumeType']+' | '+str(vol['VolumeSize'])+'GB |
')
114 | append('
')
115 |
116 | #Snapshots
117 | response = client.describe_snapshots(OwnerIds=[owner_id])
118 | snapshots=[]
119 | for snapshot in response['Snapshots']:
120 | if 'ami' not in snapshot['Description']:
121 | snapshots.append(snapshot['SnapshotId'])
122 |
123 | if len(snapshots) > 0:
124 | append('
Unused SnaphostsResource | Snapshot ID | ')
125 | for snap in snapshots:
126 | append('Snapshot | '+snap+' |
')
127 | append('
')
128 |
129 |
130 | #Securit Groups
131 | response = client.describe_security_groups()
132 | all_sec_groups = []
133 | for SecGrp in response['SecurityGroups']:
134 | all_sec_groups.append(SecGrp['GroupName'])
135 |
136 | sec_groups_in_use = []
137 | response = client.describe_instances(
138 | Filters=[
139 | {
140 | 'Name': 'instance-state-name',
141 | 'Values': ['running', 'stopped']
142 | }
143 | ])
144 |
145 | for r in response['Reservations']:
146 | for inst in r['Instances']:
147 | if inst['SecurityGroups'][0]['GroupName'] not in sec_groups_in_use:
148 | sec_groups_in_use.append(inst['SecurityGroups'][0]['GroupName'])
149 |
150 | unused_sec_groups = []
151 |
152 | for groups in all_sec_groups:
153 | if groups not in sec_groups_in_use:
154 | unused_sec_groups.append(groups)
155 |
156 | if len(unused_sec_groups) > 0:
157 | append('
Unused Security GroupsResource | Security Group Name | ')
158 | for sg in unused_sec_groups:
159 | append('Security Group | '+sg+' |
')
160 | append('
')
161 |
162 | #ELBv1
163 | client = boto3.client('elb', region_name=region)
164 | response = client.describe_load_balancers()
165 | elbs=[]
166 | for ELB in response['LoadBalancerDescriptions']:
167 | if len(ELB['Instances']) == 0:
168 | elbs.append(ELB['LoadBalancerName'])
169 |
170 | if len(elbs) > 0:
171 | append('
Unused Classic ELBsResource | ELB Name | ')
172 | for elb in elbs:
173 | append('ELB | '+elb+' |
')
174 | append('
')
175 |
176 | #Ami's
177 | client = boto3.client('ec2', region_name=region)
178 |
179 | instances = client.describe_instances()
180 | used_amis = []
181 | for reservation in instances['Reservations']:
182 | for instance in reservation['Instances']:
183 | used_amis.append(instance['ImageId'])
184 |
185 | custom_images = client.describe_images(
186 | Filters=[
187 | {
188 | 'Name': 'state',
189 | 'Values': [
190 | 'available'
191 | ]
192 | },
193 | ],
194 | Owners= ['self']
195 | )
196 |
197 | custom_amis_list = []
198 |
199 | for image in custom_images['Images']:
200 | custom_amis_list.append(image['ImageId'])
201 |
202 | if len(custom_amis_list) > 0:
203 | for custom_ami in custom_amis_list:
204 | if custom_ami not in used_amis:
205 | append('
Unused AMIsResource | AMI Id/th>')
206 | for ami in custom_amis_list:
207 | append(' | AMI | '+ami+' |
')
208 | append('
')
209 |
210 | #ELBv2
211 | client = boto3.client('elbv2', region_name=region)
212 |
213 | response_targets = client.describe_target_groups()
214 | response_lbs = client.describe_load_balancers()
215 |
216 | elbsv2_arns=[]
217 | elbsv2_target_arns=[]
218 |
219 | elbsv2_unused=[]
220 | elbsv2_unhealthy=[]
221 | elbsv2_nolisteners=[]
222 |
223 | #ELBv2 check Target groups
224 | for tg in response_targets['TargetGroups']:
225 | elbsv2_target_arns.append(tg['TargetGroupArn'])
226 |
227 | for target_group in elbsv2_target_arns:
228 | response_healthy = client.describe_target_health(TargetGroupArn=target_group)
229 | for target in response_healthy['TargetHealthDescriptions']:
230 | if target['TargetHealth']['State'] == "unused" :
231 | elbsv2_unused.append(target_group)
232 | elif target['TargetHealth']['State'] == "unhealthy" :
233 | elbsv2_unhealthy.append(target_group)
234 |
235 | #ELBv2 check Load Balancers without Listeners
236 | for ELB in response_lbs['LoadBalancers']:
237 | elbsv2_arns.append(ELB['LoadBalancerArn'])
238 |
239 | for ELB in elbsv2_arns:
240 | response_listeners = client.describe_listeners(LoadBalancerArn=ELB)
241 | if response_listeners['Listeners'] == []:
242 | elbsv2_nolisteners.append(ELB)
243 |
244 | # Fill HTML ELBsv2
245 | if len(elbsv2_unused) > 0:
246 | append('
Unused Target GroupsResource | ELB Name | ')
247 | for elbv2 in elbsv2_unused:
248 | append('ELB | '+elbv2+' |
')
249 | append('
')
250 |
251 | if len(elbsv2_unhealthy) > 0:
252 | append('
Unhealthy Target GroupsResource | ELB Name | ')
253 | for elbv2 in elbsv2_unhealthy:
254 | append('ELB | '+elbv2+' |
')
255 | append('
')
256 |
257 | if len(elbsv2_nolisteners) > 0:
258 | append('
LoadBalancer with no ListenersResource | ELB Name | ')
259 | for elbv2 in elbsv2_nolisteners:
260 | append('ELB | '+elbv2+' |
')
261 | append('
')
262 |
263 | #RDS
264 | client = boto3.client('rds', region_name=region)
265 | response = client.describe_db_instances()
266 | rds_instances = []
267 | unused_rds_instances = []
268 |
269 | for rds in response['DBInstances']:
270 | rds_instances.append(rds['DBInstanceIdentifier'])
271 | #rds_instances(rds['DBInstanceIdentifier']) += rds['Endpoint']['Address']
272 |
273 | # cloudwatch (check last connections RDS)
274 | client_cloudwatch = boto3.client('cloudwatch', region_name=region)
275 |
276 | end_date = datetime.utcnow()
277 | start_date = end_date - timedelta(days=30)
278 |
279 | # Getting DatabaseConnections metrics for the last 30 days
280 | for db in rds_instances:
281 | connection_statistics = client_cloudwatch.get_metric_statistics(
282 | Namespace='AWS/RDS',
283 | MetricName='DatabaseConnections',
284 | Dimensions=[
285 | {
286 | 'Name': 'DBInstanceIdentifier',
287 | 'Value': db
288 | },
289 | ],
290 | StartTime=start_date,
291 | EndTime=end_date,
292 | Period=86400,
293 | Statistics=["Maximum"]
294 | )
295 |
296 | total_connection_count = sum(data_point['Maximum']
297 | for data_point in connection_statistics['Datapoints'])
298 |
299 | if total_connection_count == 0:
300 | unused_rds_instances.append(db)
301 |
302 | # Fill HTML RDS
303 | if len(unused_rds_instances) > 0:
304 | append('
Unused RDS Instances (No connection last 30 days)Resource | RDS DB | ')
305 | for rds in unused_rds_instances:
306 | append('RDS | '+rds+' |
')
307 | append('
')
308 |
309 | #Autoscaling
310 | client = boto3.client('autoscaling', region_name=region)
311 | response = client.describe_launch_configurations()
312 | LC_list=[]
313 | for LC in response['LaunchConfigurations']:
314 | LC_name = LC['LaunchConfigurationName']
315 | LC_list.append(LC_name)
316 | response1 = client.describe_auto_scaling_groups()
317 | for ASG in response1['AutoScalingGroups']:
318 | if ASG.get('LaunchConfigurationName') in LC_list:
319 | LC_list.remove(ASG['LaunchConfigurationName'])
320 | LCs=[]
321 | for LC in LC_list:
322 | LCs.append(LC)
323 |
324 | if len(LCs) > 0:
325 | append('
Unused Launch ConfigurationsResource | LC Name | ')
326 | for lc in LCs:
327 | append('LC | '+lc+' |
')
328 | append('
')
329 |
330 | response = client.describe_auto_scaling_groups()
331 | ASGs=[]
332 | for ASG in response['AutoScalingGroups']:
333 | if ASG['DesiredCapacity'] == 0:
334 | ASGs.append(ASG['AutoScalingGroupName'])
335 |
336 | if len(ASGs) > 0:
337 | append('
Unused Auto Scaling GroupsResource | ASG Name | ')
338 | for asg in ASGs:
339 | append('ASG | '+asg+' |
')
340 | append('
')
341 |
342 | html = """\
343 |
344 |
345 | """
346 | with open(filename, "a") as f:
347 | f.write(html+'\n')
348 | f.close()
349 |
350 |
351 | def lambda_handler(event, context):
352 | try:
353 | save_cost()
354 | send_report()
355 | except Exception as err:
356 | print(err)
357 |
--------------------------------------------------------------------------------