Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
240 lines (239 sloc) 7.97 KB
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Creates EC2 instance then an AMI from the instance",
"Parameters": {
"InstanceName": {
"Description": "Name of instance and AMI to generate",
"Type": "String",
"MinLength": "0",
"MaxLength": "256",
"Default": "my-ami-instance"
},
"InstanceType": {
"Description": "Instance type to launch EC2 instances.",
"Type": "String",
"Default": "t2.micro",
"AllowedValues": [
"t2.micro",
"t2.medium"
]
}
},
"Mappings": {
"RegionMap": {
"us-east-1": {
"BaseAMI": "ami-0b33d91d"
},
"us-east-2": {
"BaseAMI": "ami-c55673a0"
},
"us-west-1": {
"BaseAMI": "ami-165a0876"
},
"us-west-2": {
"BaseAMI": "ami-f173cc91"
}
}
},
"Resources": {
"AMICreate": {
"Type": "AWS::CloudFormation::WaitCondition",
"CreationPolicy": {
"ResourceSignal": {
"Timeout": "PT10M"
}
}
},
"Ec2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"BlockDeviceMappings": [
{
"DeviceName": "/dev/xvda",
"Ebs": {
"DeleteOnTermination": "true",
"VolumeType": "gp2",
"VolumeSize": "8"
}
}
],
"ImageId": { "Fn::FindInMap": [ "RegionMap", { "Ref": "AWS::Region" }, "BaseAMI" ]},
"InstanceType": { "Ref": "InstanceType" },
"Tags": [
{
"Key": "Name",
"Value": { "Ref": "InstanceName" }
}
],
"UserData": {
"Fn::Base64": { "Fn::Join" : ["", [
"#!/bin/bash\n",
"#\n",
"yum update -y\n",
"/opt/aws/bin/cfn-signal -e $? --region ", {"Ref": "AWS::Region"}, " --stack ", {"Ref": "AWS::StackName"}, " --resource AMICreate\n",
"shutdown -h now\n"
]
]
}
}
}
},
"AMI": {
"Type": "Custom::AMI",
"DependsOn": "AMICreate",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": "AMIFunction.Arn"
},
"InstanceId": {
"Ref": "Ec2Instance"
}
}
},
"AMIFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": "LambdaExecutionRole.Arn"
},
"Code": {
"ZipFile": { "Fn::Join": ["", [
"var response = require('cfn-response');\n",
"var AWS = require('aws-sdk');\n",
"exports.handler = function(event, context) {\n",
" console.log(\"Request received:\\n\", JSON.stringify(event));\n",
" var physicalId = event.PhysicalResourceId;\n",
" function success(data) {\n",
" return response.send(event, context, response.SUCCESS, data, physicalId);\n",
" }\n",
" function failed(e) {\n",
" return response.send(event, context, response.FAILED, e, physicalId);\n",
" }\n",
" // Call ec2.waitFor, continuing if not finished before Lambda function timeout.\n",
" function wait(waiter) {\n",
" console.log(\"Waiting: \", JSON.stringify(waiter));\n",
" event.waiter = waiter;\n",
" event.PhysicalResourceId = physicalId;\n",
" var request = ec2.waitFor(waiter.state, waiter.params);\n",
" setTimeout(()=>{\n",
" request.abort();\n",
" console.log(\"Timeout reached, continuing function. Params:\\n\", JSON.stringify(event));\n",
" var lambda = new AWS.Lambda();\n",
" lambda.invoke({\n",
" FunctionName: context.invokedFunctionArn,\n",
" InvocationType: 'Event',\n",
" Payload: JSON.stringify(event)\n",
" }).promise().then((data)=>context.done()).catch((err)=>context.fail(err));\n",
" }, context.getRemainingTimeInMillis() - 5000);\n",
" return request.promise().catch((err)=>\n",
" (err.code == 'RequestAbortedError') ?\n",
" new Promise(()=>context.done()) :\n",
" Promise.reject(err)\n",
" );\n",
" }\n",
" var ec2 = new AWS.EC2(),\n",
" instanceId = event.ResourceProperties.InstanceId;\n",
" if (event.waiter) {\n",
" wait(event.waiter).then((data)=>success({})).catch((err)=>failed(err));\n",
" } else if (event.RequestType == 'Create' || event.RequestType == 'Update') {\n",
" if (!instanceId) { failed('InstanceID required'); }\n",
" ec2.waitFor('instanceStopped', {InstanceIds: [instanceId]}).promise()\n",
" .then((data)=>\n",
" ec2.createImage({\n",
" InstanceId: instanceId,\n",
" Name: '", { "Ref": "InstanceName"}, "'\n",
" }).promise()\n",
" ).then((data)=>\n",
" wait({\n",
" state: 'imageAvailable',\n",
" params: {ImageIds: [physicalId = data.ImageId]}\n",
" })\n",
" ).then((data)=>success({})).catch((err)=>failed(err));\n",
" } else if (event.RequestType == 'Delete') {\n",
" if (physicalId.indexOf('ami-') !== 0) { return success({});}\n",
" ec2.describeImages({ImageIds: [physicalId]}).promise()\n",
" .then((data)=>\n",
" (data.Images.length === 0) ? success({}) :\n",
" ec2.deregisterImage({ImageId: physicalId}).promise()\n",
" ).then((data)=>\n",
" ec2.describeSnapshots({Filters: [{\n",
" Name: 'description',\n",
" Values: [\"*\" + physicalId + \"*\"]\n",
" }]}).promise()\n",
" ).then((data)=>\n",
" (data.Snapshots.length === 0) ? success({}) :\n",
" ec2.deleteSnapshot({SnapshotId: data.Snapshots[0].SnapshotId}).promise()\n",
" ).then((data)=>success({})).catch((err)=>failed(err));\n",
" }\n",
"};\n"
]]
}
},
"Runtime": "nodejs4.3",
"Timeout": 300
}
},
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
"arn:aws:iam::aws:policy/service-role/AWSLambdaRole"
],
"Policies": [
{
"PolicyName": "EC2Policy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeImages",
"ec2:CreateImage",
"ec2:DeregisterImage",
"ec2:DescribeSnapshots",
"ec2:DeleteSnapshot"
],
"Resource": [
"*"
]
}
]
}
}
]
}
}
},
"Outputs": {
"Ec2Instance": {
"Description": "Instance ID of AMI Master",
"Value": { "Ref": "Ec2Instance" }
},
"AMI": {
"Value": {
"Ref": "AMI"
}
}
}
}