Skip to content

Commit 637ebf8

Browse files
authored
Merge pull request #33 from aws-samples/feature/cfn-templates
Add CloudFormation templates for first 10 tutorials
2 parents 9140802 + 74c4a22 commit 637ebf8

38 files changed

+2255
-9
lines changed

cfn/cleanup-bucket.sh

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/bin/bash
2+
# Empty and delete the shared tutorial S3 bucket, then delete the CloudFormation stack.
3+
# Usage: ./cfn/cleanup-bucket.sh
4+
set -eo pipefail
5+
6+
STACK_NAME="tutorial-prereqs-bucket"
7+
8+
BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" \
9+
--query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text 2>/dev/null)
10+
11+
if [ -z "$BUCKET_NAME" ] || [ "$BUCKET_NAME" = "None" ]; then
12+
echo "No bucket stack found."
13+
exit 0
14+
fi
15+
16+
echo "Bucket: $BUCKET_NAME"
17+
echo ""
18+
echo "Contents:"
19+
aws s3 ls "s3://$BUCKET_NAME/" 2>/dev/null || echo " (empty)"
20+
echo ""
21+
echo "This will permanently delete all objects and the bucket itself."
22+
read -rp "Type the bucket name to confirm: " CONFIRM
23+
24+
if [ "$CONFIRM" != "$BUCKET_NAME" ]; then
25+
echo "Bucket name does not match. Aborting."
26+
exit 1
27+
fi
28+
29+
echo ""
30+
echo "Emptying bucket..."
31+
aws s3 rm "s3://$BUCKET_NAME" --recursive --quiet
32+
33+
aws s3api list-object-versions --bucket "$BUCKET_NAME" \
34+
--query '{Objects: Versions[].{Key:Key,VersionId:VersionId}, Quiet: true}' \
35+
--output json 2>/dev/null | \
36+
aws s3api delete-objects --bucket "$BUCKET_NAME" --delete file:///dev/stdin > /dev/null 2>&1 || true
37+
38+
aws s3api list-object-versions --bucket "$BUCKET_NAME" \
39+
--query '{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}, Quiet: true}' \
40+
--output json 2>/dev/null | \
41+
aws s3api delete-objects --bucket "$BUCKET_NAME" --delete file:///dev/stdin > /dev/null 2>&1 || true
42+
43+
echo "Deleting bucket: $BUCKET_NAME"
44+
aws s3api delete-bucket --bucket "$BUCKET_NAME"
45+
46+
echo "Deleting stack: $STACK_NAME"
47+
aws cloudformation delete-stack --stack-name "$STACK_NAME"
48+
aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME"
49+
50+
echo "Done."

cfn/prereq-bucket.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Description: >-
3+
Shared S3 bucket reference for tutorials. The bucket is created and deleted
4+
by setup-bucket.sh and cleanup-bucket.sh. This stack just exports the name
5+
so other tutorial stacks can import it.
6+
7+
Parameters:
8+
BucketName:
9+
Type: String
10+
Description: Name of the bucket created by setup-bucket.sh
11+
12+
Resources:
13+
Placeholder:
14+
Type: AWS::CloudFormation::WaitConditionHandle
15+
16+
Outputs:
17+
BucketName:
18+
Description: Name of the shared tutorial bucket
19+
Value: !Ref BucketName
20+
Export:
21+
Name: !Sub '${AWS::StackName}-BucketName'
22+
BucketArn:
23+
Description: ARN of the shared tutorial bucket
24+
Value: !Sub 'arn:aws:s3:::${BucketName}'
25+
Export:
26+
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'

cfn/setup-bucket.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
# Create the shared tutorial S3 bucket and register it with CloudFormation.
3+
# Usage: ./cfn/setup-bucket.sh
4+
set -eo pipefail
5+
6+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7+
STACK_NAME="tutorial-prereqs-bucket"
8+
ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
9+
REGION=$(aws configure get region 2>/dev/null || echo "us-east-1")
10+
BUCKET_NAME="tutorial-bucket-${ACCOUNT_ID}-${REGION}"
11+
12+
# Check if stack already exists
13+
STATUS=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" \
14+
--query 'Stacks[0].StackStatus' --output text 2>/dev/null || echo "NONE")
15+
16+
if [ "$STATUS" = "CREATE_COMPLETE" ] || [ "$STATUS" = "UPDATE_COMPLETE" ]; then
17+
EXISTING=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" \
18+
--query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text)
19+
echo "Bucket already exists: $EXISTING"
20+
exit 0
21+
fi
22+
23+
echo "Creating bucket: $BUCKET_NAME"
24+
if [ "$REGION" = "us-east-1" ]; then
25+
aws s3api create-bucket --bucket "$BUCKET_NAME"
26+
else
27+
aws s3api create-bucket --bucket "$BUCKET_NAME" \
28+
--create-bucket-configuration LocationConstraint="$REGION"
29+
fi
30+
31+
aws s3api put-bucket-encryption --bucket "$BUCKET_NAME" \
32+
--server-side-encryption-configuration \
33+
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
34+
35+
aws s3api put-public-access-block --bucket "$BUCKET_NAME" \
36+
--public-access-block-configuration \
37+
'BlockPublicAcls=true,BlockPublicPolicy=true,IgnorePublicAcls=true,RestrictPublicBuckets=true'
38+
39+
echo "Registering bucket with CloudFormation stack: $STACK_NAME"
40+
aws cloudformation deploy \
41+
--template-file "$SCRIPT_DIR/prereq-bucket.yaml" \
42+
--stack-name "$STACK_NAME" \
43+
--parameter-overrides "BucketName=$BUCKET_NAME"
44+
45+
echo "Done. Bucket: $BUCKET_NAME"
46+
echo "Other stacks can import: !ImportValue ${STACK_NAME}-BucketName"

0 commit comments

Comments
 (0)