Skip to content

Instantly share code, notes, and snippets.

@gene1wood
Last active October 24, 2022 19:22
Show Gist options
  • Save gene1wood/a00d0b9d029f40e866df to your computer and use it in GitHub Desktop.
Save gene1wood/a00d0b9d029f40e866df to your computer and use it in GitHub Desktop.
A CloudFormation template for CentOS 7 showing AMI mappings and cloud-init UserData parsing
AWSTemplateFormatVersion: 2010-09-09
Description: >
An example free tier (12 month) CentOS 7 EC2 instance with a security group
allowing SSH, a simple IAM Role, and output conveyed from the launch back out
to the CloudFormation stack outputs
Parameters:
SSHKeyName:
Description: SSH Key Name
Type: String
Mappings:
# This list of AMI IDs was produced with this code
# https://gist.github.com/gene1wood/56e42097e0f0ac1aace14cbc41ee3e11#file-create_centos7_cloudformation_ami_mapping-py
RegionMap:
ap-northeast-1:
CentOS7x8664EBSHVM: ami-045f38c93733dd48d
ap-northeast-2:
CentOS7x8664EBSHVM: ami-06cf2a72dadf92410
ap-south-1:
CentOS7x8664EBSHVM: ami-02e60be79e78fef21
ap-southeast-1:
CentOS7x8664EBSHVM: ami-0b4dd9d65556cac22
ap-southeast-2:
CentOS7x8664EBSHVM: ami-08bd00d7713a39e7d
ca-central-1:
CentOS7x8664EBSHVM: ami-033e6106180a626d0
eu-central-1:
CentOS7x8664EBSHVM: ami-04cf43aca3e6f3de3
eu-north-1:
CentOS7x8664EBSHVM: ami-5ee66f20
eu-west-1:
CentOS7x8664EBSHVM: ami-0ff760d16d9497662
eu-west-2:
CentOS7x8664EBSHVM: ami-0eab3a90fc693af19
eu-west-3:
CentOS7x8664EBSHVM: ami-0e1ab783dc9489f34
sa-east-1:
CentOS7x8664EBSHVM: ami-0b8d86d4bf91850af
us-east-1:
CentOS7x8664EBSHVM: ami-02eac2c0129f6376b
us-east-2:
CentOS7x8664EBSHVM: ami-0f2b4fc905b0bd1f1
us-west-1:
CentOS7x8664EBSHVM: ami-074e2d6769f445be5
us-west-2:
CentOS7x8664EBSHVM: ami-01ed306a12b7d1c96
Resources:
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group
SecurityGroupIngress:
- FromPort: 22
ToPort: 22
IpProtocol: tcp
CidrIp: '0.0.0.0/0'
Instance:
Type: AWS::EC2::Instance
Metadata:
UserDataComment1: The cloud-config script is delivered directly in user_data
because the CentOS 7 base AMIs run cloud-init automatically, looking for a
cloud-init config in user_data
UserDataComment2: In this example the 'dmidecode' command is run and the output
is sent back to the WaitConditionHandle which is then passed out through the
CloudFormation outputs
Properties:
ImageId: !FindInMap [ RegionMap, !Ref 'AWS::Region', CentOS7x8664EBSHVM ]
InstanceType: t2.micro # Instance size of the free tier for 12 months
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeType: gp2
DeleteOnTermination: false
VolumeSize: 30 # Free tier provides 30GiB of gp2 or standard EBS
KeyName: !Ref SSHKeyName
SecurityGroups:
- !Ref SecurityGroup
IamInstanceProfile: !Ref InstanceProfile
UserData:
Fn::Base64: !Sub |
#cloud-config
packages:
- awscli
runcmd:
- >
for i in {1..3}; do /usr/bin/easy_install
https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz
2>&1 >> /var/log/initial_user-data.log && break || sleep 10; done
- aws --region us-west-2 ec2 describe-regions --output text >> /tmp/custom-output.txt
- CFNSTATUS=$?
- >
/usr/bin/cfn-signal
--exit-code $CFNSTATUS
--data "$( < /tmp/custom-output.txt )"
"${WaitConditionHandle}" 2>&1 >> /var/log/initial_user-data.log
Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: DescribeRegions
PolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowEC2DescribeRegions
Effect: Allow
Action:
- ec2:DescribeRegions
Resource: '*'
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref Role
WaitConditionHandle:
Type: AWS::CloudFormation::WaitConditionHandle
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
DependsOn: Instance
Properties:
Handle: !Ref WaitConditionHandle
Timeout: '300'
Outputs:
CloudInitOutput:
Description: The data returned to the WaitConditionHandle from Cloud Init
Value: !GetAtt WaitCondition.Data
EC2InstancePublicDNSName:
Description: The public DNS name of the instance
Value: !GetAtt Instance.PublicDnsName
@edgreenberg
Copy link

edgreenberg commented Aug 22, 2017

I found what's wrong with the above. The clue is in the line cfn-signal: error: option --exit-code: invalid integer value: '--data'

In the template, we have this:

                "UserData":{
                    "Fn::Base64":{
                        "Fn::Join":[
                            "",
                            [
                                "#cloud-config\n",
                                "\n",
                                "runcmd:\n",
                                " - for i in {1..3}; do /usr/bin/easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz 2>&1 >> /var/log/initial_user-data.log && break || sleep 10; done\n",
                                " - dmidecode 2>&1 >> /tmp/custom-output.txt",
                                " - CFNSTATUS=$?\n",
                                " - /usr/bin/cfn-signal ",
                                "         --exit-code $CFNSTATUS ",
                                "         --data \"$( base64 -w 0 /tmp/custom-output.txt )\" ",
                                "'",
                                {
                                    "Ref":"WaitConditionHandle"
                                },
                                "'",
                                " 2>&1 >> /var/log/initial_user-data.log\n"
                            ]
                        ]
                    }
                }

It looks like $CFNSTATUS is not being set to 0 (or an error), but rather is empty. I proved this by adding a line to echo $CFNSTATUS into a file in /tmp, and also to provide a 0 to the --exit-code argument in the call to /usr/bin/cfn-signal.

Once I made those changes, the server built.

Not sure why this would be so.

@gene1wood
Copy link
Author

@edgreenberg Good catch. The reason for that bug was that the line that runs dmidecode doesn't have a trailing \n. So the line that looks like this

                                " - dmidecode 2>&1 >> /tmp/custom-output.txt",

should have looked like this

                                " - dmidecode 2>&1 >> /tmp/custom-output.txt\n",

I'm updating this template to yaml which will solve this problem.

@gene1wood
Copy link
Author

@locomotif The updated template contains the regions you'd mentioned. The AMI mapping is generated with code that you can find here (so that it won't miss any regions) : https://gist.github.com/gene1wood/56e42097e0f0ac1aace14cbc41ee3e11#file-create_centos7_cloudformation_ami_mapping-py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment