### 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](https://github.com/aws-cloudformation/cfn-lint) (cfn-lint) checks, and also a basic set of [CloudFormation Guard](https://github.com/aws-cloudformation/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 ``` git remote add upstream git@github.com: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: ``` 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](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](https://github.com/aws-cloudformation/cfn-lint) 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](https://github.com/aws-cloudformation/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](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-create-stack.html) or [AWS Command Line Interface (AWS CLI)](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-cli-creating-stack.html). - To view all the supported AWS resources and their properties, see the [Template Reference](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-reference.html). --- ### 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`````````` --- ### 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", "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" }, "Subnets": { "Description": "Subnets to be used", "Type": "List" }, "AZs": { "Description": "Availability Zones to be used", "Type": "List" }, "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": [ "", [ "\"AWS", "

Congratulations, you have successfully launched the AWS CloudFormation sample.

" ] ] }, "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 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 Subnets: Description: Subnets to be used Type: List AZs: Description: Availability Zones to be used Type: List 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 - "" - - AWS CloudFormation Logo -

Congratulations, you have successfully launched the AWS CloudFormation sample.

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": [ "", [ "

Congratulations, you have successfully launched the AWS CloudFormation sample.

", "

Version: 1.0

" ] ] }, "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 - "" - -

Congratulations, you have successfully launched the AWS CloudFormation sample.

- '

Version: 1.0

' 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": [ "", [ "\"AWS", "

Congratulations, you have successfully launched the AWS CloudFormation sample.

" ] ] }, "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 - "" - - AWS CloudFormation Logo -

