aws-cloudformation-templates

GitHub

A collection of useful CloudFormation templates

RAW Doc

README (README.md)

AWS CloudFormation Sample Templates

This repository contains sample CloudFormation templates that you can use
to help you get started on new infrastructure projects. Keep in mind that these
templates are not meant to be production-ready "QuickStarts". You should
take the time to learn how they work, adapt them to your needs, and make sure
that they meet your company's compliance standards.

Each template in this repository passes
CloudFormation Linter
(cfn-lint) checks, and also a basic set of
CloudFormation Guard
rules based on the CIS Top 20, with exceptions for some rules where it
made sense to keep the sample focused on a single use case.

Discord

Join us on Discord to discuss rain and all things CloudFormation! Connect and
interact with CloudFormation developers and experts, find channels to discuss
rain, the CloudFormation registry, StackSets, cfn-lint, Guard and more:

[](https://discord.gg/9zpd7TTRwq)

Submitting Templates

Before you submit a template, we suggest that you follow these guidelines:

- Fork the repository and create a fresh branch to work on your sample

text
git remote add upstream [email protected]:aws-cloudformation/aws-cloudformation-templates.git
git fetch upstream
git checkout -b my-branch-name upstream/main
git push -u origin

- Write the template in YAML, with a .yaml suffix (not .yml or
.template). Our test scripts will auto-generate a JSON file based on the
YAML. YAML is the source of truth for all templates in this repository.
- If your solution needs any other type of YAML file, like a K8s manifest
or a build spec, give it a .yml extension. This will cause it to be skipped
by the test scripts.
- Test your template. Can you successfully create a stack with it? When you
delete a stack, is the stack (and all of its resources) successfully deleted?
Make sure users aren't left with stray resources or stacks that have deletion
errors.
- In the Description section, add a brief description of your template. The
description should indicate what the template does and why it's useful. For
example:
text
Description: "Create a LAMP stack using a single EC2 instance and
a local MySQL database for storage. This template demonstrates using the AWS
CloudFormation bootstrap scripts to install the packages and files necessary
to deploy the Apache web server, PHP, and MySQL when the instance is
launched."

- Format your template to make it human readable:
- Err on the side of human readability. If it makes your template easier to
read, do it.
- Use cfn-lint to lint your template and make sure it is valid.
- Consider using two-space indents to reduce line wrapping.
- Review IAM resources. If you include IAM resources, follow the standard
security advice of granting least privilege (granting only the permissions
required to do a task).
- Remove secrets/credentials from your template. You might hardcode credentials
or secrets in your template when you're testing. Don't forget to remove them
before submitting your template. You can use this tool to help you scrub
secrets:
https://github.com/awslabs/git-secrets.
- Add your template to the correct folder so that others can discover it.
- Run the scripts/test-all.sh script in the directory where you're working to
make sure the template is valid.
- If you write any lambda function code, put it in a separate file and run
pylint or eslint to make sure the code is valid.

When your template is ready, submit a pull request. A member of the AWS
organization will review your request and might suggest changes.

Additional Resources

CloudFormation Linter (cfn-lint)

The CloudFormation Linter is
an indispensable tool for developing your templates. It should be a part of
every developer's workflow, and incorporated into your CI/CD pipelines.

Install cfn-lint with pip:

sh
pip install cfn-lint

CLoudFormation Rain

Rain is a command line interface
(CLI) for CloudFormation that greatly improves the experience for authoring and
deploying templates. It has many features, such as creating starter templates
for various use cases, interactive deployments, modules, and more.

Rain can be installed with Brew:

sh
brew install rain

or if you are a Go user, you can install it like this:

sh
go install github.com/aws-cloudformation/rain/cmd/rain@latest


In the AWS CloudFormation User Guide, you can view more information about the
following topics:

- Learn how to use templates to create AWS CloudFormation stacks using the
AWS Management Console
or
AWS Command Line Interface (AWS CLI).
- To view all the supported AWS resources and their properties, see the
Template Reference.

---

LICENSE (LICENSE.txt)

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

---

NOTICE (NOTICE.txt)

AWS CloudFormation Sample Templates
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.

---

APIGateway/README (APIGateway/README.md)

API Gateway RESTAPI with Lambda Non-Proxy Integration

Use this template to create a sample API Gateway with lambda non-proxy integration

#### Lambda Function

The Lambda function used is simple it parses the input event object for the name,
city, time and day properties. It returns a greeting message as a JSON object.

#### API Gateway
Method request payload

json
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "GetStartedLambdaIntegrationInputModel",
"type": "object",
"properties": {
"callerName": { "type": "string" }
}
}

Request Parameters

json
{
"method.request.path.city": "true",
"method.request.querystring.time": "true",
"method.request.header.day": "true"
}

Request Mapping Template

json
#set($inputRoot = $input.path('$'))
{
"city": "$input.params('city')",
"time": "$input.params('time')",
"day": "$input.params('day')",
"name": "$inputRoot.callerName"
}

#### Test the REST API

To test the RESTAPI
1) In Method exectuion, choose Test
2) choose POST from method drop-down list
3) In path, type Boston
4) In Query Strings, type time=morning
5) In Headers, type day:Tuesday
6) In Request Body, type {"callerName":"Bob"}
7) Choose Test
8) Verify the return response as follows:

`````````{ Good morning, Bob of Boston. Happy Tuesday`````

text
---

APIGateway/Apigateway Lambda Integration.Json (APIGateway/apigateway_lambda_integration.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"ApiType": {
"Description": "The Endpoint type for RESTApi",
"Type": "String",
"AllowedValues": [
"EDGE",
"REGIONAL",
"PRIVATE"
],
"Default": "REGIONAL"
},
"ApigatewayTimeout": {
"Description": "ApiGateway Backend Integration timeout in milliseconds",
"Type": "Number",
"Default": "29000",
"MinValue": "50",
"MaxValue": "29000"
},
"LambdaFunctionName": {
"Description": "The Name for the Lambda Function",
"Type": "String",
"Default": "My-APIGW-Integ-Function"
}
},
"Resources": {
"RestApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Description": "My Rest API",
"Name": "MyApi",
"EndpointConfiguration": {
"Types": [
{
"Ref": "ApiType"
}
]
}
}
},
"ApiResource": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"ParentId": {
"Fn::GetAtt": [
"RestApi",
"RootResourceId"
]
},
"RestApiId": {
"Ref": "RestApi"
},
"PathPart": "{city}"
}
},
"RequestModel": {
"Type": "AWS::ApiGateway::Model",
"Properties": {
"ContentType": "application/json",
"Name": "MyModel",
"RestApiId": {
"Ref": "RestApi"
},
"Schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "MyModel",
"type": "object",
"properties": {
"callerName": {
"type": "string"
}
}
}
}
},
"ApiMethod": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "ANY",
"AuthorizationType": "NONE",
"RequestParameters": {
"method.request.path.city": "true",
"method.request.querystring.time": "true",
"method.request.header.day": "true"
},
"MethodResponses": [
{
"StatusCode": "200"
}
],
"Integration": {
"IntegrationHttpMethod": "POST",
"Type": "AWS",
"TimeoutInMillis": {
"Ref": "ApigatewayTimeout"
},
"Uri": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"/invocations"
]
]
},
"RequestTemplates": {
"application/json": "#set($inputRoot = $input.path('$'))\n {\n \"city\": \"$input.params('city')\",\n \"time\": \"$input.params('time')\",\n \"day\": \"$input.params('day')\",\n \"name\": \"$inputRoot.callerName\"\n }\n"
},
"IntegrationResponses": [
{
"StatusCode": "200"
}
]
},
"ResourceId": {
"Ref": "ApiResource"
},
"RestApiId": {
"Ref": "RestApi"
},
"RequestModels": {
"application/json": {
"Ref": "RequestModel"
}
}
}
},
"LambdaFunction": {
"Type": "AWS::Lambda::Function",
"Metadata": {
"cfn-lint": {
"config": {
"ignore_checks": [
"E3012"
]
}
},
"guard": {
"SuppressedRules": [
"LAMBDA_INSIDE_VPC"
]
}
},
"Properties": {
"Code": {
"ZipFile": {
"Rain::Embed": "handler.py"
}
},
"Handler": "index.lambda_handler",
"FunctionName": {
"Ref": "LambdaFunctionName"
},
"MemorySize": "128",
"Runtime": "python3.12",
"Timeout": "10",
"Role": {
"Fn::GetAtt": [
"LambdaIamRole",
"Arn"
]
}
}
},
"LambdaIamRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"RoleName": "LambdaRole",
"Policies": [
{
"PolicyName": "LambdaApipolicy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup"
],
"Resource": {
"Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*"
}
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": {
"Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:*"
}
}
]
}
}
]
}
},
"LambdaApiGatewayInvoke": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Join": [
"",
[
"arn:aws:execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "RestApi"
},
"///*"
]
]
}
}
}
}
}

---

APIGateway/Apigateway Lambda Integration.Yaml (APIGateway/apigateway_lambda_integration.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
ApiType:
Description: The Endpoint type for RESTApi
Type: String
AllowedValues:
- EDGE
- REGIONAL
- PRIVATE
Default: REGIONAL

ApigatewayTimeout:
Description: ApiGateway Backend Integration timeout in milliseconds
Type: Number
Default: "29000"
MinValue: "50"
MaxValue: "29000"

LambdaFunctionName:
Description: The Name for the Lambda Function
Type: String
Default: My-APIGW-Integ-Function

Resources:
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Description: My Rest API
Name: MyApi
EndpointConfiguration:
Types:
- !Ref ApiType

ApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt RestApi.RootResourceId
RestApiId: !Ref RestApi
PathPart: '{city}'

RequestModel:
Type: AWS::ApiGateway::Model
Properties:
ContentType: application/json
Name: MyModel
RestApiId: !Ref RestApi
Schema:
$schema: http://json-schema.org/draft-04/schema#
title: MyModel
type: object
properties:
callerName:
type: string

ApiMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: ANY
AuthorizationType: NONE
RequestParameters:
method.request.path.city: "true"
method.request.querystring.time: "true"
method.request.header.day: "true"
MethodResponses:
- StatusCode: "200"
Integration:
IntegrationHttpMethod: POST
Type: AWS
TimeoutInMillis: !Ref ApigatewayTimeout
Uri: !Join
- ""
- - 'arn:'
- !Ref AWS::Partition
- ':apigateway:'
- !Ref AWS::Region
- :lambda:path/2015-03-31/functions/
- !GetAtt LambdaFunction.Arn
- /invocations
RequestTemplates:
application/json: |
#set($inputRoot = $input.path('$'))
{
"city": "$input.params('city')",
"time": "$input.params('time')",
"day": "$input.params('day')",
"name": "$inputRoot.callerName"
}
IntegrationResponses:
- StatusCode: "200"
ResourceId: !Ref ApiResource
RestApiId: !Ref RestApi
RequestModels:
application/json: !Ref RequestModel

LambdaFunction:
Type: AWS::Lambda::Function
Metadata:
cfn-lint:
config:
ignore_checks:
- E3012
guard:
SuppressedRules:
- LAMBDA_INSIDE_VPC
Properties:
Code:
ZipFile: !Rain::Embed handler.py
Handler: index.lambda_handler
FunctionName: !Ref LambdaFunctionName
MemorySize: "128"
Runtime: python3.12
Timeout: "10"
Role: !GetAtt LambdaIamRole.Arn

LambdaIamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
RoleName: LambdaRole
Policies:
- PolicyName: LambdaApipolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
Resource: !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*
- Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/${LambdaFunctionName}:*

LambdaApiGatewayInvoke:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt LambdaFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Join
- ""
- - 'arn:aws:execute-api:'
- !Ref AWS::Region
- ':'
- !Ref AWS::AccountId
- ':'
- !Ref RestApi
- ///*

---

AppRunner/AppRunnerServiceFromECR.Json (AppRunner/AppRunnerServiceFromECR.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Sample Template AppRunnerService: This template demonstrates the creation of a App Runner Service from existing ECR Repository. WARNING This template creates an AWS App Runner Service. You will be billed for the AWS resources used if you create a stack from this template.",
"Metadata": {
"License": "Apache-2.0"
},
"Parameters": {
"ECRURL": {
"Description": "URL of the ECR Repository.",
"Type": "String",
"Default": "123456789012.dkr.ecr.us-east-2.amazonaws.com/cfntest:apache"
},
"TCPPORT": {
"Description": "Port on which the container is listening.",
"Type": "Number",
"Default": 80
}
},
"Resources": {
"AppRunnerRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"build.apprunner.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "root",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:DescribeImages",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability"
],
"Resource": "*"
}
]
}
}
]
}
},
"AppRunner": {
"Type": "AWS::AppRunner::Service",
"Properties": {
"ServiceName": {
"Fn::Join": [
"",
[
{
"Ref": "AWS::StackName"
},
"-service"
]
]
},
"SourceConfiguration": {
"AuthenticationConfiguration": {
"AccessRoleArn": {
"Fn::GetAtt": [
"AppRunnerRole",
"Arn"
]
}
},
"AutoDeploymentsEnabled": true,
"ImageRepository": {
"ImageRepositoryType": "ECR",
"ImageIdentifier": {
"Ref": "ECRURL"
},
"ImageConfiguration": {
"Port": {
"Ref": "TCPPORT"
}
}
}
}
}
}
},
"Outputs": {
"AppRunner": {
"Description": "URL of the deployed App Runner Service",
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Fn::GetAtt": [
"AppRunner",
"ServiceUrl"
]
}
]
]
}
}
}
}

---

