Working with CFNDSL in AWS CloudFormation

CFNDSL is a tool created by stevenjack 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"
  }
}
  1. 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.
  2. 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:
    1. AWS::AutoScaling::AutoScalingGroup is AutoScaling_AutoScalingGroup
    2. AWS::IAM::InstanceProfile is IAM_InstanceProfile
    3. AWS::Lambda::Function is Lambda_Function
    4. AWS::RDS::DBSecurityGroup is RDS_DBSecurityGroup
  3. 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.

One thought on “Working with CFNDSL in AWS CloudFormation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s