Lambda function to take AMI backup (python)

Various improvements are planned in the future.

Please refer to the following for settings etc. http://pict3.hatenablog.com/entry/2015/12/09/104015

#!/usr/bin/python
# -*- coding: utf-8 -*-

import json

import boto3
from boto3.session import Session

import time
from datetime import datetime as dt

import pprint

TAG_KEY_BACKUP_GENERATION = 'Backup-Generation'
TAG_KEY_AUTO_BACKUP       = 'Backup-Type'
TAG_VAL_AUTO_BACKUP       = 'auto'

print('Loading function')

pp = pprint.PrettyPrinter(indent=4)

#Function name: lambda_handler
def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))

    ec2_client   = boto3.client('ec2')
    ec2_resource = boto3.resource('ec2')

    ret = execute_ami_backup_task(ec2_client, ec2_resource)
    print 'AMI buckup task is completed(%s).' % (ret)

    return 0
    raise Exception('Something went wrong')


#Function name: execute_ami_backup_task
#Return value: Execution result
#Arguments: ec2_client
#       : ec2_resource
#Function: Perform AMI backup
def execute_ami_backup_task(ec2_client, ec2_resource):
    response = ec2_client.describe_instances()

    exec_time = dt.now().strftime('%Y%m%d%H%M%S')

    result = True
    for ec2_group in response['Reservations']:
        for instance_info in ec2_group['Instances']:
            ret = is_target(instance_info)
            if (ret == False):
                continue
        
            ret = create_buckup_image(ec2_client, ec2_resource, instance_info, exec_time)
            if not ret:
                print 'create_buckup_image(%s) was failed.' % (instance_info['InstanceId'])
                result = False

                continue

            ret = delete_old_image(ec2_client, ec2_resource, instance_info)
            if not ret:
                print 'delete_old_image(%s) was failed.' % (instance_info['InstanceId'])
                result = False

                continue
    
    return result


#Function name: is_target
#Return value: Backup required
#Arguments: instance_info <dict>
#Function: Judge the necessity of backup
def is_target(instance_info):
    val = get_tag_value(
        instance_info, 
        TAG_KEY_BACKUP_GENERATION
    )

    if val is None:
        return False

    return True


#Function name: get_tag_value
#Return value: Tag value (None if there is no match for the key)
#Arguments: instance_info <dict>
#       : key <str>
#Function: Get the tag value of the specified key from the instance information
def get_tag_value(instance_info, key):
    tags = instance_info['Tags']
    for tag in tags:
        if not (key == tag['Key']):
            continue
        
        return tag['Value']

    return None


#Function name: create_buckup_image
#Return value: Execution result
#Arguments: ec2_client
#       : ec2_resource
#       : instance_info <dict>
#       : exec_time <str>
#Function: Create a backup image
def create_buckup_image(ec2_client, ec2_resource, instance_info, exec_time):
    inst_id = instance_info['InstanceId']
    name    = get_tag_value(instance_info, 'Name')
    if name is None:
        print('Get name error!!')
        
        return False

    image_name = name + '-' + exec_time

    response = ec2_client.create_image(
        InstanceId  = inst_id,
        Name        = image_name,
        Description = image_name,
        NoReboot    = True
    )

    image_id = response['ImageId']
    print '%s was created.' % (image_id)

    #Just in case, wait until the image & snapshot to be tagged is completed.
    time.sleep(10)

    tags  = construct_backup_tags(instance_info)
    image = ec2_resource.Image(image_id)

    set_tags_to_image(image, tags)

    set_tags_to_snapshot(ec2_resource, image, tags, image_name)

    return True


#Function name: construct_backup_tags
#Return value: Tags group
#Arguments: instance_info <dict>
#Function: Configure a group of tags for back-up settings
def construct_backup_tags(instance_info):
    ret_tags = []
    tags = instance_info['Tags']
    for tag in tags:
        if (TAG_KEY_BACKUP_GENERATION == tag['Key']):
            continue

        ret_tags.append(tag)

    t = {u'Value': TAG_VAL_AUTO_BACKUP, u'Key': TAG_KEY_AUTO_BACKUP}
    ret_tags.append(t)

    return ret_tags


#Function name: set_tags_to_image
#Return value: non
#Argument: image
#       : tags <list>
#Function: Set tag information on the AMI image
def set_tags_to_image(image, tags):
    image.create_tags(Tags = tags)

    return


#Function name: set_tags_to_snapshot
#Return value: non
#Arguments: ec2_resource
#       : image
#       : tags <list>
#       : image_name <str>
#Function: Set tag information in the snapshot
def set_tags_to_snapshot(ec2_resource, image, tags, image_name):
    for dev in image.block_device_mappings:
        #Not applicable except for EBS
        if not dev.has_key('Ebs'):
            continue

        #Deleted once to replace the Name tag
        name_idx = get_name_tag_index(tags)
        tags.pop(name_idx)

        #Name tag setting
        dev_name = dev['DeviceName'][5:]
        name = image_name + '-' + dev_name
        t = {u'Value': name, u'Key': 'Name'}
        tags.append(t)
       
        snapshot_id = dev['Ebs']['SnapshotId']
        snapshot = ec2_resource.Snapshot(snapshot_id)

        snapshot.create_tags(Tags = tags)

    return