AppRunner/AppRunnerServiceFromECR.Yaml (AppRunner/AppRunnerServiceFromECR.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: 'AWS CloudFormation Sample Template AppRunnerService: This template demonstrates the creation of a App Runner Service from existing ECR Repository. WARNING This template creates an AWS App Runner Service. You will be billed for the AWS resources used if you create a stack from this template.'

Metadata:
License: Apache-2.0

Parameters:
ECRURL:
Description: URL of the ECR Repository.
Type: String
Default: 123456789012.dkr.ecr.us-east-2.amazonaws.com/cfntest:apache

TCPPORT:
Description: Port on which the container is listening.
Type: Number
Default: 80

Resources:
AppRunnerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- build.apprunner.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:DescribeImages
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
Resource: '*'

AppRunner:
Type: AWS::AppRunner::Service
Properties:
ServiceName: !Join
- ""
- - !Ref AWS::StackName
- -service
SourceConfiguration:
AuthenticationConfiguration:
AccessRoleArn: !GetAtt AppRunnerRole.Arn
AutoDeploymentsEnabled: true
ImageRepository:
ImageRepositoryType: ECR
ImageIdentifier: !Ref ECRURL
ImageConfiguration:
Port: !Ref TCPPORT

# Default Configurations
# InstanceConfiguration:
# Cpu: 1024
# Memory: 2GB
Outputs:
AppRunner:
Description: URL of the deployed App Runner Service
Value: !Join
- ""
- - https://
- !GetAtt AppRunner.ServiceUrl

---

AutoScaling/AutoScalingMultiAZWithNotifications.Json (AutoScaling/AutoScalingMultiAZWithNotifications.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Create a multi-az, load balanced and Auto Scaled sample web site running on\nan Apache Web Server. The application is configured to span all\nAvailability Zones in the region and is Auto-Scaled based on the CPU\nutilization of the web servers. Notifications will be sent to the operator\nemail address on scaling events. The instances are load balanced with a\nsimple health check against the default web page. WARNING This template\ncreates one or more Amazon EC2 instances and an Elastic Load Balancer. You\nwill be billed for the AWS resources used if you create a stack from this\ntemplate.\n",
"Parameters": {
"InstanceType": {
"Description": "WebServer EC2 instance type",
"Type": "String",
"Default": "t4g.micro"
},
"OperatorEMail": {
"Description": "Email address to notify if there are any scaling operations",
"Type": "String"
},
"KeyName": {
"Description": "The EC2 Key Pair to allow SSH access to the instances",
"Type": "AWS::EC2::KeyPair::KeyName",
"ConstraintDescription": "must be the name of an existing EC2 KeyPair."
},
"SSHLocation": {
"Description": "The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"Default": "192.168.1.0/24",
"MinLength": 9,
"MaxLength": 18,
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
},
"LatestAmiId": {
"Type": "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>",
"Default": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64"
},
"KmsKeyArn": {
"Description": "KMS Key ARN to encrypt data",
"Type": "String"
},
"CertificateArn": {
"Description": "Certificate ARN for HTTPS",
"Type": "String"
},
"SecurityGroups": {
"Description": "Security Groups to be used",
"Type": "List<AWS::EC2::SecurityGroup::Id>"
},
"Subnets": {
"Description": "Subnets to be used",
"Type": "List<AWS::EC2::Subnet::Id>"
},
"AZs": {
"Description": "Availability Zones to be used",
"Type": "List<AWS::EC2::AvailabilityZone::Name>"
},
"VPC": {
"Description": "VPC to be used",
"Type": "AWS::EC2::VPC::Id"
}
},
"Mappings": {
"Region2Examples": {
"us-east-1": {
"Examples": "https://s3.amazonaws.com/cloudformation-examples-us-east-1"
},
"us-west-2": {
"Examples": "https://s3-us-west-2.amazonaws.com/cloudformation-examples-us-west-2"
},
"us-west-1": {
"Examples": "https://s3-us-west-1.amazonaws.com/cloudformation-examples-us-west-1"
},
"eu-west-1": {
"Examples": "https://s3-eu-west-1.amazonaws.com/cloudformation-examples-eu-west-1"
},
"eu-central-1": {
"Examples": "https://s3-eu-central-1.amazonaws.com/cloudformation-examples-eu-central-1"
},
"ap-southeast-1": {
"Examples": "https://s3-ap-southeast-1.amazonaws.com/cloudformation-examples-ap-southeast-1"
},
"ap-northeast-1": {
"Examples": "https://s3-ap-northeast-1.amazonaws.com/cloudformation-examples-ap-northeast-1"
},
"ap-northeast-2": {
"Examples": "https://s3-ap-northeast-2.amazonaws.com/cloudformation-examples-ap-northeast-2"
},
"ap-southeast-2": {
"Examples": "https://s3-ap-southeast-2.amazonaws.com/cloudformation-examples-ap-southeast-2"
},
"ap-south-1": {
"Examples": "https://s3-ap-south-1.amazonaws.com/cloudformation-examples-ap-south-1"
},
"us-east-2": {
"Examples": "https://s3-us-east-2.amazonaws.com/cloudformation-examples-us-east-2"
},
"sa-east-1": {
"Examples": "https://s3-sa-east-1.amazonaws.com/cloudformation-examples-sa-east-1"
},
"cn-north-1": {
"Examples": "https://s3.cn-north-1.amazonaws.com.cn/cloudformation-examples-cn-north-1"
}
}
},
"Resources": {
"NotificationTopic": {
"Type": "AWS::SNS::Topic",
"Properties": {
"DisplayName": {
"Fn::Sub": "${AWS::StackName}-NotificationTopic"
},
"Subscription": [
{
"Endpoint": {
"Ref": "OperatorEMail"
},
"Protocol": "email"
}
],
"KmsMasterKeyId": {
"Ref": "KmsKeyArn"
}
}
},
"LaunchTemplate": {
"Type": "AWS::EC2::LaunchTemplate",
"Metadata": {
"AWS::CloudFormation::Init": {
"config": {
"packages": {
"yum": {
"httpd": []
}
},
"files": {
"/var/www/html/index.html": {
"content": {
"Fn::Join": [
"",
[
"<img src=\"",
{
"Fn::FindInMap": [
"Region2Examples",
{
"Ref": "AWS::Region"
},
"Examples"
]
},
"/cloudformation_graphic.png\" alt=\"AWS CloudFormation Logo\"/>",
"<h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>"
]
]
},
"mode": "000644",
"owner": "root",
"group": "root"
},
"/etc/cfn/cfn-hup.conf": {
"content": {
"Fn::Sub": "[main]\nstack=${AWS::StackId}\nregion=${AWS::Region}\n"
},
"mode": "000400",
"owner": "root",
"group": "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
"content": {
"Fn::Sub": "[cfn-auto-reloader-hook]\ntriggers=post.update\npath=Resources.LaunchTemplate.Metadata.AWS::CloudFormation::Init\naction=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region}\nrunas=root\n"
}
}
},
"services": {
"sysvinit": {
"httpd": {
"enabled": true,
"ensureRunning": true
},
"cfn-hup": {
"enabled": true,
"ensureRunning": true,
"files": [
"/etc/cfn/cfn-hup.conf",
"/etc/cfn/hooks.d/cfn-auto-reloader.conf"
]
}
}
}
}
}
},
"Properties": {
"LaunchTemplateName": {
"Fn::Sub": "${AWS::StackName}-LaunchTemplate"
},
"LaunchTemplateData": {
"ImageId": {
"Ref": "LatestAmiId"
},
"InstanceType": {
"Ref": "InstanceType"
},
"SecurityGroupIds": {
"Ref": "SecurityGroups"
},
"KeyName": {
"Ref": "KeyName"
},
"BlockDeviceMappings": [
{
"DeviceName": "/dev/sda1",
"Ebs": {
"VolumeSize": 32
}
}
],
"UserData": {
"Fn::Base64": {
"Fn::Sub": "#!/bin/bash\n/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region}\n/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}\n"
}
},
"TagSpecifications": [
{
"ResourceType": "instance",
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Sub": "${AWS::StackName}-Instance"
}
}
]
}
]
}
}
},
"WebServerGroup": {
"CreationPolicy": {
"ResourceSignal": {
"Timeout": "PT5M",
"Count": 1
}
},
"UpdatePolicy": {
"AutoScalingRollingUpdate": {
"MinInstancesInService": 1,
"MaxBatchSize": 1,
"PauseTime": "PT5M",
"WaitOnResourceSignals": true
}
},
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Metadata": {
"cfn-lint": {
"config": {
"ignore_checks": [
"E3014"
]
}
}
},
"Properties": {
"AvailabilityZones": {
"Ref": "AZs"
},
"LaunchTemplate": {
"LaunchTemplateId": {
"Ref": "LaunchTemplate"
},
"Version": {
"Fn::GetAtt": [
"LaunchTemplate",
"LatestVersionNumber"
]
}
},
"MinSize": "1",
"MaxSize": "3",
"TargetGroupARNs": [
{
"Ref": "TargetGroup"
}
],
"NotificationConfigurations": [
{
"TopicARN": {
"Ref": "NotificationTopic"
},
"NotificationTypes": [
"autoscaling:EC2_INSTANCE_LAUNCH",
"autoscaling:EC2_INSTANCE_LAUNCH_ERROR",
"autoscaling:EC2_INSTANCE_TERMINATE",
"autoscaling:EC2_INSTANCE_TERMINATE_ERROR"
]
}
],
"HealthCheckType": "ELB",
"VPCZoneIdentifier": {
"Ref": "Subnets"
}
}
},
"WebServerScaleUpPolicy": {
"Type": "AWS::AutoScaling::ScalingPolicy",
"Properties": {
"AdjustmentType": "ChangeInCapacity",
"AutoScalingGroupName": {
"Ref": "WebServerGroup"
},
"Cooldown": "60",
"ScalingAdjustment": 1
}
},
"WebServerScaleDownPolicy": {
"Type": "AWS::AutoScaling::ScalingPolicy",
"Properties": {
"AdjustmentType": "ChangeInCapacity",
"AutoScalingGroupName": {
"Ref": "WebServerGroup"
},
"Cooldown": "60",
"ScalingAdjustment": -1
}
},
"CPUAlarmHigh": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmDescription": "Scale-up if CPU > 90% for 10 minutes",
"MetricName": "CPUUtilization",
"Namespace": "AWS/EC2",
"Statistic": "Average",
"Period": 300,
"EvaluationPeriods": 2,
"Threshold": 90,
"AlarmActions": [
{
"Ref": "WebServerScaleUpPolicy"
}
],
"Dimensions": [
{
"Name": "AutoScalingGroupName",
"Value": {
"Ref": "WebServerGroup"
}
}
],
"ComparisonOperator": "GreaterThanThreshold"
}
},
"CPUAlarmLow": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"AlarmDescription": "Scale-down if CPU < 70% for 10 minutes",
"MetricName": "CPUUtilization",
"Namespace": "AWS/EC2",
"Statistic": "Average",
"Period": 300,
"EvaluationPeriods": 2,
"Threshold": 70,
"AlarmActions": [
{
"Ref": "WebServerScaleDownPolicy"
}
],
"Dimensions": [
{
"Name": "AutoScalingGroupName",
"Value": {
"Ref": "WebServerGroup"
}
}
],
"ComparisonOperator": "LessThanThreshold"
}
},
"ElasticLoadBalancer": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Scheme": "internet-facing",
"SecurityGroups": [
{
"Ref": "LoadBalancerSecurityGroup"
}
],
"Subnets": {
"Ref": "Subnets"
},
"Type": "application"
}
},
"LoadBalancerSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Allows inbound traffic on port 443",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": 443,
"ToPort": 443,
"CidrIp": "0.0.0.0/0"
}
],
"VpcId": {
"Ref": "VPC"
}
}
},
"LoadBalancerListener": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"Properties": {
"DefaultActions": [
{
"Type": "forward",
"TargetGroupArn": {
"Ref": "TargetGroup"
}
}
],
"LoadBalancerArn": {
"Ref": "ElasticLoadBalancer"
},
"Port": 443,
"Protocol": "HTTPS",
"SslPolicy": "ELBSecurityPolicy-2016-08",
"Certificates": [
{
"CertificateArn": {
"Ref": "CertificateArn"
}
}
]
}
},
"TargetGroup": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"HealthCheckPath": "/",
"Name": "MyTargetGroup",
"Port": 80,
"Protocol": "HTTP",
"TargetType": "instance",
"VpcId": {
"Ref": "VPC"
}
}
},
"InstanceSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Metadata": {
"guard": {
"SuppressedRules": [
"INCOMING_SSH_DISABLED"
]
}
},
"Properties": {
"GroupDescription": "Enable SSH access and HTTP from the load balancer only",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": 22,
"ToPort": 22,
"CidrIp": {
"Ref": "SSHLocation"
}
},
{
"IpProtocol": "tcp",
"FromPort": 80,
"ToPort": 80,
"SourceSecurityGroupId": {
"Ref": "LoadBalancerSecurityGroup"
}
}
]
}
}
},
"Outputs": {
"URL": {
"Description": "The URL of the website",
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Fn::GetAtt": [
"ElasticLoadBalancer",
"DNSName"
]
}
]
]
}
}
}
}

---

AutoScaling/AutoScalingMultiAZWithNotifications.Yaml (AutoScaling/AutoScalingMultiAZWithNotifications.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: |
Create a multi-az, load balanced and Auto Scaled sample web site running on
an Apache Web Server. The application is configured to span all
Availability Zones in the region and is Auto-Scaled based on the CPU
utilization of the web servers. Notifications will be sent to the operator
email address on scaling events. The instances are load balanced with a
simple health check against the default web page. WARNING This template
creates one or more Amazon EC2 instances and an Elastic Load Balancer. You
will be billed for the AWS resources used if you create a stack from this
template.

Parameters:
InstanceType:
Description: WebServer EC2 instance type
Type: String
Default: t4g.micro

OperatorEMail:
Description: Email address to notify if there are any scaling operations
Type: String

KeyName:
Description: The EC2 Key Pair to allow SSH access to the instances
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: must be the name of an existing EC2 KeyPair.

SSHLocation:
Description: The IP address range that can be used to SSH to the EC2 instances
Type: String
Default: 192.168.1.0/24
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.

LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64

KmsKeyArn:
Description: KMS Key ARN to encrypt data
Type: String

CertificateArn:
Description: Certificate ARN for HTTPS
Type: String

SecurityGroups:
Description: Security Groups to be used
Type: List<AWS::EC2::SecurityGroup::Id>

Subnets:
Description: Subnets to be used
Type: List<AWS::EC2::Subnet::Id>

AZs:
Description: Availability Zones to be used
Type: List<AWS::EC2::AvailabilityZone::Name>

VPC:
Description: VPC to be used
Type: AWS::EC2::VPC::Id

Mappings:
Region2Examples:
us-east-1:
Examples: https://s3.amazonaws.com/cloudformation-examples-us-east-1
us-west-2:
Examples: https://s3-us-west-2.amazonaws.com/cloudformation-examples-us-west-2
us-west-1:
Examples: https://s3-us-west-1.amazonaws.com/cloudformation-examples-us-west-1
eu-west-1:
Examples: https://s3-eu-west-1.amazonaws.com/cloudformation-examples-eu-west-1
eu-central-1:
Examples: https://s3-eu-central-1.amazonaws.com/cloudformation-examples-eu-central-1
ap-southeast-1:
Examples: https://s3-ap-southeast-1.amazonaws.com/cloudformation-examples-ap-southeast-1
ap-northeast-1:
Examples: https://s3-ap-northeast-1.amazonaws.com/cloudformation-examples-ap-northeast-1
ap-northeast-2:
Examples: https://s3-ap-northeast-2.amazonaws.com/cloudformation-examples-ap-northeast-2
ap-southeast-2:
Examples: https://s3-ap-southeast-2.amazonaws.com/cloudformation-examples-ap-southeast-2
ap-south-1:
Examples: https://s3-ap-south-1.amazonaws.com/cloudformation-examples-ap-south-1
us-east-2:
Examples: https://s3-us-east-2.amazonaws.com/cloudformation-examples-us-east-2
sa-east-1:
Examples: https://s3-sa-east-1.amazonaws.com/cloudformation-examples-sa-east-1
cn-north-1:
Examples: https://s3.cn-north-1.amazonaws.com.cn/cloudformation-examples-cn-north-1

Resources:
NotificationTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: !Sub ${AWS::StackName}-NotificationTopic
Subscription:
- Endpoint: !Ref OperatorEMail
Protocol: email
KmsMasterKeyId: !Ref KmsKeyArn

LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
files:
/var/www/html/index.html:
content: !Join
- ""
- - <img src="
- !FindInMap
- Region2Examples
- !Ref AWS::Region
- Examples
- /cloudformation_graphic.png" alt="AWS CloudFormation Logo"/>
- <h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>
mode: "000644"
owner: root
group: root
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
mode: "000400"
owner: root
group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.LaunchTemplate.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region}
runas=root
services:
sysvinit:
httpd:
enabled: true
ensureRunning: true
cfn-hup:
enabled: true
ensureRunning: true
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
Properties:
LaunchTemplateName: !Sub ${AWS::StackName}-LaunchTemplate
LaunchTemplateData:
ImageId: !Ref LatestAmiId
InstanceType: !Ref InstanceType
SecurityGroupIds: !Ref SecurityGroups
KeyName: !Ref KeyName
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: 32
UserData: !Base64
Fn::Sub: |
#!/bin/bash
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Name
Value: !Sub ${AWS::StackName}-Instance

WebServerGroup:
Type: AWS::AutoScaling::AutoScalingGroup
CreationPolicy:
ResourceSignal:
Timeout: PT5M
Count: 1
UpdatePolicy:
AutoScalingRollingUpdate:
MinInstancesInService: 1
MaxBatchSize: 1
PauseTime: PT5M
WaitOnResourceSignals: true
Metadata:
cfn-lint:
config:
ignore_checks:
- E3014
Properties:
AvailabilityZones: !Ref AZs
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: "1"
MaxSize: "3"
TargetGroupARNs:
- !Ref TargetGroup
NotificationConfigurations:
- TopicARN: !Ref NotificationTopic
NotificationTypes:
- autoscaling:EC2_INSTANCE_LAUNCH
- autoscaling:EC2_INSTANCE_LAUNCH_ERROR
- autoscaling:EC2_INSTANCE_TERMINATE
- autoscaling:EC2_INSTANCE_TERMINATE_ERROR
HealthCheckType: ELB
VPCZoneIdentifier: !Ref Subnets

WebServerScaleUpPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: ChangeInCapacity
AutoScalingGroupName: !Ref WebServerGroup
Cooldown: "60"
ScalingAdjustment: 1

WebServerScaleDownPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: ChangeInCapacity
AutoScalingGroupName: !Ref WebServerGroup
Cooldown: "60"
ScalingAdjustment: -1

CPUAlarmHigh:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmDescription: Scale-up if CPU > 90% for 10 minutes
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 90
AlarmActions:
- !Ref WebServerScaleUpPolicy
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref WebServerGroup
ComparisonOperator: GreaterThanThreshold

CPUAlarmLow:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmDescription: Scale-down if CPU < 70% for 10 minutes
MetricName: CPUUtilization
Namespace: AWS/EC2
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 70
AlarmActions:
- !Ref WebServerScaleDownPolicy
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref WebServerGroup
ComparisonOperator: LessThanThreshold

ElasticLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
SecurityGroups:
- !Ref LoadBalancerSecurityGroup
Subnets: !Ref Subnets
Type: application

LoadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allows inbound traffic on port 443
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC

LoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
LoadBalancerArn: !Ref ElasticLoadBalancer
Port: 443
Protocol: HTTPS
SslPolicy: ELBSecurityPolicy-2016-08
Certificates:
- CertificateArn: !Ref CertificateArn

TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckPath: /
Name: MyTargetGroup
Port: 80
Protocol: HTTP
TargetType: instance
VpcId: !Ref VPC

InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Metadata:
guard:
SuppressedRules:
- INCOMING_SSH_DISABLED
Properties:
GroupDescription: Enable SSH access and HTTP from the load balancer only
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHLocation
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup

Outputs:
URL:
Description: The URL of the website
Value: !Join
- ""
- - https://
- !GetAtt ElasticLoadBalancer.DNSName

---

