DEV Community

Thomas Taylor for AWS Community Builders

Posted on • Originally published at how.wtf

A guide for deploying CloudFormation with CLI using Rain

Last year, I wrote a post describing the difference between aws cloudformation deploy and aws cloudformation create-stack. I concluded that the easiest method for deploying CloudFormation templates was cloudformation deploy since it handled change sets on your behalf.

My opinion has changed; I discovered a new tool named Rain that is maintained by AWS.

What is Rain

For folks needing to deploy raw CloudFormation templates, rain is your hero. The tool brings you a lot of power:

  1. Allows input parameters
  2. Showcases real-time updates for stack deployments
  3. Filters deployment logs sensibly
  4. Provides the ability to generate CloudFormation templates via AI
  5. Manipulates stack sets
  6. Formats template files

And many more features! As a previous user of CloudFormation, this tool appears amazing.

Installing Rain

There are a few options for installing Rain, but I'll use the golang install for this tutorial.

go install github.com/aws-cloudformation/rain/cmd/rain@latest
Enter fullscreen mode Exit fullscreen mode

For MacOS users, brew is easy:

brew install rain
Enter fullscreen mode Exit fullscreen mode

The remaining releases are featured in their GitHub repository.

Build CloudFormation stacks using Rain

The build command accepts a prompt parameter to generate CloudFormation templates using AI. Let's use it for generating a CloudFormation template with a S3 bucket.

rain build -p "A s3 bucket that is secure"
Enter fullscreen mode Exit fullscreen mode

Output:

AWSTemplateFormatVersion: 2010-09-09
Description: Create an S3 bucket

Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-secure-bucket
      AccessControl: Private
      VersioningConfiguration:
        Status: Enabled
Enter fullscreen mode Exit fullscreen mode

Although the AccessControl property is a considered legacy, it's great that the template was generated on my behalf!

In addition, the build property will generate a skeleton for resources as well:

rain build AWS::Route53::CidrCollection
Enter fullscreen mode Exit fullscreen mode

or

rain build CidrCollection
Enter fullscreen mode Exit fullscreen mode

This outputs a template with all the parameters and dummy CHANGEME values for a Route53 CIDR collection:

AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Resources:
  MyCidrCollection:
    Type: AWS::Route53::CidrCollection
    Properties:
      Locations:
        - CidrList:
            - CHANGEME
          LocationName: CHANGEME
      Name: CHANGEME

Outputs:
  MyCidrCollectionArn:
    Value: !GetAtt MyCidrCollection.Arn

  MyCidrCollectionId:
    Value: !GetAtt MyCidrCollection.Id
Enter fullscreen mode Exit fullscreen mode

If you prefer json, there's a flag for that:

rain build CidrCollection --json
Enter fullscreen mode Exit fullscreen mode

If you supply the command an ambigious term like "collection", it provides you a warning:

rain build collection
Enter fullscreen mode Exit fullscreen mode

Output:

Ambiguous resource type 'collection'; could be any of:
  AWS::DevOpsGuru::ResourceCollection
  AWS::Location::GeofenceCollection
  AWS::OpenSearchServerless::Collection
  AWS::Rekognition::Collection
  AWS::Route53::CidrCollection
Enter fullscreen mode Exit fullscreen mode

Bootstrap using Rain

Before deploying stacks into an environment, let's run the bootstrap command:

rain bootstrap
Enter fullscreen mode Exit fullscreen mode

Output:

Rain needs to create an S3 bucket called 'rain-artifacts-012345678901-us-east-1'. Continue? (Y/n)
Enter fullscreen mode Exit fullscreen mode

This command creates an artifacts bucket that rain references when deploying stacks.

It has the following naming convention:

rain-artifacts-{accountId}-{regionName}
Enter fullscreen mode Exit fullscreen mode

Deploying stacks using Rain

In this section, we'll explore deploying templates with rain.

Deploying an S3 bucket

Using the S3 template rain build generated, let's deploy it!

This is my current directory structure:

project
└── s3.yml
Enter fullscreen mode Exit fullscreen mode

This is the command I'll use to deploy the s3.yml with a stack name of s3-bucket-stack:

rain deploy s3.yml s3-bucket-stack
Enter fullscreen mode Exit fullscreen mode

If rain bootstrap was not executed before deploying, it'll prompt you to do so.

My deployment failed with the following message:

CloudFormation will make the following changes:
Stack s3-bucket-stack:
  + AWS::S3::Bucket MyS3Bucket
Do you wish to continue? (Y/n)
Deploying template 's3.yml' as stack 's3-bucket-stack' in us-east-1.
Stack s3-bucket-stack: ROLLBACK_COMPLETE
Messages:
  - MyS3Bucket: Resource handler returned message: "my-secure-bucket already exists (Service: S3, Status Code: 0, Request ID: null)" (RequestToken: c4a528c1-2fa0-e75e-efb1-5cfacc2aebd6, HandlerErrorCode: AlreadyExists)
failed deploying stack 's3-bucket-stack'
Enter fullscreen mode Exit fullscreen mode

Since S3 is a global service, the bucket name my-secure-bucket exists in another account or region. I modified the BucketName to be how-wtf-rain-bucket and issued the same command:

Deleted existing, empty stack.
CloudFormation will make the following changes:
Stack s3-bucket-stack:
  + AWS::S3::Bucket MyS3Bucket
Do you wish to continue? (Y/n)
Deploying template 's3.yml' as stack 's3-bucket-stack' in us-east-1.
Stack s3-bucket-stack: CREATE_COMPLETE
Successfully deployed s3-bucket-stack
Enter fullscreen mode Exit fullscreen mode

