Skip to content

Commit be28584

Browse files
committed
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
1 parent 49f07d9 commit be28584

15 files changed

Lines changed: 1071 additions & 0 deletions

File tree

cfn/prereq-bucket.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Description: >-
3+
Shared S3 bucket for tutorial artifacts. Deploy once, reference from other
4+
tutorial stacks via cross-stack export. Empty the bucket before deleting.
5+
6+
Resources:
7+
TutorialBucket:
8+
Type: AWS::S3::Bucket
9+
DeletionPolicy: Delete
10+
Properties:
11+
BucketEncryption:
12+
ServerSideEncryptionConfiguration:
13+
- ServerSideEncryptionRule:
14+
ServerSideEncryptionByDefault:
15+
SSEAlgorithm: AES256
16+
Tags:
17+
- Key: tutorial
18+
Value: shared-bucket
19+
20+
Outputs:
21+
BucketName:
22+
Description: Name of the shared tutorial bucket
23+
Value: !Ref TutorialBucket
24+
Export:
25+
Name: !Sub '${AWS::StackName}-BucketName'
26+
BucketArn:
27+
Description: ARN of the shared tutorial bucket
28+
Value: !GetAtt TutorialBucket.Arn
29+
Export:
30+
Name: !Sub '${AWS::StackName}-BucketArn'

cfn/prereq-vpc-private.yaml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Description: >-
3+
Shared VPC with private subnets only across two AZs.
4+
No internet gateway or NAT gateway. Use VPC endpoints for service access.
5+
Deploy once, reference from tutorial stacks via cross-stack exports.
6+
7+
Resources:
8+
VPC:
9+
Type: AWS::EC2::VPC
10+
Properties:
11+
CidrBlock: 10.1.0.0/16
12+
EnableDnsSupport: true
13+
EnableDnsHostnames: true
14+
Tags:
15+
- Key: Name
16+
Value: !Sub '${AWS::StackName}'
17+
18+
PrivateSubnet1:
19+
Type: AWS::EC2::Subnet
20+
Properties:
21+
VpcId: !Ref VPC
22+
CidrBlock: 10.1.1.0/24
23+
AvailabilityZone: !Select [0, !GetAZs '']
24+
Tags:
25+
- Key: Name
26+
Value: !Sub '${AWS::StackName}-private-1'
27+
28+
PrivateSubnet2:
29+
Type: AWS::EC2::Subnet
30+
Properties:
31+
VpcId: !Ref VPC
32+
CidrBlock: 10.1.2.0/24
33+
AvailabilityZone: !Select [1, !GetAZs '']
34+
Tags:
35+
- Key: Name
36+
Value: !Sub '${AWS::StackName}-private-2'
37+
38+
PrivateRouteTable:
39+
Type: AWS::EC2::RouteTable
40+
Properties:
41+
VpcId: !Ref VPC
42+
43+
PrivateSubnet1RouteAssoc:
44+
Type: AWS::EC2::SubnetRouteTableAssociation
45+
Properties:
46+
SubnetId: !Ref PrivateSubnet1
47+
RouteTableId: !Ref PrivateRouteTable
48+
49+
PrivateSubnet2RouteAssoc:
50+
Type: AWS::EC2::SubnetRouteTableAssociation
51+
Properties:
52+
SubnetId: !Ref PrivateSubnet2
53+
RouteTableId: !Ref PrivateRouteTable
54+
55+
S3Endpoint:
56+
Type: AWS::EC2::VPCEndpoint
57+
Properties:
58+
VpcId: !Ref VPC
59+
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
60+
RouteTableIds:
61+
- !Ref PrivateRouteTable
62+
63+
Outputs:
64+
VpcId:
65+
Value: !Ref VPC
66+
Export:
67+
Name: !Sub '${AWS::StackName}-VpcId'
68+
PrivateSubnet1Id:
69+
Value: !Ref PrivateSubnet1
70+
Export:
71+
Name: !Sub '${AWS::StackName}-PrivateSubnet1'
72+
PrivateSubnet2Id:
73+
Value: !Ref PrivateSubnet2
74+
Export:
75+
Name: !Sub '${AWS::StackName}-PrivateSubnet2'
76+
PrivateSubnets:
77+
Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
78+
Export:
79+
Name: !Sub '${AWS::StackName}-PrivateSubnets'
80+
PrivateRouteTableId:
81+
Value: !Ref PrivateRouteTable
82+
Export:
83+
Name: !Sub '${AWS::StackName}-PrivateRouteTable'

cfn/prereq-vpc-public.yaml

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Description: >-
3+
Shared VPC with public and private subnets across two AZs.
4+
Includes internet gateway, NAT gateway, and route tables.
5+
Deploy once, reference from tutorial stacks via cross-stack exports.
6+
7+
Resources:
8+
VPC:
9+
Type: AWS::EC2::VPC
10+
Properties:
11+
CidrBlock: 10.0.0.0/16
12+
EnableDnsSupport: true
13+
EnableDnsHostnames: true
14+
Tags:
15+
- Key: Name
16+
Value: !Sub '${AWS::StackName}'
17+
18+
InternetGateway:
19+
Type: AWS::EC2::InternetGateway
20+
21+
GatewayAttachment:
22+
Type: AWS::EC2::VPCGatewayAttachment
23+
Properties:
24+
VpcId: !Ref VPC
25+
InternetGatewayId: !Ref InternetGateway
26+
27+
PublicSubnet1:
28+
Type: AWS::EC2::Subnet
29+
Properties:
30+
VpcId: !Ref VPC
31+
CidrBlock: 10.0.1.0/24
32+
AvailabilityZone: !Select [0, !GetAZs '']
33+
MapPublicIpOnLaunch: true
34+
Tags:
35+
- Key: Name
36+
Value: !Sub '${AWS::StackName}-public-1'
37+
38+
PublicSubnet2:
39+
Type: AWS::EC2::Subnet
40+
Properties:
41+
VpcId: !Ref VPC
42+
CidrBlock: 10.0.2.0/24
43+
AvailabilityZone: !Select [1, !GetAZs '']
44+
MapPublicIpOnLaunch: true
45+
Tags:
46+
- Key: Name
47+
Value: !Sub '${AWS::StackName}-public-2'
48+
49+
PrivateSubnet1:
50+
Type: AWS::EC2::Subnet
51+
Properties:
52+
VpcId: !Ref VPC
53+
CidrBlock: 10.0.3.0/24
54+
AvailabilityZone: !Select [0, !GetAZs '']
55+
Tags:
56+
- Key: Name
57+
Value: !Sub '${AWS::StackName}-private-1'
58+
59+
PrivateSubnet2:
60+
Type: AWS::EC2::Subnet
61+
Properties:
62+
VpcId: !Ref VPC
63+
CidrBlock: 10.0.4.0/24
64+
AvailabilityZone: !Select [1, !GetAZs '']
65+
Tags:
66+
- Key: Name
67+
Value: !Sub '${AWS::StackName}-private-2'
68+
69+
NatEip:
70+
Type: AWS::EC2::EIP
71+
DependsOn: GatewayAttachment
72+
73+
NatGateway:
74+
Type: AWS::EC2::NatGateway
75+
Properties:
76+
AllocationId: !GetAtt NatEip.AllocationId
77+
SubnetId: !Ref PublicSubnet1
78+
Tags:
79+
- Key: Name
80+
Value: !Sub '${AWS::StackName}-nat'
81+
82+
PublicRouteTable:
83+
Type: AWS::EC2::RouteTable
84+
Properties:
85+
VpcId: !Ref VPC
86+
87+
PublicRoute:
88+
Type: AWS::EC2::Route
89+
DependsOn: GatewayAttachment
90+
Properties:
91+
RouteTableId: !Ref PublicRouteTable
92+
DestinationCidrBlock: 0.0.0.0/0
93+
GatewayId: !Ref InternetGateway
94+
95+
PublicSubnet1RouteAssoc:
96+
Type: AWS::EC2::SubnetRouteTableAssociation
97+
Properties:
98+
SubnetId: !Ref PublicSubnet1
99+
RouteTableId: !Ref PublicRouteTable
100+
101+
PublicSubnet2RouteAssoc:
102+
Type: AWS::EC2::SubnetRouteTableAssociation
103+
Properties:
104+
SubnetId: !Ref PublicSubnet2
105+
RouteTableId: !Ref PublicRouteTable
106+
107+
PrivateRouteTable:
108+
Type: AWS::EC2::RouteTable
109+
Properties:
110+
VpcId: !Ref VPC
111+
112+
PrivateRoute:
113+
Type: AWS::EC2::Route
114+
Properties:
115+
RouteTableId: !Ref PrivateRouteTable
116+
DestinationCidrBlock: 0.0.0.0/0
117+
NatGatewayId: !Ref NatGateway
118+
119+
PrivateSubnet1RouteAssoc:
120+
Type: AWS::EC2::SubnetRouteTableAssociation
121+
Properties:
122+
SubnetId: !Ref PrivateSubnet1
123+
RouteTableId: !Ref PrivateRouteTable
124+
125+
PrivateSubnet2RouteAssoc:
126+
Type: AWS::EC2::SubnetRouteTableAssociation
127+
Properties:
128+
SubnetId: !Ref PrivateSubnet2
129+
RouteTableId: !Ref PrivateRouteTable
130+
131+
Outputs:
132+
VpcId:
133+
Value: !Ref VPC
134+
Export:
135+
Name: !Sub '${AWS::StackName}-VpcId'
136+
PublicSubnet1Id:
137+
Value: !Ref PublicSubnet1
138+
Export:
139+
Name: !Sub '${AWS::StackName}-PublicSubnet1'
140+
PublicSubnet2Id:
141+
Value: !Ref PublicSubnet2
142+
Export:
143+
Name: !Sub '${AWS::StackName}-PublicSubnet2'
144+
PrivateSubnet1Id:
145+
Value: !Ref PrivateSubnet1
146+
Export:
147+
Name: !Sub '${AWS::StackName}-PrivateSubnet1'
148+
PrivateSubnet2Id:
149+
Value: !Ref PrivateSubnet2
150+
Export:
151+
Name: !Sub '${AWS::StackName}-PrivateSubnet2'
152+
PublicSubnets:
153+
Value: !Join [',', [!Ref PublicSubnet1, !Ref PublicSubnet2]]
154+
Export:
155+
Name: !Sub '${AWS::StackName}-PublicSubnets'
156+
PrivateSubnets:
157+
Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
158+
Export:
159+
Name: !Sub '${AWS::StackName}-PrivateSubnets'