AutoScaling/AutoScalingRollingUpdates.Json (AutoScaling/AutoScalingRollingUpdates.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Sample Template ELBGuidedAutoScalingRollingUpdates: This example creates an auto scaling group behind a load balancer with a simple health check. The Auto Scaling launch configuration includes an update policy that will keep 2 instances running while doing an autoscaling rolling update. The update will roll forward only when the ELB health check detects an updated instance in-service. WARNING This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.",
"Metadata": {
"License": "Apache-2.0"
},
"Parameters": {
"InstanceType": {
"Description": "WebServer EC2 instance type",
"Type": "String",
"AllowedValues": [
"t1.micro",
"t2.nano",
"t2.micro",
"t2.small",
"t2.medium",
"t2.large",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m2.xlarge",
"m2.2xlarge",
"m2.4xlarge",
"m3.medium",
"m3.large",
"m3.xlarge",
"m3.2xlarge",
"m4.large",
"m4.xlarge",
"m4.2xlarge",
"m4.4xlarge",
"m4.10xlarge",
"c1.medium",
"c1.xlarge",
"c3.large",
"c3.xlarge",
"c3.2xlarge",
"c3.4xlarge",
"c3.8xlarge",
"c4.large",
"c4.xlarge",
"c4.2xlarge",
"c4.4xlarge",
"c4.8xlarge",
"g2.2xlarge",
"g2.8xlarge",
"r3.large",
"r3.xlarge",
"r3.2xlarge",
"r3.4xlarge",
"r3.8xlarge",
"i2.xlarge",
"i2.2xlarge",
"i2.4xlarge",
"i2.8xlarge",
"d2.xlarge",
"d2.2xlarge",
"d2.4xlarge",
"d2.8xlarge",
"hs1.8xlarge",
"cr1.8xlarge",
"cc2.8xlarge"
],
"Default": "t2.small",
"ConstraintDescription": "must be a valid EC2 instance type."
},
"KeyName": {
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"Type": "AWS::EC2::KeyPair::KeyName",
"ConstraintDescription": "must be the name of an existing EC2 KeyPair."
},
"SSHLocation": {
"Description": "The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"Default": "0.0.0.0/0",
"MinLength": "9",
"MaxLength": "18",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
}
},
"Mappings": {
"AWSInstanceType2Arch": {
"t1.micro": {
"Arch": "PV64"
},
"t2.nano": {
"Arch": "HVM64"
},
"t2.micro": {
"Arch": "HVM64"
},
"t2.small": {
"Arch": "HVM64"
},
"t2.medium": {
"Arch": "HVM64"
},
"t2.large": {
"Arch": "HVM64"
},
"m1.small": {
"Arch": "PV64"
},
"m1.medium": {
"Arch": "PV64"
},
"m1.large": {
"Arch": "PV64"
},
"m1.xlarge": {
"Arch": "PV64"
},
"m2.xlarge": {
"Arch": "PV64"
},
"m2.2xlarge": {
"Arch": "PV64"
},
"m2.4xlarge": {
"Arch": "PV64"
},
"m3.medium": {
"Arch": "HVM64"
},
"m3.large": {
"Arch": "HVM64"
},
"m3.xlarge": {
"Arch": "HVM64"
},
"m3.2xlarge": {
"Arch": "HVM64"
},
"m4.large": {
"Arch": "HVM64"
},
"m4.xlarge": {
"Arch": "HVM64"
},
"m4.2xlarge": {
"Arch": "HVM64"
},
"m4.4xlarge": {
"Arch": "HVM64"
},
"m4.10xlarge": {
"Arch": "HVM64"
},
"c1.medium": {
"Arch": "PV64"
},
"c1.xlarge": {
"Arch": "PV64"
},
"c3.large": {
"Arch": "HVM64"
},
"c3.xlarge": {
"Arch": "HVM64"
},
"c3.2xlarge": {
"Arch": "HVM64"
},
"c3.4xlarge": {
"Arch": "HVM64"
},
"c3.8xlarge": {
"Arch": "HVM64"
},
"c4.large": {
"Arch": "HVM64"
},
"c4.xlarge": {
"Arch": "HVM64"
},
"c4.2xlarge": {
"Arch": "HVM64"
},
"c4.4xlarge": {
"Arch": "HVM64"
},
"c4.8xlarge": {
"Arch": "HVM64"
},
"g2.2xlarge": {
"Arch": "HVMG2"
},
"g2.8xlarge": {
"Arch": "HVMG2"
},
"r3.large": {
"Arch": "HVM64"
},
"r3.xlarge": {
"Arch": "HVM64"
},
"r3.2xlarge": {
"Arch": "HVM64"
},
"r3.4xlarge": {
"Arch": "HVM64"
},
"r3.8xlarge": {
"Arch": "HVM64"
},
"i2.xlarge": {
"Arch": "HVM64"
},
"i2.2xlarge": {
"Arch": "HVM64"
},
"i2.4xlarge": {
"Arch": "HVM64"
},
"i2.8xlarge": {
"Arch": "HVM64"
},
"d2.xlarge": {
"Arch": "HVM64"
},
"d2.2xlarge": {
"Arch": "HVM64"
},
"d2.4xlarge": {
"Arch": "HVM64"
},
"d2.8xlarge": {
"Arch": "HVM64"
},
"hi1.4xlarge": {
"Arch": "HVM64"
},
"hs1.8xlarge": {
"Arch": "HVM64"
},
"cr1.8xlarge": {
"Arch": "HVM64"
},
"cc2.8xlarge": {
"Arch": "HVM64"
}
},
"AWSRegionArch2AMI": {
"us-east-1": {
"PV64": "ami-2a69aa47",
"HVM64": "ami-6869aa05",
"HVMG2": "ami-a41a3fb3"
},
"us-west-2": {
"PV64": "ami-7f77b31f",
"HVM64": "ami-7172b611",
"HVMG2": "ami-caf253aa"
},
"us-west-1": {
"PV64": "ami-a2490dc2",
"HVM64": "ami-31490d51",
"HVMG2": "ami-00347e60"
},
"eu-west-1": {
"PV64": "ami-4cdd453f",
"HVM64": "ami-f9dd458a",
"HVMG2": "ami-e2f7bd91"
},
"eu-west-2": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-886369ec",
"HVMG2": "NOT_SUPPORTED"
},
"eu-central-1": {
"PV64": "ami-6527cf0a",
"HVM64": "ami-ea26ce85",
"HVMG2": "ami-d2ff04bd"
},
"ap-northeast-1": {
"PV64": "ami-3e42b65f",
"HVM64": "ami-374db956",
"HVMG2": "ami-4c78d52d"
},
"ap-northeast-2": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-2b408b45",
"HVMG2": "NOT_SUPPORTED"
},
"ap-southeast-1": {
"PV64": "ami-df9e4cbc",
"HVM64": "ami-a59b49c6",
"HVMG2": "ami-f3f95990"
},
"ap-southeast-2": {
"PV64": "ami-63351d00",
"HVM64": "ami-dc361ebf",
"HVMG2": "ami-3a122e59"
},
"ap-south-1": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-ffbdd790",
"HVMG2": "ami-21a7d34e"
},
"us-east-2": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-f6035893",
"HVMG2": "NOT_SUPPORTED"
},
"ca-central-1": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-730ebd17",
"HVMG2": "NOT_SUPPORTED"
},
"sa-east-1": {
"PV64": "ami-1ad34676",
"HVM64": "ami-6dd04501",
"HVMG2": "NOT_SUPPORTED"
},
"cn-north-1": {
"PV64": "ami-77559f1a",
"HVM64": "ami-8e6aa0e3",
"HVMG2": "NOT_SUPPORTED"
}
}
},
"Resources": {
"WebServerGroup": {
"CreationPolicy": {
"ResourceSignal": {
"Timeout": "PT15M",
"Count": 2
}
},
"UpdatePolicy": {
"AutoScalingRollingUpdate": {
"MaxBatchSize": 1,
"MinInstancesInService": 1,
"PauseTime": "PT15M",
"WaitOnResourceSignals": true
}
},
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AvailabilityZones": {
"Fn::GetAZs": null
},
"LaunchConfigurationName": {
"Ref": "LaunchConfig"
},
"MinSize": 2,
"MaxSize": 4,
"LoadBalancerNames": [
{
"Ref": "ElasticLoadBalancer"
}
]
}
},
"LaunchConfig": {
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Metadata": {
"AWS::CloudFormation::Init": {
"configSets": {
"full_install": [
"install_cfn",
"install_app",
"verify_instance_health"
]
},
"install_cfn": {
"files": {
"/etc/cfn/cfn-hup.conf": {
"content": {
"Fn::Sub": "[main]\nstack=${AWS::StackId}\nregion=${AWS::Region}\n"
},
"mode": "000400",
"owner": "root",
"group": "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
"content": {
"Fn::Sub": "[cfn-auto-reloader-hook]\ntriggers=post.update\npath=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init\naction=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets full_install --region ${AWS::Region}\nrunas=root\n"
}
}
},
"services": {
"sysvinit": {
"cfn-hup": {
"enabled": "true",
"ensureRunning": "true",
"files": [
"/etc/cfn/cfn-hup.conf",
"/etc/cfn/hooks.d/cfn-auto-reloader.conf"
]
}
}
}
},
"install_app": {
"packages": {
"yum": {
"httpd": []
}
},
"files": {
"/var/www/html/index.html": {
"content": {
"Fn::Join": [
"",
[
"<h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>",
"<p>Version: 1.0</p>"
]
]
},
"mode": "000644",
"owner": "root",
"group": "root"
}
},
"services": {
"sysvinit": {
"httpd": {
"enabled": "true",
"ensureRunning": "true"
}
}
}
},
"verify_instance_health": {
"commands": {
"ELBHealthCheck": {
"command": {
"Fn::Sub": "until [ \"$state\" == \"\\\"InService\\\"\" ]; do state=$(aws --region ${AWS::Region} elb describe-instance-health --load-balancer-name ${ElasticLoadBalancer} --instances $(curl -s http://169.254.169.254/latest/meta-data/instance-id) --query InstanceStates[0].State); sleep 10; done"
}
}
}
}
}
},
"Properties": {
"KeyName": {
"Ref": "KeyName"
},
"ImageId": {
"Fn::FindInMap": [
"AWSRegionArch2AMI",
{
"Ref": "AWS::Region"
},
{
"Fn::FindInMap": [
"AWSInstanceType2Arch",
{
"Ref": "InstanceType"
},
"Arch"
]
}
]
},
"InstanceType": {
"Ref": "InstanceType"
},
"SecurityGroups": [
{
"Ref": "InstanceSecurityGroup"
}
],
"IamInstanceProfile": {
"Ref": "WebServerInstanceProfile"
},
"UserData": {
"Fn::Base64": {
"Fn::Sub": "#!/bin/bash -xe\nyum install -y aws-cfn-bootstrap\n/opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource LaunchConfig --configsets full_install --region ${AWS::Region}\n/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WebServerGroup --region ${AWS::Region}\n"
}
}
}
},
"ElasticLoadBalancer": {
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties": {
"AvailabilityZones": {
"Fn::GetAZs": null
},
"CrossZone": "true",
"Listeners": [
{
"LoadBalancerPort": "80",
"InstancePort": "80",
"Protocol": "HTTP"
}
],
"HealthCheck": {
"Target": "HTTP:80/",
"HealthyThreshold": "3",
"UnhealthyThreshold": "5",
"Interval": "30",
"Timeout": "5"
}
}
},
"InstanceSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Enable SSH access and HTTP access on the configured port",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": {
"Ref": "SSHLocation"
}
},
{
"IpProtocol": "tcp",
"FromPort": "80",
"ToPort": "80",
"CidrIp": "0.0.0.0/0"
}
]
}
},
"WebServerInstanceProfile": {
"Type": "AWS::IAM::InstanceProfile",
"Properties": {
"Path": "/",
"Roles": [
{
"Ref": "DescribeHealthRole"
}
]
}
},
"DescribeHealthRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "describe-instance-health-policy",
"PolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:DescribeInstanceHealth"
],
"Resource": "*"
}
]
}
}
]
}
}
},
"Outputs": {
"URL": {
"Description": "URL of the website",
"Value": {
"Fn::Join": [
"",
[
"http://",
{
"Fn::GetAtt": [
"ElasticLoadBalancer",
"DNSName"
]
}
]
]
}
}
}
}

---

AutoScaling/AutoScalingRollingUpdates.Yaml (AutoScaling/AutoScalingRollingUpdates.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: 'AWS CloudFormation Sample Template ELBGuidedAutoScalingRollingUpdates: This example creates an auto scaling group behind a load balancer with a simple health check. The Auto Scaling launch configuration includes an update policy that will keep 2 instances running while doing an autoscaling rolling update. The update will roll forward only when the ELB health check detects an updated instance in-service. WARNING This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.'

Metadata:
License: Apache-2.0

Parameters:
InstanceType:
Description: WebServer EC2 instance type
Type: String
AllowedValues:
- t1.micro
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
- m1.small
- m1.medium
- m1.large
- m1.xlarge
- m2.xlarge
- m2.2xlarge
- m2.4xlarge
- m3.medium
- m3.large
- m3.xlarge
- m3.2xlarge
- m4.large
- m4.xlarge
- m4.2xlarge
- m4.4xlarge
- m4.10xlarge
- c1.medium
- c1.xlarge
- c3.large
- c3.xlarge
- c3.2xlarge
- c3.4xlarge
- c3.8xlarge
- c4.large
- c4.xlarge
- c4.2xlarge
- c4.4xlarge
- c4.8xlarge
- g2.2xlarge
- g2.8xlarge
- r3.large
- r3.xlarge
- r3.2xlarge
- r3.4xlarge
- r3.8xlarge
- i2.xlarge
- i2.2xlarge
- i2.4xlarge
- i2.8xlarge
- d2.xlarge
- d2.2xlarge
- d2.4xlarge
- d2.8xlarge
- hs1.8xlarge
- cr1.8xlarge
- cc2.8xlarge
Default: t2.small
ConstraintDescription: must be a valid EC2 instance type.

KeyName:
Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: must be the name of an existing EC2 KeyPair.

SSHLocation:
Description: The IP address range that can be used to SSH to the EC2 instances
Type: String
Default: 0.0.0.0/0
MinLength: "9"
MaxLength: "18"
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.

Mappings:
AWSInstanceType2Arch:
t1.micro:
Arch: PV64
t2.nano:
Arch: HVM64
t2.micro:
Arch: HVM64
t2.small:
Arch: HVM64
t2.medium:
Arch: HVM64
t2.large:
Arch: HVM64
m1.small:
Arch: PV64
m1.medium:
Arch: PV64
m1.large:
Arch: PV64
m1.xlarge:
Arch: PV64
m2.xlarge:
Arch: PV64
m2.2xlarge:
Arch: PV64
m2.4xlarge:
Arch: PV64
m3.medium:
Arch: HVM64
m3.large:
Arch: HVM64
m3.xlarge:
Arch: HVM64
m3.2xlarge:
Arch: HVM64
m4.large:
Arch: HVM64
m4.xlarge:
Arch: HVM64
m4.2xlarge:
Arch: HVM64
m4.4xlarge:
Arch: HVM64
m4.10xlarge:
Arch: HVM64
c1.medium:
Arch: PV64
c1.xlarge:
Arch: PV64
c3.large:
Arch: HVM64
c3.xlarge:
Arch: HVM64
c3.2xlarge:
Arch: HVM64
c3.4xlarge:
Arch: HVM64
c3.8xlarge:
Arch: HVM64
c4.large:
Arch: HVM64
c4.xlarge:
Arch: HVM64
c4.2xlarge:
Arch: HVM64
c4.4xlarge:
Arch: HVM64
c4.8xlarge:
Arch: HVM64
g2.2xlarge:
Arch: HVMG2
g2.8xlarge:
Arch: HVMG2
r3.large:
Arch: HVM64
r3.xlarge:
Arch: HVM64
r3.2xlarge:
Arch: HVM64
r3.4xlarge:
Arch: HVM64
r3.8xlarge:
Arch: HVM64
i2.xlarge:
Arch: HVM64
i2.2xlarge:
Arch: HVM64
i2.4xlarge:
Arch: HVM64
i2.8xlarge:
Arch: HVM64
d2.xlarge:
Arch: HVM64
d2.2xlarge:
Arch: HVM64
d2.4xlarge:
Arch: HVM64
d2.8xlarge:
Arch: HVM64
hi1.4xlarge:
Arch: HVM64
hs1.8xlarge:
Arch: HVM64
cr1.8xlarge:
Arch: HVM64
cc2.8xlarge:
Arch: HVM64

AWSRegionArch2AMI:
us-east-1:
PV64: ami-2a69aa47
HVM64: ami-6869aa05
HVMG2: ami-a41a3fb3
us-west-2:
PV64: ami-7f77b31f
HVM64: ami-7172b611
HVMG2: ami-caf253aa
us-west-1:
PV64: ami-a2490dc2
HVM64: ami-31490d51
HVMG2: ami-00347e60
eu-west-1:
PV64: ami-4cdd453f
HVM64: ami-f9dd458a
HVMG2: ami-e2f7bd91
eu-west-2:
PV64: NOT_SUPPORTED
HVM64: ami-886369ec
HVMG2: NOT_SUPPORTED
eu-central-1:
PV64: ami-6527cf0a
HVM64: ami-ea26ce85
HVMG2: ami-d2ff04bd
ap-northeast-1:
PV64: ami-3e42b65f
HVM64: ami-374db956
HVMG2: ami-4c78d52d
ap-northeast-2:
PV64: NOT_SUPPORTED
HVM64: ami-2b408b45
HVMG2: NOT_SUPPORTED
ap-southeast-1:
PV64: ami-df9e4cbc
HVM64: ami-a59b49c6
HVMG2: ami-f3f95990
ap-southeast-2:
PV64: ami-63351d00
HVM64: ami-dc361ebf
HVMG2: ami-3a122e59
ap-south-1:
PV64: NOT_SUPPORTED
HVM64: ami-ffbdd790
HVMG2: ami-21a7d34e
us-east-2:
PV64: NOT_SUPPORTED
HVM64: ami-f6035893
HVMG2: NOT_SUPPORTED
ca-central-1:
PV64: NOT_SUPPORTED
HVM64: ami-730ebd17
HVMG2: NOT_SUPPORTED
sa-east-1:
PV64: ami-1ad34676
HVM64: ami-6dd04501
HVMG2: NOT_SUPPORTED
cn-north-1:
PV64: ami-77559f1a
HVM64: ami-8e6aa0e3
HVMG2: NOT_SUPPORTED

Resources:
WebServerGroup:
CreationPolicy:
ResourceSignal:
Timeout: PT15M
Count: 2
UpdatePolicy:
AutoScalingRollingUpdate:
MaxBatchSize: 1
MinInstancesInService: 1
PauseTime: PT15M
WaitOnResourceSignals: true
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AvailabilityZones: !GetAZs
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: 2
MaxSize: 4
LoadBalancerNames:
- !Ref ElasticLoadBalancer

LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Metadata:
AWS::CloudFormation::Init:
configSets:
full_install:
- install_cfn
- install_app
- verify_instance_health
install_cfn:
files:
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
mode: "000400"
owner: root
group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.LaunchTemplate.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchTemplate --configsets full_install --region ${AWS::Region}
runas=root
services:
sysvinit:
cfn-hup:
enabled: "true"
ensureRunning: "true"
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
install_app:
packages:
yum:
httpd: []
files:
/var/www/html/index.html:
content: !Join
- ""
- - <h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>
- '<p>Version: 1.0</p>'
mode: "000644"
owner: root
group: root
services:
sysvinit:
httpd:
enabled: "true"
ensureRunning: "true"
verify_instance_health:
commands:
ELBHealthCheck:
command: !Sub until [ "$state" == "\"InService\"" ]; do state=$(aws --region ${AWS::Region} elb describe-instance-health --load-balancer-name ${ElasticLoadBalancer} --instances $(curl -s http://169.254.169.254/latest/meta-data/instance-id) --query InstanceStates[0].State); sleep 10; done
Properties:
LaunchTemplateData:
KeyName: !Ref KeyName
ImageId: !FindInMap
- AWSRegionArch2AMI
- !Ref AWS::Region
- !FindInMap
- AWSInstanceType2Arch
- !Ref InstanceType
- Arch
InstanceType: !Ref InstanceType
SecurityGroups:
- !Ref InstanceSecurityGroup
IamInstanceProfile:
Name: !Ref WebServerInstanceProfile
UserData: !Base64
Fn::Sub: |
#!/bin/bash -xe
yum install -y aws-cfn-bootstrap
/opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource LaunchTemplate --configsets full_install --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WebServerGroup --region ${AWS::Region}

ElasticLoadBalancer:
Type: AWS::ElasticLoadBalancing::LoadBalancer
Properties:
AvailabilityZones: !GetAZs
CrossZone: "true"
Listeners:
- LoadBalancerPort: "80"
InstancePort: "80"
Protocol: HTTP
HealthCheck:
Target: HTTP:80/
HealthyThreshold: "3"
UnhealthyThreshold: "5"
Interval: "30"
Timeout: "5"

InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable SSH access and HTTP access on the configured port
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp: !Ref SSHLocation
- IpProtocol: tcp
FromPort: "80"
ToPort: "80"
CidrIp: 0.0.0.0/0

WebServerInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref DescribeHealthRole

DescribeHealthRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: describe-instance-health-policy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- elasticloadbalancing:DescribeInstanceHealth
Resource: '*'

Outputs:
URL:
Description: URL of the website
Value: !Join
- ""
- - http://
- !GetAtt ElasticLoadBalancer.DNSName

---

AutoScaling/AutoScalingScheduledAction.Json (AutoScaling/AutoScalingScheduledAction.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Sample Template AutoScalingScheduledAction: Create a load balanced, Auto Scaled sample website. This example creates an Auto Scaling group with time-based scheduled actions behind a load balancer with a simple health check. WARNING This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.",
"Metadata": {
"License": "Apache-2.0"
},
"Parameters": {
"InstanceType": {
"Description": "WebServer EC2 instance type",
"Type": "String",
"AllowedValues": [
"t1.micro",
"t2.nano",
"t2.micro",
"t2.small",
"t2.medium",
"t2.large",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m2.xlarge",
"m2.2xlarge",
"m2.4xlarge",
"m3.medium",
"m3.large",
"m3.xlarge",
"m3.2xlarge",
"m4.large",
"m4.xlarge",
"m4.2xlarge",
"m4.4xlarge",
"m4.10xlarge",
"c1.medium",
"c1.xlarge",
"c3.large",
"c3.xlarge",
"c3.2xlarge",
"c3.4xlarge",
"c3.8xlarge",
"c4.large",
"c4.xlarge",
"c4.2xlarge",
"c4.4xlarge",
"c4.8xlarge",
"g2.2xlarge",
"g2.8xlarge",
"r3.large",
"r3.xlarge",
"r3.2xlarge",
"r3.4xlarge",
"r3.8xlarge",
"i2.xlarge",
"i2.2xlarge",
"i2.4xlarge",
"i2.8xlarge",
"d2.xlarge",
"d2.2xlarge",
"d2.4xlarge",
"d2.8xlarge",
"hs1.8xlarge",
"cr1.8xlarge",
"cc2.8xlarge"
],
"Default": "t2.small",
"ConstraintDescription": "must be a valid EC2 instance type."
},
"KeyName": {
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"Type": "AWS::EC2::KeyPair::KeyName",
"ConstraintDescription": "must be the name of an existing EC2 KeyPair."
},
"SSHLocation": {
"Description": "The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"Default": "0.0.0.0/0",
"MinLength": "9",
"MaxLength": "18",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
}
},
"Mappings": {
"Region2Examples": {
"us-east-1": {
"Examples": "https://s3.amazonaws.com/cloudformation-examples-us-east-1"
},
"us-west-2": {
"Examples": "https://s3-us-west-2.amazonaws.com/cloudformation-examples-us-west-2"
},
"us-west-1": {
"Examples": "https://s3-us-west-1.amazonaws.com/cloudformation-examples-us-west-1"
},
"eu-west-1": {
"Examples": "https://s3-eu-west-1.amazonaws.com/cloudformation-examples-eu-west-1"
},
"eu-central-1": {
"Examples": "https://s3-eu-central-1.amazonaws.com/cloudformation-examples-eu-central-1"
},
"ap-southeast-1": {
"Examples": "https://s3-ap-southeast-1.amazonaws.com/cloudformation-examples-ap-southeast-1"
},
"ap-northeast-1": {
"Examples": "https://s3-ap-northeast-1.amazonaws.com/cloudformation-examples-ap-northeast-1"
},
"ap-northeast-2": {
"Examples": "https://s3-ap-northeast-2.amazonaws.com/cloudformation-examples-ap-northeast-2"
},
"ap-southeast-2": {
"Examples": "https://s3-ap-southeast-2.amazonaws.com/cloudformation-examples-ap-southeast-2"
},
"ap-south-1": {
"Examples": "https://s3-ap-south-1.amazonaws.com/cloudformation-examples-ap-south-1"
},
"us-east-2": {
"Examples": "https://s3-us-east-2.amazonaws.com/cloudformation-examples-us-east-2"
},
"sa-east-1": {
"Examples": "https://s3-sa-east-1.amazonaws.com/cloudformation-examples-sa-east-1"
},
"cn-north-1": {
"Examples": "https://s3.cn-north-1.amazonaws.com.cn/cloudformation-examples-cn-north-1"
}
},
"AWSInstanceType2Arch": {
"t1.micro": {
"Arch": "PV64"
},
"t2.nano": {
"Arch": "HVM64"
},
"t2.micro": {
"Arch": "HVM64"
},
"t2.small": {
"Arch": "HVM64"
},
"t2.medium": {
"Arch": "HVM64"
},
"t2.large": {
"Arch": "HVM64"
},
"m1.small": {
"Arch": "PV64"
},
"m1.medium": {
"Arch": "PV64"
},
"m1.large": {
"Arch": "PV64"
},
"m1.xlarge": {
"Arch": "PV64"
},
"m2.xlarge": {
"Arch": "PV64"
},
"m2.2xlarge": {
"Arch": "PV64"
},
"m2.4xlarge": {
"Arch": "PV64"
},
"m3.medium": {
"Arch": "HVM64"
},
"m3.large": {
"Arch": "HVM64"
},
"m3.xlarge": {
"Arch": "HVM64"
},
"m3.2xlarge": {
"Arch": "HVM64"
},
"m4.large": {
"Arch": "HVM64"
},
"m4.xlarge": {
"Arch": "HVM64"
},
"m4.2xlarge": {
"Arch": "HVM64"
},
"m4.4xlarge": {
"Arch": "HVM64"
},
"m4.10xlarge": {
"Arch": "HVM64"
},
"c1.medium": {
"Arch": "PV64"
},
"c1.xlarge": {
"Arch": "PV64"
},
"c3.large": {
"Arch": "HVM64"
},
"c3.xlarge": {
"Arch": "HVM64"
},
"c3.2xlarge": {
"Arch": "HVM64"
},
"c3.4xlarge": {
"Arch": "HVM64"
},
"c3.8xlarge": {
"Arch": "HVM64"
},
"c4.large": {
"Arch": "HVM64"
},
"c4.xlarge": {
"Arch": "HVM64"
},
"c4.2xlarge": {
"Arch": "HVM64"
},
"c4.4xlarge": {
"Arch": "HVM64"
},
"c4.8xlarge": {
"Arch": "HVM64"
},
"g2.2xlarge": {
"Arch": "HVMG2"
},
"g2.8xlarge": {
"Arch": "HVMG2"
},
"r3.large": {
"Arch": "HVM64"
},
"r3.xlarge": {
"Arch": "HVM64"
},
"r3.2xlarge": {
"Arch": "HVM64"
},
"r3.4xlarge": {
"Arch": "HVM64"
},
"r3.8xlarge": {
"Arch": "HVM64"
},
"i2.xlarge": {
"Arch": "HVM64"
},
"i2.2xlarge": {
"Arch": "HVM64"
},
"i2.4xlarge": {
"Arch": "HVM64"
},
"i2.8xlarge": {
"Arch": "HVM64"
},
"d2.xlarge": {
"Arch": "HVM64"
},
"d2.2xlarge": {
"Arch": "HVM64"
},
"d2.4xlarge": {
"Arch": "HVM64"
},
"d2.8xlarge": {
"Arch": "HVM64"
},
"hi1.4xlarge": {
"Arch": "HVM64"
},
"hs1.8xlarge": {
"Arch": "HVM64"
},
"cr1.8xlarge": {
"Arch": "HVM64"
},
"cc2.8xlarge": {
"Arch": "HVM64"
}
},
"AWSRegionArch2AMI": {
"us-east-1": {
"PV64": "ami-2a69aa47",
"HVM64": "ami-6869aa05",
"HVMG2": "ami-50b4f047"
},
"us-west-2": {
"PV64": "ami-7f77b31f",
"HVM64": "ami-7172b611",
"HVMG2": "ami-002bf460"
},
"us-west-1": {
"PV64": "ami-a2490dc2",
"HVM64": "ami-31490d51",
"HVMG2": "ami-699ad409"
},
"eu-west-1": {
"PV64": "ami-4cdd453f",
"HVM64": "ami-f9dd458a",
"HVMG2": "ami-f0e0a483"
},
"eu-central-1": {
"PV64": "ami-6527cf0a",
"HVM64": "ami-ea26ce85",
"HVMG2": "ami-d9d62ab6"
},
"ap-northeast-1": {
"PV64": "ami-3e42b65f",
"HVM64": "ami-374db956",
"HVMG2": "ami-78ba6619"
},
"ap-northeast-2": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-2b408b45",
"HVMG2": "NOT_SUPPORTED"
},
"ap-southeast-1": {
"PV64": "ami-df9e4cbc",
"HVM64": "ami-a59b49c6",
"HVMG2": "ami-56e84c35"
},
"ap-southeast-2": {
"PV64": "ami-63351d00",
"HVM64": "ami-dc361ebf",
"HVMG2": "ami-2589b946"
},
"ap-south-1": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-ffbdd790",
"HVMG2": "ami-f7354198"
},
"us-east-2": {
"PV64": "NOT_SUPPORTED",
"HVM64": "ami-f6035893",
"HVMG2": "NOT_SUPPORTED"
},
"sa-east-1": {
"PV64": "ami-1ad34676",
"HVM64": "ami-6dd04501",
"HVMG2": "NOT_SUPPORTED"
},
"cn-north-1": {
"PV64": "ami-77559f1a",
"HVM64": "ami-8e6aa0e3",
"HVMG2": "NOT_SUPPORTED"
}
}
},
"Resources": {
"WebServerGroup": {
"CreationPolicy": {
"ResourceSignal": {
"Timeout": "PT15M"
}
},
"UpdatePolicy": {
"AutoScalingRollingUpdate": {
"MinInstancesInService": 1,
"MaxBatchSize": 1,
"PauseTime": "PT15M",
"WaitOnResourceSignals": true
}
},
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AvailabilityZones": {
"Fn::GetAZs": null
},
"LaunchConfigurationName": {
"Ref": "LaunchConfig"
},
"MinSize": 2,
"MaxSize": 5,
"LoadBalancerNames": [
{
"Ref": "ElasticLoadBalancer"
}
]
}
},
"ScheduledActionUp": {
"Type": "AWS::AutoScaling::ScheduledAction",
"Properties": {
"AutoScalingGroupName": {
"Ref": "WebServerGroup"
},
"MaxSize": "10",
"MinSize": "5",
"Recurrence": "0 7 *"
}
},
"ScheduledActionDown": {
"Type": "AWS::AutoScaling::ScheduledAction",
"Properties": {
"AutoScalingGroupName": {
"Ref": "WebServerGroup"
},
"MaxSize": "1",
"MinSize": "1",
"Recurrence": "0 19 *"
}
},
"LaunchConfig": {
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Metadata": {
"Comment": "Install a simple application",
"AWS::CloudFormation::Init": {
"config": {
"packages": {
"yum": {
"httpd": []
}
},
"files": {
"/var/www/html/index.html": {
"content": {
"Fn::Join": [
"",
[
"<img src=\"",
{
"Fn::FindInMap": [
"Region2Examples",
{
"Ref": "AWS::Region"
},
"Examples"
]
},
"/cloudformation_graphic.png\" alt=\"AWS CloudFormation Logo\"/>",
"<h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>"
]
]
},
"mode": "000644",
"owner": "root",
"group": "root"
},
"/etc/cfn/cfn-hup.conf": {
"content": {
"Fn::Join": [
"",
[
"[main] ",
"stack=",
{
"Ref": "AWS::StackId"
},
" ",
"region=",
{
"Ref": "AWS::Region"
},
" "
]
]
},
"mode": "000400",
"owner": "root",
"group": "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
"content": {
"Fn::Join": [
"",
[
"[cfn-auto-reloader-hook] ",
"triggers=post.update ",
"path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init ",
"action=/opt/aws/bin/cfn-init -v ",
" --stack ",
{
"Ref": "AWS::StackName"
},
" --resource LaunchConfig ",
" --region ",
{
"Ref": "AWS::Region"
},
" ",
"runas=root "
]
]
}
}
},
"services": {
"sysvinit": {
"httpd": {
"enabled": "true",
"ensureRunning": "true"
},
"cfn-hup": {
"enabled": "true",
"ensureRunning": "true",
"files": [
"/etc/cfn/cfn-hup.conf",
"/etc/cfn/hooks.d/cfn-auto-reloader.conf"
]
}
}
}
}
}
},
"Properties": {
"KeyName": {
"Ref": "KeyName"
},
"ImageId": {
"Fn::FindInMap": [
"AWSRegionArch2AMI",
{
"Ref": "AWS::Region"
},
{
"Fn::FindInMap": [
"AWSInstanceType2Arch",
{
"Ref": "InstanceType"
},
"Arch"
]
}
]
},
"SecurityGroups": [
{
"Ref": "InstanceSecurityGroup"
}
],
"InstanceType": {
"Ref": "InstanceType"
},
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash -xe ",
"yum update -y aws-cfn-bootstrap ",
"/opt/aws/bin/cfn-init -v ",
" --stack ",
{
"Ref": "AWS::StackName"
},
" --resource LaunchConfig ",
" --region ",
{
"Ref": "AWS::Region"
},
" ",
"/opt/aws/bin/cfn-signal -e $? ",
" --stack ",
{
"Ref": "AWS::StackName"
},
" --resource WebServerGroup ",
" --region ",
{
"Ref": "AWS::Region"
},
" "
]
]
}
}
}
},
"ElasticLoadBalancer": {
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties": {
"AvailabilityZones": {
"Fn::GetAZs": null
},
"CrossZone": "true",
"Listeners": [
{
"LoadBalancerPort": "80",
"InstancePort": "80",
"Protocol": "HTTP"
}
],
"HealthCheck": {
"Target": "HTTP:80/",
"HealthyThreshold": "3",
"UnhealthyThreshold": "5",
"Interval": "30",
"Timeout": "5"
}
}
},
"InstanceSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Enable SSH access and HTTP access on the configured port",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": {
"Ref": "SSHLocation"
}
},
{
"IpProtocol": "tcp",
"FromPort": "80",
"ToPort": "80",
"CidrIp": "0.0.0.0/0"
}
]
}
}
},
"Outputs": {
"URL": {
"Description": "URL of the website",
"Value": {
"Fn::Join": [
"",
[
"http://",
{
"Fn::GetAtt": [
"ElasticLoadBalancer",
"DNSName"
]
}
]
]
}
}
}
}

---

AutoScaling/AutoScalingScheduledAction.Yaml (AutoScaling/AutoScalingScheduledAction.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: 'AWS CloudFormation Sample Template AutoScalingScheduledAction: Create a load balanced, Auto Scaled sample website. This example creates an Auto Scaling group with time-based scheduled actions behind a load balancer with a simple health check. WARNING This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.'

Metadata:
License: Apache-2.0

Parameters:
InstanceType:
Description: WebServer EC2 instance type
Type: String
AllowedValues:
- t1.micro
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
- m1.small
- m1.medium
- m1.large
- m1.xlarge
- m2.xlarge
- m2.2xlarge
- m2.4xlarge
- m3.medium
- m3.large
- m3.xlarge
- m3.2xlarge
- m4.large
- m4.xlarge
- m4.2xlarge
- m4.4xlarge
- m4.10xlarge
- c1.medium
- c1.xlarge
- c3.large
- c3.xlarge
- c3.2xlarge
- c3.4xlarge
- c3.8xlarge
- c4.large
- c4.xlarge
- c4.2xlarge
- c4.4xlarge
- c4.8xlarge
- g2.2xlarge
- g2.8xlarge
- r3.large
- r3.xlarge
- r3.2xlarge
- r3.4xlarge
- r3.8xlarge
- i2.xlarge
- i2.2xlarge
- i2.4xlarge
- i2.8xlarge
- d2.xlarge
- d2.2xlarge
- d2.4xlarge
- d2.8xlarge
- hs1.8xlarge
- cr1.8xlarge
- cc2.8xlarge
Default: t2.small
ConstraintDescription: must be a valid EC2 instance type.

KeyName:
Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: must be the name of an existing EC2 KeyPair.

SSHLocation:
Description: The IP address range that can be used to SSH to the EC2 instances
Type: String
Default: 0.0.0.0/0
MinLength: "9"
MaxLength: "18"
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.

Mappings:
Region2Examples:
us-east-1:
Examples: https://s3.amazonaws.com/cloudformation-examples-us-east-1
us-west-2:
Examples: https://s3-us-west-2.amazonaws.com/cloudformation-examples-us-west-2
us-west-1:
Examples: https://s3-us-west-1.amazonaws.com/cloudformation-examples-us-west-1
eu-west-1:
Examples: https://s3-eu-west-1.amazonaws.com/cloudformation-examples-eu-west-1
eu-central-1:
Examples: https://s3-eu-central-1.amazonaws.com/cloudformation-examples-eu-central-1
ap-southeast-1:
Examples: https://s3-ap-southeast-1.amazonaws.com/cloudformation-examples-ap-southeast-1
ap-northeast-1:
Examples: https://s3-ap-northeast-1.amazonaws.com/cloudformation-examples-ap-northeast-1
ap-northeast-2:
Examples: https://s3-ap-northeast-2.amazonaws.com/cloudformation-examples-ap-northeast-2
ap-southeast-2:
Examples: https://s3-ap-southeast-2.amazonaws.com/cloudformation-examples-ap-southeast-2
ap-south-1:
Examples: https://s3-ap-south-1.amazonaws.com/cloudformation-examples-ap-south-1
us-east-2:
Examples: https://s3-us-east-2.amazonaws.com/cloudformation-examples-us-east-2
sa-east-1:
Examples: https://s3-sa-east-1.amazonaws.com/cloudformation-examples-sa-east-1
cn-north-1:
Examples: https://s3.cn-north-1.amazonaws.com.cn/cloudformation-examples-cn-north-1

AWSInstanceType2Arch:
t1.micro:
Arch: PV64
t2.nano:
Arch: HVM64
t2.micro:
Arch: HVM64
t2.small:
Arch: HVM64
t2.medium:
Arch: HVM64
t2.large:
Arch: HVM64
m1.small:
Arch: PV64
m1.medium:
Arch: PV64
m1.large:
Arch: PV64
m1.xlarge:
Arch: PV64
m2.xlarge:
Arch: PV64
m2.2xlarge:
Arch: PV64
m2.4xlarge:
Arch: PV64
m3.medium:
Arch: HVM64
m3.large:
Arch: HVM64
m3.xlarge:
Arch: HVM64
m3.2xlarge:
Arch: HVM64
m4.large:
Arch: HVM64
m4.xlarge:
Arch: HVM64
m4.2xlarge:
Arch: HVM64
m4.4xlarge:
Arch: HVM64
m4.10xlarge:
Arch: HVM64
c1.medium:
Arch: PV64
c1.xlarge:
Arch: PV64
c3.large:
Arch: HVM64
c3.xlarge:
Arch: HVM64
c3.2xlarge:
Arch: HVM64
c3.4xlarge:
Arch: HVM64
c3.8xlarge:
Arch: HVM64
c4.large:
Arch: HVM64
c4.xlarge:
Arch: HVM64
c4.2xlarge:
Arch: HVM64
c4.4xlarge:
Arch: HVM64
c4.8xlarge:
Arch: HVM64
g2.2xlarge:
Arch: HVMG2
g2.8xlarge:
Arch: HVMG2
r3.large:
Arch: HVM64
r3.xlarge:
Arch: HVM64
r3.2xlarge:
Arch: HVM64
r3.4xlarge:
Arch: HVM64
r3.8xlarge:
Arch: HVM64
i2.xlarge:
Arch: HVM64
i2.2xlarge:
Arch: HVM64
i2.4xlarge:
Arch: HVM64
i2.8xlarge:
Arch: HVM64
d2.xlarge:
Arch: HVM64
d2.2xlarge:
Arch: HVM64
d2.4xlarge:
Arch: HVM64
d2.8xlarge:
Arch: HVM64
hi1.4xlarge:
Arch: HVM64
hs1.8xlarge:
Arch: HVM64
cr1.8xlarge:
Arch: HVM64
cc2.8xlarge:
Arch: HVM64

AWSRegionArch2AMI:
us-east-1:
PV64: ami-2a69aa47
HVM64: ami-6869aa05
HVMG2: ami-50b4f047
us-west-2:
PV64: ami-7f77b31f
HVM64: ami-7172b611
HVMG2: ami-002bf460
us-west-1:
PV64: ami-a2490dc2
HVM64: ami-31490d51
HVMG2: ami-699ad409
eu-west-1:
PV64: ami-4cdd453f
HVM64: ami-f9dd458a
HVMG2: ami-f0e0a483
eu-central-1:
PV64: ami-6527cf0a
HVM64: ami-ea26ce85
HVMG2: ami-d9d62ab6
ap-northeast-1:
PV64: ami-3e42b65f
HVM64: ami-374db956
HVMG2: ami-78ba6619
ap-northeast-2:
PV64: NOT_SUPPORTED
HVM64: ami-2b408b45
HVMG2: NOT_SUPPORTED
ap-southeast-1:
PV64: ami-df9e4cbc
HVM64: ami-a59b49c6
HVMG2: ami-56e84c35
ap-southeast-2:
PV64: ami-63351d00
HVM64: ami-dc361ebf
HVMG2: ami-2589b946
ap-south-1:
PV64: NOT_SUPPORTED
HVM64: ami-ffbdd790
HVMG2: ami-f7354198
us-east-2:
PV64: NOT_SUPPORTED
HVM64: ami-f6035893
HVMG2: NOT_SUPPORTED
sa-east-1:
PV64: ami-1ad34676
HVM64: ami-6dd04501
HVMG2: NOT_SUPPORTED
cn-north-1:
PV64: ami-77559f1a
HVM64: ami-8e6aa0e3
HVMG2: NOT_SUPPORTED

Resources:
WebServerGroup:
CreationPolicy:
ResourceSignal:
Timeout: PT15M
UpdatePolicy:
AutoScalingRollingUpdate:
MinInstancesInService: 1
MaxBatchSize: 1
PauseTime: PT15M
WaitOnResourceSignals: true
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AvailabilityZones: !GetAZs
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MinSize: 2
MaxSize: 5
LoadBalancerNames:
- !Ref ElasticLoadBalancer

ScheduledActionUp:
Type: AWS::AutoScaling::ScheduledAction
Properties:
AutoScalingGroupName: !Ref WebServerGroup
MaxSize: "10"
MinSize: "5"
Recurrence: 0 7 *

ScheduledActionDown:
Type: AWS::AutoScaling::ScheduledAction
Properties:
AutoScalingGroupName: !Ref WebServerGroup
MaxSize: "1"
MinSize: "1"
Recurrence: 0 19 *

LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Metadata:
Comment: Install a simple application
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
files:
/var/www/html/index.html:
content: !Join
- ""
- - <img src="
- !FindInMap
- Region2Examples
- !Ref AWS::Region
- Examples
- /cloudformation_graphic.png" alt="AWS CloudFormation Logo"/>
- <h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>
mode: "000644"
owner: root
group: root
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
mode: "000400"
owner: root
group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub |
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.LaunchTemplate.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region}
runas=root
services:
sysvinit:
httpd:
enabled: "true"
ensureRunning: "true"
cfn-hup:
enabled: "true"
ensureRunning: "true"
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
Properties:
LaunchTemplateData:
KeyName: !Ref KeyName
ImageId: !FindInMap
- AWSRegionArch2AMI
- !Ref AWS::Region
- !FindInMap
- AWSInstanceType2Arch
- !Ref InstanceType
- Arch
SecurityGroups:
- !Ref InstanceSecurityGroup
InstanceType: !Ref InstanceType
UserData: !Base64
Fn::Sub: |
#!/bin/bash -xe
yum install -y aws-cfn-bootstrap
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchTemplate --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}

