├── readme.md └── reaper.py /readme.md: -------------------------------------------------------------------------------- 1 | # Reaper Lambda 2 | 3 | This lambda function: 4 | 5 | •Finds all instances in the selected region(s) 6 | 7 | •Ensures all instances are tagged with "expiration_date" of "never" or 8 | with an integer (epoch time). 9 | 10 | •Terminates any instances without an expiration_date 11 | 12 | •Terminates any instances past their expiration date 13 | 14 | ## Yes, this will blow up your AWS account if instances are not tagged! 15 | 16 | ## Notes 17 | •This may not work in large AWS accounts. For this reason 18 | I've split it out by region - you may specify a smaller array of regions 19 | at the top. If your account is exceptionally large, it would be a very 20 | minor effort to add an additional for loop that iterates over each VPC 21 | in a region. Feel free to add a PR! 22 | 23 | •We use epoch time because integers are easy to compare and I'm lazy. 24 | Deal with it or fix it in your own environment :) 25 | 26 | •If you tag your instance with an expiration_date that causes a 27 | ValueError, I don't think I've handled that properly. Please don't be 28 | manually tagging your stuff. We have terraform and cloudformation for 29 | this, people! 30 | -------------------------------------------------------------------------------- /reaper.py: -------------------------------------------------------------------------------- 1 | #### WARNING #### 2 | # This script iterates through all the specified regions and terminates any instances 3 | # which do not have an expiration_date tag, or any instances wherein the expiration_date 4 | # tag is before the current epoch. If you're not using epoch time, or if you haven't 5 | # tagged your instances, this will blow up your whole AWS account. 6 | # Consider this fair warning! 7 | 8 | import boto3 9 | import logging 10 | import time 11 | 12 | # Boto logging 13 | logger = logging.getLogger() 14 | logger.setLevel(logging.INFO) 15 | 16 | # This might need to be split across multiple regions if your AWS account is huge. 17 | regions = ['us-west-1', 'us-west-2', 'us-east-1', 'us-east-2', 'eu-west-1', 'eu-west-2', 'ap-northeast-1', 'ap-southeast-2'] 18 | 19 | def lambda_handler(event, context): 20 | 21 | for each in regions: 22 | 23 | ec2client = boto3.client('ec2', region_name='{}'.format(region)) 24 | 25 | # Create empty arrays to avoid TypeErrors if any regions return empty 26 | all_instances = [] 27 | all_instances.extend(grab_all_instances(each)) 28 | 29 | expired_instances = [] 30 | expired_instances.extend(check_expired_instances(each, all_instances)) 31 | 32 | tagged_instances = [] 33 | tagged_instances.extend(list_instances_by_tag('expiration_date', each)) 34 | 35 | # Make an array of all untagged & expired instances 36 | instances_to_delete = [] 37 | instances_to_delete.extend(all_instances) 38 | 39 | # Remove instances that are properly tagged for expiration 40 | print 'comparing expired instances: {} to all instances tagged for expiration: {}'.format(expired_instances, tagged_instances) 41 | for each in tagged_instances: 42 | if each not in expired_instances: 43 | instances_to_delete.remove(each) 44 | 45 | # Iterate over instances_to_delete and terminate each one of them 46 | print "instances to delete: {}".format(instances_to_delete) 47 | for instance in instances_to_delete: 48 | # TEST THIS FIRST! Uncomment the stop command if you're ready to go! 49 | print "terminating {}".format(instance) 50 | #ec2client.terminate_instances(InstanceIds=[''.format(instance)]) 51 | 52 | 53 | 54 | def grab_all_instances(region): 55 | # Snags ID of all instances in a region 56 | ec2client = boto3.client('ec2', region_name='{}'.format(region)) 57 | all_ec2_instances = [] 58 | 59 | response = ec2client.describe_instances() 60 | for reservation in response["Reservations"]: 61 | for instance in reservation["Instances"]: 62 | all_ec2_instances.append(str(instance["InstanceId"])) 63 | print 'got all instances for {}: {}'.format(region, all_ec2_instances) 64 | return all_ec2_instances 65 | 66 | def list_instances_by_tag(tagkey, region): 67 | # When passed a tag key, this will return a list of InstanceIDs that were found. 68 | 69 | ec2client = boto3.client('ec2', region_name='{}'.format(region)) 70 | 71 | response = ec2client.describe_instances(Filters=[{'Name': 'tag:'+tagkey, 'Values': ['*']}]) 72 | instancelist_from_tag = [] 73 | for reservation in (response["Reservations"]): 74 | for instance in reservation["Instances"]: 75 | instancelist_from_tag.append(instance["InstanceId"]) 76 | print 'got tagged instance list for {}: {}'.format(region, instancelist_from_tag) 77 | return instancelist_from_tag 78 | 79 | def check_expired_instances(region, instances_to_check): 80 | # When passed a region and list of instance IDs, this will return a list of 81 | # InstanceIDs that were found to be past their expiration date 82 | 83 | ec2client = boto3.client('ec2', region_name='{}'.format(region)) 84 | expired_list = [] 85 | epoch_time = int(time.time()) 86 | if instances_to_check: 87 | print 'checking following instances for expiration_date: {}'.format(instances_to_check) 88 | for instanceID in instances_to_check: 89 | try: 90 | ec2instance = ec2client.describe_instances(InstanceIds=['{}'.format(instanceID)]) 91 | for reservation in ec2instance["Reservations"]: 92 | for instance in reservation["Instances"]: 93 | for tags in instance["Tags"]: 94 | print "found tags for {}:{}".format(instanceID, tags) 95 | if tags["Key"] == 'expiration_date': 96 | if tags["Value"] != 'never': 97 | expiration_date = int(tags["Value"]) 98 | print 'checking expiration date {} versus current epoch: {}'.format(expiration_date, epoch_time) 99 | if expiration_date < epoch_time: 100 | expired_list.append(instanceID) 101 | 102 | except Exception as e: 103 | print "Failed to retrieve info for {}: {}".format(instanceID, e) 104 | print 'got expired instances for {}: {}'.format(region, expired_list) 105 | return expired_list 106 | 107 | --------------------------------------------------------------------------------