└── 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('
') 95 | for eip in eips: 96 | append('') 97 | append('
Disassociated EIPS
ResourceIp Address
EIP'+eip+'
') 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('
') 112 | for vol in volumes: 113 | append('') 114 | append('
Unattached Volumes
ResourceVolume IDVolume TypeVolume Size
Volume '+vol['VolumeId']+''+vol['VolumeType']+''+str(vol['VolumeSize'])+'GB
') 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('
') 125 | for snap in snapshots: 126 | append('') 127 | append('
Unused Snaphosts
ResourceSnapshot ID
Snapshot'+snap+'
') 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('
') 158 | for sg in unused_sec_groups: 159 | append('') 160 | append('
Unused Security Groups
ResourceSecurity Group Name
Security Group'+sg+'
') 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('
') 172 | for elb in elbs: 173 | append('') 174 | append('
Unused Classic ELBs
ResourceELB Name
ELB'+elb+'
') 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('
') 208 | append('
Unused AMIs
ResourceAMI Id/th>') 206 | for ami in custom_amis_list: 207 | append('
AMI'+ami+'
') 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('
') 247 | for elbv2 in elbsv2_unused: 248 | append('') 249 | append('
Unused Target Groups
ResourceELB Name
ELB'+elbv2+'
') 250 | 251 | if len(elbsv2_unhealthy) > 0: 252 | append('
') 253 | for elbv2 in elbsv2_unhealthy: 254 | append('') 255 | append('
Unhealthy Target Groups
ResourceELB Name
ELB'+elbv2+'
') 256 | 257 | if len(elbsv2_nolisteners) > 0: 258 | append('
') 259 | for elbv2 in elbsv2_nolisteners: 260 | append('') 261 | append('
LoadBalancer with no Listeners
ResourceELB Name
ELB'+elbv2+'
') 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('
') 305 | for rds in unused_rds_instances: 306 | append('') 307 | append('
Unused RDS Instances (No connection last 30 days)
ResourceRDS DB
RDS'+rds+'
') 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('
') 326 | for lc in LCs: 327 | append('') 328 | append('
Unused Launch Configurations
ResourceLC Name
LC'+lc+'
') 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('
') 338 | for asg in ASGs: 339 | append('') 340 | append('
Unused Auto Scaling Groups
ResourceASG Name
ASG'+asg+'
') 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 | --------------------------------------------------------------------------------