ElasticLoadBalancer:
Type: AWS::ElasticLoadBalancing::LoadBalancer
Properties:
AvailabilityZones: !GetAZs
CrossZone: "true"
Listeners:
- LoadBalancerPort: "80"
InstancePort: "80"
Protocol: HTTP
HealthCheck:
Target: HTTP:80/
HealthyThreshold: "3"
UnhealthyThreshold: "5"
Interval: "30"
Timeout: "5"

InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable SSH access and HTTP access on the configured port
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: "22"
ToPort: "22"
CidrIp: !Ref SSHLocation
- IpProtocol: tcp
FromPort: "80"
ToPort: "80"
CidrIp: 0.0.0.0/0

Outputs:
URL:
Description: URL of the website
Value: !Join
- ""
- - http://
- !GetAtt ElasticLoadBalancer.DNSName

---

Overview

These CloudFormation templates can automatically setup PrivateLink infrastructures securely connecting to SAP server.

Pre-requisites

- You already have a working SAP Gateway server that you can connect to

- You already have an AWS account with Admin permission

- You already setup a VPC (with private subnets within) in the AWS account and ensure
- Either SAP server in EC2 of that VPC
- Or SAP server can be reachable wihin that VPC (e.g. on-premise SAP connected through AWS Site-to-Site VPN)

- You already have a public resolvable DNS name that can be used to connect to their SAP server
- Either DNS in Route53 of same account
- Or DNS in Route53 of another account
- Or DNS registered outside of AWS

Use CloudFormation Templates

1. Log into AWS account console with Admin permission

2. Check and choose the region of your SAP-reachable VPC
- PrivateLink are regional service that clients can only call through PrivateLink in the same AWS region
- If you want to setup PrivateLink in a different region, you need to first create a new VPC in the new region and setup VPC Peering with SAP-reachable VPC (noted that the CIDR of new VPC must not overlap with current VPC's IP range)

3. Go to CloudFormation console and click Create stack

4. In the CreateStack console, tick Template is ready and Upload a template file

5. If the DNS in Route53 within same AWS account (full-automation)
1. Upload
SapPrivateLink.yaml and fill all the inputs
2. Click Next all the way (accept all acknowledgements if asked) to create that stack
3. Stack creating will take a while, monitor any updates shown in
Events tab to know if and where got failures (Stack creation will auto-rollback if anything failure and will cleanup everything it created)
4. After stack created, click
Outputs tab, and click each of those to monitor and confirm below things:
- Certificate's status shows
Issued, this should always be issued otherwise Stack creation will be stuck there waiting
- VPCEndpointService's DomainVerification shows
Verified (it may take a few minutes, just keep refresh and monitoring)
- TargetGroup's health check shows
Healthy (it may take a few minutes, just keep refresh and monitoring), if not healthy need check if you give correct port or protocol to connect to your SAP or if the EC2 security group blocks the traffic
5. If above checks all pass, the PrivateLink infrastructure created successfully, and can just use the VPCEndpointService's service name and DNS name to connect to SAP through PrivateLink
6. If anything fails and want to retry, can just manually delete the stack to cleanup all created resources and start over

6. If the DNS outside of that AWS account (semi-automation with two manual domain verification steps)
1. Upload
SapPrivateLinkNoHostedZone.yaml and fill all the inputs
2. Click Next all the way (accept all acknowledgements if asked) to create that stack
3. Stack creating will take a while, monitor any updates shown in
Events tab to know if and where got failures (Stack creation will auto-rollback if anything failure and will cleanup everything it created)
4. Stack creating will stop at certificate creating step (noted that if you earlier verified the certificate with the same domain, it may auto-verify and automatically create certificate), go to that certificate in
Certificate Manager console, in the Domains section take note of CNAME name and CNAME value
5. Go to your DNS provider (in AWS it is Route53) trying to add a new record with below entries:
- Type:
CNAME
- Record Name: the
CNAME name of certificate (noted that CNAME should end with your domain name, if in Route53 the domain name suffix auto-populated, you just need to enter the first string segment before .)
- Record Value: the
CNAME value of certificate
6. If above done correctly, the domain will get verified and certificate will show
Issued (it can take a few minutes), and cloudformation creation will continue
7. After stack created, click Outputs tab and click into the VPCEndpointService URL, and take note of
Domain verification name and Domain verification value
8. Go to your DNS provider (in AWS it is Route53) trying to add a new record with below entries:
- Type:
TXT
- Record Name: the
Domain verification name ending with .<domain name> (if in Route53 where domain name suffix auto-populated, just need to enter the Domain verification name)
- Record Value:
Domain verification value within double quotes (check your DNS provider if double quotes needed or not, like in Route53, double quotes not needed as will be auto-populated)
9. If above done correctly, the domain will get verified shown in VPCEndpointService (it can take a few minutes)
10. Then go back to stack Outputs tab and just check the TargetGroup's health check shows
Healthy (it may take a few minutes, just keep refresh and monitoring), if not healthy need check if you give correct port or protocol to connect to your SAP or if the EC2 security group blocks the traffic
11. If health check pass, the PrivateLink infrastructure created successfully, and can just use the VPCEndpointService's service name and DNS name to connect to SAP through PrivateLink
12. If anything fails and want to retry, can just manually delete the stack to cleanup all created resources and start over

7. You can find all resources created in the CloudFormation stack's Resources tab (you can fine tune by directly editting resource, but it is not advised to delete those resources as they can make CloudFormation delete failing or stucking for quite long time, always delete CloudFormation stack to delete and cleanup resources)

8. If CloudFormation stuck in deleting, it is very possible there are existing connections to the created VPC Endpoint Service, need to manually reject those connections and CloudFormation's deletion will resume automatically

---

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "CloudFormation template to create PrivateLink infrastructure",
"Metadata": {
"AWS::CloudFormation::Interface": {
"ParameterGroups": [
{
"Label": {
"default": "Domain Configuration"
},
"Parameters": [
"DomainName",
"HostedZone"
]
},
{
"Label": {
"default": "Network Configuration"
},
"Parameters": [
"VpcId",
"Subnets"
]
},
{
"Label": {
"default": "SAP Configuration"
},
"Parameters": [
"IP",
"Protocol",
"Port",
"HealthCheckPath",
"InVpc"
]
}
]
}
},
"Parameters": {
"DomainName": {
"Description": "The fully qualified or wildcard domain name of DNS",
"Type": "String"
},
"HostedZone": {
"Description": "The public hostedZone of above domain name",
"Type": "AWS::Route53::HostedZone::Id"
},
"VpcId": {
"Description": "VpcId of your existing Virtual Private Cloud (VPC) where SAP resides",
"Type": "AWS::EC2::VPC::Id"
},
"Subnets": {
"Description": "The private subnets (must include one where SAP resides) of above VPC, recommend choose multiple covering different AZs",
"Type": "List<AWS::EC2::Subnet::Id>"
},
"SecurityGroups": {
"Type": "List<AWS::EC2::SecurityGroup::Id>"
},
"IP": {
"Description": "SAP Gateway's private IP address within VPC",
"Type": "String"
},
"Protocol": {
"Description": "SAP Gateway's connect protocol",
"Type": "String",
"AllowedValues": [
"HTTP",
"HTTPS"
],
"Default": "HTTP"
},
"Port": {
"Description": "SAP Gateway's HTTP or HTTPS (match with protocol you choose above) port number",
"Type": "Number",
"Default": 50000
},
"HealthCheckPath": {
"Description": "SAP Gateway's ping path to do health check",
"Type": "String",
"Default": "/sap/public/ping"
},
"InVpc": {
"Description": "Choose Yes if SAP resides in above VPC; choose No otherwise (in cases of above VPC just peers with another SAP residing VPC)",
"Type": "String",
"AllowedValues": [
"Yes",
"No"
],
"Default": "Yes"
}
},
"Rules": {
"SubnetsInVPC": {
"Assertions": [
{
"Assert": {
"Fn::EachMemberEquals": [
[
"Subnets",
"VpcId"
],
{
"Ref": "VpcId"
}
]
},
"AssertDescription": "All subnets must in the VPC"
}
]
}
},
"Conditions": {
"IpInVpc": {
"Fn::Equals": [
{
"Ref": "InVpc"
},
"Yes"
]
},
"SapUseHttps": {
"Fn::Equals": [
{
"Ref": "Protocol"
},
"HTTPS"
]
}
},
"Transform": "AWS::LanguageExtensions",
"Resources": {
"ASCPrivateLinkCertificate": {
"Type": "AWS::CertificateManager::Certificate",
"Properties": {
"DomainName": {
"Ref": "DomainName"
},
"ValidationMethod": "DNS",
"DomainValidationOptions": [
{
"DomainName": {
"Ref": "DomainName"
},
"HostedZoneId": {
"Ref": "HostedZone"
}
}
]
}
},
"ASCPrivateLinkLambdaRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
}
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "ASCPrivateLinkLambdaPolicy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ec2:DescribeVpcEndpointServiceConfigurations",
"ec2:ModifyVpcEndpointServiceConfiguration",
"route53:ChangeResourceRecordSets"
],
"Resource": "*"
}
]
}
}
]
}
},
"ASCPrivateLinkLambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Description": "Lambda function to help with private link infrastructure setup",
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"ASCPrivateLinkLambdaRole",
"Arn"
]
},
"Timeout": 900,
"Runtime": "python3.12",
"VpcConfig": {
"SubnetIds": {
"Ref": "Subnets"
},
"SecurityGroupIds": {
"Ref": "SecurityGroups"
}
},
"Code": {
"ZipFile": {
"Fn::Sub": [
"import boto3\nimport cfnresponse\nimport logging\ndef handler(event, context):\n print('Receive event: {} and context: {}'.format(str(event), str(context)))\n responseData = {}\n eventType = event['RequestType'].strip()\n props = event['ResourceProperties']\n try:\n if eventType in ('Create'):\n match props['Action']:\n case 'EnablePrivateDNS':\n dnsClient = boto3.client('route53')\n ec2Client = boto3.client('ec2')\n serviceId = props['ServiceId']\n domainName = props['DomainName']\n hostedZoneId = props['HostedZoneId']\n ec2Client.modify_vpc_endpoint_service_configuration(ServiceId=serviceId, PrivateDnsName=domainName)\n validationRecord = ec2Client.describe_vpc_endpoint_service_configurations(ServiceIds=[serviceId])['ServiceConfigurations'][0]['PrivateDnsNameConfiguration']\n dnsClient.change_resource_record_sets(\n HostedZoneId=hostedZoneId,\n ChangeBatch={\n 'Changes': [\n {\n 'Action': 'UPSERT',\n 'ResourceRecordSet': {\n 'Type': validationRecord['Type'],\n 'Name': '{}.{}'.format(validationRecord['Name'], domainName[2:] if domainName.startswith('*') else domainName),\n 'ResourceRecords': [{'Value': '\"{}\"'.format(validationRecord['Value'])}],\n 'TTL': 300\n }\n }\n ]\n }\n )\n case _:\n raise Exception('Unsupported action')\n else:\n print('Skip on resource UPDATE and DELETE')\n cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)\n except Exception as e:\n logging.exception(e)\n cfnresponse.send(event, context, cfnresponse.FAILED, responseData)\n",
{
"Region": {
"Ref": "AWS::Region"
}
}
]
}
}
}
},
"ASCPrivateLinkEnablePrivateDNS": {
"Type": "Custom::CustomResource",
"DependsOn": "ASCPrivateLinkVPCES",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"ASCPrivateLinkLambdaFunction",
"Arn"
]
},
"Action": "EnablePrivateDNS",
"ServiceId": {
"Ref": "ASCPrivateLinkVPCES"
},
"DomainName": {
"Ref": "DomainName"
},
"HostedZoneId": {
"Ref": "HostedZone"
}
}
},
"ASCPrivateLinkNLB": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Type": "network",
"Scheme": "internal",
"Subnets": {
"Ref": "Subnets"
},
"LoadBalancerAttributes": [
{
"Key": "load_balancing.cross_zone.enabled",
"Value": true
}
]
}
},
"ASCPrivateLinkTargetGroup": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"Properties": {
"VpcId": {
"Ref": "VpcId"
},
"Protocol": {
"Fn::If": [
"SapUseHttps",
"TLS",
"TCP"
]
},
"Port": 443,
"TargetType": "ip",
"Targets": [
{
"AvailabilityZone": {
"Fn::If": [
"IpInVpc",
{
"Ref": "AWS::NoValue"
},
"all"
]
},
"Id": {
"Ref": "IP"
},
"Port": {
"Ref": "Port"
}
}
],
"HealthCheckPath": {
"Ref": "HealthCheckPath"
},
"HealthCheckProtocol": {
"Ref": "Protocol"
}
}
},
"ASCPrivateLinkListener": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"Properties": {
"LoadBalancerArn": {
"Ref": "ASCPrivateLinkNLB"
},
"Protocol": "TLS",
"Port": 443,
"SslPolicy": "ELBSecurityPolicy-TLS13-1-0-2021-06",
"Certificates": [
{
"CertificateArn": {
"Ref": "ASCPrivateLinkCertificate"
}
}
],
"DefaultActions": [
{
"Type": "forward",
"TargetGroupArn": {
"Ref": "ASCPrivateLinkTargetGroup"
}
}
]
}
},
"ASCPrivateLinkVPCES": {
"Type": "AWS::EC2::VPCEndpointService",
"Properties": {
"AcceptanceRequired": false,
"NetworkLoadBalancerArns": [
{
"Ref": "ASCPrivateLinkNLB"
}
]
}
},
"ASCPrivateLinkVPCESPermission": {
"Type": "AWS::EC2::VPCEndpointServicePermissions",
"Properties": {
"AllowedPrincipals": [
"appflow.amazonaws.com"
],
"ServiceId": {
"Ref": "ASCPrivateLinkVPCES"
}
}
}
},
"Outputs": {
"CertificateURL": {
"Description": "Go to this URL to check certificate issued or not (also find domain verification details)",
"Value": {
"Fn::Sub": [
"https://${AWS::Region}.console.aws.amazon.com/acm/home?region=${AWS::Region}#/certificates/${CertificateId}",
{
"CertificateId": {
"Fn::Select": [
1,
{
"Fn::Split": [
"/",
{
"Ref": "ASCPrivateLinkCertificate"
}
]
}
]
}
}
]
}
},
"VPCEndpointServiceURL": {
"Description": "Go to this URL to check VPC Endpoint Service attributes (also find privateDNS domain verification details)",
"Value": {
"Fn::Sub": "https://${AWS::Region}.console.aws.amazon.com/vpc/home?region=${AWS::Region}#VpcEndpointServiceDetails:EndpointServiceId=${ASCPrivateLinkVPCES}"
}
},
"TargetGroupURL": {
"Description": "Go to this URL to check target group health check status (to see if got connectivity to SAP)",
"Value": {
"Fn::Sub": "https://${AWS::Region}.console.aws.amazon.com/ec2/home?region=${AWS::Region}#TargetGroup:targetGroupArn=${ASCPrivateLinkTargetGroup}"
}
}
}
}

---

AWSTemplateFormatVersion: "2010-09-09"

Description: CloudFormation template to create PrivateLink infrastructure

Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Domain Configuration
Parameters:
- DomainName
- HostedZone
- Label:
default: Network Configuration
Parameters:
- VpcId
- Subnets
- Label:
default: SAP Configuration
Parameters:
- IP
- Protocol
- Port
- HealthCheckPath
- InVpc

Parameters:
DomainName:
Description: The fully qualified or wildcard domain name of DNS
Type: String

HostedZone:
Description: The public hostedZone of above domain name
Type: AWS::Route53::HostedZone::Id

VpcId:
Description: VpcId of your existing Virtual Private Cloud (VPC) where SAP resides
Type: AWS::EC2::VPC::Id

Subnets:
Description: The private subnets (must include one where SAP resides) of above VPC, recommend choose multiple covering different AZs
Type: List<AWS::EC2::Subnet::Id>

SecurityGroups:
Type: List<AWS::EC2::SecurityGroup::Id>

IP:
Description: SAP Gateway's private IP address within VPC
Type: String

Protocol:
Description: SAP Gateway's connect protocol
Type: String
AllowedValues:
- HTTP
- HTTPS
Default: HTTP

Port:
Description: SAP Gateway's HTTP or HTTPS (match with protocol you choose above) port number
Type: Number
Default: 50000

HealthCheckPath:
Description: SAP Gateway's ping path to do health check
Type: String
Default: /sap/public/ping

InVpc:
Description: Choose Yes if SAP resides in above VPC; choose No otherwise (in cases of above VPC just peers with another SAP residing VPC)
Type: String
AllowedValues:
- Yes
- No
Default: Yes

Rules:
SubnetsInVPC:
Assertions:
- Assert:
Fn::EachMemberEquals:
- !ValueOf
- Subnets
- VpcId
- !Ref VpcId
AssertDescription: All subnets must in the VPC

Conditions:
IpInVpc: !Equals
- !Ref InVpc
- Yes

SapUseHttps: !Equals
- !Ref Protocol
- HTTPS

Transform: AWS::LanguageExtensions

Resources:
ASCPrivateLinkCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: !Ref DomainName
HostedZoneId: !Ref HostedZone

ASCPrivateLinkLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
Path: /
Policies:
- PolicyName: ASCPrivateLinkLambdaPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- ec2:DescribeVpcEndpointServiceConfigurations
- ec2:ModifyVpcEndpointServiceConfiguration
- route53:ChangeResourceRecordSets
Resource: '*'

ASCPrivateLinkLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Description: Lambda function to help with private link infrastructure setup
Handler: index.handler
Role: !GetAtt ASCPrivateLinkLambdaRole.Arn
Timeout: 900
Runtime: python3.12
VpcConfig:
SubnetIds: !Ref Subnets
SecurityGroupIds: !Ref SecurityGroups
Code:
ZipFile: !Sub
- |
import boto3
import cfnresponse
import logging
def handler(event, context):
print('Receive event: {} and context: {}'.format(str(event), str(context)))
responseData = {}
eventType = event['RequestType'].strip()
props = event['ResourceProperties']
try:
if eventType in ('Create'):
match props['Action']:
case 'EnablePrivateDNS':
dnsClient = boto3.client('route53')
ec2Client = boto3.client('ec2')
serviceId = props['ServiceId']
domainName = props['DomainName']
hostedZoneId = props['HostedZoneId']
ec2Client.modify_vpc_endpoint_service_configuration(ServiceId=serviceId, PrivateDnsName=domainName)
validationRecord = ec2Client.describe_vpc_endpoint_service_configurations(ServiceIds=[serviceId])['ServiceConfigurations'][0]['PrivateDnsNameConfiguration']
dnsClient.change_resource_record_sets(
HostedZoneId=hostedZoneId,
ChangeBatch={
'Changes': [
{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Type': validationRecord['Type'],
'Name': '{}.{}'.format(validationRecord['Name'], domainName[2:] if domainName.startswith('*') else domainName),
'ResourceRecords': [{'Value': '"{}"'.format(validationRecord['Value'])}],
'TTL': 300
}
}
]
}
)
case _:
raise Exception('Unsupported action')
else:
print('Skip on resource UPDATE and DELETE')
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
except Exception as e:
logging.exception(e)
cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
- Region: !Ref AWS::Region

