Skip to content

Instantly share code, notes, and snippets.

@TristanV12
Created April 15, 2019 14:49
Show Gist options
  • Select an option

  • Save TristanV12/81daf5244b6d878418b0e6616e5365d0 to your computer and use it in GitHub Desktop.

Select an option

Save TristanV12/81daf5244b6d878418b0e6616e5365d0 to your computer and use it in GitHub Desktop.
AWS Lambda Function that backs up ec2 images and volumes from one primary region to a disaster recovery region
import boto3
from datetime import date, timedelta
import datetime
###################
# Global Constants
###################
TESTING = False # true if we are ignoring the time interval
START_HOUR = 4 # start at 4 am
HOURS_IN_DAY = 24 # unlikely to change
TAG_NAME = "Backup" # all items to be backed up must have this tag name with the number of times PER DAY that they should be backed up
EXPIRATION_DAYS = 8 # number of days it should take for images and volumes to expire (7 days, expires on the eighth)
#####################
# Region Information
#####################
primary_region = "us-east-1" # Primary region: N. Virginia
dr_region = "us-east-2" # Disaster recovery region: Ohio
###############
# Boto clients
###############
source_client = boto3.client('ec2')
destination_client = boto3.client('ec2', dr_region)
##################################################################
# Function that finds all of the possible intervals for this hour
##################################################################
def times_per_day():
print("Retrieving intervals...")
hour = datetime.datetime.now().hour - 4 #get hour in EST
adjusted_time = ((hour - START_HOUR) + HOURS_IN_DAY) % HOURS_IN_DAY
# corner cases
if adjusted_time == 0 or TESTING:
output = list(range(1, HOURS_IN_DAY + 1))
print("Intervals retrieved:", output)
output = list(map(str, output))
return output
output = []
for i in range(1, adjusted_time):
if HOURS_IN_DAY % i == 0 and adjusted_time % i == 0:
output.append(str(HOURS_IN_DAY // i))
if HOURS_IN_DAY % adjusted_time == 0:
output.append(str(HOURS_IN_DAY // adjusted_time))
print("Intervals retrieved:", output)
return output
######################################
# Maps from reservations to instances
######################################
def instance_helper(reservation):
return reservation["Instances"][0]
#####################################
# Gets all current running instances
#####################################
def get_instances(intervals):
print("Getting instances...")
output = source_client.describe_instances(Filters=[{'Name':'tag-key', 'Values':[TAG_NAME]}, {'Name':'tag-value', 'Values':intervals}])["Reservations"]
if output != None:
output = list(map(instance_helper, output))
else:
output = []
output = items_in_interval(output, intervals)
print("Got {:d} instances".format(len(output)))
return output
################################################
# Helper function for getting a given tag value
################################################
def get_tag_value(key, tags):
if tags == None:
return ""
for tag in tags:
if tag["Key"] == key:
return tag["Value"]
return ""
##############################################################
# Makes sure that a given interval contains the backup number
# Double checks the filter function
##############################################################
def items_in_interval(items, intervals):
output = []
for item in items:
if get_tag_value(TAG_NAME, item["Tags"]) in intervals:
output.append(item)
return output
###################################################
# Creates images for all of the required instances
###################################################
def create_images(instances):
if len(instances) == 0:
return
now = datetime.datetime.now()
datestring = date.isoformat(now)
resources = []
for instance in instances:
image = source_client.create_image(InstanceId=instance["InstanceId"], Name=get_tag_value("Name", instance["Tags"]) + "-" + datestring + "-" + str(now.hour), NoReboot=True) # make SURE NoReboot is true
resources.append(image["ImageId"])
source_client.create_tags(Resources=[image["ImageId"]], Tags=instance["Tags"])
source_client.create_tags(Resources=resources, Tags=[{'Key':'Saved', 'Value':'False'}])
#################################
# Gets all of the unsaved images
#################################
def get_unsaved_images():
print("Getting unsaved images...")
output = source_client.describe_images(Filters=[{'Name':'tag-key', 'Values':['Saved']}, {'Name': 'tag-value', 'Values': ['False']}])["Images"]
if output == None:
output = []
newoutput = []
for item in output:
if get_tag_value("Saved", item["Tags"]) == "False":
newoutput.append(item)
print("Found {:d} unsaved images".format(len(newoutput)))
return newoutput
##################################################
# Copies unsaved images back to the backup region
##################################################
def copy_images(images):
if len(images) == 0:
return
resources = []
for image in images:
newimage = destination_client.copy_image(SourceImageId=image["ImageId"], SourceRegion=primary_region, Name=image["Name"])
destination_client.create_tags(Resources=[newimage["ImageId"]], Tags=image["Tags"])
source_client.create_tags(Resources=[image["ImageId"]], Tags=[{'Key':'Saved', 'Value':'True'}])
#############################################
# Gets all existing volumes (likely useless)
#############################################
def get_volumes(intervals):
print("Getting volumes...")
output = source_client.describe_volumes(Filters=[{'Name':'tag-key', 'Values':[TAG_NAME]}, {'Name':'tag-value', 'Values':intervals}])["Volumes"]
if output == None:
output = []
print("Got {:d} volumes".format(len(output)))
return output
#########################################
# Generic way to expire a list of images
#########################################
def expire_images(client, images):
for image in images:
client.deregister_image(ImageId=image["ImageId"])
for bdm in image["BlockDeviceMappings"]:
client.delete_snapshot(SnapshotId=bdm["Ebs"]["SnapshotId"])
######################################################
# Expire all images and snapshots past the given date
######################################################
def expire_unneeded_images():
unneeded_days_ago = date.isoformat(date.today() - timedelta(EXPIRATION_DAYS)) + '*'
unneeded_images_source = source_client.describe_images(Filters=[{'Name':'creation-date', 'Values':[unneeded_days_ago]}, {'Name':'tag-key', 'Values':['Saved']}])["Images"]
unneeded_images_destination = destination_client.describe_images(Filters=[{'Name':'creation-date', 'Values':[unneeded_days_ago]}, {'Name':'tag-key', 'Values':['Saved']}])["Images"]
expire_images(source_client, unneeded_images_source)
expire_images(destination_client, unneeded_images_destination)
################
# Main function
################
def lambda_handler(event, context):
intervals = times_per_day()
# Backup instances
instances = get_instances(intervals)
create_images(instances)
# Copy new instances to the backup region
unsaved_images = get_unsaved_images()
copy_images(unsaved_images)
# Delete expired images and snapshots
expire_unneeded_images()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment