IMHO one of the most important missing pieces in CloudFormation is not having the ability to explicitly specify a SLEEP or WAIT function. It is not that it is totally impossible to the implement the same, except that, it would involve having a hard work around one of which is to inject the wait script via the user data with WaitHandle and WaitConditions on EC2 instances.
"DelayWaitHandle" : {
"Type" : "AWS::CloudFormation::WaitConditionHandle",
"Properties" : {
}
},
"DelayWaitCondition" : {
"Type" : "AWS::CloudFormation::WaitCondition",
"DependsOn" : "RemoteDesktopServer",
"Properties" : {
"Handle" : { "Ref" : "DelayWaitHandle" },
"Timeout" : "3600"
}
},
"RemoteDesktopServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-123456",
"InstanceType": "t2.micro",
"SubnetId": "subnet-12345",
"UserData" : {
"Fn::Base64" : {
"Fn::Join" : ["",
[
"<powershell>\n",
"Start-Sleep -s ", {"Ref" : "DelayTimeSecs" },
"\n",
"cfn-signal.exe ", {"Fn::Base64" : {"Ref" : "DelayWaitHandle"}},
"\n",
"</powershell>\n"
]
]
}
}
},
"DependsOn" : "HFMServer"
}
There is absolutely no downside to the approach and also it is easier to bake the wait script inside the CloudFormation template; the problem would arise if you wanted to have the same WAIT functionality to be implemented for the stacks which do not have EC2 instances as part of the stack.
I tried and succeeded using Amazon Lambda for the purpose instead of EC2. To summarize the implementation; a CloudFormation custom resource would call a lambda function and all that the function would do is to respond after 5 minutes [current maximum execution time for lambda function execution time]. Again, if you need to wait longer than 5 minutes, you would use the DependsOn in CloudFormation and cascade the Custom Resource Calling.
WAIT FUNCTION – AWS LAMBDA
exports.handler = function(event, context) { //Respond with a SUCCESS Signal regardless of create/update/delete
if (event.RequestType == "Delete") {
sendResponse(event, context, "SUCCESS");
return;
}
setTimeout(function() {
console.log('Slept');
Data = {"val1":"val1"}; // Some Dummy Value to be returned back to the CloudFormation Stack
return sendResponse(event, context, "SUCCESS",Data);
}, 294000); //slightly less than 5 mins - currently hardcoded but be possible accept dynamically
};
//Sends response to the pre-signed S3 URL
function sendResponse(event, context, responseStatus, responseData) {
var responseBody = JSON.stringify({
Status: responseStatus,
Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName,
PhysicalResourceId: context.logStreamName,
StackId: event.StackId,
RequestId: event.RequestId,
LogicalResourceId: event.LogicalResourceId,
Data: responseData
});
console.log("RESPONSE BODY:\n", responseBody);
var https = require("https");
var url = require("url");
var parsedUrl = url.parse(event.ResponseURL);
var options = {
hostname: parsedUrl.hostname,
port: 443,
path: parsedUrl.path,
method: "PUT",
headers: {
"content-type": "",
"content-length": responseBody.length
}
};
var request = https.request(options, function(response) {
console.log("STATUS: " + response.statusCode);
console.log("HEADERS: " + JSON.stringify(response.headers));
// Tell AWS Lambda that the function execution is done
context.succeed('Stopped');
});
request.on("error", function(error) {
console.log("sendResponse Error:\n", error);
// Tell AWS Lambda that the function execution is done
context.done();
});
// write data to request body
request.write(responseBody);
request.end();
}
Below is the sample Code where you create a VPC and a Security Group after waiting a 5 minute delay time and 10 minute delay.
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Lambda Delay",
"Parameters": {
"SubnetAZ": {
"Description": "CIDR for AZ",
"Type": "AWS::EC2::AvailabilityZone::Name",
"Default": "us-east-1d"
}
},
"Mappings" : {
"CIDRMappings" : {
"VPC" : { "cidr" : "10.5.0.0/16" }
}
},
"Resources": {
"VPC": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.5.0.0/16",
"Tags": [
{"Key": "Name", "Value": "Delay Test VPC" }
]
}
},
"DelayLambdaFunction1" : {
"Type" : "Custom::DelayLambdaFunction1",
"DependsOn" : "VPC",
"Properties" : {
"ServiceToken" : "arn:aws:lambda:us-east-1:123456789:function:DelayFunction"
}
},
"Delay5MinsSG": {
"Type": "AWS::EC2::SecurityGroup",
"DependsOn" : "VPC",
"Properties": {
"GroupDescription": "After 5 SG 1",
"VpcId": {
"Ref": "VPC"
},
"SecurityGroupIngress": [
{
"IpProtocol": "-1",
"CidrIp":
{
"Fn::FindInMap": [ "CIDRMappings", "VPC", "cidr" ]
}
}
],
"SecurityGroupEgress": [
{
"IpProtocol": "-1",
"CidrIp": "0.0.0.0/0"
}
],
"Tags": [
{
"Key": "Name",
"Value": "Delay5MinsSG"
},
{"Key":"Lambda", "Value":{ "Fn::GetAtt" : ["DelayLambdaFunction1", "val1"]}}
]
}
},
"DelayLambdaFunction2a" : {
"Type" : "Custom::DelayLambdaFunction2a",
"DependsOn" : "DelayLambdaFunction1",
"Properties" : {
"ServiceToken" : "arn:aws:lambda:us-east-1:123456789:function:DelayFunction"
}
},
"DelayLambdaFunction2b" : {
"Type" : "Custom::DelayLambdaFunction2b",
"DependsOn" : "DelayLambdaFunction2a",
"Properties" : {
"ServiceToken" : "arn:aws:lambda:us-east-1:123456789:function:DelayFunction"
}
},
"Delay10MinsSG": {
"Type": "AWS::EC2::SecurityGroup",
"DependsOn" : "Delay5MinsSG",
"Properties": {
"GroupDescription": "Delay10MinsSG",
"VpcId": {
"Ref": "VPC"
},
"SecurityGroupIngress": [
{
"IpProtocol": "-1",
"CidrIp":
{
"Fn::FindInMap": [ "CIDRMappings", "VPC", "cidr" ]
}
}
],
"SecurityGroupEgress": [
{
"IpProtocol": "-1",
"CidrIp": "0.0.0.0/0"
}
],
"Tags": [
{
"Key": "Name",
"Value": "Delay10MinsSG"
},
{"Key":"Lambda", "Value":{ "Fn::Join" : [ " ", [{ "Fn::GetAtt" : ["DelayLambdaFunction2a", "val1"]}, { "Fn::GetAtt" : ["DelayLambdaFunction2b", "val1"]} ] ] }}
]
}
}
}
}
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Pinterest
Email