There are more AWS SDK examples available in the AWS Doc SDK Examples
Creating your first CloudFormation stack
The following code example shows how to:
Create a CloudFormation template
Test the web server
Clean up resources
- Bash
-
- AWS CLI with Bash script
-
Note
There's more on GitHub. Find the complete example and learn how to set up and run in the Sample developer tutorials
repository. #!/bin/bash # CloudFormation Getting Started Script # This script creates a CloudFormation stack with a web server and security group, # monitors the stack creation, and provides cleanup options. set -euo pipefail # Set up logging with secure permissions LOG_FILE="cloudformation-tutorial.log" touch "$LOG_FILE" chmod 600 "$LOG_FILE" exec > >(tee -a "$LOG_FILE") 2>&1 echo "===================================================" echo "AWS CloudFormation Getting Started Tutorial" echo "===================================================" echo "This script will create a CloudFormation stack with:" echo "- An EC2 instance running a simple web server" echo "- A security group allowing HTTP access from your IP" echo "" echo "Starting at: $(date)" echo "" # Function to validate IP address format validate_ip() { local ip=$1 if ! [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then return 1 fi local IFS=. local -a octets=($ip) for octet in "${octets[@]}"; do if ((octet > 255)); then return 1 fi done return 0 } # Function to clean up resources cleanup() { echo "" echo "===================================================" echo "CLEANING UP RESOURCES" echo "===================================================" if [ -n "${STACK_NAME:-}" ]; then echo "Deleting CloudFormation stack: $STACK_NAME" aws cloudformation delete-stack --stack-name "$STACK_NAME" --region "${AWS_REGION:-us-east-1}" || true echo "Waiting for stack deletion to complete..." aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME" --region "${AWS_REGION:-us-east-1}" 2>/dev/null || true echo "Stack deletion complete." fi if [ -f "${TEMPLATE_FILE:-}" ]; then echo "Removing local template file: $TEMPLATE_FILE" shred -vfz -n 3 "$TEMPLATE_FILE" 2>/dev/null || rm -f "$TEMPLATE_FILE" fi echo "Cleanup completed at: $(date)" } # Function to handle errors handle_error() { echo "" echo "===================================================" echo "ERROR: $1" echo "===================================================" echo "Resources created before error:" if [ -n "${STACK_NAME:-}" ]; then echo "- CloudFormation stack: $STACK_NAME" fi echo "" echo "Cleaning up resources automatically..." cleanup exit 1 } # Set up trap for script interruption trap 'handle_error "Script interrupted"' INT TERM trap 'cleanup' EXIT # Validate AWS region AWS_REGION="${AWS_REGION:-us-east-1}" if ! [[ "$AWS_REGION" =~ ^[a-z]{2}-[a-z]+-[0-9]{1}$ ]]; then handle_error "Invalid AWS_REGION format: $AWS_REGION" fi # Generate a unique stack name with timestamp TIMESTAMP=$(date +%s) STACK_NAME="MyTestStack-${TIMESTAMP}" TEMPLATE_FILE="webserver-template-${TIMESTAMP}.yaml" # Verify AWS CLI is installed if ! command -v aws &> /dev/null; then handle_error "AWS CLI is not installed or not in PATH" fi # Verify curl is installed if ! command -v curl &> /dev/null; then handle_error "curl is not installed or not in PATH" fi # Verify AWS credentials are configured if ! aws sts get-caller-identity --region "$AWS_REGION" &> /dev/null; then handle_error "AWS credentials not configured or invalid" fi # Step 1: Create the CloudFormation template file echo "Creating CloudFormation template file: $TEMPLATE_FILE" cat > "$TEMPLATE_FILE" << 'EOF' AWSTemplateFormatVersion: 2010-09-09 Description: CloudFormation Template for WebServer with Security Group and EC2 Instance Metadata: AWS::CloudFormation::Init: config: packages: yum: httpd: [] services: sysvinit: httpd: enabled: true ensureRunning: true Parameters: LatestAmiId: Description: The latest Amazon Linux 2 AMI from the Parameter Store Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>' Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' InstanceType: Description: WebServer EC2 instance type Type: String Default: t2.micro AllowedValues: - t3.micro - t2.micro ConstraintDescription: must be a valid EC2 instance type. MyIP: Description: Your IP address in CIDR format (e.g 203.0.113.1/32). Type: String MinLength: '9' MaxLength: '18' AllowedPattern: '^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$' ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. Resources: WebServerRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy' WebServerInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref WebServerRole WebServerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow HTTP access via specified IP address SecurityGroupEgress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Description: Allow HTTP outbound - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 Description: Allow HTTPS outbound for package updates SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref MyIP Description: HTTP access from specified IP WebServer: Type: AWS::EC2::Instance Properties: ImageId: !Ref LatestAmiId InstanceType: !Ref InstanceType IamInstanceProfile: !Ref WebServerInstanceProfile SecurityGroupIds: - !Ref WebServerSecurityGroup Monitoring: true MetadataOptions: HttpEndpoint: enabled HttpTokens: required HttpPutResponseHopLimit: 1 UserData: !Base64 | #!/bin/bash set -euo pipefail exec > >(tee /var/log/user-data.log) exec 2>&1 yum update -y yum install -y httpd systemctl start httpd systemctl enable httpd echo "<html><body><h1>Hello World!</h1></body></html>" > /var/www/html/index.html chmod 644 /var/www/html/index.html Outputs: WebsiteURL: Value: !Join - '' - - http:// - !GetAtt WebServer.PublicDnsName Description: Website URL InstanceId: Value: !Ref WebServer Description: EC2 Instance ID SecurityGroupId: Value: !Ref WebServerSecurityGroup Description: Security Group ID EOF chmod 600 "$TEMPLATE_FILE" if [ ! -f "$TEMPLATE_FILE" ]; then handle_error "Failed to create template file" fi # Step 2: Validate the template echo "" echo "Validating CloudFormation template..." if ! VALIDATION_RESULT=$(aws cloudformation validate-template \ --template-body "file://$TEMPLATE_FILE" \ --region "$AWS_REGION" 2>&1); then handle_error "Template validation failed: $VALIDATION_RESULT" fi echo "Template validation successful." # Step 3: Get the user's public IP address echo "" echo "Retrieving your public IP address..." MY_IP="" for endpoint in "https://checkip.amazonaws.com" "https://api.ipify.org" "https://icanhazip.com"; do if MY_IP=$(curl -s --max-time 5 "$endpoint" 2>/dev/null); then MY_IP="${MY_IP//[[:space:]]/}" if validate_ip "$MY_IP"; then break fi MY_IP="" fi done if [ -z "$MY_IP" ]; then handle_error "Failed to retrieve public IP address from multiple sources" fi MY_IP_CIDR="${MY_IP}/32" echo "Your public IP address: $MY_IP_CIDR" # Step 4: Create the CloudFormation stack echo "" echo "Creating CloudFormation stack: $STACK_NAME" echo "This will create an EC2 instance and security group." if ! CREATE_RESULT=$(aws cloudformation create-stack \ --stack-name "$STACK_NAME" \ --template-body "file://$TEMPLATE_FILE" \ --parameters \ ParameterKey=InstanceType,ParameterValue=t2.micro \ ParameterKey=MyIP,ParameterValue="$MY_IP_CIDR" \ --capabilities CAPABILITY_IAM \ --tags Key=project,Value=doc-smith Key=tutorial,Value=cloudformation-gs \ --region "$AWS_REGION" \ --output text 2>&1); then handle_error "Stack creation failed: $CREATE_RESULT" fi STACK_ID="${CREATE_RESULT//[[:space:]]/}" echo "Stack creation initiated. Stack ID: $STACK_ID" # Step 5: Monitor stack creation echo "" echo "Monitoring stack creation..." echo "This may take a few minutes." if ! aws cloudformation wait stack-create-complete \ --stack-name "$STACK_NAME" \ --region "$AWS_REGION" 2>/dev/null; then STACK_STATUS=$(aws cloudformation describe-stacks \ --stack-name "$STACK_NAME" \ --query "Stacks[0].StackStatus" \ --region "$AWS_REGION" \ --output text 2>/dev/null || echo "UNKNOWN") if [[ "$STACK_STATUS" =~ ROLLBACK|FAILED ]]; then handle_error "Stack creation failed. Status: $STACK_STATUS" fi fi echo "Stack creation completed successfully." # Step 6: List stack resources echo "" echo "Resources created by the stack:" aws cloudformation list-stack-resources \ --stack-name "$STACK_NAME" \ --region "$AWS_REGION" \ --query "StackResourceSummaries[*].{LogicalID:LogicalResourceId, Type:ResourceType, Status:ResourceStatus}" \ --output table # Step 7: Get stack outputs echo "" echo "Stack outputs:" if ! OUTPUTS=$(aws cloudformation describe-stacks \ --stack-name "$STACK_NAME" \ --region "$AWS_REGION" \ --query "Stacks[0].Outputs" \ --output json 2>&1); then handle_error "Failed to retrieve stack outputs" fi echo "$OUTPUTS" # Extract the WebsiteURL WEBSITE_URL=$(aws cloudformation describe-stacks \ --stack-name "$STACK_NAME" \ --region "$AWS_REGION" \ --query "Stacks[0].Outputs[?OutputKey=='WebsiteURL'].OutputValue" \ --output text 2>/dev/null || echo "") if [ -z "$WEBSITE_URL" ]; then handle_error "Failed to extract WebsiteURL from stack outputs" fi echo "" echo "WebsiteURL: $WEBSITE_URL" echo "" echo "You can access the web server by opening the above URL in your browser." echo "You should see a simple 'Hello World!' message." # Step 8: Test the connection via CLI with timeout echo "" echo "Testing connection to the web server..." MAX_RETRIES=5 RETRY_COUNT=0 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do if HTTP_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$WEBSITE_URL" 2>/dev/null); then if [ "$HTTP_RESPONSE" == "200" ]; then echo "Connection successful! HTTP status code: $HTTP_RESPONSE" break elif [ "$HTTP_RESPONSE" == "000" ]; then RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then echo "Connection test attempt $RETRY_COUNT/$MAX_RETRIES - retrying in 10 seconds..." sleep 10 else echo "Warning: Connection test failed after $MAX_RETRIES attempts" fi else echo "Warning: Connection test returned HTTP status code: $HTTP_RESPONSE" break fi else RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then echo "Connection test attempt $RETRY_COUNT/$MAX_RETRIES - retrying in 10 seconds..." sleep 10 else echo "Warning: Connection test failed after $MAX_RETRIES attempts" fi fi done # Step 9: Auto-confirm cleanup echo "" echo "===================================================" echo "CLEANUP CONFIRMATION" echo "===================================================" echo "Resources created:" echo "- CloudFormation stack: $STACK_NAME" echo " - EC2 instance" echo " - Security group" echo " - IAM role and instance profile" echo "" echo "Proceeding with cleanup of all created resources..." cleanup echo "" echo "===================================================" echo "Tutorial completed at: $(date)" echo "Log file: $LOG_FILE" echo "==================================================="-
For API details, see the following topics in AWS CLI Command Reference.
-
Create a REST API to track COVID-19 data
Getting started with Amazon EKS