ASCPrivateLinkEnablePrivateDNS:
Type: Custom::CustomResource
DependsOn: ASCPrivateLinkVPCES
Properties:
ServiceToken: !GetAtt ASCPrivateLinkLambdaFunction.Arn
Action: EnablePrivateDNS
ServiceId: !Ref ASCPrivateLinkVPCES
DomainName: !Ref DomainName
HostedZoneId: !Ref HostedZone

ASCPrivateLinkNLB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Type: network
Scheme: internal
Subnets: !Ref Subnets
LoadBalancerAttributes:
- Key: load_balancing.cross_zone.enabled # SAP may only live in one AZ, need route to it if traffic from different AZ
Value: true

ASCPrivateLinkTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId: !Ref VpcId
Protocol: !If
- SapUseHttps
- TLS
- TCP
Port: 443
TargetType: ip
Targets:
- AvailabilityZone: !If
- IpInVpc
- !Ref AWS::NoValue
- all
Id: !Ref IP
Port: !Ref Port
HealthCheckPath: !Ref HealthCheckPath
HealthCheckProtocol: !Ref Protocol

ASCPrivateLinkListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ASCPrivateLinkNLB
Protocol: TLS
Port: 443
SslPolicy: ELBSecurityPolicy-TLS13-1-0-2021-06
Certificates:
- CertificateArn: !Ref ASCPrivateLinkCertificate
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ASCPrivateLinkTargetGroup

ASCPrivateLinkVPCES:
Type: AWS::EC2::VPCEndpointService
Properties:
AcceptanceRequired: false
NetworkLoadBalancerArns:
- !Ref ASCPrivateLinkNLB

ASCPrivateLinkVPCESPermission:
Type: AWS::EC2::VPCEndpointServicePermissions
Properties:
AllowedPrincipals:
- appflow.amazonaws.com
ServiceId: !Ref ASCPrivateLinkVPCES

Outputs:
CertificateURL:
Description: Go to this URL to check certificate issued or not (also find domain verification details)
Value: !Sub
- https://${AWS::Region}.console.aws.amazon.com/acm/home?region=${AWS::Region}#/certificates/${CertificateId}
- CertificateId: !Select
- 1
- !Split
- /
- !Ref ASCPrivateLinkCertificate

VPCEndpointServiceURL:
Description: Go to this URL to check VPC Endpoint Service attributes (also find privateDNS domain verification details)
Value: !Sub https://${AWS::Region}.console.aws.amazon.com/vpc/home?region=${AWS::Region}#VpcEndpointServiceDetails:EndpointServiceId=${ASCPrivateLinkVPCES}

TargetGroupURL:
Description: Go to this URL to check target group health check status (to see if got connectivity to SAP)
Value: !Sub https://${AWS::Region}.console.aws.amazon.com/ec2/home?region=${AWS::Region}#TargetGroup:targetGroupArn=${ASCPrivateLinkTargetGroup}

---

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "CloudFormation template to create PrivateLink infrastructure",
"Metadata": {
"AWS::CloudFormation::Interface": {
"ParameterGroups": [
{
"Label": {
"default": "Domain Configuration"
},
"Parameters": [
"DomainName"
]
},
{
"Label": {
"default": "Network Configuration"
},
"Parameters": [
"VpcId",
"Subnets"
]
},
{
"Label": {
"default": "SAP Configuration"
},
"Parameters": [
"IP",
"Protocol",
"Port",
"HealthCheckPath",
"InVpc"
]
}
]
}
},
"Parameters": {
"DomainName": {
"Description": "The fully qualified or wildcard domain name of DNS",
"Type": "String"
},
"VpcId": {
"Description": "VpcId of your existing Virtual Private Cloud (VPC) where SAP resides",
"Type": "AWS::EC2::VPC::Id"
},
"Subnets": {
"Description": "The private subnets (must include one where SAP resides) of above VPC, recommend choose multiple covering different AZs",
"Type": "List<AWS::EC2::Subnet::Id>"
},
"SecurityGroups": {
"Type": "List<AWS::EC2::SecurityGroup::Id>"
},
"IP": {
"Description": "SAP Gateway's private IP address within VPC",
"Type": "String"
},
"Protocol": {
"Description": "SAP Gateway's connect protocol",
"Type": "String",
"AllowedValues": [
"HTTP",
"HTTPS"
],
"Default": "HTTP"
},
"Port": {
"Description": "SAP Gateway's HTTP or HTTPS (match with protocol you choose above) port number",
"Type": "Number",
"Default": 50000
},
"HealthCheckPath": {
"Description": "SAP Gateway's ping path to do health check",
"Type": "String",
"Default": "/sap/public/ping"
},
"InVpc": {
"Description": "Choose Yes if SAP resides in above VPC; choose No otherwise (in cases of above VPC just peers with another SAP residing VPC)",
"Type": "String",
"AllowedValues": [
"Yes",
"No"
],
"Default": "Yes"
}
},
"Rules": {
"SubnetsInVPC": {
"Assertions": [
{
"Assert": {
"Fn::EachMemberEquals": [
[
"Subnets",
"VpcId"
],
{
"Ref": "VpcId"
}
]
},
"AssertDescription": "All subnets must in the VPC"
}
]
}
},
"Conditions": {
"IpInVpc": {
"Fn::Equals": [
{
"Ref": "InVpc"
},
"Yes"
]
},
"SapUseHttps": {
"Fn::Equals": [
{
"Ref": "Protocol"
},
"HTTPS"
]
}
},
"Resources": {
"ASCPrivateLinkCertificate": {
"Type": "AWS::CertificateManager::Certificate",
"Properties": {
"DomainName": {
"Ref": "DomainName"
},
"ValidationMethod": "DNS"
}
},
"ASCPrivateLinkLambdaRole": {
"Type": "AWS::IAM::Role",
"DependsOn": "ASCPrivateLinkCertificate",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
}
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "ASCPrivateLinkLambdaPolicy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"ec2:DescribeVpcEndpointServiceConfigurations",
"ec2:ModifyVpcEndpointServiceConfiguration"
],
"Resource": "*"
}
]
}
}
]
}
},
"ASCPrivateLinkLambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Description": "Lambda function to help with private link infrastructure setup",
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"ASCPrivateLinkLambdaRole",
"Arn"
]
},
"Timeout": 900,
"Runtime": "python3.12",
"VpcConfig": {
"SubnetIds": {
"Ref": "Subnets"
},
"SecurityGroupIds": {
"Ref": "SecurityGroups"
}
},
"Code": {
"ZipFile": "import boto3\nimport cfnresponse\nimport logging\ndef handler(event, context):\n print('Receive event: {} and context: {}'.format(str(event), str(context)))\n responseData = {}\n eventType = event['RequestType'].strip()\n props = event['ResourceProperties']\n try:\n if eventType in ('Create'):\n match props['Action']:\n case 'EnablePrivateDNS':\n dnsClient = boto3.client('route53')\n ec2Client = boto3.client('ec2')\n serviceId = props['ServiceId']\n domainName = props['DomainName']\n ec2Client.modify_vpc_endpoint_service_configuration(ServiceId=serviceId, PrivateDnsName=domainName)\n validationRecord = ec2Client.describe_vpc_endpoint_service_configurations(ServiceIds=[serviceId])['ServiceConfigurations'][0]['PrivateDnsNameConfiguration']\n responseData['validationRecord'] = validationRecord\n case _:\n raise Exception('Unsupported action')\n else:\n print('Skip on resource UPDATE and DELETE')\n cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)\n except Exception as e:\n logging.exception(e)\n cfnresponse.send(event, context, cfnresponse.FAILED, responseData)\n"
}
}
},
"ASCPrivateLinkEnablePrivateDNS": {
"Type": "Custom::CustomResource",
"DependsOn": "ASCPrivateLinkVPCES",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"ASCPrivateLinkLambdaFunction",
"Arn"
]
},
"Action": "EnablePrivateDNS",
"ServiceId": {
"Ref": "ASCPrivateLinkVPCES"
},
"DomainName": {
"Ref": "DomainName"
}
}
},
"ASCPrivateLinkNLB": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"DependsOn": "ASCPrivateLinkCertificate",
"Properties": {
"Type": "network",
"Scheme": "internal",
"Subnets": {
"Ref": "Subnets"
},
"LoadBalancerAttributes": [
{
"Key": "load_balancing.cross_zone.enabled",
"Value": true
}
]
}
},
"ASCPrivateLinkTargetGroup": {
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
"DependsOn": "ASCPrivateLinkCertificate",
"Properties": {
"VpcId": {
"Ref": "VpcId"
},
"Protocol": {
"Fn::If": [
"SapUseHttps",
"TLS",
"TCP"
]
},
"Port": 443,
"TargetType": "ip",
"Targets": [
{
"AvailabilityZone": {
"Fn::If": [
"IpInVpc",
{
"Ref": "AWS::NoValue"
},
"all"
]
},
"Id": {
"Ref": "IP"
},
"Port": {
"Ref": "Port"
}
}
],
"HealthCheckPath": {
"Ref": "HealthCheckPath"
},
"HealthCheckProtocol": {
"Ref": "Protocol"
}
}
},
"ASCPrivateLinkListener": {
"Type": "AWS::ElasticLoadBalancingV2::Listener",
"Properties": {
"LoadBalancerArn": {
"Ref": "ASCPrivateLinkNLB"
},
"Protocol": "TLS",
"Port": 443,
"SslPolicy": "ELBSecurityPolicy-TLS13-1-0-2021-06",
"Certificates": [
{
"CertificateArn": {
"Ref": "ASCPrivateLinkCertificate"
}
}
],
"DefaultActions": [
{
"Type": "forward",
"TargetGroupArn": {
"Ref": "ASCPrivateLinkTargetGroup"
}
}
]
}
},
"ASCPrivateLinkVPCES": {
"Type": "AWS::EC2::VPCEndpointService",
"Properties": {
"AcceptanceRequired": false,
"NetworkLoadBalancerArns": [
{
"Ref": "ASCPrivateLinkNLB"
}
]
}
},
"ASCPrivateLinkVPCESPermission": {
"Type": "AWS::EC2::VPCEndpointServicePermissions",
"Properties": {
"AllowedPrincipals": [
"appflow.amazonaws.com"
],
"ServiceId": {
"Ref": "ASCPrivateLinkVPCES"
}
}
}
},
"Outputs": {
"CertificateURL": {
"Description": "Go to this URL to check certificate issued or not (also find domain verification details)",
"Value": {
"Fn::Sub": [
"https://${AWS::Region}.console.aws.amazon.com/acm/home?region=${AWS::Region}#/certificates/${CertificateId}",
{
"CertificateId": {
"Fn::Select": [
1,
{
"Fn::Split": [
"/",
{
"Ref": "ASCPrivateLinkCertificate"
}
]
}
]
}
}
]
}
},
"VPCEndpointServiceURL": {
"Description": "Go to this URL to check VPC Endpoint Service attributes (also find privateDNS domain verification details)",
"Value": {
"Fn::Sub": "https://${AWS::Region}.console.aws.amazon.com/vpc/home?region=${AWS::Region}#VpcEndpointServiceDetails:EndpointServiceId=${ASCPrivateLinkVPCES}"
}
},
"TargetGroupURL": {
"Description": "Go to this URL to check target group health check status (to see if got connectivity to SAP)",
"Value": {
"Fn::Sub": "https://${AWS::Region}.console.aws.amazon.com/ec2/home?region=${AWS::Region}#TargetGroup:targetGroupArn=${ASCPrivateLinkTargetGroup}"
}
}
}
}

---

AWSTemplateFormatVersion: "2010-09-09"

Description: CloudFormation template to create PrivateLink infrastructure

Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Domain Configuration
Parameters:
- DomainName
- Label:
default: Network Configuration
Parameters:
- VpcId
- Subnets
- Label:
default: SAP Configuration
Parameters:
- IP
- Protocol
- Port
- HealthCheckPath
- InVpc

Parameters:
DomainName:
Description: The fully qualified or wildcard domain name of DNS
Type: String

VpcId:
Description: VpcId of your existing Virtual Private Cloud (VPC) where SAP resides
Type: AWS::EC2::VPC::Id

Subnets:
Description: The private subnets (must include one where SAP resides) of above VPC, recommend choose multiple covering different AZs
Type: List<AWS::EC2::Subnet::Id>

SecurityGroups:
Type: List<AWS::EC2::SecurityGroup::Id>

IP:
Description: SAP Gateway's private IP address within VPC
Type: String

Protocol:
Description: SAP Gateway's connect protocol
Type: String
AllowedValues:
- HTTP
- HTTPS
Default: HTTP

Port:
Description: SAP Gateway's HTTP or HTTPS (match with protocol you choose above) port number
Type: Number
Default: 50000

HealthCheckPath:
Description: SAP Gateway's ping path to do health check
Type: String
Default: /sap/public/ping

InVpc:
Description: Choose Yes if SAP resides in above VPC; choose No otherwise (in cases of above VPC just peers with another SAP residing VPC)
Type: String
AllowedValues:
- Yes
- No
Default: Yes

Rules:
SubnetsInVPC:
Assertions:
- Assert:
Fn::EachMemberEquals:
- !ValueOf
- Subnets
- VpcId
- !Ref VpcId
AssertDescription: All subnets must in the VPC

Conditions:
IpInVpc: !Equals
- !Ref InVpc
- Yes

SapUseHttps: !Equals
- !Ref Protocol
- HTTPS

Resources:
ASCPrivateLinkCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
ValidationMethod: DNS

ASCPrivateLinkLambdaRole:
Type: AWS::IAM::Role
DependsOn: ASCPrivateLinkCertificate
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
Path: /
Policies:
- PolicyName: ASCPrivateLinkLambdaPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- ec2:DescribeVpcEndpointServiceConfigurations
- ec2:ModifyVpcEndpointServiceConfiguration
Resource: '*'

ASCPrivateLinkLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Description: Lambda function to help with private link infrastructure setup
Handler: index.handler
Role: !GetAtt ASCPrivateLinkLambdaRole.Arn
Timeout: 900
Runtime: python3.12
VpcConfig:
SubnetIds: !Ref Subnets
SecurityGroupIds: !Ref SecurityGroups
Code:
ZipFile: |
import boto3
import cfnresponse
import logging
def handler(event, context):
print('Receive event: {} and context: {}'.format(str(event), str(context)))
responseData = {}
eventType = event['RequestType'].strip()
props = event['ResourceProperties']
try:
if eventType in ('Create'):
match props['Action']:
case 'EnablePrivateDNS':
dnsClient = boto3.client('route53')
ec2Client = boto3.client('ec2')
serviceId = props['ServiceId']
domainName = props['DomainName']
ec2Client.modify_vpc_endpoint_service_configuration(ServiceId=serviceId, PrivateDnsName=domainName)
validationRecord = ec2Client.describe_vpc_endpoint_service_configurations(ServiceIds=[serviceId])['ServiceConfigurations'][0]['PrivateDnsNameConfiguration']
responseData['validationRecord'] = validationRecord
case _:
raise Exception('Unsupported action')
else:
print('Skip on resource UPDATE and DELETE')
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
except Exception as e:
logging.exception(e)
cfnresponse.send(event, context, cfnresponse.FAILED, responseData)

ASCPrivateLinkEnablePrivateDNS:
Type: Custom::CustomResource
DependsOn: ASCPrivateLinkVPCES
Properties:
ServiceToken: !GetAtt ASCPrivateLinkLambdaFunction.Arn
Action: EnablePrivateDNS
ServiceId: !Ref ASCPrivateLinkVPCES
DomainName: !Ref DomainName

ASCPrivateLinkNLB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn: ASCPrivateLinkCertificate
Properties:
Type: network
Scheme: internal
Subnets: !Ref Subnets
LoadBalancerAttributes:
- Key: load_balancing.cross_zone.enabled # SAP may only live in one AZ, need route to it if traffic from different AZ
Value: true

ASCPrivateLinkTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn: ASCPrivateLinkCertificate
Properties:
VpcId: !Ref VpcId
Protocol: !If
- SapUseHttps
- TLS
- TCP
Port: 443
TargetType: ip
Targets:
- AvailabilityZone: !If
- IpInVpc
- !Ref AWS::NoValue
- all
Id: !Ref IP
Port: !Ref Port
HealthCheckPath: !Ref HealthCheckPath
HealthCheckProtocol: !Ref Protocol

ASCPrivateLinkListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ASCPrivateLinkNLB
Protocol: TLS
Port: 443
SslPolicy: ELBSecurityPolicy-TLS13-1-0-2021-06
Certificates:
- CertificateArn: !Ref ASCPrivateLinkCertificate
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ASCPrivateLinkTargetGroup

ASCPrivateLinkVPCES:
Type: AWS::EC2::VPCEndpointService
Properties:
AcceptanceRequired: false
NetworkLoadBalancerArns:
- !Ref ASCPrivateLinkNLB

ASCPrivateLinkVPCESPermission:
Type: AWS::EC2::VPCEndpointServicePermissions
Properties:
AllowedPrincipals:
- appflow.amazonaws.com
ServiceId: !Ref ASCPrivateLinkVPCES

Outputs:
CertificateURL:
Description: Go to this URL to check certificate issued or not (also find domain verification details)
Value: !Sub
- https://${AWS::Region}.console.aws.amazon.com/acm/home?region=${AWS::Region}#/certificates/${CertificateId}
- CertificateId: !Select
- 1
- !Split
- /
- !Ref ASCPrivateLinkCertificate

VPCEndpointServiceURL:
Description: Go to this URL to check VPC Endpoint Service attributes (also find privateDNS domain verification details)
Value: !Sub https://${AWS::Region}.console.aws.amazon.com/vpc/home?region=${AWS::Region}#VpcEndpointServiceDetails:EndpointServiceId=${ASCPrivateLinkVPCES}

TargetGroupURL:
Description: Go to this URL to check target group health check status (to see if got connectivity to SAP)
Value: !Sub https://${AWS::Region}.console.aws.amazon.com/ec2/home?region=${AWS::Region}#TargetGroup:targetGroupArn=${ASCPrivateLinkTargetGroup}

---

CloudFormation/Fn Foreach Ddb.Json (CloudFormation/fn-foreach-ddb.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "This template demonstrates how to use the Fn::ForEach function to create mutiple tables",
"Transform": "AWS::LanguageExtensions",
"Resources": {
"Fn::ForEach::Tables": [
"TableName",
[
"Points",
"Score",
"Name",
"Leaderboard"
],
{
"DynamoDB${TableName}": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
"TableName": {
"Ref": "TableName"
},
"AttributeDefinitions": [
{
"AttributeName": "id",
"AttributeType": "S"
}
],
"KeySchema": [
{
"AttributeName": "id",
"KeyType": "HASH"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": "5",
"WriteCapacityUnits": "5"
}
}
}
}
]
}
}

---

CloudFormation/Fn Foreach Ddb.Yaml (CloudFormation/fn-foreach-ddb.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: This template demonstrates how to use the Fn::ForEach function to create mutiple tables

Transform: AWS::LanguageExtensions

Resources:
Fn::ForEach::Tables:
- TableName
- - Points
- Score
- Name
- Leaderboard
- DynamoDB${TableName}:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Ref TableName
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: "5"
WriteCapacityUnits: "5"

---

CloudFormation/Fn Foreach S3 Outputs.Json (CloudFormation/fn-foreach-s3-outputs.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Demonstrate using the Fn::ForEach function to create multiple buckets and outputs for each one",
"Transform": "AWS::LanguageExtensions",
"Resources": {
"Fn::ForEach::Buckets": [
"Identifier",
[
"A",
"B",
"C"
],
{
"S3Bucket${Identifier}": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": true,
"BlockPublicPolicy": true,
"IgnorePublicAcls": true,
"RestrictPublicBuckets": true
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
}
}
]
},
"Outputs": {
"Fn::ForEach::BucketOutputs": [
"Identifier",
[
"A",
"B",
"C"
],
{
"Fn::ForEach::GetAttLoop": [
"Property",
[
"Arn"
],
{
"S3Bucket${Identifier}${Property}": {
"Value": {
"Fn::GetAtt": [
{
"Fn::Sub": "S3Bucket${Identifier}"
},
{
"Ref": "Property"
}
]
}
}
}
]
}
]
}
}

