Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CloudFormation: Create resources if they do not exist, but do not delete them

I have the following CloudFormation template. (It is based off default the template created for running C# Web API in AWS Lambda, but that may not be relevant.)

It creates an AWS Lambda function. The template also creates an IAM Role and a DynamoDB table if the names of existing resources are not supplied as arguments.

That part works. If no names are supplied for the role and table, they are created.

The problem exists when I run the template a second time to perform updates: at this point, my role and table exist, so I provide the names as arguments. However, when CloudFormation runs the second time, the resources it created the first time (the role and table) are deleted.

Is there some way to set up the template such that it will create new resources if they don't exist, but not delete them if they are already present?

I haven't done a lot with CloudFormation, but I did go through the documentation. The closest I found was setting a stack policy, but it doesn't seem to be part of the template. It looks like I would have to do this in the management console after the fact.

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Transform" : "AWS::Serverless-2016-10-31",
  "Description" : "...",

  "Parameters" : {
    "ShouldCreateTable" : {
      "Type" : "String",        
      "AllowedValues" : ["true", "false"],
      "Description" : "If true then the underlying DynamoDB table will be created with the CloudFormation stack."
    },  
    "TableName" : {
        "Type" : "String",
        "Description" : "Name of DynamoDB table to be used for underlying data store. If left blank a new table will be created.",
        "MinLength" : "0"
    },
    "ShouldCreateRole" : {
      "Type" : "String",        
      "AllowedValues" : ["true", "false"],
      "Description" : "If true then the role for the Lambda function will be created with the CloudFormation stack."
    },  
    "RoleARN" : {
        "Type" : "String",
        "Description" : "ARN of the IAM Role used to run the Lambda function. If left blank a new role will be created.",
        "MinLength" : "0"
    }
  },

  "Conditions" : {
    "CreateDynamoTable" : {"Fn::Equals" : [{"Ref" : "ShouldCreateTable"}, "true"]},
    "TableNameGenerated" : {"Fn::Equals" : [{"Ref" : "TableName"}, ""]},
    "CreateRole":{"Fn::Equals" : [{"Ref" : "ShouldCreateRole"}, "true"]},
    "RoleGenerated" : {"Fn::Equals" : [{"Ref" : "RoleARN"}, ""]}
  },

  "Resources" : {

    "Get" : {
      "Type" : "AWS::Serverless::Function",
      "Properties": {
        ...
        "Role": {"Fn::If" : ["CreateRole", {"Fn::GetAtt":["LambdaRole", "Arn"]}, {"Ref":"RoleARN"}]},
        "Environment" : {
          "Variables" : {
            "AppDynamoTable" : { "Fn::If" : ["CreateDynamoTable", {"Ref":"DynamoTable"}, { "Ref" : "TableName" } ] }
          }
        },
        ...
      }
    },

    "LambdaRole":{
        "Type":"AWS::IAM::Role",
        "Condition":"CreateRole",
        "Properties":{
            "ManagedPolicyArns":["arn:aws:iam::aws:policy/AWSLambdaFullAccess"],
            "AssumeRolePolicyDocument": {
               "Version" : "2012-10-17",
               "Statement": [ {
                  "Effect": "Allow",
                  "Principal": {
                     "Service": [ "lambda.amazonaws.com" ]
                  },
                  "Action": [ "sts:AssumeRole" ]
               } ]
            },
            "Policies": [  {
                "PolicyName": "root",
                "PolicyDocument": {
                        "Version": "2012-10-17",
                        "Statement": [
                            {
                                "Effect": "Allow",
                                "Action": [
                                    "dynamodb:Query",
                                    "dynamodb:Scan",
                                    "dynamodb:PutItem",
                                    "dynamodb:GetItem",
                                    "dynamodb:UpdateItem",
                                    "dynamodb:DeleteItem",
                                    "logs:CreateLogGroup",
                                    "logs:CreateLogStream",
                                    "logs:PutLogEvents"
                                ],
                                "Resource": [
                                    "*"
                                ]
                            }
                        ]
                    }
                }
            ]
        }
    },

    "DynamoTable" : {
        "Type" : "AWS::DynamoDB::Table",
        "Condition" : "CreateDynamoTable",
        "Properties" : {
            "TableName" : { "Fn::If" : ["TableNameGenerated", {"Ref" : "AWS::NoValue" }, { "Ref" : "TableName" } ] },
            "AttributeDefinitions": [
                { "AttributeName" : "id", "AttributeType" : "S" }
            ],
            "KeySchema" : [
                { "AttributeName" : "id", "KeyType" : "HASH"}
            ],          
            "ProvisionedThroughput" : { "ReadCapacityUnits" : "5", "WriteCapacityUnits" : "5" }
        }
    }
  },

  "Outputs" : {
    "UnderlyingDynamoTable" : {
        "Value" : { "Fn::If" : ["CreateDynamoTable", {"Ref":"DynamoTable"}, { "Ref" : "TableName" } ] }
    },
    "LambdaRole" : {
        "Value" : {"Fn::If" : ["CreateRole", {"Fn::GetAtt":["LambdaRole", "Arn"]}, {"Ref":"RoleARN"} ] }
    }
  }
}

I could just remove the creation step and create the resource manually before the API Gateway, but it seems like what I am trying to do should be possible.

like image 249
SouthShoreAK Avatar asked Jul 17 '17 15:07

SouthShoreAK


People also ask

What happens by default when one of the resource in CloudFormation stack Cannot be created?

Q: What happens when one of the resources in a stack cannot be created successfully? By default, the “automatic rollback on error” feature is enabled. This will direct CloudFormation to only create or update all resources in your stack if all individual operations succeed.

Does CloudFormation delete existing resources?

When you remove a resource from your template, and update a stack from this template, the resources will be deleted. There is no way to avoid that.

What happens if we delete the resource created by CloudFormation and try to create the same stack?

If you delete a resource that was created by a CloudFormation stack, then your stack fails to update, and you get an error message.

Does deleting a CloudFormation stack delete the resources?

To delete a stack, you run the aws cloudformation delete-stack command. You must specify the name of the stack that you want to delete. When you delete a stack, you delete the stack and all its resources.


1 Answers

When you are updating your existing stack, do not change the parameters. So even when you are updating your stack, set ShouldCreateTable to true.

Yes, it seems counter-intuitive when updating your stack to say "Create a table", when a table already exists, but you need to do that.

The reason is this:

  1. When the stack is created, you set ShouldCreateTable to true, and the template applies it's conditional logic and creates the table as it's own managed resource.
  2. When the stack is updated, you set ShouldCreateTable to false, and the template applies it's conditional logic and determines you no longer need the managed table because you're providing your own now. The resource should be deleted. It does not recognize that the table is the same.

When using your template, only say ShouldCreateTable == false if you are providing your own table that you created.

like image 82
Matt Houser Avatar answered Sep 29 '22 20:09

Matt Houser