Skip to content

Instantly share code, notes, and snippets.

@k4kratik
Last active June 6, 2021 12:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save k4kratik/0bb1b8df9d7042f808a0111b637e2bd4 to your computer and use it in GitHub Desktop.
Save k4kratik/0bb1b8df9d7042f808a0111b637e2bd4 to your computer and use it in GitHub Desktop.
AWSTemplateFormatVersion: "2010-09-09"
Description: Schedule automatic deletion of CloudFormation stacks
# Advance way to customize our Parameters inputs, looks very good to the users :)
Metadata:
License: Apache-2.0
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: TTL configuration
Parameters:
- TTL
- Label:
default: Original CF configuration
Parameters:
- InstanceType
- KeyName
- LatestAmiId
- SSHLocation
ParameterLabels:
TTL:
default: Time-to-live
InstanceType:
default: Instance Type
KeyName:
default: Private Key Pair Name
LatestAmiId:
default: Latest AMI ID
SSHLocation:
default: SSH Allowed IP
Parameters:
TTL:
Type: Number
Description: Time-to-live in minutes for the stack.
InstanceType:
Description: WebServer EC2 instance type
Type: String
Default: t3.small
AllowedValues:
[
t2.nano,
t2.micro,
t2.small,
t2.medium,
t2.large,
t2.xlarge,
t2.2xlarge,
t3.nano,
t3.micro,
t3.small,
t3.medium,
t3.large,
t3.xlarge,
t3.2xlarge,
m4.large,
m4.xlarge,
m4.2xlarge,
m4.4xlarge,
m4.10xlarge,
m5.large,
m5.xlarge,
m5.2xlarge,
m5.4xlarge,
c5.large,
c5.xlarge,
c5.2xlarge,
c5.4xlarge,
c5.9xlarge,
g3.8xlarge,
r5.large,
r5.xlarge,
r5.2xlarge,
r5.4xlarge,
r3.12xlarge,
i3.xlarge,
i3.2xlarge,
i3.4xlarge,
i3.8xlarge,
d2.xlarge,
d2.2xlarge,
d2.4xlarge,
d2.8xlarge,
]
ConstraintDescription: must be a valid EC2 instance type.
KeyName:
Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: must be the name of an existing EC2 KeyPair.
SSHLocation:
Description: The IP address range that can be used to SSH to the EC2 instances
Type: String
MinLength: "9"
MaxLength: "18"
Default: 0.0.0.0/0
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
LatestAmiId:
Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
# We will define all resources first which we need for deletion to work, then we will define our actual resources.
# Resources used for auto deletion feature -
# - DeleteCFNLambdaExecutionRole, DeleteCFNLambda, PermissionForDeleteCFNLambda, DeleteStackEventRule (RESOURCES FOR DELETION)
# - BasicLambdaExecutionRole, GenerateCronExpLambda, GenerateCronExpression (RESOURCES FOR CRON EXPRESSION GENERATION)
Resources:
# RESOURCES FOR CRON EXPRESSION GENERATION - START
BasicLambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: ["lambda.amazonaws.com"]
Action: "sts:AssumeRole"
Path: "/"
Policies:
- PolicyName: "lambda_policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "arn:aws:logs:*:*:*"
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
yyyy = delete_at_time.year
month = delete_at_time.month
dd = delete_at_time.day
# minutes hours day month day-of-week year
cron_exp = "cron({} {} {} {} ? {})".format(mm, hh, dd, month, yyyy)
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
GenerateCronExpression:
Type: "Custom::GenerateCronExpression"
Version: "1.0"
Properties:
ServiceToken: !GetAtt GenerateCronExpLambda.Arn
ttl: !Ref "TTL"
# RESOURCES FOR CRON EXPRESSION GENERATION - END
# RESOURCES FOR DELETION - START
DeleteCFNLambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: ["lambda.amazonaws.com"]
Action: "sts:AssumeRole"
Path: "/"
#! I have added this for simplicity, Avoid granting Admin Access to your role, instead fine tune what permission it needs to deleting resources mentioned in the CF Template.
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AdministratorAccess" #! CAUTION
Policies:
- PolicyName: "lambda_policy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "arn:aws:logs:*:*:*"
- Effect: "Allow"
Action:
- "cloudformation:DeleteStack"
Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*"
DeleteCFNLambda:
Type: "AWS::Lambda::Function"
DependsOn:
- DeleteCFNLambdaExecutionRole
Properties:
FunctionName: !Sub "DeleteCFNLambda-${AWS::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 "AWS::StackName"
Handler: "index.handler"
Runtime: "python3.6"
Timeout: "5"
Role: !GetAtt DeleteCFNLambdaExecutionRole.Arn
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"
PermissionForDeleteCFNLambda:
Type: "AWS::Lambda::Permission"
Properties:
FunctionName: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:DeleteCFNLambda-${AWS::StackName}"
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn: !GetAtt DeleteStackEventRule.Arn
# RESOURCES FOR DELETION - END
#? Actual Resources I wanted to created Originally - START
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref "InstanceType"
SecurityGroups: [!Ref "InstanceSecurityGroup"]
KeyName: !Ref "KeyName"
ImageId: !Ref "LatestAmiId"
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable SSH access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp: !Ref "SSHLocation"
IPAddress:
Type: AWS::EC2::EIP
IPAssoc:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId: !Ref "EC2Instance"
EIP: !Ref "IPAddress"
#? Actual Resources - END
Outputs:
InstanceId:
Description: InstanceId of the newly created EC2 instance
Value: !Ref "EC2Instance"
InstanceIPAddress:
Description: IP address of the newly created EC2 instance
Value: !GetAtt EC2Instance.PublicIp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment