Saturday, January 28, 2017

AWS Serverless Application Model: Here we go!

AWS Serverless Application Model (SAM) was released a couple months ago. The punch line of this new release in my mind is the ability to version your lambda function code and your cloudformation template next to each other. The idea being to have completely packaged serverless application that deploy from a single repository.


I spent an afternoon playing around with AWS SAM, and I'm already a pretty big fan. It makes deploying lambda functions a lot easier, especially when you have different accounts you want to use them in.

The example below is to create a lambda function that tags EBS volumes as they become available

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  EbsVolumeAvailableTagger:
    Type: AWS::Serverless::Function
    Properties:
      Handler: ebs_available_date_tagger.lambda_handler
      Role: !GetAtt EbsCleanerIAMRole.Arn
      Runtime: python2.7
  IAMEbsVolumeListTagPolicy: 
    Type: "AWS::IAM::Policy"
    DependsOn: EbsCleanerIAMRole
    Properties:
        PolicyName: !Sub "Role=EBSCleaner,Env=${AccountParameter},Service=Lambda,Rights=RW"
        PolicyDocument: 
            Version: "2012-10-17"
            Statement: 
                - 
                    Effect: "Allow"
                    Action:
                        - "ec2:CreateTags"
                        - "ec2:DeleteTags"
                        - "ec2:DescribeTags"
                        - "ec2:DescribeVolumeAttribute"
                        - "ec2:DescribeVolumeStatus"
                        - "ec2:DescribeVolumes"
                    Resource: 
                        - "*"
        Roles:
          - !Ref EbsCleanerIAMRole
  EbsCleanerIAMRole: 
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "Role=EbsCleaner,Env=${AccountParameter},Service=Lambda"
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Principal: 
              Service: 
                - "lambda.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"
Parameters:
  AccountParameter:
    Type: String
    Default: NoPHI
    AllowedValues:
    - Prod
    - Staging
    - Corporate
    Description: Enter the account where this lambda function is being created. Will
      be used to properly name the created IAM role

And then the python that it runs
import boto3
import re
import logging
import time

def lambda_handler(event, context):

    # Number of days to wait before deleting a volume
    
    volumeDaysOld = 30
    
    # Get a cloudwatch logger
    logger = logging.getLogger('EbsVolumeCleanup')
    logger.setLevel(logging.DEBUG)
    
    # Obtain boto3 resources
    logger.info('Getting boto 3 resources')
    opsworksClient = boto3.client('opsworks')
    ec2Client = boto3.client('ec2')

    availableVolumes = ec2Client.describe_volumes(Filters=[{'Name':'status','Values':['available']}])

    availableVolumesToTag = []

    for volume in availableVolumes['Volumes']:
        logger.info(volume)
        if 'Tags' in volume:
            tags = volume['Tags']
            availableDate = (tag for tag in tags if tag['Key'] == 'volumeAvailableDate').next()
            if availableDate:
                logger.info('Volume was available' + availableDate['Value'])
            else:
                logger.info('Volume not yet tagged')
                availableVolumesToTag.append(volume['VolumeId'])
        else:
            availableVolumesToTag.append(volume['VolumeId'])
    
    logger.info('Volumes to be tagged available: ' + "-" + str(len(availableVolumesToTag)) + " " + "|".join(availableVolumesToTag))
    if availableVolumesToTag:
        ec2Client.create_tags(Resources=availableVolumesToTag,Tags=[{'Key':'volumeAvailableDate','Value':time.strftime("%d/%m/%Y")}])

    return 0

If you put the two of these into a directory together, you can use the aws cloudformation package and deploy CLI commands to push them to your account.
aws cloudformation package --template-file ec2-management-cft.yml --output-template-file instance-management-cft-staging.yml --s3-bucket cft-deployment-bucket --s3-prefix "lambda/ec2-management

aws cloudformation deploy --template-file instance-management-cft-sandbox.yml --stack-name Sandbox-InstanceManagement --capabilities CAPABILITY_NAMED_IAM --parameter-overrides AccountParameter=Staging
Those commands will package your lambda function, inserting the correct CodeUri property.