---

CloudFormation/Fn Foreach S3 Outputs.Yaml (CloudFormation/fn-foreach-s3-outputs.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: Demonstrate using the Fn::ForEach function to create multiple buckets and outputs for each one

Transform: AWS::LanguageExtensions

Resources:
Fn::ForEach::Buckets:
- Identifier
- - A
- B
- C
- S3Bucket${Identifier}:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
DeletionPolicy: Retain
UpdateReplacePolicy: Retain

Outputs:
Fn::ForEach::BucketOutputs:
- Identifier
- - A
- B
- C
- Fn::ForEach::GetAttLoop:
- Property
- - Arn
- S3Bucket${Identifier}${Property}:
Value: !GetAtt
- !Sub S3Bucket${Identifier}
- !Ref Property

---

CloudFormation/CustomResources/Getfromjson/README (CloudFormation/CustomResources/getfromjson/README.md)

getfromjson


Overview

getfromjson is a module for Python that is meant to run in an AWS
Lambda
function that, in turn, backs
one (or more) AWS
CloudFormation
custom
resource

that you declare and use to get a given value out of an input JSON
data structure and an input search argument you both provide.

For more information on Lambda-backed custom resources, see
Lambda-backed custom
resources
.

There are two parts you'll need to set up. First, you set up the
infrastructure needed to support
getfromjson: you do this with the
src/getfromjson.yml CloudFormation template, that describes the
following resources:

- the Lambda function and the getfromjson.py module for Python, that
will back custom resource consumers; this function will be
responsible for returning values from an input JSON data structure
and search argument, that you both provide;

- the AWS Identity and Access Management
(IAM)
execution
role

for the Lambda function;

- the Amazon CloudWatch
Logs

log group for the Lambda function.

The _Setup_ section, further down in this document, shows you how to
create resources above, in a given AWS account and AWS region, by
using the template mentioned earlier to create a CloudFormation stack.

Next, you consume the Lambda function, that you previously created in
a given AWS account and AWS region, with custom resources that you
declare in other CloudFormation templates (that you'll use to create
new stacks) where you'll pass in both JSON data and a search argument
for the data. The
example-templates/ directory contains samples that
illustrate how to consume
getfromjson with Lambda-backed custom
resources; the following snippet shows you an overview on how to
consume, in another template, the Lambda function (the example below
consumes custom resources' values in the
Outputs section of the
template, but you can consume such values also from properties of
other resources you describe in the
Resources section of the
template):


Resources:
GetFromJsonCustomResourceSampleGetFromList:
Type: Custom::GetFromJson
Properties:
ServiceTimeout: 1
ServiceToken: !ImportValue Custom-GetFromJson
json_data: '["test0", "test1", "test2"]'
search: '[2]'

GetFromJsonCustomResourceSampleGetFromMap:
Type: Custom::GetFromJson
Properties:
ServiceTimeout: 1
ServiceToken: !ImportValue Custom-GetFromJson
json_data: '{"test": {"test1": ["x", "y"]}}'
search: '["test"]["test1"][1]'

Outputs:
GetFromJsonCustomResourceSampleGetFromListValue:
Value: !GetAtt GetFromJsonCustomResourceSampleGetFromList.Data

GetFromJsonCustomResourceSampleGetFromMapValue:
Value: !GetAtt GetFromJsonCustomResourceSampleGetFromMap.Data

text
whereas the GetFromJsonCustomResourceSampleGetFromListValue and
GetFromJsonCustomResourceSampleGetFromMapValue outputs, once you'll
create the stack, will show
test2 and y respectively as the
returned values.

Note also the ServiceToken: !ImportValue Custom-GetFromJson line,
that the example above uses to tell the custom resource what is the
Amazon Resource Name
(ARN)

of the Lambda function that backs the custom resource itself. The
Lambda function's ARN is
exported
in the
getfromjson template with the Custom-GetFromJson export
name: in the example above, you use the
Fn::ImportValue intrinsic
function

to reference the ARN from the export.

For more information on CloudFormation custom resources in a
CloudFormation template, see the
AWS::CloudFormation::CustomResource
reference.


Input and output: values and limits

The following are supported input and output values:

- input:

- json_data:

- json_data maximum length: 4,096 bytes;

- map keys can contain alphanumeric characters, dashes, and
underscore characters;

- map values can contain Unicode characters;

- search:

- search maximum length: 256 bytes;

- map keys can contain alphanumeric characters, dashes, and
underscore characters;

- list indexes must be integers (such as, [0] instead of
["0"]);

- output:

- custom resource response: 4,096 bytes maximum; this is a
CloudFormation quota - for more information, see _Custom
resource response_ in Understand CloudFormation
quotas
;

- returned value can contain Unicode characters;

- returned value type is represented as a Unicode string.


Setup

Install rain, that
you'll use to create CloudFormation stacks to manage resources. When
ready, create the Lambda function, its execution role and log group,
in a given AWS account and AWS region (the example below uses
us-east-1 as the AWS region; change this value as needed); such
resources will be backing the custom resource consumer:


rain deploy src/getfromjson.yml getfromjson \
--region us-east-1
text
Note that the TagName parameter for the getfromjson.yml template
is optional, and you can omit it if needed: its value defaults to
GetFromJson. TagName is used to add a tag called Name, with the
value for the
TagName parameter, to resources that the template
describes (the Lambda function that backs relevant custom resources,
the function's execution role, and the function's log group).


Usage

Please make sure to follow the _Setup_ section above before
continuing. When ready, create a CloudFormation stack that uses an
example template showing you how to invoke the Lambda function (that
you created earlier) that backs up the custom resources you'll use to
extract values from example JSON input (extracted values will be
available in the
Outputs section for the example stack you'll
create; note that you can also choose to consume such values from
properties of other resources you describe in the
Resources section
of the template):


rain deploy example-templates/getfromjson-consumer.yml getfromjson-consumer \
--region us-east-1
text

Development

Install the AWS Serverless Application Model Command Line Interface
(AWS SAM CLI)

in your workstation. When done, refer to the documentation on
Installing Docker to use with the AWS SAM
CLI
.

Install rain, that
you'll use to create CloudFormation stacks to manage, on your behalf,
the resources you'll need.

Next, create and activate a virtual
environment
for Python
using the following commands:


python -m venv venv
source venv/bin/activate
text
Next, install the following Python module(s) in your activated
environment:

python -m pip install --upgrade -r requirements-dev.txt
text
To run unit tests for getfromjson on your machine, use the following
command:

pytest --cov
text
To speed up the development lifecycle, you can locally invoke the
Lambda function code, and pass input events for your test use cases
(the unit tests for
getfromjson do something similar as well, and by
invoking the Lambda function locally you add an integration testing
flavor to your development lifecycle). To do so, run the following
command from the root level of the project:

./run-local-invoke
text
The script above uses the SAM CLI to invoke the Lambda function code
locally on your machine; you'll need to have Docker installed and
running. The SAM CLI uses the content of the
template.yml file in
the
src directory to determine which settings to use for aspects
that include which
Runtime to use, and the MemorySize: if you'll
need to adjust some of these values, make sure you reflect your
changes also in relevant parts of the
src/getfromjson.yml
CloudFormation template; note that the value for
Handler though
needs to have a different prefix depending on the file you use (it
should be
Handler: getfromjson.lambda_handler in the SAM template,
and
Handler: index.lambda_handler in the CloudFormation template).

Note: the code for getfromjson, by default, uses the INFO logging
level - you'll need to update the following line and use a different
logging level (such as,
logging.DEBUG) when developing and
troubleshooting the code):


LOGGER.setLevel(logging.INFO)
text
When ready to create the infrastructure for getfromjson, use the
following commands to do so (the example below uses
us-east-1 as the
AWS region; change this value as needed):

pylint src/ \
&& mypy \
&& pytest --cov \
&& bandit -c bandit.yaml -r src/ \
&& rain deploy src/getfromjson.yml getfromjson \
--region us-east-1
text
To create a stack with example custom resource consumers, run the
following command:

rain deploy example-templates/getfromjson-consumer.yml getfromjson-consumer \
--region us-east-1
text
---

CloudFormation/CustomResources/Getfromjson/Bandit.Yml (CloudFormation/CustomResources/getfromjson/bandit.yml)

For more information, see https://bandit.readthedocs.io/en/latest/config.html


exclude_dirs:
- tests

---

CloudFormation/CustomResources/Getfromjson/Requirements Dev (CloudFormation/CustomResources/getfromjson/requirements-dev.txt)

bandit>=1.7.9
cfn-lint>=1.3.6
cfnresponse>=1.1.4
mypy>=1.10.1
pip>=24.1
pylint>=3.2.3
pytest-cov>=5.0.0
setuptools>=70.1.1

---

CloudFormation/CustomResources/Getfromjson/Example Templates/Getfromjson Consumer.Yml (CloudFormation/CustomResources/getfromjson/example-templates/getfromjson-consumer.yml)

AWSTemplateFormatVersion: "2010-09-09"

Description: This AWS CloudFormation template describes a sample CloudFormation custom resource consumer for the GetFromJson Lambda-backed custom resource provider.

Parameters:
GetFromListJsonData:
Description: Example JSON data representing a list of values.
Type: String
Default: '["test0", "test1", "test2"]'

GetFromListJsonDataQuery:
Description: Example query for JSON data representing a list of values.
Type: String
Default: '[2]'

GetFromMapJsonData:
Description: Example JSON data representing a map data structure.
Type: String
Default: '{"test": {"test1": ["x", "y"]}}'

GetFromMapJsonDataQuery:
Description: Example query for JSON data representing a map data structure.
Type: String
Default: '["test"]["test1"][1]'

Resources:
GetFromJsonCustomResourceSampleGetFromList:
Type: Custom::GetFromJson
Properties:
ServiceTimeout: 1
ServiceToken: !ImportValue Custom-GetFromJson
json_data: !Ref GetFromListJsonData
search: !Ref GetFromListJsonDataQuery

GetFromJsonCustomResourceSampleGetFromMap:
Type: Custom::GetFromJson
Properties:
ServiceTimeout: 1
ServiceToken: !ImportValue Custom-GetFromJson
json_data: !Ref GetFromMapJsonData
search: !Ref GetFromMapJsonDataQuery

Outputs:
GetFromJsonCustomResourceSampleGetFromListValue:
Value: !GetAtt GetFromJsonCustomResourceSampleGetFromList.Data

GetFromJsonCustomResourceSampleGetFromMapValue:
Value: !GetAtt GetFromJsonCustomResourceSampleGetFromMap.Data

---

CloudFormation/CustomResources/Getfromjson/Src/Getfromjson.Yml (CloudFormation/CustomResources/getfromjson/src/getfromjson.yml)

AWSTemplateFormatVersion: "2010-09-09"

Description: This template describes a Lambda function for a CloudFormation custom resource to consume and get a given value out of an input JSON data structure and an input search argument.

Parameters:
TagName:
Description: Value for the Name tag.
Type: String
Default: GetFromJson

Resources:
GetFromJsonLambdaFunction:
Type: AWS::Lambda::Function
DependsOn: GetFromJsonLogGroup
Metadata:
cfn-lint:
config:
ignore_checks:
- E3012 # Used for the
!Rain::Embed directive below, whose getfromjson.py argument is deemed by cfn-lint not to be a string type.
Properties:
Code:
ZipFile: !Rain::Embed getfromjson.py
Description: Lambda function for a CloudFormation custom resource to consume and get a given value out of an input JSON data structure and an input search argument.
FunctionName: GetFromJson
Handler: index.lambda_handler
MemorySize: 128
Role: !GetAtt GetFromJsonLambdaFunctionExecutionRole.Arn
Runtime: python3.12
Tags:
- Key: Name
Value: !Ref TagName
Timeout: 10

GetFromJsonLambdaFunctionExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Version: "2012-10-17"
Path: /
Policies:
- PolicyDocument:
Statement:
- Action:
- logs:CreateLogStream
- logs:PutLogEvents
Effect: Allow
Resource: !Sub arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/GetFromJson*
Version: "2012-10-17"
PolicyName: GetFromJsonLambdaFunctionExecutionRole
RoleName: !Sub GetFromJson-${AWS::Region}
Tags:
- Key: Name
Value: !Ref TagName

GetFromJsonLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/lambda/GetFromJson
RetentionInDays: 3653
Tags:
- Key: Name
Value: !Ref TagName

Outputs:
GetFromJsonLambdaFunctionArn:
Value: !GetAtt GetFromJsonLambdaFunction.Arn
Export:
Name: Custom-GetFromJson

---

CloudFormation/CustomResources/Getfromjson/Src/Template.Yml (CloudFormation/CustomResources/getfromjson/src/template.yml)

AWSTemplateFormatVersion: "2010-09-09"

Description: AWS SAM template for testing the getfromjson module on your machine.

Transform: AWS::Serverless-2016-10-31

Globals:
Function:
MemorySize: 128
Timeout: 10

Resources:
GetFromJsonLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: getfromjson.lambda_handler
Runtime: python3.12

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Consume From List Retrieval Error.Json (CloudFormation/CustomResources/getfromjson/src/events/event-consume-from-list-retrieval-error.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "[\"test0\", \"test1\", \"test2\"]",
"search": "[3]"
}
}

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Consume From List.Json (CloudFormation/CustomResources/getfromjson/src/events/event-consume-from-list.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "[\"test0\", \"test1\", \"test2\"]",
"search": "[2]"
}
}

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Consume From Map Retrieval Error.Json (CloudFormation/CustomResources/getfromjson/src/events/event-consume-from-map-retrieval-error.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "{\"test\": {\"test-1\": [\"x\", \"y\"]}}",
"search": "[\"test\"][\"test-2\"][1]"
}
}

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Consume From Map.Json (CloudFormation/CustomResources/getfromjson/src/events/event-consume-from-map.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "{\"test\": {\"test-1\": [\"x\", \"y\"]}}",
"search": "[\"test\"][\"test-1\"][1]"
}
}

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Empty Json Data Input.Json (CloudFormation/CustomResources/getfromjson/src/events/event-empty-json-data-input.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "",
"search": "[\"test\"][\"test-1\"][1]"
}
}

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Empty Search Input.Json (CloudFormation/CustomResources/getfromjson/src/events/event-empty-search-input.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "{\"test\": {\"test-1\": [\"x\", \"y\"]}}",
"search": ""
}
}

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Invalid Json Data Input.Json (CloudFormation/CustomResources/getfromjson/src/events/event-invalid-json-data-input.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "invalid",
"search": "[\"test\"][\"test-1\"][1]"
}
}

---

CloudFormation/CustomResources/Getfromjson/Src/Events/Event Invalid Search Input.Json (CloudFormation/CustomResources/getfromjson/src/events/event-invalid-search-input.json)

{
"RequestType": "Create",
"ResponseURL": "https://test-only-pre-signed-S3-response-URL",
"StackId": "arn:aws:cloudformation:us-east-1:111122223333:stack/test-only-stack/test-only-id",
"RequestId": "test_only_unique_id",
"ResourceType": "Custom::GetFromJson",
"LogicalResourceId": "TestOnly",
"ResourceProperties": {
"json_data": "{\"test\": {\"test-1\": [\"x\", \"y\"]}}",
"search": "invalid"
}
}

---

CloudFormation/MacrosExamples/Boto3/README (CloudFormation/MacrosExamples/Boto3/README.md)

How to install and use the Boto3 macro in your AWS account

The Boto3 macro adds the ability to create CloudFormation resources that
represent operations performed by boto3. Each
Boto3 resource represents one function call.

A typical use case for this macro might be, for example, to provide some basic
configuration of resources.

Deploying

1. You will need an S3 bucket to store the CloudFormation artifacts:
* If you don't have one already, create one with
aws s3 mb s3://<bucket name>

2. Package the CloudFormation template. The provided template uses the AWS
Serverless Application
Model

so must be transformed before you can deploy it.

shell
aws cloudformation package \
--template-file macro.yaml \
--s3-bucket <your bucket name here> \
--output-template-file packaged.yaml
text
3. Deploy the packaged CloudFormation template to a CloudFormation stack:
shell
aws cloudformation deploy \
--stack-name boto3-macro \
--template-file packaged.yaml \
--capabilities CAPABILITY_IAM
text
4. To test out the macro's capabilities, try launching the provided example template:
shell
aws cloudformation deploy \
--stack-name boto3-macro-example \
--template-file example.packaged.yaml \
--capabilities CAPABILITY_IAM
text

Usage

To make use of the macro, add Transform: Boto3 to the top level of your
CloudFormation template.

Here is a trivial example template that adds a readme file to a new CodeCommit
repository:

yaml
Transform: Boto3
Resources:
Repo:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: my-repo

AddReadme:
Type: Boto3::CodeCommit.put_file
Mode: Create
Properties:
RepositoryName: !GetAtt Repo.Name
BranchName: master
FileContent: "Hello, world!"
FilePath: README.md
CommitMessage: Add a readme file
Name: CloudFormation

text

Features

Resource type

The resource Type is used to identify a boto3
client

and the method of that client to execute.

The Type must start with Boto3:: and be followed by the name of a client, a
. and finally the name of a method.

The client name will be converted to lower case so that you can use resource
names that look similar to other CloudFormation resource types.

Examples:
*
Boto3::CodeCommit.put_file
*
Boto3::IAM.put_user_permissions_boundary
*
Boto3::EC2.create_snapshot

Resource mode

The resource may contain a Mode property which specifies whether the boto3
call should be made on
Create, Update, Delete or any combination of
those.

The Mode may either be a string or a list of strings. For example:

* Mode: Create
*
Mode: Delete
*
Mode: [Create, Update]

Resource properties

The Properties of the resource will be passed to the specified boto3 method
as arguments. The name of each property will be modified so that it started
with a lower-case character so that you can use property names that look
similar to other CloudFormation resource properties.

Controlling the order of execution

You can use the standard CloudFormation property DependsOn when you need to
ensure that your
Boto3 resources are executed in the correct order.

Examples


The following resource:

yaml
ChangeBinaryTypes:
Type: Boto3::CloudFormation.execute_change_set
Mode: [Create, Update]
Properties:
ChangeSetName: !Ref ChangeSet
StackName: !Ref Stack
text
will result in running the equivalent of the following:
python
boto3.client("cloudformation").execute_change_set(changeSetName=<value of ChangeSet>, stackName=<value of StackName>)
text
when the stack is created or updated.

Author

Steve Engledow
Senior Solutions Builder
Amazon Web Services

---

CloudFormation/MacrosExamples/Boto3/Example.Json (CloudFormation/MacrosExamples/Boto3/example.json)

{
"Transform": "Boto3",
"Resources": {
"Repo": {
"Type": "AWS::CodeCommit::Repository",
"Properties": {
"RepositoryName": "my-repo"
}
},
"AddReadme": {
"Type": "Boto3::CodeCommit.put_file",
"Properties": {
"RepositoryName": {
"Fn::GetAtt": [
"Repo",
"Name"
]
},
"BranchName": "master",
"FileContent": "Hello, world",
"FilePath": "README.md",
"CommitMessage": "Add another README.md",
"Name": "CloudFormation"
},
"Mode": "Create"
}
}
}

---