Congratulations, you have successfully launched the AWS CloudFormation sample.

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 --- ### AWSSupplyChain/SapPrivateLink/README (AWSSupplyChain/SapPrivateLink/README.md) **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](https://docs.aws.amazon.com/vpn/latest/s2svpn/SetUpVPNConnections.html)) - 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](https://docs.aws.amazon.com/vpc/latest/peering/working-with-vpc-peering.html) 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 `.` (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 --- ### AWSSupplyChain/SapPrivateLink/SapPrivateLink.Json (AWSSupplyChain/SapPrivateLink/SapPrivateLink.json) { "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" }, "SecurityGroups": { "Type": "List" }, "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}" } } } } --- ### AWSSupplyChain/SapPrivateLink/SapPrivateLink.Yaml (AWSSupplyChain/SapPrivateLink/SapPrivateLink.yaml) 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 SecurityGroups: Type: List 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} --- ### AWSSupplyChain/SapPrivateLink/SapPrivateLinkNoHostedZone.Json (AWSSupplyChain/SapPrivateLink/SapPrivateLinkNoHostedZone.json) { "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" }, "SecurityGroups": { "Type": "List" }, "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}" } } } } --- ### AWSSupplyChain/SapPrivateLink/SapPrivateLinkNoHostedZone.Yaml (AWSSupplyChain/SapPrivateLink/SapPrivateLinkNoHostedZone.yaml) 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 SecurityGroups: Type: List 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](https://aws.amazon.com/lambda/) function that, in turn, backs one (or more) [AWS CloudFormation](https://aws.amazon.com/cloudformation/) [custom resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) 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](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html). 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)](https://aws.amazon.com/iam/) [execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html) for the Lambda function; - the [Amazon CloudWatch Logs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/WhatIsCloudWatchLogs.html) 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 ``` 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)](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html) of the Lambda function that backs the custom resource itself. The Lambda function's ARN is [exported](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-exports.html) in the `getfromjson` template with the `Custom-GetFromJson` export name: in the example above, you use the `Fn::ImportValue` [intrinsic function](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html) to reference the ARN from the export. For more information on CloudFormation custom resources in a CloudFormation template, see the `AWS::CloudFormation::CustomResource` [reference](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-customresource.html). ## 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](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html); - returned value can contain Unicode characters; - returned value type is represented as a Unicode string. ## Setup Install [rain](https://github.com/aws-cloudformation/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 ``` 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 ``` ## Development Install the [AWS Serverless Application Model Command Line Interface (AWS SAM CLI) ](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli.html) in your workstation. When done, refer to the documentation on [Installing Docker to use with the AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-docker.html). Install [rain](https://github.com/aws-cloudformation/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](https://docs.python.org/3/library/venv.html) for Python using the following commands: ``` python -m venv venv source venv/bin/activate ``` Next, install the following Python module(s) in your activated environment: ``` python -m pip install --upgrade -r requirements-dev.txt ``` To run unit tests for `getfromjson` on your machine, use the following command: ``` pytest --cov ``` 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 ``` 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) ``` 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 ``` 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 ``` --- ### 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](http://boto3.readthedocs.io/). 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://` 2. Package the CloudFormation template. The provided template uses [the AWS Serverless Application Model](https://aws.amazon.com/about-aws/whats-new/2016/11/introducing-the-aws-serverless-application-model/) so must be transformed before you can deploy it. ```shell aws cloudformation package \ --template-file macro.yaml \ --s3-bucket \ --output-template-file packaged.yaml ``` 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 ``` 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 ``` ## 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 ``` ## Features ### Resource type The resource `Type` is used to identify a [boto3 client](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/clients.html) 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 ``` will result in running the equivalent of the following: ```python boto3.client("cloudformation").execute_change_set(changeSetName=, stackName=) ``` when the stack is created or updated. ## Author [Steve Engledow](https://linkedin.com/in/stilvoid) 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://` 2. Package the Macro CloudFormation template. The provided template uses [the AWS Serverless Application Model](https://aws.amazon.com/about-aws/whats-new/2016/11/introducing-the-aws-serverless-application-model/) so must be transformed before you can deploy it. ```shell aws cloudformation package \ --template-file template.yaml \ --s3-bucket \ --output-template-file packaged.yaml ``` 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 ``` 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 ``` ### 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 ``` #### 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'] ``` #### 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'] ``` 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 ``` ### 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 ``` ## Authors [Jose Ferraris](https://github.com/j0lly) AWS ProServ DevOps Consultant Amazon Web Services [Dan Johns](https://github.com/danjhd) 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](https://github.com/aws-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 ``` ## Basic Usage Place the transform where you would like the output to be placed and provide the [parameters](#parameters) for the [operation](#available-operations) 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 ``` 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) | Date to use (defaults to `now`) | ISO Date | | [Date2](#date2) | Date to use for [`Days`](#days) operation (also defaults to `now`) | ISO Date | | [Days](#days) | Number of days to add or subtract in the [`Add`](#add) or [`Subtract`](#subtract] operations | Integer | ## Date This parameter is used in the [`Current`](#current), [`Add`](#add), and [`Subtract`](#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`](#current), [`Add`](#add), and [`Subtract`](#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`](#add), and [`Subtract`](#subtract) operations. It should be an integer, if it is omitted, it defaults to zero (`0`) ## Available Operations | Name | Description | Parameters | |------|-------------|------------| | [Current](#current) | Returns the current date (or value of the [`Date`](#date) parameter | [`Date`](#date) (optional) | | [Add](#add) | Adds number of days to the [`Date`](#date) | [`Date`](#date) defaults to `now()`, [`Days`](#days-1) defaults to `0` | | [Subtract](#subtract) | Subtracts number of days from the [`Date`](#date) | `Date` defaults to `now()`, [`Days`](#days-1) defaults to `0` | | [Days](#days-1) | Returns the number of days from between two dates | [`Date`](#date) defaults to `now()`, [`Date2`](#date2) defaults to `now()` | ### Current Returns a copy of the current date and time in ISO format ### Add Adds the [`Days`](#days) parameter value to the `Date` value. ### Subtract Subtracts the [`Days`](#days) from the [`Date`](#date) ### Days Calculates the number of days between [`Date`](#date) and [`Date2`](#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:*:*:* ---