Using cfn-lint to validate your CloudFormation template
Tags: aws, cloudformation
Published: 2020-05-30 11:22:05

If you've used CloudFormation before you'll already know about the `validate-template` command on AWS CLI. If you've used this command before, you might also know that this validator *only* checks the syntax of your template. I can think of countless cases where my deployments failed due to a simple mistake that wasn't caught by this validator. This post will use a simple CloudFormation template to explain how `cfn-lint` can help you identify various errors in your stack before deploying it.

# Sections
- [Example Template Setup] (#chapter-1)
- [cfn-lint Usage] (#chapter-2)

# Example Template Setup <a name="chapter-1"></a>
```
AWSTemplateFormatVersion: "2010-09-09"
Description: Example template
Parameters:
  Environment:
    Type: String
    Default: 'dev'
Conditions:
  IsDev: !Equals [ !Ref Environment, 'dev' ]
Resources:
  Function:
    Type: AWS::Lambda::Function
    Condition: IsDev
    Properties:
      Role: !Ref FunctionRole # Role property requires an ARN, not the resource ID
      Handler: index.handler
      Code:
        S3Bucket: my-bucket
        S3Key: function.zip
      MemorySize: 3000 # Invalid memory size
      Runtime: nodejs12.x
      Timeout: 5
      TracingConfig:
        Mode: !If [IsTracingActive, 'true', 'false'] # Undefined condition IsTracingActive
  FunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Policies:
        - PolicyName: all
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: '*'
                Resource: '*'
Outputs:
  FunctionArn: # Export without matching condition to resource
    Description: Lambda ARN
    Value: !GetAtt Function.Arn
    Export:
      Name: !Sub ${Environment}-lambda-arn
```
This is the template that we'll use to test `cfn-lint`. It sets up a simple Lambda function with an IAM role and the lambda ARN is exported as an output. Before we start, let's highlight some of the issues with this template:

1. `Role` property on the lambda is not an ARN. Running a `Ref` on an IAM role will return the logical ID of the role.

2. `MemorySize` of 3000 is invalid. Lambda memory size can range from 128 MB to 3008 MB in 64 MB increments.

3. Condition `IsTracingActive` is undefined.

4. `LambdaFunction` is only deployed when the condition `IsDev` is true, but it's export does not have the condition defined.

Here's the output of the built-in `validate-template` command for this stack:

```
{
    "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]", 
    "Description": "Example template", 
    "Parameters": [
        {
            "DefaultValue": "dev", 
            "NoEcho": false, 
            "ParameterKey": "Environment"
        }
    ], 
    "Capabilities": [
        "CAPABILITY_IAM"
    ]
}
```
As you can see, the validator does not identify any issues with this stack. Deploying this stack however will result in a failure. Let's see what `cfn-lint` can do for us in the next section.


# cfn-lint Usage <a name="chapter-2"></a>
`cfn-lint` is a command line tool which examines your CloudFormation template and returns various suggestions. Let's run this tool and see what it has to say about our example stack.

```
> cfn-lint template.yml

E3008 Property "Role" has no valid Refs to Resources at Resources/Function/Properties/Role/Ref
template.yml:14:7

E2530 You must specify a value that is greater than or equal to 128, and it must be a multiple of 64. You cannot specify a size larger than 3008. Error at Resources/Function/Properties/MemorySize
template.yml:19:7

E8002 Condition IsTracingActive is not defined.
template.yml:23:9

W1001 GetAtt to resource "Function" that may not be available when condition "IsDev" is False at Outputs/FunctionArn/Value/Fn::GetAtt
template.yml:47:5
```

As you can see, `cfn-lint` output covers all 4 issues that we previously highlighted in the form of warnings and errors. Errors indicate issues that will prevent your CloudFormation stack from deploying, and warnings indicate potential issues in your stack that you might want to think about.

It's worth noting that not all warnings are necessarily highlighting an issue that you *must* fix. Everything depends on the context. If you wish to ignore warnings from the output, you can use the ignore_checks argument:
```
> cfn-lint template.yml -i W
```

`cfn-lint` also comes with extensive configuration support which allows you to customise your linting rules.

For a complete set of commands and installation guides, please refer to the [github repository] (https://github.com/aws-cloudformation/cfn-python-lint).


# Conclusion
We looked at how `cfn-lint` can be used as an additional validator for our CloudFormation templates. It provides a way to check the semantics of our templates and helps us catch mistakes before making a deployment. Some of the CI pipelines that I work on uses `cfn-lint` to decide whether the deployment step should execute. I hope this tool can help you iterate quicker and make your CloudFormation development experience a bit more enjoyable.