cleanup.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/bin/bash
2+
# Delete a tutorial's CloudFormation stack and optionally clean up prerequisites.
3+
# Usage: ./cleanup.sh <tutorial-dir>
4+
# ./cleanup.sh --prereqs # delete prerequisite stacks
5+
set -eo pipefail
6+
7+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8+
PREREQ_STACK="tutorial-prereqs"
9+
10+
if [ "$1" = "--prereqs" ]; then
11+
echo "=== Prerequisite stacks ==="
12+
for STACK in $(aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE \
13+
--query "StackSummaries[?starts_with(StackName, '$PREREQ_STACK')].StackName" --output text 2>/dev/null); do
14+
echo " $STACK"
15+
done
16+
17+
echo ""
18+
echo "Prerequisite stacks are shared across tutorials."
19+
echo "Only delete them when you're done with all tutorials."
20+
read -rp "Delete all prerequisite stacks? (y/n): " CHOICE
21+
[[ ! "$CHOICE" =~ ^[Yy]$ ]] && exit 0
22+
23+
# Handle bucket prereq — must empty first
24+
BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name "$PREREQ_STACK-bucket" \
25+
--query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text 2>/dev/null)
26+
if [ -n "$BUCKET_NAME" ] && [ "$BUCKET_NAME" != "None" ]; then
27+
OBJ_COUNT=$(aws s3api list-objects-v2 --bucket "$BUCKET_NAME" --query 'KeyCount' --output text 2>/dev/null || echo "0")
28+
if [ "$OBJ_COUNT" -gt 0 ] 2>/dev/null; then
29+
echo ""
30+
echo "Bucket $BUCKET_NAME contains $OBJ_COUNT objects."
31+
read -rp "Empty the bucket? (y/n): " EMPTY
32+
if [[ "$EMPTY" =~ ^[Yy]$ ]]; then
33+
echo "Emptying bucket..."
34+
aws s3 rm "s3://$BUCKET_NAME" --recursive --quiet
35+
aws s3api list-object-versions --bucket "$BUCKET_NAME" \
36+
--query '{Objects: Versions[].{Key:Key,VersionId:VersionId}, Quiet: true}' \
37+
--output json 2>/dev/null | \
38+
aws s3api delete-objects --bucket "$BUCKET_NAME" --delete file:///dev/stdin > /dev/null 2>&1 || true
39+
echo " Emptied"
40+
else
41+
echo "Cannot delete bucket stack while bucket has objects."
42+
exit 1
43+
fi
44+
fi
45+
aws cloudformation delete-stack --stack-name "$PREREQ_STACK-bucket"
46+
echo "Deleting $PREREQ_STACK-bucket..."
47+
aws cloudformation wait stack-delete-complete --stack-name "$PREREQ_STACK-bucket" 2>/dev/null
48+
echo " Deleted"
49+
fi
50+
51+
# Handle VPC prereqs — delete cleanly unless tutorial stacks still reference them
52+
for VPC_TYPE in vpc-public vpc-private; do
53+
VPC_STACK="$PREREQ_STACK-$VPC_TYPE"
54+
STATUS=$(aws cloudformation describe-stacks --stack-name "$VPC_STACK" --query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NONE")
55+
if [ "$STATUS" != "NONE" ] && [ "$STATUS" != "DELETE_COMPLETE" ]; then
56+
echo "Deleting $VPC_STACK..."
57+
aws cloudformation delete-stack --stack-name "$VPC_STACK"
58+
aws cloudformation wait stack-delete-complete --stack-name "$VPC_STACK" 2>/dev/null && echo " Deleted" || echo " Failed (other stacks may still import from it)"
59+
fi
60+
done
61+
exit 0
62+
fi
63+
64+
# Delete a tutorial stack
65+
TUT_DIR="$1"
66+
[ -z "$TUT_DIR" ] && echo "Usage: $0 <tutorial-dir> | --prereqs" && exit 1
67+
68+
STACK_NAME="tutorial-$(echo "$TUT_DIR" | sed 's/^[0-9]*-//')"
69+
70+
# Check if stack exists
71+
STATUS=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" \
72+
--query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NONE")
73+
74+
if [ "$STATUS" = "NONE" ] || [ "$STATUS" = "DELETE_COMPLETE" ]; then
75+
echo "Stack $STACK_NAME does not exist."
76+
exit 0
77+
fi
78+
79+
echo "Stack: $STACK_NAME (status: $STATUS)"
80+
echo "Resources:"
81+
aws cloudformation list-stack-resources --stack-name "$STACK_NAME" \
82+
--query 'StackResourceSummaries[].{Type:ResourceType,LogicalId:LogicalResourceId,Status:ResourceStatus}' --output table
83+
84+
echo ""
85+
read -rp "Delete stack $STACK_NAME? (y/n): " CHOICE
86+
[[ ! "$CHOICE" =~ ^[Yy]$ ]] && exit 0
87+
88+
echo "Deleting..."
89+
aws cloudformation delete-stack --stack-name "$STACK_NAME"
90+
aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME"
91+
echo "Stack $STACK_NAME deleted."
92+
93+
echo ""
94+
echo "Note: Prerequisite stacks are still running. To delete them:"
95+
echo " $0 --prereqs"

0 commit comments

Comments
 (0)