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.
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.
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.
If you delete a resource that was created by a CloudFormation stack, then your stack fails to update, and you get an error message.
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.
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:
ShouldCreateTable
to true
, and the template applies it's conditional logic and creates the table as it's own managed resource.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With