DEV Community

Cover image for Run AWS CloudFormation Via CLI + Use Cases (Template and Resources)
Nurul Ramadhona for AWS Community Builders

Posted on • Updated on

Run AWS CloudFormation Via CLI + Use Cases (Template and Resources)

AWS CloudFormation is a "free" service that helps us to provide and manage AWS resources only through a template. One of the benefits that I love is we don't need to look for which modules are still valid to use or which ones that have been deprecated because AWS provides the complete information on the documentation, click here. What we need to define in the template are provided there. One of the most important thing about this service that you have to remember is we are free to use the CloudFormation service but you'll be billed for every services created through the stack (the ones that you define in the template).

Alright, we'll do anything with CloudFormation through AWS CLI. So, make sure you've installed AWS CLI and set up the credential. If you're ready with them all, let's start!

1. Create Template
In the first version of the template, I want to create S3 bucket with versioning enabled. I prepare this for the next step because when we create stack by uploading the template (stored locally) via CLI, it doesn't show us where it'll be stored (I'll tell you in a second below).
Ok, I named the template cfn-demo.yaml and it's going to be:

Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: cfn-bucket-template
      VersioningConfiguration:
        Status: Enabled
Enter fullscreen mode Exit fullscreen mode

2. Create Stack
As I mentioned above when we create stack by uploading the template (stored locally) via CLI, it doesn't show us where it'll be stored. It's different with when we create stack by uploading the file via Console which it'll be stored in S3 bucket generated by CloudFormation itself, but that bucket isn't deleted even though the stack has been deleted.

Upload Template via Console
Bucket Created
Stack Deleted
Bucket Still Exist

Then, to prevent that undeleted bucket off the stack. We're gonna create the bucket first, upload the template to the bucket, and use the object as the template source. I just wanna let you know that we can do this.

Let's create the stack!

$ aws cloudformation create-stack --stack-name cfn-demo --template-body file://cfn-demo.yaml
{
    "StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
Enter fullscreen mode Exit fullscreen mode

Please wait till the status is complete.

$ aws cloudformation describe-stacks --stack-name cfn-demo
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
            "StackName": "cfn-demo",
            "CreationTime": "2022-05-08T13:24:17.386Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Let's verify the S3 bucket! Has it been created or not?

$ aws s3 ls
2022-05-08 20:24:45 cfn-bucket-template
Enter fullscreen mode Exit fullscreen mode

3. Update Stack
To update the stack, we'll have some steps to do to follow the scenario. Those are:

  • Make any changes to the template. In this case, I'll create more resources (VPC, Subnet, Internet Gateway, Route Table, 2 Security Groups, and an EC2 instance).
  • Upload the second version of the template to S3 bucket.
  • Update stack by using the S3 object as template source.

cfn-demo.yaml (second version)

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  EC2Type:
    Description: Type of instance
    Type: String
    Default: t3.micro
    AllowedValues:
    - t3.micro
    - t3.small
    - t3.medium
    - t3.large

Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: cfn-bucket-template
      VersioningConfiguration:
        Status: Enabled
  VirtualPrivateCloud:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 172.16.0.0/16
      Tags:
      - Key: "Name"
        Value: "CFN VPC"
  Subnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VirtualPrivateCloud
      CidrBlock: 172.16.1.0/28
      AvailabilityZone: ap-southeast-3a
      Tags:
      - Key: "Name"
        Value: "CFN Subnet"
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: "Name"
        Value: "CFN IGW"
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VirtualPrivateCloud
      InternetGatewayId: !Ref InternetGateway
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VirtualPrivateCloud
      Tags:
      - Key: "Name"
        Value: "CFN Route"
  Route:
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  SubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet 
      RouteTableId: !Ref RouteTable
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: Allow SSH HTTP
      SecurityGroupIngress:
      - CidrIp: 0.0.0.0/0
        IpProtocol: tcp
        FromPort: 22
        ToPort: 22
      - CidrIp: 0.0.0.0/0
        IpProtocol: tcp
        FromPort: 80
        ToPort: 80
      VpcId: !Ref VirtualPrivateCloud
      Tags:
      - Key: "Name"
        Value: "CFN SG"
  SecurityGroup2:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: Only Allow SSH
      SecurityGroupIngress:
      - CidrIp: 0.0.0.0/0
        IpProtocol: tcp
        FromPort: 22
        ToPort: 22
      VpcId: !Ref VirtualPrivateCloud
      Tags:
      - Key: "Name"
        Value: "CFN SG2"
  KeyPair:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: ec2-user
      PublicKeyMaterial: ssh-rsa VWXYZ.....(your pubkey goes here)
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref EC2Type
      AvailabilityZone: ap-southeast-3a
      ImageId: "ami-021fb2b73ff1efc96"
      KeyName: "ec2-user"
      BlockDeviceMappings:
      - DeviceName: "/dev/xvda"
        Ebs:
          DeleteOnTermination: "true"
          VolumeSize: "8"
          VolumeType: "gp2"
      NetworkInterfaces:
      - AssociatePublicIpAddress: "true"
        DeleteOnTermination: "true"
        DeviceIndex: 0
        SubnetId: !Ref Subnet
        GroupSet: 
          - Ref: SecurityGroup2
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            yum update -y
            yum install -y httpd
            systemctl enable httpd
            systemctl start httpd
      Tags:
      - Key: "Name"
        Value: "CFN Instance"

Outputs:
  EC2IpAddress:
    Description: "EC2 Public IP Address"
    Value: !GetAtt EC2Instance.PublicIp
Enter fullscreen mode Exit fullscreen mode

Upload the file to S3 bucket by using copy:

$ aws s3 cp cfn-demo.yaml s3://cfn-bucket-template
upload: ./cfn-demo.yaml to s3://cfn-bucket-template/cfn-demo.yaml
$ aws s3 ls s3://cfn-bucket-template
2022-05-08 20:31:46       4256 cfn-demo.yaml
Enter fullscreen mode Exit fullscreen mode

Then, let's update the stack by using S3 object URL as template source.

$ aws cloudformation update-stack --stack-name cfn-demo --template-url https://cfn-bucket-template.s3.ap-southeast-3.amazonaws.com/cfn-demo.yaml
{
    "StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
Enter fullscreen mode Exit fullscreen mode

Please wait till the status is complete and we get the output.

$ aws cloudformation describe-stacks --stack-name cfn-demo
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
            "StackName": "cfn-demo",
            "Parameters": [
                {
                    "ParameterKey": "EC2Type",
                    "ParameterValue": "t3.micro"
                }
            ],
            "CreationTime": "2022-05-08T13:24:17.386Z",
            "LastUpdatedTime": "2022-05-08T13:33:13.916Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "EC2IpAddress",
                    "OutputValue": "108.136.169.98",
                    "Description": "EC2 Public IP Address"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Let's verify the EC2 instance port by accessing it via SSH and web browser.

$ ssh ec2-user@108.136.169.98 

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-16-1-8 ~]$ exit
logout
Connection to 108.136.169.98 closed.
Enter fullscreen mode Exit fullscreen mode

First Instance

Why do we verify the ports? Because we've created 2 security groups and we already used the first one for our EC2 instance. Now, let's make a change to the template and upload it again to the S3 bucket. From this action, we'll have two versions of the object (template that stored in S3 bucket).

Before (to allow SSH and HTTP):

        GroupSet: 
          - Ref: SecurityGroup
Enter fullscreen mode Exit fullscreen mode

After (to only allow SSH):

        GroupSet: 
          - Ref: SecurityGroup2
Enter fullscreen mode Exit fullscreen mode

Then, upload that file!

$ aws s3 cp cfn-demo.yaml s3://cfn-bucket-template
upload: ./cfn-demo.yaml to s3://cfn-bucket-template/cfn-demo.yaml
Enter fullscreen mode Exit fullscreen mode

Now, let's update the stack again by using S3 object URL as template source!
(It'll use the newest version of the template)

$ aws cloudformation update-stack --stack-name cfn-demo --template-url https://cfn-bucket-template.s3.ap-southeast-3.amazonaws.com/cfn-demo.yaml
{
    "StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
Enter fullscreen mode Exit fullscreen mode

The output:

$ aws cloudformation describe-stacks --stack-name cfn-demo
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
            "StackName": "cfn-demo",
            "Parameters": [
                {
                    "ParameterKey": "EC2Type",
                    "ParameterValue": "t3.micro"
                }
            ],
            "CreationTime": "2022-05-08T13:24:17.386Z",
            "LastUpdatedTime": "2022-05-08T13:40:42.678Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "EC2IpAddress",
                    "OutputValue": "108.136.160.193",
                    "Description": "EC2 Public IP Address"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Even though we only change the security group, but through the stack it'll replace EC2 instance with the new one. So, maybe it can be a review for us to choose which will be executed through CloudFormation or using other ways let's say manually maybe via the Console, CLI, or anything. Here I'm just gonna show you the main points when we use CloudFormation via CLI and use S3 to store template.

Ok, let's continue to check! The current instance should be only accessible via SSH.

$ telnet 108.136.160.193 80
Trying 108.136.160.193...
^C
$ ssh ec2-user@108.136.160.193

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-16-1-12 ~]$ exit
logout
Connection to 108.136.160.193 closed.
Enter fullscreen mode Exit fullscreen mode

HTTP Closed

It works! Now, I want to make one more change but I'll only use the first (oldest) object version without make any changes to the template. I just want to be back by creating a new instance using first security group that allows SSH and HTTP. This is one of the benefits by using S3 versioning to store the template.

Get the version ID and I'll take the bottom one!

$ aws s3api list-object-versions --bucket cfn-bucket-template --query 'Versions[].[Key, VersionId]'
[
    [
        "cfn-demo.yaml",
        "vtPszUgfpn8S9VZj5r6uHWKYL9HSlDRV"
    ],
    [
        "cfn-demo.yaml",
        "0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf"
    ]
]
Enter fullscreen mode Exit fullscreen mode

Now, let's do our last update to the stack by using S3 object URL as template source! Please don't forget to mention the version ID by adding ?versionId=xxx right after the object URL.

$ aws cloudformation update-stack --stack-name cfn-demo --template-url https://cfn-bucket-template.s3.ap-southeast-3.amazonaws.com/cfn-demo.yaml?versionId=0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf
{
    "StackId": "arn:aws:cloudformation:ap-southeast-3:01234567890:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70"
}
Enter fullscreen mode Exit fullscreen mode

The output:

$ aws cloudformation describe-stacks --stack-name cfn-demo
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-demo/2953bb10-ced2-11ec-a796-06bb617a2d70",
            "StackName": "cfn-demo",
            "Parameters": [
                {
                    "ParameterKey": "EC2Type",
                    "ParameterValue": "t3.micro"
                }
            ],
            "CreationTime": "2022-05-08T13:24:17.386Z",
            "LastUpdatedTime": "2022-05-08T13:49:09.816Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Outputs": [
                {
                    "OutputKey": "EC2IpAddress",
                    "OutputValue": "108.136.160.169",
                    "Description": "EC2 Public IP Address"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Then, HTTP port of our newest EC2 instance should be opened back.
HTTP Back

4. Delete Template
We've uploaded 2 versions of template to S3 bucket. So, we have to delete the object versions before delete the stack because we upload the files manually (aren't included in the stack).

$ aws s3api delete-object --bucket cfn-bucket-template --key cfn-demo.yaml --version-id vtPszUgfpn8S9VZj5r6uHWKYL9HSlDRV
{
    "VersionId": "vtPszUgfpn8S9VZj5r6uHWKYL9HSlDRV"
}
$ aws s3api delete-object --bucket cfn-bucket-template --key cfn-demo.yaml --version-id 0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf
{
    "VersionId": "0DY8PO_LdRI_1WcppeMBSFc_ey5Djuxf"
}
Enter fullscreen mode Exit fullscreen mode

5. Delete Stack
Now, the bucket is empty and we're ready to delete the stack.

$ aws cloudformation delete-stack --stack-name cfn-demo
$ aws cloudformation describe-stacks --stack-name cfn-demo

An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id cfn-demo does not exist
Enter fullscreen mode Exit fullscreen mode

It's a wrap. We already reached the end of this post.
What we've done?

  1. Create CloudFormation template with the following tasks:
    • Create S3 Bucket
    • Create EC2 (VPC, Subnet, Internet Gateway, Route Table, Security Group, and EC2 instance)
  2. Create, update, and delete CloudFormation stack via AWS CLI
  3. Upload object to the S3 bucket via AWS CLI
  4. Delete S3 object version via AWS CLI
  5. Use 2 different CloudFormation template sources, local file and S3 URL.

That's it! Thank you for coming and I'm looking forward to your feedback. Follow me to get notified when my new post is published!

Discussion (0)