Learning the CloudFormation Template Language
This lesson references the code in the aws-connectedcar-dotnet-serverless repository. If you're new to this course, see the introduction for information about setting up your workstation and getting the sample code.
Before we dive into all the details with the code tutorials, let's go through some basics for the CloudFormation template language.
Starting with Templates
CloudFormation templates are JSON- or YAML-formatted files in which you define the AWS resources to be created or updated, along with input parameters and named output values. Templates can also define stack resources that reference other templates, to create what are called nested stacks.
Choosing Between JSON & YAML Formats
With CloudFormation you have the option to use the JSON format for the templates. But the sample code in this course uses the YAML format throughout for two reasons. First, YAML is a cleaner, more readable syntax that I believe is better suited to this kind of configuration. Second, there are cases with CloudFormation where you need to embed snippets of JSON configuration within resource properties. When you embed a JSON snippet within a JSON template, the snippet has to be escaped which makes it more or less unreadable. Choosing the YAML format for templates eliminates this problem.
Note: there is a tool that lets you convert between the two formats on the CloudFormation section of the console.
Defining Template Parameters
Templates can include a set of named parameters of a variety of different types, including primitives like strings or numbers, as well as complex types. Parameters can also specify default values, lists of allowed values, and can include regex-style value constraints. The code below shows some of these options. For example, the ServiceName parameter on lines 7-10 demonstrates the use of a default value. Parameters with defaults like this can be omitted from the values specified from the command line. Another example is the EnvironmentName parameter on lines 11-17, which has a list of allowed values. If you supply a parameter value that doesn’t match the allowed values when calling the "create-stack" or "update-stack" CloudFormation commands, then the command will fail before it creates or updates any resources.
Defining Template Outputs
Templates can also define a set of named outputs, as shown below from the same template. Some resource properties have values that are generated during the deployment process, such as API Gateway IDs or Cognito User Pool IDs. Named outputs provide a way to query for the value of these generated property values after a stack has been created.
Using Functions & Pseudo Parameters
All of the elements in a template with symbolic names, such as parameters or resources, have default properties whose values can be referenced using the !Ref function. The template language also includes a range of intrinsic functions to do things like interpolate strings, combine string fragments, or get values for named resource attributes. Additionally, there are a selection of pseudo parameters that you can reference when interpolating strings. These pseudo parameters can return values at build-time for things like the currently used AWS account ID or region name.
Here’s a code sample from the services.yaml template, showing some of these language features. Line 57, at the bottom, shows the !Ref function in use; Lines 32 and 44 show the !Sub function performing string interpolation:
Defining Resources
Between the parameters at the top and the outputs at the bottom, templates always include one or more resources that they’re defining. Each resource has a logical name that you can reference elsewhere in the template using the !Ref function. Under this logical name, and indented one level, you’ll always find a type declaration. The type declaration determines what kind of resource gets created or updated by CloudFormation, and it defines the structure of elements for that resource in the template. As shown below on lines 66-73, these elements are always children of the Properties element that follows the type declaration:
The first step when defining resources in CloudFormation is to search the AWS documentation for the target resource type. For example, searching for “cloudFormation dynamoDb table” will lead you to the documentation for the AWS::DynamoDB::Table resource type, shown below:
This documentation outlines the elements required for the resource in question, but there’s three additional ways that this documentation is important. First, it tells you which attribute or property is the default that’s returned by the !Ref function. Sometimes this is the user-define name, sometimes it’s a generated ID, and other times it’s the ARN. So you need to check the documentation to be sure.
Second, there is usually a list of attributes whose values can be obtained by name using the !GetAtt function (see the bottom half of the screen shot below). The documentation will itemize those that are available with this function. Lastly, some resource attributes will have suitable default values you can use. For those cases where you opt to use the default values, you don’t have to include the attributes in your resource definition.
Using SAM Templates
One last topic to note is the Serverless Application Model (SAM) template format. This is a special format that provides some extra, more concise resource types made for defining APIs with Lambda event handlers. Line two in the example below shows the Transform declaration indicating that it's a SAM template. You can still use standard resource types in a SAM template. The difference is that the SAM-specific resource types are transformed on-the-fly by CloudFormation to the standard types when stacks are created or updated.