Infrastructure & Automation

Scheduling automatic deletion of AWS CloudFormation stacks

Have you ever set up a temporary AWS CloudFormation stack for demo or testing purposes and wished you could schedule automatic deletion of the stack rather than having to remember to clean it up after you are done? If so, this blog post is for you.

I present a mechanism to automatically delete your demonstration stacks after a configured Time to Live (TTL) has passed. The architecture diagram of the feature contains the following components:

  • Computed cron expression: An AWS Lambda function that takes as an input a TTL value in minutes and computes a cron expression that will evaluate to true after the specified number of minutes have elapsed since the stack was launched.
  • Cron Event rule: A CloudWatch Events rule that is set up with the computed cron expression and triggers the Delete Named Stack Lambda function when the cron expression evaluates to true.
  • Delete Named Stack: A Lambda function that takes as an input a StackName and runs the AWS CloudFormation DeleteStack API.

Important: For security reasons, the Delete Named Stack Lambda function has been granted restricted permission to delete only the named stack. For this to work and successfully delete your stack, you must create the main stack using the Administrator service role for AWS CloudFormation. I have provided a template that you can use to provision the Administrator service role.

diagram showing services and process

Implementation

I have used an AWS CloudFormation template to build the feature. The template takes two inputs:

Parameter label
(name)
Description
Stack name
(StackName)
Stack name that will be deleted after TTL minutes have elapsed.
Time to live
(TTL)
TTL in minutes for the stack.

You can view the full template by visiting the GitHub repository. Or keep reading to dive into the main components of the solution.

Generating the cron expression from TTL input

I have written a Lambda function, using Python 3.6 runtime, that computes the time delta from the current time until TT: and returns a cron expression string that is required for setting up the CloudWatch Events rule. Then I execute the GenerateCronExpLambda function using an AWS CloudFormation custom resource: GenerateCronExpression.

  GenerateCronExpLambda:
    Type: "AWS::Lambda::Function"
    Properties:
      Code:
        ZipFile: |
          from datetime import datetime, timedelta
          import os
          import logging
          import json
          import cfnresponse
          
          def deletion_time(ttl):
              delete_at_time = datetime.now() + timedelta(minutes=int(ttl))
              hh = delete_at_time.hour
              mm = delete_at_time.minute
              cron_exp = "cron({} {} * * ? *)".format(mm, hh)
              return cron_exp
          
          def handler(event, context):
            print('Received event: %s' % json.dumps(event))
            status = cfnresponse.SUCCESS
            try:
                if event['RequestType'] == 'Delete':
                    cfnresponse.send(event, context, status, {})
                else:
                    ttl = event['ResourceProperties']['ttl']
                    responseData = {}
                    responseData['cron_exp'] = deletion_time(ttl)
                    cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
            except Exception as e:
                logging.error('Exception: %s' % e, exc_info=True)
                status = cfnresponse.FAILED
                cfnresponse.send(event, context, status, {}, None)
      Handler: "index.handler"
      Runtime: "python3.6"
      Timeout: "5"
      Role: !GetAtt BasicLambdaExecutionRole.Arn

  # A Custom resource that uses the lambda function to generate our cluster token
  GenerateCronExpression:
    Type: "Custom::GenerateCronExpression"
    Version: "1.0"
    Properties:
      ServiceToken: !GetAtt GenerateCronExpLambda.Arn
      ttl: !Ref 'TTL'

Setting up the CloudWatch Events rule

I use the computed cron expression as the ScheduleExpression property value when setting up DeleteStackEventRule.

  DeleteStackEventRule:
     DependsOn:
       - DeleteCFNLambda
       - GenerateCronExpression
     Type: "AWS::Events::Rule"
     Properties:
       Description: Delete stack event
       ScheduleExpression: !GetAtt GenerateCronExpression.cron_exp
       State: "ENABLED"
       Targets: 
          - 
            Arn: !GetAtt DeleteCFNLambda.Arn
            Id: 'DeleteCFNLambda'

Deleting the AWS CloudFormation stack after TTL

I create a new Lambda function to delete the stack. It is invoked from the CloudWatch Events rule when TTL has elapsed. The stack name is passed as an environment variable to the Lambda function, and it references the input provided at the time of stack launch. I use the Boto 3 CloudFormation API to delete the stack.

  DeleteCFNLambda:
    Type: "AWS::Lambda::Function"
    DependsOn:
      - DeleteCFNLambdaExecutionRole
    Properties:
      FunctionName: !Sub "DeleteCFNLambda-${StackName}"
      Code:
        ZipFile: |
          import boto3
          import os
          import json
          
          stack_name = os.environ['stackName']
          
          def delete_cfn(stack_name):
              try:
                  cfn = boto3.resource('cloudformation')
                  stack = cfn.Stack(stack_name)
                  stack.delete()
                  return "SUCCESS"
              except:
                  return "ERROR" 
          
          def handler(event, context):
              print("Received event:")
              print(json.dumps(event))
              return delete_cfn(stack_name)
      Environment:
        Variables:
          stackName: !Ref 'StackName'
      Handler: "index.handler"
      Runtime: "python3.6"
      Timeout: "5"
      Role: !GetAtt DeleteCFNLambdaExecutionRole.Arn

Usage

You can use the template in one of the following two ways:

The first way to use the template is to set up the scheduled automatic deletion of any stack that has already been created. For example, in the screenshot, ttl-stack will delete my-demo-stack after 120 minutes.

Note: my-demo-stack must have been created with Administrator service role.

template in console

The second way to use the template is to launch it as a nested stack from your main stack, passing in the name of the parent stack and a desired TTL value to the nested stack. When the TTL has elapsed, DeleteCFNLambda will delete the main stack as well as clean up the nested stack. The following AWS CloudFormation template illustrates this. It creates one AWS Systems Manager parameter resource and consumes the TTL stack as a nested resource, passing its name as a reference and the TTL as 5 minutes.

AWSTemplateFormatVersion: '2010-09-09'
Description: Demo stack, creates one SSM parameter and gets deleted after 5 minutes.
Resources:
  DemoParameter:
    Type: "AWS::SSM::Parameter"
    Properties:
      Type: "String"
      Value: "date"
      Description: "SSM Parameter for running date command."
      AllowedPattern: "^[a-zA-Z]{1,10}$"
  DeleteAfterTTLStack:
    Type: "AWS::CloudFormation::Stack"
    Properties:
      TemplateURL: 'https://s3.amazonaws.com/cftests/cfn-stack-ttl/templates/cfn-stack-ttl.yaml'
      Parameters:
        StackName: !Ref 'AWS::StackName'
        TTL: '5'

Conclusion

I have presented a serverless solution packaged as an AWS CloudFormation template that can be used independently or as a nested resource in your CloudFormation stacks to automatically delete demo stacks after a configured amount of time.

If you want to dig deeper into the code, please check out the GitHub repository and create issues for providing feedback or suggesting enhancements. Open-source code contributions are welcome as pull requests.