A couple of awesome things to note:

  1. It handled the failure of the stack gracefully and gave me the exact issue
  2. I deployed again and it took care of everything
  3. I did this all without touching the AWS console.

I am impressed by this tool already!

Deploying a Lambda function

I want to test the rain pkg command with lambda artifacts. Like the cloudformation package command, we specify a lambda directory or handler and it'll handle zipping and uploading it!

I created a file named lambda.yml with the following contents:

AWSTemplateFormatVersion: 2010-09-09
Description: Create an lambda function

Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  MyLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: lambdafunction/
      FunctionName: MyLovelyFunction
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn 
      Runtime: python3.12

Outputs:
  LambdaFunctionArn:
    Description: The ARN of the Lambda function
    Value: !GetAtt MyLambdaFunction.Arn
Enter fullscreen mode Exit fullscreen mode

This stack creates two resources:

  1. A lambda execution role with basic permissions granted by the AWSLambdaBasicExecutionRole policy
  2. A lambda function with the lambdafunction/ contents

And outputs the lambda function arn.

I ran the fmt command with the --verify flag:

rain fmt lambda.yml --verify
Enter fullscreen mode Exit fullscreen mode

The --verify command is best saved for CI environments since it outputs a 0 or 1 depending if the formatting is correct.

Output:

lambda.yml: would reformat
Enter fullscreen mode Exit fullscreen mode

I ran it with the --write command so that it fixed them:

rain fmt lambda.yml --write
Enter fullscreen mode Exit fullscreen mode

Let's run the pkg command with an output file named out.yml:

rain pkg lambda.yml --output out.yml
Enter fullscreen mode Exit fullscreen mode

Here is a snapshot of my directory structure after doing the pkg command:

project
├── lambda.yml
├── lambdafunction
│   └── index.py
└── out.yml
Enter fullscreen mode Exit fullscreen mode

Now let's deploy the out.yml template:

rain deploy out.yml lambda-function-stack
Enter fullscreen mode Exit fullscreen mode

Output:

CloudFormation will make the following changes:
Stack lambda-function-stack:
  + AWS::IAM::Role LambdaExecutionRole
  + AWS::Lambda::Function MyLambdaFunction
Do you wish to continue? (Y/n)
Deploying template 'out.yml' as stack 'lambda-function-stack' in us-east-1.
Stack lambda-function-stack: CREATE_COMPLETE
  Outputs:
    LambdaFunctionArn: arn:aws:lambda:us-east-1:012345678901:function:MyLovelyFunction # The ARN of the Lambda function
Successfully deployed lambda-function-stack
Enter fullscreen mode Exit fullscreen mode

Using the LambdaFunctionArn output, let's invoke the function to ensure everything worked correctly:

aws lambda invoke --function-name MyLovelyFunction response.txt
Enter fullscreen mode Exit fullscreen mode

Here is the resulting response.txt:

"Hello world!"
Enter fullscreen mode Exit fullscreen mode

View logs of deployments

Another powerful feature of rain is that it can display sensible logs for stack deployments.

Using log command

Using the lambda function stack from the prior section, we can inspect the logs using the log command:

rain log lambda-function-stack
Enter fullscreen mode Exit fullscreen mode

Output:

No interesting log messages to display. To see everything, use the --all flag
Enter fullscreen mode Exit fullscreen mode

Since there were no "interesting" log messages to display, let's see them all!

rain log lambda-function-stack --all
Enter fullscreen mode Exit fullscreen mode

Output:

Jan 14 20:08:52 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) CREATE_COMPLETE
Jan 14 20:08:50 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_COMPLETE
Jan 14 20:08:43 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_IN_PROGRESS "Resource creation Initiated"
Jan 14 20:08:42 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_IN_PROGRESS
Jan 14 20:08:40 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_COMPLETE
Jan 14 20:08:24 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_IN_PROGRESS "Resource creation Initiated"
Jan 14 20:08:22 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_IN_PROGRESS
Jan 14 20:08:19 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) CREATE_IN_PROGRESS "User Initiated"
Jan 14 20:08:12 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) REVIEW_IN_PROGRESS "User Initiated"
Enter fullscreen mode Exit fullscreen mode

Generating Gantt chart for deployment times

Another cool feature is viewing the Gantt chart for the deployment times of different resources:

rain log lambda-function-stack --chart > chart.html
Enter fullscreen mode Exit fullscreen mode

Here is the resulting html page:

Gantt chart displaying deployment times for various resources using Rain

Destroying stacks using Rain

Using the prior sections' stacks, let's use the rm command to showcase the stack deletion process:

rain rm lambda-function-stack
Enter fullscreen mode Exit fullscreen mode

Output:

Stack lambda-function-stack: CREATE_COMPLETE
Are you sure you want to delete this stack? (y/N) y
Successfully deleted stack 'lambda-function-stack'
Enter fullscreen mode Exit fullscreen mode

and the S3 bucket stack with the -y so that we don't have to confirm again:

rain rm s3-bucket-stack -y
Enter fullscreen mode Exit fullscreen mode

Output:

Successfully deleted stack 's3-bucket-stack'
Enter fullscreen mode Exit fullscreen mode

Other features

There are other features not covered in this guide:

  1. Stack sets
  2. Forecasting errors using rain forecast (experimental)
  3. Using rain's instrinsic functions & modules (experimental)

As previously stated, if you need to work with raw CloudFormation - give this tool a try!

Top comments (0)