From be28584fb093ca2bebb29fe29010a5d0e8484ea3 Mon Sep 17 00:00:00 2001 From: Michael Wunderlich Date: Tue, 14 Apr 2026 23:10:03 +0000 Subject: [PATCH] Add CloudFormation templates for first 10 tutorials - 10 tutorial templates (cfn-*.yaml) matching CLI scripts - 3 prerequisite templates (bucket, VPC public, VPC private) - Universal deploy.sh and cleanup.sh scripts - deploy.sh auto-detects prereqs and offers to create them - cleanup.sh --prereqs empties buckets before deletion - All templates validated with aws cloudformation validate-template --- cfn/prereq-bucket.yaml | 30 ++++ cfn/prereq-vpc-private.yaml | 83 +++++++++ cfn/prereq-vpc-public.yaml | 159 ++++++++++++++++++ cleanup.sh | 95 +++++++++++ deploy.sh | 137 +++++++++++++++ tuts/001-lightsail-gs/cfn-lightsail-gs.yaml | 30 ++++ tuts/002-vpc-gs/cfn-vpc-gs.yaml | 88 ++++++++++ .../cfn-s3-gettingstarted.yaml | 19 +++ .../cfn-cloudmap-custom-attributes.yaml | 72 ++++++++ .../cfn-cloudfront-gettingstarted.yaml | 58 +++++++ .../cfn-vpc-private-servers.yaml | 116 +++++++++++++ tuts/009-vpc-ipam-gs/cfn-vpc-ipam.yaml | 31 ++++ .../cfn-cloudmap-service-discovery.yaml | 33 ++++ .../cfn-batch-fargate.yaml | 49 ++++++ .../cfn-transitgateway.yaml | 71 ++++++++ 15 files changed, 1071 insertions(+) create mode 100644 cfn/prereq-bucket.yaml create mode 100644 cfn/prereq-vpc-private.yaml create mode 100644 cfn/prereq-vpc-public.yaml create mode 100755 cleanup.sh create mode 100755 deploy.sh create mode 100644 tuts/001-lightsail-gs/cfn-lightsail-gs.yaml create mode 100644 tuts/002-vpc-gs/cfn-vpc-gs.yaml create mode 100644 tuts/003-s3-gettingstarted/cfn-s3-gettingstarted.yaml create mode 100644 tuts/004-cloudmap-custom-attributes/cfn-cloudmap-custom-attributes.yaml create mode 100644 tuts/005-cloudfront-gettingstarted/cfn-cloudfront-gettingstarted.yaml create mode 100644 tuts/008-vpc-private-servers-gs/cfn-vpc-private-servers.yaml create mode 100644 tuts/009-vpc-ipam-gs/cfn-vpc-ipam.yaml create mode 100644 tuts/010-cloudmap-service-discovery/cfn-cloudmap-service-discovery.yaml create mode 100644 tuts/011-getting-started-batch-fargate/cfn-batch-fargate.yaml create mode 100644 tuts/012-transitgateway-gettingstarted/cfn-transitgateway.yaml diff --git a/cfn/prereq-bucket.yaml b/cfn/prereq-bucket.yaml new file mode 100644 index 0000000..725f7ae --- /dev/null +++ b/cfn/prereq-bucket.yaml @@ -0,0 +1,30 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: >- + Shared S3 bucket for tutorial artifacts. Deploy once, reference from other + tutorial stacks via cross-stack export. Empty the bucket before deleting. + +Resources: + TutorialBucket: + Type: AWS::S3::Bucket + DeletionPolicy: Delete + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionRule: + ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + Tags: + - Key: tutorial + Value: shared-bucket + +Outputs: + BucketName: + Description: Name of the shared tutorial bucket + Value: !Ref TutorialBucket + Export: + Name: !Sub '${AWS::StackName}-BucketName' + BucketArn: + Description: ARN of the shared tutorial bucket + Value: !GetAtt TutorialBucket.Arn + Export: + Name: !Sub '${AWS::StackName}-BucketArn' diff --git a/cfn/prereq-vpc-private.yaml b/cfn/prereq-vpc-private.yaml new file mode 100644 index 0000000..3a9b88f --- /dev/null +++ b/cfn/prereq-vpc-private.yaml @@ -0,0 +1,83 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: >- + Shared VPC with private subnets only across two AZs. + No internet gateway or NAT gateway. Use VPC endpoints for service access. + Deploy once, reference from tutorial stacks via cross-stack exports. + +Resources: + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.1.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}' + + PrivateSubnet1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.1.1.0/24 + AvailabilityZone: !Select [0, !GetAZs ''] + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-private-1' + + PrivateSubnet2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.1.2.0/24 + AvailabilityZone: !Select [1, !GetAZs ''] + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-private-2' + + PrivateRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + + PrivateSubnet1RouteAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet1 + RouteTableId: !Ref PrivateRouteTable + + PrivateSubnet2RouteAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet2 + RouteTableId: !Ref PrivateRouteTable + + S3Endpoint: + Type: AWS::EC2::VPCEndpoint + Properties: + VpcId: !Ref VPC + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3' + RouteTableIds: + - !Ref PrivateRouteTable + +Outputs: + VpcId: + Value: !Ref VPC + Export: + Name: !Sub '${AWS::StackName}-VpcId' + PrivateSubnet1Id: + Value: !Ref PrivateSubnet1 + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1' + PrivateSubnet2Id: + Value: !Ref PrivateSubnet2 + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2' + PrivateSubnets: + Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]] + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnets' + PrivateRouteTableId: + Value: !Ref PrivateRouteTable + Export: + Name: !Sub '${AWS::StackName}-PrivateRouteTable' diff --git a/cfn/prereq-vpc-public.yaml b/cfn/prereq-vpc-public.yaml new file mode 100644 index 0000000..52a350c --- /dev/null +++ b/cfn/prereq-vpc-public.yaml @@ -0,0 +1,159 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: >- + Shared VPC with public and private subnets across two AZs. + Includes internet gateway, NAT gateway, and route tables. + Deploy once, reference from tutorial stacks via cross-stack exports. + +Resources: + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}' + + InternetGateway: + Type: AWS::EC2::InternetGateway + + GatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + + PublicSubnet1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.0.1.0/24 + AvailabilityZone: !Select [0, !GetAZs ''] + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-public-1' + + PublicSubnet2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.0.2.0/24 + AvailabilityZone: !Select [1, !GetAZs ''] + MapPublicIpOnLaunch: true + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-public-2' + + PrivateSubnet1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.0.3.0/24 + AvailabilityZone: !Select [0, !GetAZs ''] + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-private-1' + + PrivateSubnet2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.0.4.0/24 + AvailabilityZone: !Select [1, !GetAZs ''] + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-private-2' + + NatEip: + Type: AWS::EC2::EIP + DependsOn: GatewayAttachment + + NatGateway: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NatEip.AllocationId + SubnetId: !Ref PublicSubnet1 + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-nat' + + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + + PublicRoute: + Type: AWS::EC2::Route + DependsOn: GatewayAttachment + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + PublicSubnet1RouteAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet1 + RouteTableId: !Ref PublicRouteTable + + PublicSubnet2RouteAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet2 + RouteTableId: !Ref PublicRouteTable + + PrivateRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + + PrivateRoute: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRouteTable + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway + + PrivateSubnet1RouteAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet1 + RouteTableId: !Ref PrivateRouteTable + + PrivateSubnet2RouteAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet2 + RouteTableId: !Ref PrivateRouteTable + +Outputs: + VpcId: + Value: !Ref VPC + Export: + Name: !Sub '${AWS::StackName}-VpcId' + PublicSubnet1Id: + Value: !Ref PublicSubnet1 + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet1' + PublicSubnet2Id: + Value: !Ref PublicSubnet2 + Export: + Name: !Sub '${AWS::StackName}-PublicSubnet2' + PrivateSubnet1Id: + Value: !Ref PrivateSubnet1 + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet1' + PrivateSubnet2Id: + Value: !Ref PrivateSubnet2 + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnet2' + PublicSubnets: + Value: !Join [',', [!Ref PublicSubnet1, !Ref PublicSubnet2]] + Export: + Name: !Sub '${AWS::StackName}-PublicSubnets' + PrivateSubnets: + Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]] + Export: + Name: !Sub '${AWS::StackName}-PrivateSubnets' diff --git a/cleanup.sh b/cleanup.sh new file mode 100755 index 0000000..0d52334 --- /dev/null +++ b/cleanup.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# Delete a tutorial's CloudFormation stack and optionally clean up prerequisites. +# Usage: ./cleanup.sh +# ./cleanup.sh --prereqs # delete prerequisite stacks +set -eo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PREREQ_STACK="tutorial-prereqs" + +if [ "$1" = "--prereqs" ]; then + echo "=== Prerequisite stacks ===" + for STACK in $(aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE \ + --query "StackSummaries[?starts_with(StackName, '$PREREQ_STACK')].StackName" --output text 2>/dev/null); do + echo " $STACK" + done + + echo "" + echo "Prerequisite stacks are shared across tutorials." + echo "Only delete them when you're done with all tutorials." + read -rp "Delete all prerequisite stacks? (y/n): " CHOICE + [[ ! "$CHOICE" =~ ^[Yy]$ ]] && exit 0 + + # Handle bucket prereq — must empty first + BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name "$PREREQ_STACK-bucket" \ + --query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text 2>/dev/null) + if [ -n "$BUCKET_NAME" ] && [ "$BUCKET_NAME" != "None" ]; then + OBJ_COUNT=$(aws s3api list-objects-v2 --bucket "$BUCKET_NAME" --query 'KeyCount' --output text 2>/dev/null || echo "0") + if [ "$OBJ_COUNT" -gt 0 ] 2>/dev/null; then + echo "" + echo "Bucket $BUCKET_NAME contains $OBJ_COUNT objects." + read -rp "Empty the bucket? (y/n): " EMPTY + if [[ "$EMPTY" =~ ^[Yy]$ ]]; then + echo "Emptying bucket..." + aws s3 rm "s3://$BUCKET_NAME" --recursive --quiet + aws s3api list-object-versions --bucket "$BUCKET_NAME" \ + --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}, Quiet: true}' \ + --output json 2>/dev/null | \ + aws s3api delete-objects --bucket "$BUCKET_NAME" --delete file:///dev/stdin > /dev/null 2>&1 || true + echo " Emptied" + else + echo "Cannot delete bucket stack while bucket has objects." + exit 1 + fi + fi + aws cloudformation delete-stack --stack-name "$PREREQ_STACK-bucket" + echo "Deleting $PREREQ_STACK-bucket..." + aws cloudformation wait stack-delete-complete --stack-name "$PREREQ_STACK-bucket" 2>/dev/null + echo " Deleted" + fi + + # Handle VPC prereqs — delete cleanly unless tutorial stacks still reference them + for VPC_TYPE in vpc-public vpc-private; do + VPC_STACK="$PREREQ_STACK-$VPC_TYPE" + STATUS=$(aws cloudformation describe-stacks --stack-name "$VPC_STACK" --query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NONE") + if [ "$STATUS" != "NONE" ] && [ "$STATUS" != "DELETE_COMPLETE" ]; then + echo "Deleting $VPC_STACK..." + aws cloudformation delete-stack --stack-name "$VPC_STACK" + aws cloudformation wait stack-delete-complete --stack-name "$VPC_STACK" 2>/dev/null && echo " Deleted" || echo " Failed (other stacks may still import from it)" + fi + done + exit 0 +fi + +# Delete a tutorial stack +TUT_DIR="$1" +[ -z "$TUT_DIR" ] && echo "Usage: $0 | --prereqs" && exit 1 + +STACK_NAME="tutorial-$(echo "$TUT_DIR" | sed 's/^[0-9]*-//')" + +# Check if stack exists +STATUS=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" \ + --query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NONE") + +if [ "$STATUS" = "NONE" ] || [ "$STATUS" = "DELETE_COMPLETE" ]; then + echo "Stack $STACK_NAME does not exist." + exit 0 +fi + +echo "Stack: $STACK_NAME (status: $STATUS)" +echo "Resources:" +aws cloudformation list-stack-resources --stack-name "$STACK_NAME" \ + --query 'StackResourceSummaries[].{Type:ResourceType,LogicalId:LogicalResourceId,Status:ResourceStatus}' --output table + +echo "" +read -rp "Delete stack $STACK_NAME? (y/n): " CHOICE +[[ ! "$CHOICE" =~ ^[Yy]$ ]] && exit 0 + +echo "Deleting..." +aws cloudformation delete-stack --stack-name "$STACK_NAME" +aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME" +echo "Stack $STACK_NAME deleted." + +echo "" +echo "Note: Prerequisite stacks are still running. To delete them:" +echo " $0 --prereqs" diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..ee57f0e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,137 @@ +#!/bin/bash +# Deploy a tutorial's CloudFormation stack, creating prerequisites if needed. +# Usage: ./deploy.sh [param=value ...] +# Example: ./deploy.sh 094-aws-cloudtrail-gs +# ./deploy.sh 026-kinesis-data-streams Runtime=python3.12 +set -eo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CFN_DIR="$SCRIPT_DIR/cfn" +TUTS_DIR="$SCRIPT_DIR/tuts" +PREREQ_STACK="tutorial-prereqs" + +usage() { + echo "Usage: $0 [param=value ...]" + echo "" + echo "Tutorials with CloudFormation templates:" + for dir in "$TUTS_DIR"/*/; do + TEMPLATE=$(find "$dir" -name 'cfn-*.yaml' -o -name 'cfn-*.yml' 2>/dev/null | head -1) + [ -n "$TEMPLATE" ] && echo " $(basename "$dir")" + done + exit 0 +} + +[ $# -lt 1 ] && usage + +TUT_DIR="$1" +shift +OVERRIDES="$@" + +# Find the template +TEMPLATE=$(find "$TUTS_DIR/$TUT_DIR" -name 'cfn-*.yaml' -o -name 'cfn-*.yml' 2>/dev/null | head -1) +if [ -z "$TEMPLATE" ]; then + echo "No CloudFormation template found in tuts/$TUT_DIR/" + echo "Looking for files matching cfn-*.yaml" + exit 1 +fi + +STACK_NAME="tutorial-$(echo "$TUT_DIR" | sed 's/^[0-9]*-//')" +echo "Template: $TEMPLATE" +echo "Stack: $STACK_NAME" + +# Check if the template imports from prerequisite stacks +TEMPLATE_CONTENT=$(cat "$TEMPLATE") +NEEDS_BUCKET=false +NEEDS_VPC=false + +if echo "$TEMPLATE_CONTENT" | grep -q "Fn::ImportValue.*prereqs.*BucketName\|prereq-bucket"; then + NEEDS_BUCKET=true +fi +if echo "$TEMPLATE_CONTENT" | grep -q "Fn::ImportValue.*prereqs-vpc-public\|prereq-vpc-public"; then + NEEDS_VPC=true + VPC_TYPE="public" +fi +if echo "$TEMPLATE_CONTENT" | grep -q "Fn::ImportValue.*prereqs-vpc-private\|prereq-vpc-private"; then + NEEDS_VPC=true + VPC_TYPE="private" +fi + +# Deploy prerequisites if needed +if [ "$NEEDS_BUCKET" = true ]; then + echo "" + echo "This tutorial requires a shared S3 bucket." + BUCKET_STACK=$(aws cloudformation describe-stacks --stack-name "$PREREQ_STACK-bucket" --query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NONE") + if [ "$BUCKET_STACK" = "NONE" ] || [ "$BUCKET_STACK" = "DELETE_COMPLETE" ]; then + echo "Prerequisite stack '$PREREQ_STACK-bucket' not found." + read -rp "Create it now? (y/n): " CHOICE + if [[ "$CHOICE" =~ ^[Yy]$ ]]; then + echo "Creating shared bucket..." + aws cloudformation deploy \ + --template-file "$CFN_DIR/prereq-bucket.yaml" \ + --stack-name "$PREREQ_STACK-bucket" + echo "Bucket created: $(aws cloudformation describe-stacks --stack-name "$PREREQ_STACK-bucket" --query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text)" + else + echo "Cannot proceed without the bucket prerequisite." + exit 1 + fi + else + BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name "$PREREQ_STACK-bucket" --query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text) + echo "Using existing bucket: $BUCKET_NAME" + fi +fi + +if [ "$NEEDS_VPC" = true ]; then + echo "" + VPC_STACK_NAME="$PREREQ_STACK-vpc-$VPC_TYPE" + echo "This tutorial requires a VPC ($VPC_TYPE subnets)." + VPC_STACK=$(aws cloudformation describe-stacks --stack-name "$VPC_STACK_NAME" --query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NONE") + if [ "$VPC_STACK" = "NONE" ] || [ "$VPC_STACK" = "DELETE_COMPLETE" ]; then + echo "Prerequisite stack '$VPC_STACK_NAME' not found." + read -rp "Create it now? (y/n): " CHOICE + if [[ "$CHOICE" =~ ^[Yy]$ ]]; then + echo "Creating VPC ($VPC_TYPE)..." + aws cloudformation deploy \ + --template-file "$CFN_DIR/prereq-vpc-$VPC_TYPE.yaml" \ + --stack-name "$VPC_STACK_NAME" + echo "VPC created: $(aws cloudformation describe-stacks --stack-name "$VPC_STACK_NAME" --query 'Stacks[0].Outputs[?OutputKey==`VpcId`].OutputValue' --output text)" + else + echo "Cannot proceed without the VPC prerequisite." + exit 1 + fi + else + VPC_ID=$(aws cloudformation describe-stacks --stack-name "$VPC_STACK_NAME" --query 'Stacks[0].Outputs[?OutputKey==`VpcId`].OutputValue' --output text) + echo "Using existing VPC: $VPC_ID" + fi +fi + +# Build capabilities argument +CAPA_ARG="" +if echo "$TEMPLATE_CONTENT" | grep -qE "AWS::IAM::Role|AWS::IAM::Policy|AWS::IAM::InstanceProfile"; then + CAPA_ARG="--capabilities CAPABILITY_IAM" +fi +if echo "$TEMPLATE_CONTENT" | grep -q "RoleName\|PolicyName"; then + CAPA_ARG="--capabilities CAPABILITY_NAMED_IAM" +fi + +# Build overrides argument +OVERRIDES_ARG="" +if [ -n "$OVERRIDES" ]; then + OVERRIDES_ARG="--parameter-overrides $OVERRIDES" +fi + +# Deploy +echo "" +echo "Deploying stack: $STACK_NAME" +aws cloudformation deploy \ + --template-file "$TEMPLATE" \ + --stack-name "$STACK_NAME" \ + $CAPA_ARG \ + $OVERRIDES_ARG + +echo "" +echo "Stack outputs:" +aws cloudformation describe-stacks --stack-name "$STACK_NAME" \ + --query 'Stacks[0].Outputs[].{Key:OutputKey,Value:OutputValue}' --output table 2>/dev/null || echo " (none)" + +echo "" +echo "To delete: $0 --delete $TUT_DIR" diff --git a/tuts/001-lightsail-gs/cfn-lightsail-gs.yaml b/tuts/001-lightsail-gs/cfn-lightsail-gs.yaml new file mode 100644 index 0000000..040928b --- /dev/null +++ b/tuts/001-lightsail-gs/cfn-lightsail-gs.yaml @@ -0,0 +1,30 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Lightsail getting started - instance with attached disk. + +Resources: + Instance: + Type: AWS::Lightsail::Instance + Properties: + InstanceName: !Sub '${AWS::StackName}-instance' + BlueprintId: amazon_linux_2023 + BundleId: nano_3_2 + AvailabilityZone: !Select [0, !GetAZs ''] + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + + Disk: + Type: AWS::Lightsail::Disk + Properties: + DiskName: !Sub '${AWS::StackName}-disk' + SizeInGb: 8 + AvailabilityZone: !Select [0, !GetAZs ''] + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + +Outputs: + InstanceName: + Value: !Ref Instance + DiskName: + Value: !Ref Disk diff --git a/tuts/002-vpc-gs/cfn-vpc-gs.yaml b/tuts/002-vpc-gs/cfn-vpc-gs.yaml new file mode 100644 index 0000000..fdc04ac --- /dev/null +++ b/tuts/002-vpc-gs/cfn-vpc-gs.yaml @@ -0,0 +1,88 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: VPC getting started - VPC with public subnet, internet gateway, and EC2 instance. + +Parameters: + LatestAmiId: + Type: AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 + +Resources: + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsSupport: true + EnableDnsHostnames: true + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-vpc' + + InternetGateway: + Type: AWS::EC2::InternetGateway + + GatewayAttachment: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + + PublicSubnet: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.0.1.0/24 + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [0, !GetAZs ''] + + RouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + + PublicRoute: + Type: AWS::EC2::Route + DependsOn: GatewayAttachment + Properties: + RouteTableId: !Ref RouteTable + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref InternetGateway + + SubnetRouteAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet + RouteTableId: !Ref RouteTable + + SecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Tutorial SSH access + VpcId: !Ref VPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 0.0.0.0/0 + + Instance: + Type: AWS::EC2::Instance + Properties: + InstanceType: t2.micro + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + ImageId: !Ref LatestAmiId + SubnetId: !Ref PublicSubnet + SecurityGroupIds: + - !Ref SecurityGroup + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-instance' + +Outputs: + VpcId: + Value: !Ref VPC + InstanceId: + Value: !Ref Instance + PublicIp: + Value: !GetAtt Instance.PublicIp diff --git a/tuts/003-s3-gettingstarted/cfn-s3-gettingstarted.yaml b/tuts/003-s3-gettingstarted/cfn-s3-gettingstarted.yaml new file mode 100644 index 0000000..b1ffb6b --- /dev/null +++ b/tuts/003-s3-gettingstarted/cfn-s3-gettingstarted.yaml @@ -0,0 +1,19 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: >- + S3 getting started. Uses the shared tutorial bucket from the prereq stack. + Run the CLI script to practice S3 operations on the bucket. + +Parameters: + PrereqStackName: + Type: String + Default: tutorial-prereqs-bucket + +Resources: + BucketReadyCondition: + Type: AWS::CloudFormation::WaitConditionHandle + +Outputs: + BucketName: + Description: Use this bucket with the CLI tutorial + Value: !ImportValue + Fn::Sub: '${PrereqStackName}-BucketName' diff --git a/tuts/004-cloudmap-custom-attributes/cfn-cloudmap-custom-attributes.yaml b/tuts/004-cloudmap-custom-attributes/cfn-cloudmap-custom-attributes.yaml new file mode 100644 index 0000000..35076c4 --- /dev/null +++ b/tuts/004-cloudmap-custom-attributes/cfn-cloudmap-custom-attributes.yaml @@ -0,0 +1,72 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Cloud Map custom attributes - namespace, DynamoDB table, and Lambda function. + +Resources: + Namespace: + Type: AWS::ServiceDiscovery::HttpNamespace + Properties: + Name: !Sub '${AWS::StackName}-ns' + + Table: + Type: AWS::DynamoDB::Table + DeletionPolicy: Delete + Properties: + TableName: !Sub '${AWS::StackName}-data' + BillingMode: PAY_PER_REQUEST + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + + LambdaRole: + 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 + Policies: + - PolicyName: dynamodb-access + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - dynamodb:PutItem + - dynamodb:GetItem + - dynamodb:Query + Resource: !GetAtt Table.Arn + + Function: + Type: AWS::Lambda::Function + Properties: + Runtime: python3.12 + Handler: index.handler + Role: !GetAtt LambdaRole.Arn + Environment: + Variables: + TABLE_NAME: !Ref Table + Code: + ZipFile: | + import os, boto3 + def handler(event, context): + table = boto3.resource('dynamodb').Table(os.environ['TABLE_NAME']) + return {"statusCode": 200} + +Outputs: + NamespaceId: + Value: !GetAtt Namespace.Id + TableName: + Value: !Ref Table + FunctionName: + Value: !Ref Function diff --git a/tuts/005-cloudfront-gettingstarted/cfn-cloudfront-gettingstarted.yaml b/tuts/005-cloudfront-gettingstarted/cfn-cloudfront-gettingstarted.yaml new file mode 100644 index 0000000..2463b02 --- /dev/null +++ b/tuts/005-cloudfront-gettingstarted/cfn-cloudfront-gettingstarted.yaml @@ -0,0 +1,58 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: CloudFront getting started - distribution with S3 origin. + +Resources: + OriginBucket: + Type: AWS::S3::Bucket + DeletionPolicy: Delete + + OAC: + Type: AWS::CloudFront::OriginAccessControl + Properties: + OriginAccessControlConfig: + Name: !Sub '${AWS::StackName}-oac' + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 + + Distribution: + Type: AWS::CloudFront::Distribution + Properties: + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + DistributionConfig: + Enabled: true + DefaultRootObject: index.html + Origins: + - Id: S3Origin + DomainName: !GetAtt OriginBucket.RegionalDomainName + OriginAccessControlId: !GetAtt OAC.Id + S3OriginConfig: + OriginAccessIdentity: '' + DefaultCacheBehavior: + TargetOriginId: S3Origin + ViewerProtocolPolicy: redirect-to-https + CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 + + BucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref OriginBucket + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: cloudfront.amazonaws.com + Action: s3:GetObject + Resource: !Sub '${OriginBucket.Arn}/*' + Condition: + StringEquals: + AWS:SourceArn: !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution}' + +Outputs: + DistributionDomain: + Value: !GetAtt Distribution.DomainName + BucketName: + Value: !Ref OriginBucket diff --git a/tuts/008-vpc-private-servers-gs/cfn-vpc-private-servers.yaml b/tuts/008-vpc-private-servers-gs/cfn-vpc-private-servers.yaml new file mode 100644 index 0000000..0d92e5f --- /dev/null +++ b/tuts/008-vpc-private-servers-gs/cfn-vpc-private-servers.yaml @@ -0,0 +1,116 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: VPC with private servers - NAT gateway, Auto Scaling group in private subnet. + +Parameters: + LatestAmiId: + Type: AWS::SSM::Parameter::Value + Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 + +Resources: + VPC: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.0.0.0/16 + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + EnableDnsSupport: true + EnableDnsHostnames: true + + PublicSubnet: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.0.1.0/24 + MapPublicIpOnLaunch: true + AvailabilityZone: !Select [0, !GetAZs ''] + + PrivateSubnet: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC + CidrBlock: 10.0.2.0/24 + AvailabilityZone: !Select [0, !GetAZs ''] + + IGW: + Type: AWS::EC2::InternetGateway + IGWAttach: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref IGW + + NatEip: + Type: AWS::EC2::EIP + DependsOn: IGWAttach + NatGateway: + Type: AWS::EC2::NatGateway + Properties: + AllocationId: !GetAtt NatEip.AllocationId + SubnetId: !Ref PublicSubnet + + PublicRT: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + PublicRoute: + Type: AWS::EC2::Route + DependsOn: IGWAttach + Properties: + RouteTableId: !Ref PublicRT + DestinationCidrBlock: 0.0.0.0/0 + GatewayId: !Ref IGW + PublicRTAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnet + RouteTableId: !Ref PublicRT + + PrivateRT: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref VPC + PrivateRoute: + Type: AWS::EC2::Route + Properties: + RouteTableId: !Ref PrivateRT + DestinationCidrBlock: 0.0.0.0/0 + NatGatewayId: !Ref NatGateway + PrivateRTAssoc: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PrivateSubnet + RouteTableId: !Ref PrivateRT + + SG: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Private instances + VpcId: !Ref VPC + + LaunchTemplate: + Type: AWS::EC2::LaunchTemplate + Properties: + LaunchTemplateData: + ImageId: !Ref LatestAmiId + InstanceType: t2.micro + SecurityGroupIds: + - !Ref SG + + ASG: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + LaunchTemplate: + LaunchTemplateId: !Ref LaunchTemplate + Version: !GetAtt LaunchTemplate.LatestVersionNumber + MinSize: '1' + MaxSize: '2' + DesiredCapacity: '1' + VPCZoneIdentifier: + - !Ref PrivateSubnet + +Outputs: + VpcId: + Value: !Ref VPC + ASGName: + Value: !Ref ASG diff --git a/tuts/009-vpc-ipam-gs/cfn-vpc-ipam.yaml b/tuts/009-vpc-ipam-gs/cfn-vpc-ipam.yaml new file mode 100644 index 0000000..cfd60a0 --- /dev/null +++ b/tuts/009-vpc-ipam-gs/cfn-vpc-ipam.yaml @@ -0,0 +1,31 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: VPC IPAM getting started - IPAM with pool and VPC. + +Resources: + IPAM: + Type: AWS::EC2::IPAM + Properties: + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + OperatingRegions: + - RegionName: !Ref AWS::Region + + Pool: + Type: AWS::EC2::IPAMPool + Properties: + IpamScopeId: !GetAtt IPAM.PrivateDefaultScopeId + AddressFamily: ipv4 + Locale: !Ref AWS::Region + + PoolCidr: + Type: AWS::EC2::IPAMPoolCidr + Properties: + IpamPoolId: !GetAtt Pool.IpamPoolId + Cidr: 10.0.0.0/16 + +Outputs: + IpamId: + Value: !GetAtt IPAM.IpamId + PoolId: + Value: !GetAtt Pool.IpamPoolId diff --git a/tuts/010-cloudmap-service-discovery/cfn-cloudmap-service-discovery.yaml b/tuts/010-cloudmap-service-discovery/cfn-cloudmap-service-discovery.yaml new file mode 100644 index 0000000..dd803c2 --- /dev/null +++ b/tuts/010-cloudmap-service-discovery/cfn-cloudmap-service-discovery.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Cloud Map service discovery - public DNS namespace with service. + +Parameters: + DomainName: + Type: String + Default: tutorial.example.com + Description: Domain name for the namespace + +Resources: + Namespace: + Type: AWS::ServiceDiscovery::PublicDnsNamespace + Properties: + Name: !Ref DomainName + Tags: + - Key: tutorial + Value: !Ref AWS::StackName + + Service: + Type: AWS::ServiceDiscovery::Service + Properties: + Name: web + NamespaceId: !GetAtt Namespace.Id + DnsConfig: + DnsRecords: + - Type: A + TTL: 60 + +Outputs: + NamespaceId: + Value: !GetAtt Namespace.Id + ServiceId: + Value: !GetAtt Service.Id diff --git a/tuts/011-getting-started-batch-fargate/cfn-batch-fargate.yaml b/tuts/011-getting-started-batch-fargate/cfn-batch-fargate.yaml new file mode 100644 index 0000000..d477d32 --- /dev/null +++ b/tuts/011-getting-started-batch-fargate/cfn-batch-fargate.yaml @@ -0,0 +1,49 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: AWS Batch on Fargate - compute environment and job queue. + +Resources: + BatchServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: batch.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole + + ComputeEnvironment: + Type: AWS::Batch::ComputeEnvironment + Properties: + Type: MANAGED + ComputeResources: + Type: FARGATE + MaxvCpus: 4 + Subnets: + - !Select [0, !Split [',', !ImportValue tutorial-prereqs-vpc-public-PrivateSubnets]] + SecurityGroupIds: + - !Ref BatchSecurityGroup + ServiceRole: !GetAtt BatchServiceRole.Arn + + BatchSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Batch Fargate compute environment + VpcId: !ImportValue tutorial-prereqs-vpc-public-VpcId + + JobQueue: + Type: AWS::Batch::JobQueue + Properties: + Priority: 1 + ComputeEnvironmentOrder: + - Order: 1 + ComputeEnvironment: !Ref ComputeEnvironment + +Outputs: + ComputeEnvironmentArn: + Value: !Ref ComputeEnvironment + JobQueueArn: + Value: !Ref JobQueue diff --git a/tuts/012-transitgateway-gettingstarted/cfn-transitgateway.yaml b/tuts/012-transitgateway-gettingstarted/cfn-transitgateway.yaml new file mode 100644 index 0000000..dccacf5 --- /dev/null +++ b/tuts/012-transitgateway-gettingstarted/cfn-transitgateway.yaml @@ -0,0 +1,71 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Transit Gateway getting started - TGW with two VPCs. + +Resources: + VPC1: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.0.0.0/16 + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-vpc1' + - Key: tutorial + Value: !Ref AWS::StackName + + VPC2: + Type: AWS::EC2::VPC + Properties: + CidrBlock: 10.1.0.0/16 + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-vpc2' + - Key: tutorial + Value: !Ref AWS::StackName + + Subnet1: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC1 + CidrBlock: 10.0.1.0/24 + AvailabilityZone: !Select [0, !GetAZs ''] + + Subnet2: + Type: AWS::EC2::Subnet + Properties: + VpcId: !Ref VPC2 + CidrBlock: 10.1.1.0/24 + AvailabilityZone: !Select [0, !GetAZs ''] + + TGW: + Type: AWS::EC2::TransitGateway + Properties: + Description: Tutorial transit gateway + Tags: + - Key: Name + Value: !Sub '${AWS::StackName}-tgw' + - Key: tutorial + Value: !Ref AWS::StackName + + TGWAttach1: + Type: AWS::EC2::TransitGatewayAttachment + Properties: + TransitGatewayId: !Ref TGW + VpcId: !Ref VPC1 + SubnetIds: + - !Ref Subnet1 + + TGWAttach2: + Type: AWS::EC2::TransitGatewayAttachment + Properties: + TransitGatewayId: !Ref TGW + VpcId: !Ref VPC2 + SubnetIds: + - !Ref Subnet2 + +Outputs: + TransitGatewayId: + Value: !Ref TGW + VPC1Id: + Value: !Ref VPC1 + VPC2Id: + Value: !Ref VPC2