Working with CFNDSL in AWS CloudFormation
CFNDSL is a tool originally created by Chris Howe and is an open source project begging for your contributions to improve and grow. The purpose of the tool is to provide a simple DSL for AWS CloudFormation templating with ruby.
If you’re just now joining us I suggest you take a look at the first post in our series about why you should use the DSL.
Using CFNDSL in a nutshell
Since this tool is written in ruby and CloudFormation JSON, interacting with the DSL is simply a matter of using identifiers to interact with the API. The workflow for creating CloudFormation templates with CFNDSL is quite succinct–create your ruby CFNDSL template and then execute the gem on it.
$ cfndsl my-template.rb > my-template.json
Understanding the DSL
Working with the CFNDSL syntax is straightforward once you understand how it’s constructed. If you’re new to ruby when trying to dive into this it can also make things more difficult — so pay good attention here if this is you.
Below is an example from the project’s readme; let’s dissect it.
CloudFormation { Description "Test" Parameter("One") { String Default "Test" MaxLength 15 } Output(:One,FnBase64( Ref("One"))) EC2_Instance(:MyInstance) { ImageId "ami-12345678" } }
- All identifiers are CamelCase names of the resource property keys in the original JSON schema provided by AWS for CloudFormation. For example, “MaxLength” in the instance declaration is unchanged.
- The resource type (e.g. AWS::EC2::Instance) follows a pattern throughout the DSL: “ServiceName_ResourceName” — in this example, EC2_Instance. To elaborate, here’s some more examples:
AWS::AutoScaling::AutoScalingGroup
isAutoScaling_AutoScalingGroup
AWS::IAM::InstanceProfile
isIAM_InstanceProfile
AWS::Lambda::Function
isLambda_Function
AWS::RDS::DBSecurityGroup
isRDS_DBSecurityGroup
- Identifiers throughout the DSL are method invocations — so be prepared to supply multiple parameters where needed.
Pro Tip! If you are unsure what properties a resource takes, how to format their identifiers or which data-type they accept, the project has them all listed nicely in a configuration file located in /lib/cfndsl/aws_types.yaml. This is an invaluable resource when writing templates quickly.
Using AWS Functions
AWS’ CloudFormation comes with some useful syntax for performing various functions while inside the templates. Some examples of this include: string joining, base64 encode, object referencing, and condition functions. You can probably figure that most of these intrinsic functions are mostly unused in CFNDSL since ruby provides this functionality natively, however, they are still made available for use by the DSL.
Let’s take a look at an example we’re all familiar with — adding UserData to an EC2 Instance:
AutoScaling_LaunchConfiguration(:launchConfig) { AssociatePublicIpAddress "False" IamInstanceProfile Ref(:instanceProfile) InstanceType Ref(:instanceType) ImageId Ref(:amiID) SecurityGroups([ Ref(:securityGroup) ]) UserData FnBase64(FnFormat(<<EOF, #!/bin/bash set -xe /opt/SomeApplication/bin/some-whacky-script.sh %{param_name} EOF { “param_name” => “bananas” })) }
There’s quite a few things going on in this example. We’re using an intrinsic function from CloudFormation FnBase64 to encode and a CFNDSL helper function FnFormat to allow us to parameterize the script. In addition to these shown above, there is also support for all the other functions in the DSL, you can check them out on the ruby docs page.
ProTip! Instead of writing your script directly into the template you should consider loading it in from the filesystem and formatting it appropriately. You can reduce the verbosity of the code this way and prevent duplication when you need to reuse the imported script. I’ll leave it to the reader to make this more elegant, but here’s a one-liner to accomplish it:
FnBase64(FnJoin(‘’, File.read(path).split("n").map { |line| line + "n" }))
Another way you can reduce duplication and keep your templates tidy is to extract your IAM Policy Documents into separate files and load them in where needed. Here’s an example:
IAM_Role(:instanceRole) { AssumeRolePolicyDocument(eval(File.read(policy_json_path))) }
In the above snippet the contents of your role-policy.rb file would simply be the JSON schema of your policy document. Ruby will evaluate that properly at template compile time.
Wrapping it up
The next time we meet, we’ll be discussing some of the advanced features of the tool and I’ll share with you some effective ways to troubleshoot your CloudFormation templates.
Stelligent Amazon Pollycast
|