#Function name: get_name_tag_index
#Return value: Index position of Name tag (None if there is no match for the key)
#Arguments: tags<list>
#Function: Get the index position of the Name tag in the tag list
def get_name_tag_index(tags):
    idx = 0
    for tag in tags:
        if tag['Key'] == 'Name':
            return idx

        idx += 1

    return None


#Function name: delete_old_image
#Return value: Execution result
#Arguments: ec2_client
#       : ec2_resource
#       : instance_info <dict>
#Function: Deletes images older than the retention generation
def delete_old_image(ec2_client, ec2_resource, instance_info):
    sorted_images = get_sorted_images(ec2_client, instance_info)

    generation = int(get_tag_value(instance_info, TAG_KEY_BACKUP_GENERATION))
    cnt = 0
    for img in sorted_images:
        cnt += 1
        if generation >= cnt:
            continue

        image_id  = img['ImageId']
        snapshots = get_snapshots(ec2_resource, image_id)

        #Release the AMI image
        ec2_client.deregister_image(
            ImageId = image_id
        )
        print '%s was deregistered.' % (image_id)

        #Wait until the release is complete
        time.sleep(10)

        #Delete the corresponding snapshot
        for snapshot in snapshots:
            snapshot.delete()
            print '%s was deleted.' % (snapshot)

    return True


#Function name: get_sorted_images
#Return value: Sorted image<list>
#Arguments: ec2_client
#       : instance_info <dict>
#Function: Get the AMI image list sorted in the order of creation
def get_sorted_images(ec2_client, instance_info):
    sorted_images = []
    name     = get_tag_value(instance_info, 'Name')
    response = ec2_client.describe_images(
        Owners  = ['self'],
        Filters = [{'Name': 'tag:Name',                   'Values': [name]},
                   {'Name': 'tag:' + TAG_KEY_AUTO_BACKUP, 'Values': [TAG_VAL_AUTO_BACKUP]}]
    )

    images = response['Images']
    sorted_images = sorted(
        images, 
        key = lambda x: x['CreationDate'], 
        reverse = True
    )

    return sorted_images


#Function name: get_snapshots
#Return value: Snapshot group<list>
#Arguments: ec2_resource
#       : image_id <str>
#Function: Acquires snapshots included in the AMI image
def get_snapshots(ec2_resource, image_id):
    snapshots = []
    image     = ec2_resource.Image(image_id)

    for dev in image.block_device_mappings:
        if not dev.has_key('Ebs'):
            continue

        snapshot_id = dev['Ebs']['SnapshotId']
        snapshot    = ec2_resource.Snapshot(snapshot_id)
            
        snapshots.append(snapshot)

    return snapshots

Recommended Posts

Lambda function to take AMI backup (python)
How to use Python lambda
[Introduction to Udemy Python 3 + Application] 58. Lambda
[Lambda] [Python] Post to Twitter from Lambda!
Write AWS Lambda function in Python
How to use python zip function
python function ①
[Python 3.8 ~] How to define a recursive function smartly with a lambda expression
[Python] function
python function ②
[Introduction to Udemy Python3 + Application] 48. Function definition
[python] How to use __command__, function explanation
[Introduction to Udemy Python3 + Application] 45. enumerate function
[Introduction to Udemy Python3 + Application] 41. Input function
Function to save images by date [python3]
[Introduction to Udemy Python3 + Application] 44. range function
How to access RDS from Lambda (python)
[Introduction to Udemy Python3 + Application] 46. Zip function
Connect to s3 with AWS Lambda Python
[Road to intermediate Python] Use lambda expressions
UNHEALTHY Workspaces restart Lambda rewritten to python
Updated to Python 2.7.9
python3x: lambda function
Automatically register function arguments to argparse in Python
To execute a Python enumerate function in JavaScript
python enumerate function
I was able to recurse in Python: lambda
Python> function> Closure
[Python] Generator function
Feel free to turn Python using the library into an AWS Lambda function
Sample to send slack notification with python lambda
Export RDS snapshot to S3 with Lambda (Python)
[Python] How to use hash function and tuple.
[AWS / Lambda] How to load Python external library
Upload files to Google Drive with Lambda (Python)
Python> function> Inner function
Summary of studying Python to use AWS Lambda
Python function decorator
"Backport" to python 2
[Introduction to Python] Basic usage of lambda expressions
[Introduction to Python] How to iterate with the range function?
Introduce pipe operators and function synthesis to Python (provisional)
Create Python version Lambda function (+ Lambda Layer) with Serverless Framework
[Road to Python Intermediate] Define __getattr__ function in class
I tried to get an AMI using AWS Lambda
Deploy Python3 function with Serverless Framework on AWS Lambda
Write multiple records to DynamoDB with Lambda (Python, JavaScript)
I want to AWS Lambda with Python on Mac!
Specifies the function to execute when the python program ends
Consider a conversion from a Python recursive function to a non-recursive function
Execute Python function from Powershell (how to pass arguments)
I was able to repeat it in Python: lambda
Re: Python lambda is useless ^ H ^ H ^ H ^ H ^ H Difficult to use
Python> function> Docstrings> Description to display with help () / .__ doc__
[Python] How to call a c function from python (ctypes)
About function arguments (python)
[Python] Take a screenshot
How to install Python
Function execution time (Python)
Changes from Python 3.0 to Python 3.5
Changes from Python 2 to Python 3.0