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:
- Allows input parameters
- Showcases real-time updates for stack deployments
- Filters deployment logs sensibly
- Provides the ability to generate CloudFormation templates via AI
- Manipulates stack sets
- 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.
1go install github.com/aws-cloudformation/rain/cmd/rain@latest
For MacOS users, brew
is easy:
1brew install rain
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.
1rain build -p "A s3 bucket that is secure"
Output:
1AWSTemplateFormatVersion: 2010-09-09
2Description: Create an S3 bucket
3
4Resources:
5 MyS3Bucket:
6 Type: AWS::S3::Bucket
7 Properties:
8 BucketName: my-secure-bucket
9 AccessControl: Private
10 VersioningConfiguration:
11 Status: Enabled
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:
1rain build AWS::Route53::CidrCollection
or
1rain build CidrCollection
This outputs a template with all the parameters and dummy CHANGEME
values for a Route53 CIDR collection:
1AWSTemplateFormatVersion: "2010-09-09"
2
3Description: Template generated by rain
4
5Resources:
6 MyCidrCollection:
7 Type: AWS::Route53::CidrCollection
8 Properties:
9 Locations:
10 - CidrList:
11 - CHANGEME
12 LocationName: CHANGEME
13 Name: CHANGEME
14
15Outputs:
16 MyCidrCollectionArn:
17 Value: !GetAtt MyCidrCollection.Arn
18
19 MyCidrCollectionId:
20 Value: !GetAtt MyCidrCollection.Id
If you prefer json
, there’s a flag for that:
1rain build CidrCollection --json
If you supply the command an ambigious term like “collection”, it provides you a warning:
1rain build collection
Output:
1Ambiguous resource type 'collection'; could be any of:
2 AWS::DevOpsGuru::ResourceCollection
3 AWS::Location::GeofenceCollection
4 AWS::OpenSearchServerless::Collection
5 AWS::Rekognition::Collection
6 AWS::Route53::CidrCollection
Bootstrap using Rain
Before deploying stacks into an environment, let’s run the bootstrap command:
1rain bootstrap
Output:
1Rain needs to create an S3 bucket called 'rain-artifacts-012345678901-us-east-1'. Continue? (Y/n)
This command creates an artifacts bucket that rain
references when deploying stacks.
It has the following naming convention:
1rain-artifacts-{accountId}-{regionName}
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:
1project
2└── s3.yml
This is the command I’ll use to deploy the s3.yml
with a stack name of s3-bucket-stack
:
1rain deploy s3.yml s3-bucket-stack
If rain bootstrap
was not executed before deploying, it’ll prompt you to do so.
My deployment failed with the following message:
1CloudFormation will make the following changes:
2Stack s3-bucket-stack:
3 + AWS::S3::Bucket MyS3Bucket
4Do you wish to continue? (Y/n)
5Deploying template 's3.yml' as stack 's3-bucket-stack' in us-east-1.
6Stack s3-bucket-stack: ROLLBACK_COMPLETE
7Messages:
8 - 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)
9failed deploying stack 's3-bucket-stack'
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:
1Deleted existing, empty stack.
2CloudFormation will make the following changes:
3Stack s3-bucket-stack:
4 + AWS::S3::Bucket MyS3Bucket
5Do you wish to continue? (Y/n)
6Deploying template 's3.yml' as stack 's3-bucket-stack' in us-east-1.
7Stack s3-bucket-stack: CREATE_COMPLETE
8Successfully deployed s3-bucket-stack
A couple of awesome things to note:
- It handled the failure of the stack gracefully and gave me the exact issue
- I deployed again and it took care of everything
- 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:
1AWSTemplateFormatVersion: 2010-09-09
2Description: Create an lambda function
3
4Resources:
5 LambdaExecutionRole:
6 Type: AWS::IAM::Role
7 Properties:
8 AssumeRolePolicyDocument:
9 Version: '2012-10-17'
10 Statement:
11 - Effect: Allow
12 Principal:
13 Service:
14 - lambda.amazonaws.com
15 Action:
16 - sts:AssumeRole
17 ManagedPolicyArns:
18 - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
19 MyLambdaFunction:
20 Type: AWS::Lambda::Function
21 Properties:
22 Code: lambdafunction/
23 FunctionName: MyLovelyFunction
24 Handler: index.handler
25 Role: !GetAtt LambdaExecutionRole.Arn
26 Runtime: python3.12
27
28Outputs:
29 LambdaFunctionArn:
30 Description: The ARN of the Lambda function
31 Value: !GetAtt MyLambdaFunction.Arn
This stack creates two resources:
- A lambda execution role with basic permissions granted by the
AWSLambdaBasicExecutionRole
policy - A lambda function with the
lambdafunction/
contents
And outputs the lambda function arn.
I ran the fmt
command with the --verify
flag:
1rain fmt lambda.yml --verify
The --verify
command is best saved for CI environments since it outputs a 0 or 1 depending if the formatting is correct.
Output:
1lambda.yml: would reformat
I ran it with the --write
command so that it fixed them:
1rain fmt lambda.yml --write
Let’s run the pkg
command with an output file named out.yml
:
1rain pkg lambda.yml --output out.yml
Here is a snapshot of my directory structure after doing the pkg
command:
1project
2├── lambda.yml
3├── lambdafunction
4│ └── index.py
5└── out.yml
Now let’s deploy the out.yml
template:
1rain deploy out.yml lambda-function-stack
Output:
1CloudFormation will make the following changes:
2Stack lambda-function-stack:
3 + AWS::IAM::Role LambdaExecutionRole
4 + AWS::Lambda::Function MyLambdaFunction
5Do you wish to continue? (Y/n)
6Deploying template 'out.yml' as stack 'lambda-function-stack' in us-east-1.
7Stack lambda-function-stack: CREATE_COMPLETE
8 Outputs:
9 LambdaFunctionArn: arn:aws:lambda:us-east-1:012345678901:function:MyLovelyFunction # The ARN of the Lambda function
10Successfully deployed lambda-function-stack
Using the LambdaFunctionArn
output, let’s invoke the function to ensure everything worked correctly:
1aws lambda invoke --function-name MyLovelyFunction response.txt
Here is the resulting response.txt
:
1"Hello world!"
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:
1rain log lambda-function-stack
Output:
1No interesting log messages to display. To see everything, use the --all flag
Since there were no “interesting” log messages to display, let’s see them all!
1rain log lambda-function-stack --all
Output:
1Jan 14 20:08:52 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) CREATE_COMPLETE
2Jan 14 20:08:50 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_COMPLETE
3Jan 14 20:08:43 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_IN_PROGRESS "Resource creation Initiated"
4Jan 14 20:08:42 lambda-function-stack/MyLambdaFunction (AWS::Lambda::Function) CREATE_IN_PROGRESS
5Jan 14 20:08:40 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_COMPLETE
6Jan 14 20:08:24 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_IN_PROGRESS "Resource creation Initiated"
7Jan 14 20:08:22 lambda-function-stack/LambdaExecutionRole (AWS::IAM::Role) CREATE_IN_PROGRESS
8Jan 14 20:08:19 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) CREATE_IN_PROGRESS "User Initiated"
9Jan 14 20:08:12 lambda-function-stack/lambda-function-stack (AWS::CloudFormation::Stack) REVIEW_IN_PROGRESS "User Initiated"
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
Here is the resulting html page:
Destroying stacks using Rain
Using the prior sections’ stacks, let’s use the rm
command to showcase the stack deletion process:
1rain rm lambda-function-stack
Output:
1Stack lambda-function-stack: CREATE_COMPLETE
2Are you sure you want to delete this stack? (y/N) y
3Successfully deleted stack 'lambda-function-stack'
and the S3 bucket stack with the -y
so that we don’t have to confirm again:
1rain rm s3-bucket-stack -y
Output:
1Successfully deleted stack 's3-bucket-stack'
Other features
There are other features not covered in this guide:
- Stack sets
- Forecasting errors using
rain forecast
(experimental) - Using rain’s instrinsic functions & modules (experimental)
As previously stated, if you need to work with raw CloudFormation - give this tool a try!