CloudFormation/MacrosExamples/Boto3/Example.Yaml (CloudFormation/MacrosExamples/Boto3/example.yaml)

Transform: Boto3

Resources:
Repo:
Type: AWS::CodeCommit::Repository
Properties:
RepositoryName: my-repo

AddReadme:
Type: Boto3::CodeCommit.put_file
Properties:
RepositoryName: !GetAtt Repo.Name
BranchName: master
FileContent: Hello, world
FilePath: README.md
CommitMessage: Add another README.md
Name: CloudFormation
Mode: Create

---

CloudFormation/MacrosExamples/Boto3/Macro.Json (CloudFormation/MacrosExamples/Boto3/macro.json)

{
"Transform": "AWS::Serverless-2016-10-31",
"Resources": {
"ResourceFunction": {
"Type": "AWS::Serverless::Function",
"Properties": {
"Runtime": "python3.11",
"CodeUri": "lambda",
"Handler": "resource.handler",
"Policies": "PowerUserAccess"
}
},
"MacroFunction": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"guard": {
"SuppressedRules": [
"LAMBDA_INSIDE_VPC",
"LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED"
]
}
},
"Properties": {
"Runtime": "python3.11",
"CodeUri": "lambda",
"Handler": "macro.handler",
"Environment": {
"Variables": {
"LAMBDA_ARN": {
"Fn::GetAtt": [
"ResourceFunction",
"Arn"
]
}
}
}
}
},
"Macro": {
"Type": "AWS::CloudFormation::Macro",
"Properties": {
"Name": "Boto3",
"FunctionName": {
"Fn::GetAtt": [
"MacroFunction",
"Arn"
]
}
}
}
}
}

---

CloudFormation/MacrosExamples/Boto3/Macro.Yaml (CloudFormation/MacrosExamples/Boto3/macro.yaml)

Transform: AWS::Serverless-2016-10-31

Resources:
ResourceFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.11
CodeUri: lambda
Handler: resource.handler
Policies: PowerUserAccess

MacroFunction:
Type: AWS::Serverless::Function
Metadata:
guard:
SuppressedRules:
- LAMBDA_INSIDE_VPC
- LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED
Properties:
Runtime: python3.11
CodeUri: lambda
Handler: macro.handler
Environment:
Variables:
LAMBDA_ARN: !GetAtt ResourceFunction.Arn

Macro:
Type: AWS::CloudFormation::Macro
Properties:
Name: Boto3
FunctionName: !GetAtt MacroFunction.Arn

---

CloudFormation/MacrosExamples/Count/README (CloudFormation/MacrosExamples/Count/README.md)

Count CloudFormation Macro

The Count macro provides a template-wide Count property for CloudFormation resources.
It allows you to specify multiple resources of the same type without having to cut and paste.

(Note that the Fn::ForEach intrinsic function is a likely better way to do this.)

How to install and use the Count macro in your AWS account

Deploying

1. You will need an S3 bucket to store the CloudFormation artifacts:
* If you don't have one already, create one with
aws s3 mb s3://<bucket name>

2. Package the Macro CloudFormation template. The provided template uses the AWS Serverless Application Model so must be transformed before you can deploy it.

shell
aws cloudformation package \
--template-file template.yaml \
--s3-bucket <your bucket name here> \
--output-template-file packaged.yaml
text
3. Deploy the packaged CloudFormation template to a CloudFormation stack:
shell
aws cloudformation deploy \
--stack-name Count-macro \
--template-file packaged.yaml \
--capabilities CAPABILITY_IAM
text
4. To test out the macro's capabilities, try launching the provided example template:
shell
aws cloudformation deploy \
--stack-name Count-test \
--template-file test.yaml \
--capabilities CAPABILITY_IAM
text

Usage

To make use of the macro, add Transform: Count to the top level of your CloudFormation template.

To create multiple copies of a resource, add a Count property with an integer value.

yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: Count
Resources:
Bucket:
Type: AWS::S3::Bucket
Count: 3
SQS:
Type: AWS:::SQS::Queue
Count: 2
text
#### Note
This will cause the resource "Bucket" to be multiplied 3 times. The new template will contain Bucket1, Bucket2 and Bucket3 but will not contain Bucket as this will be removed.

Enhanced Usage (using a List)

To make use of the macro, add Transform: Count to the top level of your CloudFormation template.

To create multiple copies of a resource, add a Count property with an list value.

yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: Count
Resources:
Bucket:
Type: AWS::S3::Bucket
Count: ['foo','bar','thing']
SQS:
Type: AWS:::SQS::Queue
Count: ['sample','example']
text
#### Note
This will cause the resource "Bucket" to be multiplied 3 times. The new template will contain Bucket1, Bucket2 and Bucket3 but will not contain Bucket as this will be removed.

It is multiplied 3 times since the length of the list passed in the Count property is 3.

Using placeholders


When resources are multiplied, you can put a decimal placeholder %d into any string value that you wish to be replaced with the iterator index number. If you are supplying a list instead of an integer you may also use a string placeholder %s to be replaced with the value in the list at that index.

e.g.

yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: Count
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my bucket %d
Count: 3
NewBucket:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my %s bucket %d
Count: ['foo','bar','thing']


text
Using this example, the processed template will result become:
yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
Bucket1:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my bucket 1
Bucket2:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my bucket 2
Bucket3:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my bucket 3
NewBucket1:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my foo bucket 1
NewBucket2:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my bar bucket 2
NewBucket3:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: TestKey
Value: my thing bucket 3
text

Important - Naming resources

You cannot use Count on resources that use a hardcoded name (Name: property). Duplicate names will cause a CloudFormation runtime failure.
If you wish to specify a name then you can use the decimal place holder %d in the name which will cause the name to incorporate the iterator value.

e.g.

yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
Bucket1:
Type: AWS::S3::Bucket
Properties:
BucketName: MyBucket%d
text

Authors

Jose Ferraris
AWS ProServ DevOps Consultant
Amazon Web Services

Dan Johns
Senior SA Engineer
Amazon Web Services

---

CloudFormation/MacrosExamples/Count/Event.Json (CloudFormation/MacrosExamples/Count/event.json)

{
"requestId": 43242,
"fragment": {
"Resources": {
"HelloBucket": {
"Type": "AWS::S3::Bucket",
"Count": 2,
"Properties": {
"AccessControl": "PublicRead"
}
}
}
}
}

---

CloudFormation/MacrosExamples/Count/Event Bad.Json (CloudFormation/MacrosExamples/Count/event_bad.json)

{
"requestId": 43242,
"fragment": {
"Resources": {
"HelloBucket": {
"Type": "AWS::S3::Bucket",
"Count": 3,
"Properties": {
"AccessControl": "PublicRead"
}
},
"HelloBucket1": {
"Type": "AWS::S3::Bucket",
"Count": 3,
"Properties": {
"AccessControl": "PublicRead"
}
}
}
}
}

---

CloudFormation/MacrosExamples/Count/Template.Json (CloudFormation/MacrosExamples/Count/template.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "The Count macro is an iterator for creating multiple resources\n",
"Transform": "AWS::Serverless-2016-10-31",
"Resources": {
"Macro": {
"Type": "AWS::CloudFormation::Macro",
"Properties": {
"Name": "Count",
"FunctionName": {
"Fn::GetAtt": [
"CountMacroFunction",
"Arn"
]
}
}
},
"CountMacroFunction": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"guard": {
"SuppressedRules": [
"LAMBDA_INSIDE_VPC",
"LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED"
]
}
},
"Properties": {
"CodeUri": "src",
"Handler": "index.handler",
"Runtime": "python3.11",
"Timeout": 5
}
}
}
}

---

CloudFormation/MacrosExamples/Count/Template.Yaml (CloudFormation/MacrosExamples/Count/template.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: |
The Count macro is an iterator for creating multiple resources

Transform: AWS::Serverless-2016-10-31

Resources:
Macro:
Type: AWS::CloudFormation::Macro
Properties:
Name: Count
FunctionName: !GetAtt CountMacroFunction.Arn

CountMacroFunction:
Type: AWS::Serverless::Function
Metadata:
guard:
SuppressedRules:
- LAMBDA_INSIDE_VPC
- LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED
Properties:
CodeUri: src
Handler: index.handler
Runtime: python3.11
Timeout: 5

---

CloudFormation/MacrosExamples/Count/Test.Json (CloudFormation/MacrosExamples/Count/test.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"TestCount": {
"Type": "Number",
"Default": 3
},
"TestList": {
"Type": "CommaDelimitedList",
"Default": "foo,bar"
}
},
"Transform": "Count",
"Resources": {
"BucketToCopyA": {
"Type": "AWS::S3::Bucket",
"Metadata": {
"Comment": "Suppressing typical rules for sample purposes only",
"guard": {
"SuppressedRules": [
"S3_BUCKET_LOGGING_ENABLED",
"S3_BUCKET_PUBLIC_READ_PROHIBITED",
"S3_BUCKET_PUBLIC_WRITE_PROHIBITED",
"S3_BUCKET_REPLICATION_ENABLED",
"S3_BUCKET_VERSIONING_ENABLED",
"S3_BUCKET_DEFAULT_LOCK_ENABLED",
"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
]
}
},
"Properties": {
"Tags": [
{
"Key": "TestKey",
"Value": "my bucket %d"
}
]
},
"Count": 3
},
"BucketToCopyB": {
"Type": "AWS::S3::Bucket",
"Metadata": {
"Comment": "Suppressing typical rules for sample purposes only",
"guard": {
"SuppressedRules": [
"S3_BUCKET_LOGGING_ENABLED",
"S3_BUCKET_PUBLIC_READ_PROHIBITED",
"S3_BUCKET_PUBLIC_WRITE_PROHIBITED",
"S3_BUCKET_REPLICATION_ENABLED",
"S3_BUCKET_VERSIONING_ENABLED",
"S3_BUCKET_DEFAULT_LOCK_ENABLED",
"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
]
}
},
"Properties": {
"Tags": [
{
"Key": "TestKey",
"Value": "my %s bucket %d"
}
]
},
"Count": [
"foo",
"bar"
]
},
"BucketToCopyC": {
"Type": "AWS::S3::Bucket",
"Metadata": {
"Comment": "Suppressing typical rules for sample purposes only",
"guard": {
"SuppressedRules": [
"S3_BUCKET_LOGGING_ENABLED",
"S3_BUCKET_PUBLIC_READ_PROHIBITED",
"S3_BUCKET_PUBLIC_WRITE_PROHIBITED",
"S3_BUCKET_REPLICATION_ENABLED",
"S3_BUCKET_VERSIONING_ENABLED",
"S3_BUCKET_DEFAULT_LOCK_ENABLED",
"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
]
}
},
"Properties": {
"Tags": [
{
"Key": "TestKey",
"Value": "my bucket %d"
}
]
},
"Count": {
"Ref": "TestCount"
}
},
"BucketToCopyD": {
"Type": "AWS::S3::Bucket",
"Metadata": {
"Comment": "Suppressing typical rules for sample purposes only",
"guard": {
"SuppressedRules": [
"S3_BUCKET_LOGGING_ENABLED",
"S3_BUCKET_PUBLIC_READ_PROHIBITED",
"S3_BUCKET_PUBLIC_WRITE_PROHIBITED",
"S3_BUCKET_REPLICATION_ENABLED",
"S3_BUCKET_VERSIONING_ENABLED",
"S3_BUCKET_DEFAULT_LOCK_ENABLED",
"S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
]
}
},
"Properties": {
"Tags": [
{
"Key": "TestKey",
"Value": "my %s bucket %d"
}
]
},
"Count": {
"Ref": "TestList"
}
}
}
}

---

CloudFormation/MacrosExamples/Count/Test.Yaml (CloudFormation/MacrosExamples/Count/test.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
TestCount:
Type: Number
Default: 3

TestList:
Type: CommaDelimitedList
Default: foo,bar

Transform: Count

Resources:
BucketToCopyA:
Type: AWS::S3::Bucket
Metadata:
Comment: Suppressing typical rules for sample purposes only
guard:
SuppressedRules:
- S3_BUCKET_LOGGING_ENABLED
- S3_BUCKET_PUBLIC_READ_PROHIBITED
- S3_BUCKET_PUBLIC_WRITE_PROHIBITED
- S3_BUCKET_REPLICATION_ENABLED
- S3_BUCKET_VERSIONING_ENABLED
- S3_BUCKET_DEFAULT_LOCK_ENABLED
- S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED
Properties:
Tags:
- Key: TestKey
Value: my bucket %d
Count: 3

BucketToCopyB:
Type: AWS::S3::Bucket
Metadata:
Comment: Suppressing typical rules for sample purposes only
guard:
SuppressedRules:
- S3_BUCKET_LOGGING_ENABLED
- S3_BUCKET_PUBLIC_READ_PROHIBITED
- S3_BUCKET_PUBLIC_WRITE_PROHIBITED
- S3_BUCKET_REPLICATION_ENABLED
- S3_BUCKET_VERSIONING_ENABLED
- S3_BUCKET_DEFAULT_LOCK_ENABLED
- S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED
Properties:
Tags:
- Key: TestKey
Value: my %s bucket %d
Count:
- foo
- bar

BucketToCopyC:
Type: AWS::S3::Bucket
Metadata:
Comment: Suppressing typical rules for sample purposes only
guard:
SuppressedRules:
- S3_BUCKET_LOGGING_ENABLED
- S3_BUCKET_PUBLIC_READ_PROHIBITED
- S3_BUCKET_PUBLIC_WRITE_PROHIBITED
- S3_BUCKET_REPLICATION_ENABLED
- S3_BUCKET_VERSIONING_ENABLED
- S3_BUCKET_DEFAULT_LOCK_ENABLED
- S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED
Properties:
Tags:
- Key: TestKey
Value: my bucket %d
Count: !Ref TestCount

BucketToCopyD:
Type: AWS::S3::Bucket
Metadata:
Comment: Suppressing typical rules for sample purposes only
guard:
SuppressedRules:
- S3_BUCKET_LOGGING_ENABLED
- S3_BUCKET_PUBLIC_READ_PROHIBITED
- S3_BUCKET_PUBLIC_WRITE_PROHIBITED
- S3_BUCKET_REPLICATION_ENABLED
- S3_BUCKET_VERSIONING_ENABLED
- S3_BUCKET_DEFAULT_LOCK_ENABLED
- S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED
Properties:
Tags:
- Key: TestKey
Value: my %s bucket %d
Count: !Ref TestList

---

CloudFormation/MacrosExamples/DateFunctions/README (CloudFormation/MacrosExamples/DateFunctions/README.md)

Date functions

Provides date tranformation utility functions.
Can be used in things like policies that need ISO formatted date values.

Installation

Use CloudFormation Rain to deploy the package. Rain is used in this sample to embed the contents of handler.py into the template.

sh
rain deploy date.yaml date-macro
text

Basic Usage

Place the transform where you would like the output to be placed and provide
the parameters for the operation you have
chosen.

yaml
Parameters:
Date:
Default: "This is a date"
Type: String
AllowedPattern: "^$|^\\d{4}(-\\d\\d(-\\d\\d(T\\d\\d:\\d\\d(:\\d\\d)?(\\.\\d+)?(([+-]\\d\\d:\\d\\d)|Z)?)?)?)?$"
Resources:
S3Bucket:
Type: "AWS::S3::Bucket"
Properties:
Tags:
- Key: Date
Value:
'Fn::Transform':
- Name: 'Date'
Parameters:
Date: !Ref Date
Operation: Current
text
The original use case for this was to create an SSM parameter with an 
expiration date, which is exemplified in the following snippet:
yaml
# Secret key SSM paramber
S3UserSecretAccessKey:
Type: "AWS::SSM::Parameter"
Properties:
Name: !Sub "/S3Bucket/API/Secret/${S3Bucket}"
Type: "String"
Value: !GetAtt S3AccessKey.SecretAccessKey
Description: "SSM Parameter for S3Bucket Secret"
Tier: Advanced
Policies:
Fn::Sub:
- "[{ \"Type\":\"Expiration\",\"Version\":\"1.0\",\"Attributes\": {\"Timestamp\":\"${ExpireDate}\" } }]"
- ExpireDate:
Fn::Transform:
- Name: 'Date'
# Have the date be 30 days from now...
Parameters:
# No Date passed in means we add the days to now()
Days: !Ref DaysToExpiration
Operation: "Add"

`

The Fn::Transform will place a value in the expiration policy for the above parameter
that will expire based on the
DaysToExpiration parameter
(From the
Parameters section of the CloudFormation template).

Parameters

| Name | Description | Format |
|------|-------------|--------|
| Date | Date to use (defaults to
now) | ISO Date |
| Date2 | Date to use for
Days operation (also defaults to now) | ISO Date |
| Days | Number of days to add or subtract in the
Add or Date

This parameter is used in the Current, Add,
and
Subtract operations.

It should be an ISO formatted date, and will default to the current date if it is
empty or not passed.

Date2

This parameter is also used in the Current, Add,
and
Subtract operations.

It should be an ISO formatted date, and will default to the current date if it is
empty or not passed.

Days

This parameter is used in the Add, and Subtract operations.

It should be an integer, if it is omitted, it defaults to zero (0)

Available Operations

| Name | Description | Parameters |
|------|-------------|------------|
| Current | Returns the current date (or value of the
Date parameter | Date (optional) |
| Add | Adds number of days to the
Date | Date defaults to now(), Days defaults to 0 |
| Subtract | Subtracts number of days from the
Date | Date defaults to now(), Days defaults to 0 |
| Days | Returns the number of days from between two dates |
Date defaults to now(), Date2 defaults to now() |

Current

Returns a copy of the current date and time in ISO format

Add

Adds the Days parameter value to the Date value.

Subtract

Subtracts the Days from the Date

Days

Calculates the number of days between Date and Date2`

---

CloudFormation/MacrosExamples/DateFunctions/Date.Json (CloudFormation/MacrosExamples/DateFunctions/date.json)

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Template for date macro for Cloudformation. Provides functions for date manipulation in your CloudFormation templates including getting the current date, and doing date math. Written in Python.",
"Resources": {
"TransformExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/",
"Policies": [
{
"PolicyName": "root",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:*"
],
"Resource": "arn:aws:logs:::*"
}
]
}
}
]
}
},
"TransformFunction": {
"Type": "AWS::Lambda::Function",
"Metadata": {
"guard": {
"SuppressedRules": [
"LAMBDA_INSIDE_VPC",
"LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED"
]
}
},
"Properties": {
"Code": {
"ZipFile": {
"Rain::Embed": "handler.py"
}
},
"Handler": "index.handler",
"Runtime": "python3.11",
"Role": {
"Fn::GetAtt": [
"TransformExecutionRole",
"Arn"
]
}
}
},
"TransformFunctionPermissions": {
"Type": "AWS::Lambda::Permission",
"Metadata": {
"guard": {
"SuppressedRules": [
"LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED"
]
}
},
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"TransformFunction",
"Arn"
]
},
"Principal": "cloudformation.amazonaws.com"
}
},
"Transform": {
"Type": "AWS::CloudFormation::Macro",
"Properties": {
"Name": "Date",
"Description": "Provides date processing functions",
"FunctionName": {
"Fn::GetAtt": [
"TransformFunction",
"Arn"
]
}
}
}
}
}

---

CloudFormation/MacrosExamples/DateFunctions/Date.Yaml (CloudFormation/MacrosExamples/DateFunctions/date.yaml)

AWSTemplateFormatVersion: "2010-09-09"

Description: Template for date macro for Cloudformation. Provides functions for date manipulation in your CloudFormation templates including getting the current date, and doing date math. Written in Python.

Resources:

# Role for transformation
TransformExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:*
Resource: arn:aws:logs:::*

---