cloudformation-tool
A pre-compiler for CloudFormation YAML files, allowing modularization of large CloudFormation templates.
By breaking a large template into distinct modules, including allowing parts to be loaded from git submodules or even to be downloaded on the fly, very large CloudFormation configurations become manageable and useful.
Syntax
This tool is based on the CloudFormation YAML syntax (as it is a bit more human friendly than the original JSON syntax) and extend it with a few more operation to allow for the modularization of templates. The following additional syntaxes are supported:
Including Additional Sub-templates
The CloudFormation pre-compiler introduces a new top-level element called Include
.
This element specifies a list of additional template files to be loaded and merged into the current template file.
Loading is done using the same file resolution rules as the
compile
CLI command:
- resolution is done relative to the directory of the current file
- if the path specified is missing the
.yaml
extension and a valid file with the extension exist, it will be loaded instead. - if the path specified is a directory, the file
cloud-formation.yaml
will be loaded from that directory
The merged file must be a valid CloudFormation template - it should have at least a top-level
Resources
entry and can have any other top-level entry that a standard CloudFormation
template can include (see below regarding the merging of parameters). The sub-template will
be merged back to the top template without nesting - e.g. all resources specified in a
sub-template are standard resources in the top-level Resources
list.
As logical resource names as well as parameters are merged into a single name space, these can be referenced directly from any file. Unless as specified for parameter merging below, the names used can be directly addressed as done in a normal CloudFormation template.
Parameter merging
A sub-template may specify parameters just like a standard CloudFormation template, and as long as each parameter is specified uniquely (i.e. only once) in the entire set of sub-templates that are being loaded, then they are just merged into a flat list and all parameters can be used equally from all sub-templates.
If the same parameter name is mentioned in more than one file, then special merging rules take effect:
- If the same parameter is named in more than one file, and in all instances the default value is exactly the same, than the merging is done normally - we basically ignore the duplicate settings.
- If the same parameter is named in more than one file, and uses different default value, then the new parameter is renamed using a path-specific template (i.e. based on the path to the sub-template where the new copy was encountered) and any use of that parameter in the current sub-template is also renamed automatically, as if there is full scoping of the nested sub-template (though the mangled name is not very readable due to YAML restrictions on the available characters for names). Uses of the duplicate parameter in sub-templates other than the one where it was defined should use the mangled form if they mean the new parameter or the original form if they mean the original parameter
Example:
AWSTemplateFormatVersion: "2010-09-09"
Description: "Example cloud formation template"
Parameters:
DomainName:
Description: "The DNS domain name for the system"
Type: String
Default: example.com
AMI:
Description: "The AMI ID for the image to deploy"
Type: String
Default: ami-af4333cf
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.20.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
SecurityGroupExample:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPC
GroupDescription: example security group
SecurityGroupIngress:
- { IpProtocol: icmp, CidrIp: 0.0.0.0/0, FromPort: -1, ToPort: -1 }
- { IpProtocol: tcp, CidrIp: 0.0.0.0/0, FromPort: 22, ToPort: 22 }
Include:
- network
- servers/global
- outputs.yaml
Logical resource merging
Logical resources are not mangled - if multiple resources with the same name are defined in multiple sub-templates, this is an error that would cause the tool to abort.
Loading user data files
When specifying a user-data block for a LaunchConfiguration
resource or an Instance
resource, the user-data can be loaded from an external YAML file (only YAML formatted user-data
is currently supported, sorry) by specifying the UserData
element as a map with the single
field File
that is set with the relative path to the user-data file. The user-data file is
expected to be a cloud-init configuration file with the default extension .init
.
Alternatively, the field FileTemplate
can be used under UserData
to load an external cloud-init configuration file that includes variable place holders for the
(CloudFormation intrinsic function Sub)[http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html]. The FileTemplate
mode supports all
the features described above as well as it performs the parsing detailed below, except
compression and S3 offloading - as doing so prevents CloudFormation from performing the
substitution operation. As a result, if the resulting cloud-init file is larger than 16KB
you should expect that the template will fail to create the server.
User data file parsing
The reference file will be loaded and parsed as a ("Cloud Config data" file)[http://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data]
with the special write_files
and write_directories
enhancement (see below). The result is then checked that it
does not exceed the user-data size limitation. If the file is bigger than can fit in the AWS
user-data block, it will first be compressed using gzip and if it is still too large, it will
be uploaded to S3 and the user-data block will be set with a cloud-init download reference to
the S3 object.
Enhanced write_files
The ("Cloud Config data" format supports deploying files)[http://cloudinit.readthedocs.io/en/latest/topics/examples.html#writing-out-arbitrary-files]
into the instance using the write_files
module. This normally requires the file content
to be embedded directly into the cloud-config YAML format. The cloudformation-tool supports
specifying external files to be loaded, allowing deployed files to be managed externally to
the cloud-config data (for example, if you enjoy using syntax aware editors to edit them,
are binary, or just too large).
To use an external file in write_files
instead of specifying the file content using the
content
field, use a file
field to specify the relative path to the file to be loaded.
write_directories
In the case that you want to deploy multiple files to the same directory, instead of
listing each and every file as a write_files
entries (which can get tedious after
a while, even with the file
extension), cftool
offers another cloud-init extension
as a category named write_directories
.
The write_directories
section is a list where each entry specifies a local
directory that would be deployed (with all files it includes, recursively - so make
sure it only includes files you want to deploy) to a target directory on the deployed
server. For each entry specify a source
attribute that points to a local directory
relative to the location of the cloud-init file, and a target
attribute set to an
absolute URL to where to deploy the source directory.
Example:
cloud-formation.yaml
:
LaunchConfigurationForServer:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
AssociatePublicIpAddress: true
IamInstanceProfile: !Ref InstanceProfileForServer
ImageId: !Ref AMI # read from parameters
InstanceType: !Ref InstanceType # read from parameters
KeyName: !Ref KeyName # read from parameters
SecurityGroups:
- Ref: SecurityGroupExample
UserData:
File: config.init
config.init
:
#cloud-config
write_files:
- path: /etc/default/my-app
permissions: '0755'
file: my-app.config
write_directory:
- source: my-app-data
target: /usr/share/my-app
Loading Lambda code
When specifying the Code
property of a AWS::Lambda::Function
resource, instead of
specifying the S3 bucket and object key, either of the following fields may be used:
- The field
URL
may be used to specify an HTTP URL from which the code is to be uploaded to AWS Lambda. The tool will download the code file from the specified URL, upload it to S3 and specify the correct S3 location for CloudFormation. - The field
Path
may be used to specify a local file or directory containing the code to be uploaded. If the path specifies a directory, it will be compressed and uploaded to S3 as a Zip file. If the path is a single file, it will be converted to aZipFile
, allowing implicit use of the CloudFormationcfn-response
module and the AWS SDK, but the file is also subject to allZipFile
restrictions - such as limited to 4KB size.
Example:
LambdaExample:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Join [ "-", [ route53-update, !Ref SystemTag ] ]
Runtime: java8
Code:
URL: https://github.com/GreenfieldTech/lambda-route53-updates/releases/download/0.2.5/lambda-route53-updates-0.2.5.jar
Description: Update DNS with autoscaling servers
MemorySize: 256
Timeout: 60
Handler: net.gftc.aws.route53.NotifyRecords
Environment:
Variables: # set variables here, see lambda-route53-updates for documentation ...
Role: !GetAtt [ LambdaExecutionRole, Arn ]
Caching
Some resource compilation may require uploading to S3, such as Lambda code or cloud-init setup files. In such cases, the tool will take precaution not to update the uploaded file (and thus cause a CloudFormation update) unless the content has changed. This is done by comparing the MD5 hash of the compiled object with the MD5 hash of the previously uploaded object, and re-uploading and updating CloudFormation only if a change in the actual content as been detected.
Usage
The CloudFormation Tool uses a "sub-command CLI syntax" (like GIT, for example).
Usage: cftool [options] <command> <command-options...>
The following commands are supported:
list
- List names and status of existing CloudFormation stacksparameters
- List parameters defined in the specified CloudFormation template and their default values.compile
- Compile a CloudFormation template set (including all caching needed) and output the resulting valid CloudFormation template to the console.create
- Create or update a CloudFormation stack by compiling the specified template set and uploading it to CloudFormation. If no stack with the specified name exists, then a new stack will be created, otherwise the existing stack will be updated. After sending the template to CloudFormation, the tool will immediately startmonitor
mode until the operation has completed successfully or with an error. Parameters can be specified on the command line - like for the AWS CLI - or loaded from a file or URL.monitor
- Track and display ongoing events for the named stack.status
- Check if the names stack exists or notdelete
- Delete the specified stack. After issuing the delete command, the tool will immediately startmonitor
mode until the operation has completed.servers
- List EC2 instances created and managed by this stack.
Please see the specific help for each command by running cftool <command> --help
for
more details and specific options.
Region Selection
The AWS region to be used can be select by specifying top level option (i.e. before the command name) -r <region>
, by providing the standard environment variable AWS_DEFAULT_REGION
or it will default to us-west-1
Credentials Selection
The tool will use the standard AWS credentials selection process, except when you want to use AWS CLI configured credential profiles, you may select to use a profile other than "default" by specifying the top level option (i.e. before the command name) -p <profile>
, by providing the standard environment variable AWS_DEFAULT_PROFILE
or by having a file called .awsprofile
- whose content is the name of a valid AWS REGION - in a parent directory (at any level up to the root directory).