├── cfn-init ├── index.htm └── ec2-with-cfn-init.yml ├── .gitignore ├── wait_conditions ├── curl_command_send_signal.sh ├── wait_signal_payload.json └── wait_condition_example.yml ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ └── main.yml └── ISSUE_TEMPLATE.md ├── nested_stacks ├── package_command.sh ├── compute.yml ├── security.yml └── root_stack.yml ├── modules ├── webstack-module │ ├── .rpdk-config │ ├── fragments │ │ └── sample.yml │ └── schema.json └── use_module.yml ├── static-website ├── index.html ├── basic_s3.yml └── website.yml ├── template.yaml ├── cli ├── command.sh └── basic_s3.yml ├── macros ├── macro_usage_template_level.yml ├── macro_definition_stack.yml ├── macro_usage_with_parameters.yml ├── index.mjs └── transform.mjs ├── basic_s3.yml ├── export_import ├── input_value_subnet.yml └── export_value_vpc.yml ├── dynamic_references └── resolve-parameter-simple.yml ├── stack_sets ├── simple_vpc.yml ├── AWSCloudFormationStackSetExecutionRole.yml └── AWSCloudFormationStackSetAdministrationRole.yml ├── CONTRIBUTING.md ├── NOTICE ├── custom_resources ├── use-custom-resource.yml └── custom-resource-standalone.yml ├── developer └── lint-problems.yml ├── instance_provisioning └── creation_policy.yml ├── automation ├── template.yml ├── v2 │ └── pipeline.yml └── v1 │ └── pipeline.yml ├── basic_ec2 ├── change_set_instance.yml └── ec2_nginx.yml ├── README.md ├── conditions └── conditions.yml ├── parameters └── all_parameter_types.yml └── LICENSE /cfn-init/index.htm: -------------------------------------------------------------------------------- 1 |

Hello World!

-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .tmp 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /wait_conditions/curl_command_send_signal.sh: -------------------------------------------------------------------------------- 1 | curl -T ./wait_signal_payload.json "URL_HERE" -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Codeowners for these exercise files: 2 | # * (asterisk) denotes "all files and folders" 3 | # Example: * @producer @instructor 4 | -------------------------------------------------------------------------------- /nested_stacks/package_command.sh: -------------------------------------------------------------------------------- 1 | aws cloudformation package --template-file root_stack.yml --s3-bucket --output-template-file output.yml 2 | -------------------------------------------------------------------------------- /modules/webstack-module/.rpdk-config: -------------------------------------------------------------------------------- 1 | { 2 | "artifact_type": "MODULE", 3 | "typeName": "LinkedIn::Learning::WebStack::MODULE", 4 | "settings": {} 5 | } 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wait_conditions/wait_signal_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "Status" : "SUCCESS", 3 | "UniqueId" : "20001", 4 | "Data" : "Hello World", 5 | "Reason" : "No Reason Provided" 6 | } -------------------------------------------------------------------------------- /static-website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Static Site Deployed Using CloudFormation

4 |

Hello! This site was deployed using CloudFormation. Go cloud!

5 | 6 | -------------------------------------------------------------------------------- /template.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Resources: 4 | VPC: 5 | Type: AWS::EC2::VPC 6 | Properties: 7 | CidrBlock: "10.0.0.0/16" 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /static-website/basic_s3.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: A simple CloudFormation template 3 | Resources: 4 | Bucket: 5 | Type: AWS::S3::Bucket 6 | Properties: 7 | BucketName: my-cloudformation-demo -------------------------------------------------------------------------------- /cli/command.sh: -------------------------------------------------------------------------------- 1 | aws cloudformation deploy --template-file ./basic_s3.yml --stack-name cli-test-bucket --parameter-overrides BucketName= 2 | 3 | aws cloudformation list-stacks 4 | 5 | aws s3 ls 6 | 7 | aws cloudformation delete-stack --stack-name cli-test-bucket 8 | 9 | aws cloudformation describe-stack-events --stack-name cli-test-bucket 10 | -------------------------------------------------------------------------------- /macros/macro_usage_template_level.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: 2010-09-09 3 | Description: | 4 | A template that demonstrates how to use a macro at the template level. 5 | Parameters: 6 | BucketName: 7 | Type: String 8 | Transform: [MyNoChangeMacro] 9 | Resources: 10 | Bucket: 11 | Type: AWS::S3::Bucket 12 | Properties: 13 | BucketName: !Ref BucketName -------------------------------------------------------------------------------- /basic_s3.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: A simple CloudFormation template 3 | Parameters: 4 | BucketName: 5 | Description: | 6 | Provide a bucket name to hold packaged resources 7 | Type: String 8 | Resources: 9 | Bucket: 10 | Type: AWS::S3::Bucket 11 | Properties: 12 | BucketName: !Ref BucketName 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Copy To Branches 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | copy-to-branches: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - name: Copy To Branches Action 12 | uses: planetoftheweb/copy-to-branches@v1.2 13 | env: 14 | key: main 15 | -------------------------------------------------------------------------------- /cli/basic_s3.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: A simple CloudFormation template 3 | Parameters: 4 | BucketName: 5 | Description: | 6 | Provide a bucket name to hold packaged resources 7 | Type: String 8 | Resources: 9 | Bucket: 10 | Type: AWS::S3::Bucket 11 | Properties: 12 | BucketName: !Ref BucketName 13 | -------------------------------------------------------------------------------- /export_import/input_value_subnet.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Create a subnet that depends on a vpc in another stack. 3 | Resources: 4 | MySubnet: 5 | Type: AWS::EC2::Subnet 6 | Properties: 7 | AvailabilityZone: us-east-1b 8 | CidrBlock: 10.0.0.0/24 9 | VpcId: !ImportValue ExportedVPCID 10 | Tags: 11 | - Key: Name 12 | Value: DeleteThisSubnet -------------------------------------------------------------------------------- /dynamic_references/resolve-parameter-simple.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: A simple CloudFormation template 3 | Resources: 4 | MyBucket: 5 | Type: AWS::S3::Bucket 6 | Properties: 7 | BucketName: '{{resolve:ssm:MyBucketNameParameter}}' 8 | MyBucket2: 9 | Type: AWS::S3::Bucket 10 | Properties: 11 | BucketName: '{{resolve:secretsmanager:MyBucketSecret2:SecretString:bucketname}}' -------------------------------------------------------------------------------- /macros/macro_definition_stack.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: | 4 | A template that creates a macro definition based on the pre-existing lambda ARN. 5 | Parameters: 6 | FunctionARN: 7 | Description: ARN of the macro lambda function 8 | Type: String 9 | Resources: 10 | Macro: 11 | Type: AWS::CloudFormation::Macro 12 | Properties: 13 | Name: MyNoChangeMacro 14 | Description: Definition for the macro 15 | FunctionName: !Ref FunctionARN -------------------------------------------------------------------------------- /stack_sets/simple_vpc.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Simple VPC creation 3 | Resources: 4 | MyVPC: 5 | Type: AWS::EC2::VPC 6 | Properties: 7 | CidrBlock: 10.0.0.0/16 8 | EnableDnsHostnames: true 9 | EnableDnsSupport: true 10 | InstanceTenancy: default 11 | Tags: 12 | - Key: Name 13 | Value: DeleteThisVPC 14 | Outputs: 15 | VPCID: 16 | Description: the ID of this VPC 17 | Value: !Ref MyVPC 18 | Export: 19 | Name: ExportedVPCID -------------------------------------------------------------------------------- /static-website/website.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: A simple CloudFormation template 3 | Resources: 4 | Bucket: 5 | Type: AWS::S3::Bucket 6 | Properties: 7 | BucketName: my-cloudformation-demo 8 | AccessControl: PublicRead 9 | WebsiteConfiguration: 10 | IndexDocument: index.html 11 | Outputs: 12 | WebsiteURL: 13 | Value: !GetAtt [Bucket, WebsiteURL] 14 | Description: URL for website hosted on S3 -------------------------------------------------------------------------------- /export_import/export_value_vpc.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Simple VPC creation 3 | Resources: 4 | MyVPC: 5 | Type: AWS::EC2::VPC 6 | Properties: 7 | CidrBlock: 10.0.0.0/16 8 | EnableDnsHostnames: true 9 | EnableDnsSupport: true 10 | InstanceTenancy: default 11 | Tags: 12 | - Key: Name 13 | Value: DeleteThisVPC 14 | Outputs: 15 | VPCID: 16 | Description: the ID of this VPC 17 | Value: !Ref MyVPC 18 | Export: 19 | Name: ExportedVPCID -------------------------------------------------------------------------------- /modules/use_module.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | 4 | Parameters: 5 | Subnet: 6 | Description: Subnet in which to place the resources 7 | Type: AWS::EC2::Subnet::Id 8 | # this type is not that helpful, since it just shows IDs 9 | Resources: 10 | WebStackResource: 11 | Type: Brandon::Demo::WebStack::MODULE 12 | Properties: 13 | Subnet: !Ref Subnet 14 | InstanceType: t3.micro 15 | Outputs: 16 | IPAddress: 17 | Description: Fetched from module outputs 18 | Value: WebStackResource.Outputs.InstanceIP -------------------------------------------------------------------------------- /macros/macro_usage_with_parameters.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: 2010-09-09 3 | Description: | 4 | A template that demonstrates how to use a macro at both 5 | template and snippet levels with parameters. 6 | Parameters: 7 | BucketName: 8 | Type: String 9 | Transform: 10 | - Name: MyNoChangeMacro 11 | Parameters: 12 | Hello: World 13 | Resources: 14 | Bucket: 15 | Type: AWS::S3::Bucket 16 | Properties: 17 | BucketName: !Ref BucketName 18 | 'Fn::Transform': # cfn lint incorrectly flags this 19 | Name: 'MyNoChangeMacro' 20 | Parameters: 21 | Foo: Bar -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | Contribution Agreement 3 | ====================== 4 | 5 | This repository does not accept pull requests (PRs). All pull requests will be closed. 6 | 7 | However, if any contributions (through pull requests, issues, feedback or otherwise) are provided, as a contributor, you represent that the code you submit is your original work or that of your employer (in which case you represent you have the right to bind your employer). By submitting code (or otherwise providing feedback), you (and, if applicable, your employer) are licensing the submitted code (and/or feedback) to LinkedIn and the open source community subject to the BSD 2-Clause license. 8 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 LinkedIn Corporation 2 | All Rights Reserved. 3 | 4 | Licensed under the LinkedIn Learning Exercise File License (the "License"). 5 | See LICENSE in the project root for license information. 6 | 7 | Please note, this project may automatically load third party code from external 8 | repositories (for example, NPM modules, Composer packages, or other dependencies). 9 | If so, such third party code may be subject to other license terms than as set 10 | forth above. In addition, such third party code may also depend on and load 11 | multiple tiers of dependencies. Please review the applicable licenses of the 12 | additional dependencies. 13 | -------------------------------------------------------------------------------- /macros/index.mjs: -------------------------------------------------------------------------------- 1 | export const handler = async(event) => { 2 | 3 | // var region = event.region 4 | // var accountID = event.accountID 5 | // var fragment = event.fragment 6 | // var transformId = event.transformId 7 | // var params = event.params 8 | // var requestID = event.requestId 9 | // var templateParameterValues = event.templateParameterValues 10 | 11 | console.log("Hello from lambda") 12 | console.log( "event params: " + JSON.stringify(event, 2, null)) 13 | 14 | const response = { 15 | requestId: event.requestId, 16 | status: "success", 17 | fragment: event.fragment // no change 18 | }; 19 | return response; 20 | }; 21 | -------------------------------------------------------------------------------- /wait_conditions/wait_condition_example.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | Shows a wait condition 4 | Resources: 5 | MyBucket: 6 | Type: AWS::S3::Bucket 7 | Properties: 8 | BucketName: bmr-some-bucket 9 | MyWaitCondition: 10 | Type: AWS::CloudFormation::WaitCondition 11 | Properties: 12 | Count: 1 13 | Handle: !Ref MyWaitHandle 14 | Timeout: 300 # 5 minutes 15 | DependsOn: MyBucket 16 | MyWaitHandle: 17 | Type: AWS::CloudFormation::WaitConditionHandle # we should see the URL in this event output. 18 | Outputs: 19 | MySignalData: 20 | Description: Data returned from whatever signaled the handler. 21 | Value: !GetAtt MyWaitCondition.Data -------------------------------------------------------------------------------- /nested_stacks/compute.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | Creates an EC2 instance 4 | 5 | Parameters: 6 | SecurityGroupId: 7 | Description: A security group to apply to the instance 8 | Type: String 9 | 10 | Resources: 11 | MyInstance: 12 | Type: AWS::EC2::Instance 13 | Properties: 14 | UserData: 15 | Fn::Base64: | 16 | #!/bin/bash -xe 17 | sudo amazon-linux-extras install -y nginx1 18 | sudo service nginx start 19 | ImageId: ami-0b0dcb5067f052a63 20 | InstanceType: t3.micro 21 | SecurityGroupIds: 22 | - !Ref SecurityGroupId 23 | Tags: 24 | - Key: Name 25 | Value: MyNestedNginxInstance 26 | Outputs: 27 | InstanceIp: 28 | Value: !GetAtt MyInstance.PublicIp -------------------------------------------------------------------------------- /custom_resources/use-custom-resource.yml: -------------------------------------------------------------------------------- 1 | Description: > 2 | Using a custom resource by referring to an exported Arn. 3 | Custom resource is created in custom-resource-standalone.yml 4 | Parameters: 5 | 6 | BucketName: 7 | Type: String 8 | Description: The name of the s3 bucket you want to create 9 | 10 | Resources: 11 | MyCustomResourceCallout: 12 | Type: Custom::LambdaCallout 13 | Properties: 14 | ServiceToken: !ImportValue MyCustomResourceArn 15 | BucketName: !Ref BucketName 16 | 17 | Outputs: 18 | OutputFromFunction: 19 | Description: Output from the custom function 20 | Value: !GetAtt MyCustomResourceCallout.msg 21 | 22 | ResponseText: 23 | Description: Output from the custom function 24 | Value: !GetAtt MyCustomResourceCallout.responseText -------------------------------------------------------------------------------- /nested_stacks/security.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | Standalone security group 4 | 5 | Parameters: 6 | OpenPort: 7 | Description: HTTP Port to open 8 | Type: String 9 | 10 | Resources: 11 | SecurityGroupHTTPFromWorld: 12 | Type: AWS::EC2::SecurityGroup 13 | Properties: 14 | GroupDescription: Allows port 80 from the world 15 | GroupName: !Sub 'HTTPFromAnywhere-${AWS::StackId}' 16 | 17 | # If we do not specify egress, the default 18 | # "all traffic" egress rule will be created. 19 | SecurityGroupIngress: 20 | - IpProtocol: tcp 21 | FromPort: !Ref OpenPort 22 | ToPort: !Ref OpenPort 23 | CidrIp: 0.0.0.0/0 24 | 25 | Outputs: 26 | SecurityGroupId: 27 | Value: !GetAtt SecurityGroupHTTPFromWorld.GroupId -------------------------------------------------------------------------------- /stack_sets/AWSCloudFormationStackSetExecutionRole.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Configure the AWSCloudFormationStackSetExecutionRole to enable use of your account as a target account in AWS CloudFormation StackSets. 3 | 4 | Parameters: 5 | AdministratorAccountId: 6 | Type: String 7 | Description: AWS Account Id of the administrator account (the account in which StackSets will be created). 8 | MaxLength: 12 9 | MinLength: 12 10 | 11 | Resources: 12 | ExecutionRole: 13 | Type: AWS::IAM::Role 14 | Properties: 15 | RoleName: AWSCloudFormationStackSetExecutionRole 16 | AssumeRolePolicyDocument: 17 | Version: 2012-10-17 18 | Statement: 19 | - Effect: Allow 20 | Principal: 21 | AWS: 22 | - !Ref AdministratorAccountId 23 | Action: 24 | - sts:AssumeRole 25 | Path: / 26 | ManagedPolicyArns: 27 | - !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess 28 | -------------------------------------------------------------------------------- /nested_stacks/root_stack.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | Demonstration of nested stacks 4 | 5 | Parameters: 6 | OpenPort: 7 | Description: Open HTTP on this port 8 | Type: String 9 | 10 | Resources: 11 | SecurityStack: 12 | Type: AWS::CloudFormation::Stack 13 | Properties: 14 | Parameters: 15 | OpenPort: !Ref OpenPort 16 | TemplateURL: ./security.yml 17 | 18 | ComputeStack: 19 | Type: AWS::CloudFormation::Stack 20 | Properties: 21 | Parameters: 22 | # using different variables names to show they don't need to match 23 | # This usage does NOT rely on export/ImportValue 24 | # This parameter must be defined in the compute template, just as if 25 | # it were intended to be user-defined. 26 | SecurityGroupId: !GetAtt SecurityStack.Outputs.SecurityGroupId 27 | TemplateURL: ./compute.yml 28 | 29 | Outputs: 30 | InstanceIP: 31 | Description: Get to nginx here! 32 | Value: !GetAtt ComputeStack.Outputs.InstanceIp -------------------------------------------------------------------------------- /stack_sets/AWSCloudFormationStackSetAdministrationRole.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: Configure the AWSCloudFormationStackSetAdministrationRole to enable use of AWS CloudFormation StackSets. 3 | 4 | Resources: 5 | AdministrationRole: 6 | Type: AWS::IAM::Role 7 | Properties: 8 | RoleName: AWSCloudFormationStackSetAdministrationRole 9 | AssumeRolePolicyDocument: 10 | Version: 2012-10-17 11 | Statement: 12 | - Effect: Allow 13 | Principal: 14 | Service: cloudformation.amazonaws.com 15 | Action: 16 | - sts:AssumeRole 17 | Path: / 18 | Policies: 19 | - PolicyName: AssumeRole-AWSCloudFormationStackSetExecutionRole 20 | PolicyDocument: 21 | Version: 2012-10-17 22 | Statement: 23 | - Effect: Allow 24 | Action: 25 | - sts:AssumeRole 26 | Resource: 27 | - "arn:*:iam::*:role/AWSCloudFormationStackSetExecutionRole" 28 | -------------------------------------------------------------------------------- /developer/lint-problems.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Descrition: A test stack for demonstrating Cloudformation 4 | Parameters: 5 | Subnet: 6 | Description: Subnet in which to place the resources 7 | Type: AWS::EC2::Subnet::Id 8 | SecurityGroup: 9 | Description: This group will be applied to both the ELB and instances in the ASG 10 | Type: AWS::EC2::SecurityGroup::I 11 | InstanceType: 12 | Description: The instance type for the Launch Config 13 | Type: String 14 | Default: t2.micro 15 | AllowedValues: 16 | - t2.nano 17 | - t2.micro 18 | - t2.small 19 | - t2.medium 20 | - t2.large 21 | Resources: 22 | My_instance: 23 | Type: AWS::EC2::Instance 24 | Properties: 25 | UserData: 26 | Fn::Base64: !Sub | 27 | #!/bin/bash -xe 28 | yum install nginx -y 29 | sudo service nginx start 30 | ImageId: ami-087c17d1fe0178315 31 | InstanceType: 32 | Ref: InstanceType 33 | SecurityGroupIds: 34 | - Ref: SecurityGroup 35 | SubnetId: 36 | Ref: Subnet -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## Issue Overview 9 | 10 | 11 | ## Describe your environment 12 | 13 | 14 | ## Steps to Reproduce 15 | 16 | 1. 17 | 2. 18 | 3. 19 | 4. 20 | 21 | ## Expected Behavior 22 | 23 | 24 | ## Current Behavior 25 | 26 | 27 | ## Possible Solution 28 | 29 | 30 | ## Screenshots / Video 31 | 32 | 33 | ## Related Issues 34 | 35 | -------------------------------------------------------------------------------- /instance_provisioning/creation_policy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: A test stack for demonstrating Cloudformation 4 | Parameters: 5 | Subnet: 6 | Description: Subnet in which to place the resources 7 | Type: AWS::EC2::Subnet::Id 8 | InstanceType: 9 | Description: The instance type for the Launch Config 10 | Type: String 11 | Default: t2.micro 12 | AllowedValues: 13 | - t2.nano 14 | - t2.micro 15 | - t2.small 16 | - t2.medium 17 | - t2.large 18 | Resources: 19 | MyInstance: 20 | Type: AWS::EC2::Instance 21 | Properties: 22 | UserData: 23 | Fn::Base64: !Sub | 24 | #!/bin/bash -xe 25 | sudo amazon-linux-extras install -y nginx1 26 | sudo service nginx start 27 | /opt/aws/bin/cfn-signal -exit-code 0 --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region} 28 | CreationPolicy: # note the nesting. CreationPolicy is not under Properties, but a direct child of the resource. 29 | ResourceSignal: 30 | Timeout: PT15M 31 | Count: 5 32 | ImageId: ami-087c17d1fe0178315 33 | InstanceType: 34 | Ref: InstanceType 35 | SubnetId: 36 | Ref: Subnet -------------------------------------------------------------------------------- /automation/template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: A test stack for demonstrating Cloudformation 4 | Resources: 5 | SecurityGroupHTTPFromWorld: 6 | Type: AWS::EC2::SecurityGroup 7 | Properties: 8 | GroupDescription: Allows port 80 from the world 9 | GroupName: HTTPFromWorld 10 | # If we do not specify egress, the default 11 | # "all traffic" egress rule will be created. 12 | SecurityGroupIngress: 13 | - IpProtocol: tcp 14 | FromPort: 80 15 | ToPort: 80 16 | CidrIp: 0.0.0.0/0 17 | # Do I need VPC ID? 18 | Tags: 19 | - Key: Name 20 | Value: HTTPFromWorld 21 | MyInstance: 22 | Type: AWS::EC2::Instance 23 | Properties: 24 | UserData: 25 | Fn::Base64: | 26 | #!/bin/bash -xe 27 | sudo amazon-linux-extras install -y nginx1 28 | sudo service nginx start 29 | ImageId: ami-087c17d1fe0178315 30 | InstanceType: t3.small 31 | SecurityGroupIds: 32 | - !GetAtt SecurityGroupHTTPFromWorld.GroupId 33 | Tags: 34 | - Key: Name 35 | Value: MyNginxInstance 36 | Outputs: 37 | InstanceIP: 38 | Description: The public IP of the instance 39 | Value: !GetAtt MyInstance.PublicIp -------------------------------------------------------------------------------- /macros/transform.mjs: -------------------------------------------------------------------------------- 1 | export const handler = async(event) => { 2 | 3 | // var region = event.region 4 | // var accountID = event.accountID 5 | // var fragment = event.fragment 6 | // var transformId = event.transformId 7 | // var params = event.params 8 | // var requestID = event.requestId 9 | // var templateParameterValues = event.templateParameterValues 10 | 11 | console.log("Hello from lambda") 12 | console.log( "event params: " + JSON.stringify(event, 2, null)) 13 | 14 | 15 | // START: TRANSFORM THE JSON 16 | var tags = [ 17 | { "Key" : "Created by", 18 | "Value" : "me" 19 | }, 20 | { "Key" : "Created on", 21 | "Value" : new Date().toISOString() 22 | } 23 | ] 24 | // event.fragment is already parsed into JSON 25 | if ( event.fragment.hasOwnProperty("Resources") ) { 26 | var resources = event.fragment["Resources"] 27 | Object.keys( resources ).forEach( (resourceLabel) => { 28 | console.log("Encountered resource " + resourceLabel ) 29 | resources[resourceLabel]["Properties"]["Tags"] = tags // note: this will replace any existing tags 30 | }) 31 | } 32 | console.log("Converted fragment: " + JSON.stringify(event.fragment, 2, null)) 33 | // END: TRANSFORM THE JSON 34 | 35 | const response = { 36 | requestId: event.requestId, 37 | status: "success", 38 | fragment: event.fragment // no change 39 | }; 40 | return response; 41 | }; 42 | -------------------------------------------------------------------------------- /modules/webstack-module/fragments/sample.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: A test stack for demonstrating Cloudformation 4 | Parameters: 5 | Subnet: 6 | Description: Subnet in which to place the resources 7 | Type: AWS::EC2::Subnet::Id 8 | # this type is not that helpful, since it just shows IDs 9 | InstanceType: 10 | Description: The instance type for the Launch Config 11 | Type: String 12 | Default: t3.micro 13 | AllowedValues: 14 | - t3.nano 15 | - t3.micro 16 | - t3.small 17 | - t3.medium 18 | - t3.large 19 | Resources: 20 | SecurityGroupHTTPFromWorld: 21 | Type: AWS::EC2::SecurityGroup 22 | Properties: 23 | GroupDescription: Allows port 80 from the world 24 | GroupName: !Sub 'HTTPFromWorld-${AWS::StackName}' 25 | # If we do not specify egress, the default 26 | # "all traffic" egress rule will be created. 27 | SecurityGroupIngress: 28 | - IpProtocol: tcp 29 | FromPort: 80 30 | ToPort: 80 31 | CidrIp: 0.0.0.0/0 32 | # Do I need VPC ID? 33 | Tags: 34 | - Key: Name 35 | Value: HTTPFromWorld 36 | MyInstance: 37 | Type: AWS::EC2::Instance 38 | Properties: 39 | UserData: 40 | Fn::Base64: | 41 | #!/bin/bash -xe 42 | sudo amazon-linux-extras install -y nginx1 43 | sudo service nginx start 44 | ImageId: ami-0b0dcb5067f052a63 45 | InstanceType: 46 | Ref: InstanceType 47 | SecurityGroupIds: 48 | - !GetAtt SecurityGroupHTTPFromWorld.GroupId 49 | SubnetId: 50 | Ref: Subnet 51 | Tags: 52 | - Key: Name 53 | Value: MyNginxInstance 54 | Outputs: 55 | InstanceIP: 56 | Description: The public IP of the instance 57 | Value: !GetAtt MyInstance.PublicIp -------------------------------------------------------------------------------- /basic_ec2/change_set_instance.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: A test stack for demonstrating Cloudformation 4 | Parameters: 5 | Subnet: 6 | Description: Subnet in which to place the resources 7 | Type: AWS::EC2::Subnet::Id 8 | # this type is not that helpful, since it just shows IDs 9 | InstanceType: 10 | Description: The instance type for the Launch Config 11 | Type: String 12 | Default: t2.micro 13 | AllowedValues: 14 | - t2.nano 15 | - t2.micro 16 | - t2.small 17 | - t2.medium 18 | - t2.large 19 | AMI: 20 | Description: a reference to the AMI you want 21 | Type: AWS::EC2::Image::Id 22 | Default: ami-0b0dcb5067f052a63 23 | InstanceName: 24 | Description: The Name tag that will get applied to the instance 25 | Type: String 26 | Default: NginxDemo 27 | Resources: 28 | SecurityGroupHTTPFromWorld: 29 | Type: AWS::EC2::SecurityGroup 30 | Properties: 31 | GroupDescription: Allows port 80 from the world 32 | GroupName: HTTPFromWorld 33 | # If we do not specify egress, the default 34 | # "all traffic" egress rule will be created. 35 | SecurityGroupIngress: 36 | - IpProtocol: tcp 37 | FromPort: 22 38 | ToPort: 22 39 | CidrIp: 0.0.0.0/0 40 | # Do I need VPC ID? 41 | MyInstance: 42 | Type: AWS::EC2::Instance 43 | Properties: 44 | UserData: 45 | Fn::Base64: | 46 | #!/bin/bash -xe 47 | sudo amazon-linux-extras install -y nginx1 48 | sudo service nginx start 49 | ImageId: !Ref AMI 50 | InstanceType: 51 | Ref: InstanceType 52 | SecurityGroupIds: 53 | - !GetAtt SecurityGroupHTTPFromWorld.GroupId 54 | SubnetId: 55 | Ref: Subnet 56 | Tags: 57 | - Key: Name 58 | Value: !Ref InstanceName -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced AWS CloudFormation for Enterprise 2 | This is the repository for the LinkedIn Learning course Advanced AWS CloudFormation for Enterprise. The full course is available from [LinkedIn Learning][lil-course-url]. 3 | 4 | ![Advanced AWS CloudFormation for Enterprise][lil-thumbnail-url] 5 | 6 | With AWS CloudFormation, DevOps professionals are able to create a collection of AWS and third-party resources, then easily provision, manage, and automate them. Join IT architect and Notre Dame professor Brandon Rich as he covers the skills you need to know to get the most out of AWS CloudFormation for enterprise. 7 | 8 | Expand your technical know-how as a DevOps pro and AWS system administrator, exploring built-in functions, the AWS command line, custom resources, composable and reusable templates, CloudFormation macros, and more. By the end of this course, you’ll be ready to not only provision and manage your own resource collections, but to successfully automate them as well. 9 | 10 | 11 | 12 | ## Instructions 13 | - For most of the course content, you'll be fine to just clone this repository and work from a local copy. 14 | - However, if you want to make changes or if you would like to follow along with the later video on automating templates via Code Pipeline, you'll need to fork the repo into your own account. This will give you a copy that you can treat as your own. 15 | 16 | ### Instructor 17 | 18 | Brandon Rich 19 | 20 | Application Integration Architect 21 | 22 | 23 | 24 | Check out my other courses on [LinkedIn Learning](https://www.linkedin.com/learning/instructors/brandon-rich). 25 | 26 | [lil-course-url]: https://www.linkedin.com/learning/advanced-aws-cloudformation-for-enterprise?dApp=59033956 27 | [lil-thumbnail-url]: https://media.licdn.com/dms/image/C560DAQG7yL6ZYpTN_g/learning-public-crop_288_512/0/1676575007930?e=2147483647&v=beta&t=a6INafXm9UNSh13rEQhOun8yv57P8QVYAWYYqX6Ph4Y 28 | -------------------------------------------------------------------------------- /custom_resources/custom-resource-standalone.yml: -------------------------------------------------------------------------------- 1 | Description: > 2 | Simple custom resource demo 3 | Resources: 4 | 5 | LambdaExecutionRole: 6 | Type: AWS::IAM::Role 7 | Properties: 8 | AssumeRolePolicyDocument: 9 | Version: "2012-10-17" 10 | Statement: 11 | - Effect: Allow 12 | Principal: 13 | Service: 14 | - lambda.amazonaws.com 15 | Action: 16 | - 'sts:AssumeRole' 17 | Description: Allows Cloudformation to call services on your behalf. Slight change. 18 | ManagedPolicyArns: 19 | - arn:aws:iam::aws:policy/AmazonS3FullAccess 20 | - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 21 | RoleName: CloudformationServiceRole 22 | 23 | MyCustomResourceFunction: 24 | Type: AWS::Lambda::Function 25 | Properties: 26 | Code: 27 | ZipFile: | 28 | var response = require('cfn-response'); 29 | var aws = require("aws-sdk"); 30 | 31 | exports.handler = function(event, context) { 32 | 33 | var responseText = "starting function" 34 | var s3 = new aws.S3(); 35 | var bucketName = event.ResourceProperties.BucketName; 36 | responseText = "got bucket name: " + bucketName 37 | 38 | if ( event.RequestType == 'Create' ) { 39 | 40 | s3.createBucket( { Bucket: bucketName }, function(err,data) { 41 | responseText = "created bucket " + bucketName 42 | }) 43 | 44 | } else if ( event.RequestType == 'Delete' ) { 45 | 46 | s3.deleteBucket({Bucket: bucketName}, function(err,data){ 47 | responseText = "deleted bucket " + bucketName 48 | }) 49 | 50 | } 51 | 52 | var responseData = {msg: "hello world!", responseText: responseText}; 53 | response.send(event, context, response.SUCCESS, responseData); 54 | 55 | }; 56 | Handler: index.handler 57 | Timeout: 30 58 | Runtime: nodejs14.x 59 | Role: !GetAtt LambdaExecutionRole.Arn 60 | 61 | Outputs: 62 | MyCustomResourceArn: 63 | Description: The ARN of the custom resource, exported for use by others 64 | Value: !GetAtt MyCustomResourceFunction.Arn 65 | Export: 66 | Name: MyCustomResourceArn 67 | -------------------------------------------------------------------------------- /basic_ec2/ec2_nginx.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: A test stack for demonstrating Cloudformation 4 | Parameters: 5 | Subnet: 6 | Description: Subnet in which to place the resources 7 | Type: AWS::EC2::Subnet::Id 8 | # this type is not that helpful, since it just shows IDs 9 | KeyPair: 10 | Description: The key you'll use to log in to this instance 11 | Type: AWS::EC2::KeyPair::KeyName 12 | InstanceType: 13 | Description: The instance type for the Launch Config 14 | Type: String 15 | Default: t2.micro 16 | AllowedValues: 17 | - t2.nano 18 | - t2.micro 19 | - t2.small 20 | - t2.medium 21 | - t2.large 22 | Resources: 23 | SecurityGroupHTTPFromWorld: 24 | Type: AWS::EC2::SecurityGroup 25 | Properties: 26 | GroupDescription: Allows port 80 from the world 27 | GroupName: HTTPFromWorld 28 | # If we do not specify egress, the default 29 | # "all traffic" egress rule will be created. 30 | SecurityGroupIngress: 31 | - IpProtocol: tcp 32 | FromPort: 80 33 | ToPort: 80 34 | CidrIp: 0.0.0.0/0 35 | # Do I need VPC ID? 36 | Tags: 37 | - Key: Name 38 | Value: HTTPFromWorld 39 | SecurityGroupSSHFromWorld: 40 | Type: AWS::EC2::SecurityGroup 41 | Properties: 42 | GroupDescription: Allows port 22 from the world 43 | GroupName: SSHFromWorld 44 | # If we do not specify egress, the default 45 | # "all traffic" egress rule will be created. 46 | SecurityGroupIngress: 47 | - IpProtocol: tcp 48 | FromPort: 22 49 | ToPort: 22 50 | CidrIp: 0.0.0.0/0 51 | # Do I need VPC ID? 52 | Tags: 53 | - Key: Name 54 | Value: SSHFromWorld 55 | MyInstance: 56 | Type: AWS::EC2::Instance 57 | Properties: 58 | UserData: 59 | Fn::Base64: | 60 | #!/bin/bash -xe 61 | sudo amazon-linux-extras install -y nginx1 62 | sudo service nginx start 63 | ImageId: ami-0b0dcb5067f052a63 64 | KeyName: !Ref KeyPair 65 | InstanceType: 66 | Ref: InstanceType 67 | SecurityGroupIds: 68 | - !GetAtt SecurityGroupHTTPFromWorld.GroupId 69 | - !GetAtt SecurityGroupSSHFromWorld.GroupId 70 | SubnetId: 71 | Ref: Subnet 72 | Tags: 73 | - Key: Name 74 | Value: MyNginxInstance 75 | Outputs: 76 | InstanceIP: 77 | Description: The public IP of the instance 78 | Value: !GetAtt MyInstance.PublicIp -------------------------------------------------------------------------------- /modules/webstack-module/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "typeName": "LinkedIn::Learning::WebStack::MODULE", 3 | "description": "Schema for Module Fragment of type LinkedIn::Learning::WebStack::MODULE", 4 | "properties": { 5 | "Parameters": { 6 | "type": "object", 7 | "properties": { 8 | "Subnet": { 9 | "type": "object", 10 | "properties": { 11 | "Type": { 12 | "type": "string" 13 | }, 14 | "Description": { 15 | "type": "string" 16 | } 17 | }, 18 | "required": [ 19 | "Type", 20 | "Description" 21 | ], 22 | "description": "Subnet in which to place the resources" 23 | }, 24 | "InstanceType": { 25 | "type": "object", 26 | "properties": { 27 | "Type": { 28 | "type": "string" 29 | }, 30 | "Description": { 31 | "type": "string" 32 | } 33 | }, 34 | "required": [ 35 | "Type", 36 | "Description" 37 | ], 38 | "description": "The instance type for the Launch Config" 39 | } 40 | } 41 | }, 42 | "Resources": { 43 | "properties": { 44 | "SecurityGroupHTTPFromWorld": { 45 | "type": "object", 46 | "properties": { 47 | "Type": { 48 | "type": "string", 49 | "const": "AWS::EC2::SecurityGroup" 50 | }, 51 | "Properties": { 52 | "type": "object" 53 | } 54 | } 55 | }, 56 | "MyInstance": { 57 | "type": "object", 58 | "properties": { 59 | "Type": { 60 | "type": "string", 61 | "const": "AWS::EC2::Instance" 62 | }, 63 | "Properties": { 64 | "type": "object" 65 | } 66 | } 67 | } 68 | }, 69 | "type": "object", 70 | "additionalProperties": false 71 | } 72 | }, 73 | "additionalProperties": true 74 | } 75 | -------------------------------------------------------------------------------- /cfn-init/ec2-with-cfn-init.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: A test stack for demonstrating Cloudformation 4 | Parameters: 5 | KeyPair: 6 | Description: The key you'll use to log in to this instance 7 | Type: AWS::EC2::KeyPair::KeyName 8 | Subnet: 9 | Description: Subnet in which to place the resources 10 | Type: AWS::EC2::Subnet::Id 11 | # this type is not that helpful, since it just shows IDs 12 | InstanceType: 13 | Description: The instance type for the Launch Config 14 | Type: String 15 | Default: t2.micro 16 | AllowedValues: 17 | - t2.nano 18 | - t2.micro 19 | - t2.small 20 | - t2.medium 21 | - t2.large 22 | Resources: 23 | SecurityGroupHTTPFromWorld: 24 | Type: AWS::EC2::SecurityGroup 25 | Properties: 26 | GroupDescription: Allows port 80 from the world 27 | GroupName: !Sub HTTPFromWorld-${AWS::StackName} 28 | # If we do not specify egress, the default 29 | # "all traffic" egress rule will be created. 30 | SecurityGroupIngress: 31 | - IpProtocol: tcp 32 | FromPort: 22 33 | ToPort: 22 34 | CidrIp: 0.0.0.0/0 35 | # Do I need VPC ID? 36 | MyInstance: 37 | Type: AWS::EC2::Instance 38 | CreationPolicy: 39 | ResourceSignal: 40 | Timeout: PT10M 41 | Metadata: 42 | AWS::Cloudformation::Init: 43 | config: 44 | packages: 45 | groups: 46 | users: 47 | sources: 48 | files: 49 | commands: 50 | - sudo amazon-linux-extras install -y nginx1 51 | services: 52 | systemd: 53 | nginx: 54 | enabled: true 55 | ensureRunning: true 56 | Properties: 57 | KeyName: !Ref KeyPair 58 | UserData: 59 | Fn::Base64: !Sub | 60 | #!/bin/bash -xe 61 | yum install -y aws-cfn-bootstrap 62 | /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region} 63 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region} 64 | ImageId: ami-0b0dcb5067f052a63 65 | InstanceType: 66 | Ref: InstanceType 67 | SecurityGroupIds: 68 | - !GetAtt SecurityGroupHTTPFromWorld.GroupId 69 | - !GetAtt SecurityGroupSSHFromWorld.GroupId 70 | SubnetId: 71 | Ref: Subnet 72 | SecurityGroupSSHFromWorld: 73 | Type: AWS::EC2::SecurityGroup 74 | Properties: 75 | GroupDescription: Allows port 22 from the world 76 | GroupName: !Sub SSHFromWorld-${AWS::StackName} 77 | # If we do not specify egress, the default 78 | # "all traffic" egress rule will be created. 79 | SecurityGroupIngress: 80 | - IpProtocol: tcp 81 | FromPort: 22 82 | ToPort: 22 83 | CidrIp: 0.0.0.0/0 84 | # Do I need VPC ID? 85 | Tags: 86 | - Key: Name 87 | Value: SSHFromWorld 88 | Outputs: 89 | InstanceIP: 90 | Description: The public IP of the instance 91 | Value: !GetAtt MyInstance.PublicIp -------------------------------------------------------------------------------- /conditions/conditions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Description: A test stack for demonstrating Cloudformation 4 | Parameters: 5 | Subnet: 6 | Description: Subnet in which to place the resources 7 | Type: AWS::EC2::Subnet::Id 8 | # this type is not that helpful, since it just shows IDs 9 | KeyPair: 10 | Description: The key you'll use to log in to this instance 11 | Type: AWS::EC2::KeyPair::KeyName 12 | AllowSSH: # note, another approach would be to make the condition evaluate whether the user enters a keypair or not, but this is more explicit. 13 | Description: | 14 | Used to determine if we create and attach a security group for 15 | SSH and whether we attach a keypair to this instance 16 | Type: String 17 | AllowedValues: 18 | - true 19 | - false 20 | Environment: 21 | Description: | 22 | Specifies the environment for which you're deploying. Will 23 | be used to drive conditionals governing what resources to create 24 | Type: String 25 | AllowedValues: 26 | - production 27 | - development 28 | Conditions: 29 | IsProduction: !Equals [ !Ref Environment, 'production' ] 30 | AllowSSHCondition: !Equals [ !Ref AllowSSH, 'true' ] # condition should not have the exact same name as the input param 31 | Resources: 32 | SecurityGroupHTTPFromWorld: 33 | Type: AWS::EC2::SecurityGroup 34 | Properties: 35 | GroupDescription: Allows port 80 from the world 36 | GroupName: !Sub 'HTTPFromWorld-${AWS::StackName}' 37 | # If we do not specify egress, the default 38 | # "all traffic" egress rule will be created. 39 | SecurityGroupIngress: 40 | - IpProtocol: tcp 41 | FromPort: 80 42 | ToPort: 80 43 | CidrIp: 0.0.0.0/0 44 | # Do I need VPC ID? 45 | Tags: 46 | - Key: Name 47 | Value: HTTPFromWorld 48 | SecurityGroupSSHFromWorld: 49 | Type: AWS::EC2::SecurityGroup 50 | Condition: AllowSSHCondition 51 | Properties: 52 | GroupDescription: Allows port 22 from the world 53 | GroupName: !Sub 'SSHFromWorld-${AWS::StackName}' 54 | # If we do not specify egress, the default 55 | # "all traffic" egress rule will be created. 56 | SecurityGroupIngress: 57 | - IpProtocol: tcp 58 | FromPort: 22 59 | ToPort: 22 60 | CidrIp: 0.0.0.0/0 61 | # Do I need VPC ID? 62 | Tags: 63 | - Key: Name 64 | Value: SSHFromWorld 65 | MyInstance: 66 | Type: AWS::EC2::Instance 67 | Properties: 68 | UserData: 69 | Fn::Base64: | 70 | #!/bin/bash -xe 71 | sudo amazon-linux-extras install -y nginx1 72 | sudo service nginx start 73 | ImageId: ami-0b0dcb5067f052a63 74 | KeyName: !Ref KeyPair 75 | InstanceType: !If [ IsProduction, "t3.large", "t3.nano" ] # two different synxtax styles shown for !If 76 | SecurityGroupIds: 77 | - !GetAtt SecurityGroupHTTPFromWorld.GroupId 78 | - !If 79 | - AllowSSHCondition 80 | - !GetAtt SecurityGroupSSHFromWorld.GroupId 81 | - !Ref AWS::NoValue # when this is evaluated, it essentially erases this line, so we don't attempt to attach a non-existent security group 82 | # note that it has to be referenced like a parameter. 83 | SubnetId: 84 | Ref: Subnet 85 | Tags: 86 | - Key: Name 87 | Value: !Join 88 | - '-' 89 | - - 'MyNginxInstance' 90 | - !Ref Environment 91 | - !If [ AllowSSHCondition, 'withSSH', 'noSSH' ] 92 | Outputs: 93 | InstanceIP: 94 | Description: The public IP of the instance 95 | Value: !GetAtt MyInstance.PublicIp -------------------------------------------------------------------------------- /parameters/all_parameter_types.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | This template demos all the built-in AWS Cloudformation parameter types. They will appear in alpha order. 4 | It is possible to influence the appearance order by using AWS::CloudFormation::Interface, but it is fairly 5 | involved. It's best to limit your input parameters, lest different stacks made from the same template 6 | diverge too much from each other. 7 | Parameters: 8 | BucketName: 9 | Type: String 10 | Description: The name of a bucket. This appears near the user prompt. 11 | Default: MyDefaultBucketName 12 | 13 | AllowedValuesString: 14 | Type: String 15 | Description: This param uses AllowedValues 16 | AllowedValues: 17 | - red 18 | - blue 19 | - green 20 | 21 | SomeNumber: 22 | Type: Number 23 | Description: This is a Number type 24 | 25 | ListParam: 26 | Type: List 27 | Description: This uses type List expects a comma-separated list of integers 28 | 29 | CommaDelimitedListParam: 30 | Type: CommaDelimitedList 31 | Description: This is a type CommaDelimitedList and expects strings separated by commas. 32 | 33 | AvailabilityZoneName: 34 | Description: AWS::EC2::AvailabilityZone::Name 35 | Type: AWS::EC2::AvailabilityZone::Name 36 | 37 | # "Note that the AWS CloudFormation console doesn't show a drop-down 38 | # list of values for this parameter type." . So what good is it? 39 | EC2ImageId: 40 | Description: AWS::EC2::Image::Id 41 | Type: AWS::EC2::Image::Id 42 | 43 | EC2InstanceId: 44 | Description: AWS::EC2::Instance::Id 45 | Type: AWS::EC2::Instance::Id 46 | 47 | KeyPairName: 48 | Description: AWS::EC2::KeyPair::KeyName 49 | Type: AWS::EC2::KeyPair::KeyName 50 | 51 | SecurityGroupName: 52 | Description: AWS::EC2::SecurityGroup::GroupName 53 | Type: AWS::EC2::SecurityGroup::GroupName 54 | 55 | SecurityGroupId: 56 | Description: AWS::EC2::SecurityGroup::Id 57 | Type: AWS::EC2::SecurityGroup::Id 58 | 59 | SubnetID: 60 | Description: AWS::EC2::Subnet::Id 61 | Type: AWS::EC2::Subnet::Id 62 | 63 | VolumeID: 64 | Description: AWS::EC2::Volume::Id 65 | Type: AWS::EC2::Volume::Id 66 | 67 | VPCId: 68 | Description: AWS::EC2::VPC::Id 69 | Type: AWS::EC2::VPC::Id 70 | 71 | Route53HostedZoneId: 72 | Description: AWS::Route53::HostedZone::Id 73 | Type: AWS::Route53::HostedZone::Id 74 | 75 | AZNameList: 76 | Description: List 77 | Type: List 78 | 79 | EC2ImageIdList: 80 | Description: List 81 | Type: List 82 | 83 | EC2InstanceIdList: 84 | Description: List 85 | Type: List 86 | 87 | SecurityGroupNameList: 88 | Description: List 89 | Type: List 90 | 91 | SecurityGroupIdList: 92 | Description: List 93 | Type: List 94 | 95 | SubnetIdList: 96 | Description: List 97 | Type: List 98 | 99 | VolumeIdList: 100 | Description: List 101 | Type: List 102 | 103 | VPCIdList: 104 | Description: List 105 | Type: List 106 | 107 | Route53HostedZoneIdList: 108 | Description: List 109 | Type: List 110 | 111 | Resources: 112 | MyBucket: 113 | Type: AWS::S3::Bucket 114 | Properties: 115 | BucketName: !Ref BucketName -------------------------------------------------------------------------------- /automation/v2/pipeline.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | 4 | Parameters: 5 | 6 | RepoID: 7 | Description: The GitHub repo ID in the form username/repo_name 8 | Default: brandonrich/linkedin_learning_cloudformation 9 | Type: String 10 | 11 | TemplatePath: 12 | Description: The name of the GitHub repo 13 | Default: automation/template.yml 14 | Type: String 15 | 16 | BucketName: 17 | Description: | 18 | Provide the name of a bucket to be used by CodePipeline | 19 | when it fetches code from GitHub. Remember that bucket names must be globally unique. 20 | Type: String 21 | 22 | ConnectionARN: 23 | Description: The ARN of the CodeStar connection to GitHub 24 | Type: String 25 | 26 | Resources: 27 | MyBucket: 28 | Type: AWS::S3::Bucket 29 | Properties: 30 | BucketName: !Ref BucketName 31 | 32 | MyPipeline: 33 | Type: AWS::CodePipeline::Pipeline 34 | Properties: 35 | ArtifactStore: 36 | Type: S3 37 | Location: !Ref MyBucket 38 | Name: MyInfrastructureAsCodePipeline 39 | RestartExecutionOnUpdate: true 40 | RoleArn: !GetAtt CodePipelineServiceRole.Arn 41 | Stages: 42 | # each of these is called a StageDeclaration 43 | # See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages.html 44 | - Name: Source 45 | Actions: 46 | # each of these is called an ActionDeclaration. 47 | # see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html 48 | - Name: Source 49 | InputArtifacts: [] 50 | ActionTypeId: 51 | # For all available action types and their values, see 52 | # https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#actions-valid-providers 53 | Category: Source 54 | Owner: AWS 55 | Version: 1 # This is actually GitHub v2, but since it's through CodeStar, it's v1. 56 | Provider: CodeStarSourceConnection 57 | OutputArtifacts: 58 | - Name: SourceCode 59 | Configuration: 60 | # These params are described here: 61 | # https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-CodestarConnectionSource.html 62 | ConnectionArn: !Ref ConnectionARN 63 | FullRepositoryId: !Ref RepoID 64 | BranchName: main 65 | RunOrder: 1 66 | # Build and Deploy, etc., stages would follow. Here is an example 67 | - Name: Deploy 68 | Actions: 69 | - Name: CloudFormationDeploy 70 | ActionTypeId: 71 | Category: Deploy 72 | Owner: AWS 73 | Provider: CloudFormation 74 | Version: '1' 75 | InputArtifacts: 76 | - Name: SourceCode 77 | Configuration: 78 | ActionMode: CREATE_UPDATE 79 | Capabilities: CAPABILITY_IAM # this declares that the template being built will contain IAM changes. I think this triggers that assertion checkbox for th euser. 80 | # since we will not be running this manually, we need to provide 81 | # a role to permit the automated Cloudformation to do things 82 | RoleArn: !GetAtt CloudformationServiceRole.Arn 83 | StackName: MyGitHubAutomatedTemplate 84 | # this refers to the input artifact by name. Too bad we can't declare a 85 | # constant in the app, so we would not be repeating it in a hard-coded fashion. 86 | TemplatePath: !Sub 87 | - 'SourceCode::${template_path}' 88 | - template_path: !Ref TemplatePath 89 | RunOrder: 1 90 | 91 | CodePipelineServiceRole: 92 | Type: AWS::IAM::Role 93 | Properties: 94 | AssumeRolePolicyDocument: 95 | Version: "2012-10-17" 96 | Statement: 97 | - Effect: Allow 98 | Principal: 99 | Service: 100 | - codepipeline.amazonaws.com 101 | Action: 102 | - 'sts:AssumeRole' 103 | Description: Allows CodePipeline to call services on your behalf. 104 | Policies: 105 | - PolicyName: SuperAdminEverything 106 | PolicyDocument: 107 | Version: "2012-10-17" 108 | Statement: 109 | - Effect: Allow 110 | Action: '*' 111 | Resource: '*' 112 | RoleName: CodePipelineServiceRole 113 | 114 | CloudformationServiceRole: 115 | Type: AWS::IAM::Role 116 | Properties: 117 | AssumeRolePolicyDocument: 118 | Version: "2012-10-17" 119 | Statement: 120 | - Effect: Allow 121 | Principal: 122 | Service: 123 | - cloudformation.amazonaws.com 124 | Action: 125 | - 'sts:AssumeRole' 126 | Description: Allows Cloudformation to call services on your behalf. Slight change. 127 | ManagedPolicyArns: 128 | - arn:aws:iam::aws:policy/AmazonS3FullAccess 129 | - arn:aws:iam::aws:policy/AmazonEC2FullAccess # this should allow not only creating instances, but security groups as well 130 | RoleName: CloudformationServiceRole 131 | -------------------------------------------------------------------------------- /automation/v1/pipeline.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: | 3 | 4 | Parameters: 5 | 6 | RepoOwner: 7 | Description: The username of the owner of the repo 8 | Type: String 9 | 10 | RepoName: 11 | Description: The name of the GitHub repo 12 | Type: String 13 | 14 | TemplatePath: 15 | Description: The name of the GitHub repo 16 | Default: automation/template.yml 17 | Type: String 18 | 19 | BucketName: 20 | Description: | 21 | Provide the name of a bucket to be used by CodePipeline | 22 | when it fetches code from GitHub. Remember that bucket names must be globally unique. 23 | Type: String 24 | 25 | Resources: 26 | MyBucket: 27 | Type: AWS::S3::Bucket 28 | Properties: 29 | BucketName: !Ref BucketName 30 | 31 | MyPipeline: 32 | Type: AWS::CodePipeline::Pipeline 33 | Properties: 34 | ArtifactStore: 35 | Type: S3 36 | Location: !Ref MyBucket 37 | Name: MyInfrastructureAsCodePipeline 38 | RestartExecutionOnUpdate: true 39 | RoleArn: !GetAtt CodePipelineServiceRole.Arn 40 | Stages: 41 | # each of these is called a StageDeclaration 42 | # See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages.html 43 | - Name: Source 44 | Actions: 45 | # each of these is called an ActionDeclaration. 46 | # see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html 47 | - Name: Source 48 | InputArtifacts: [] 49 | ActionTypeId: 50 | # For all available action types and their values, see 51 | # https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#actions-valid-providers 52 | Category: Source 53 | Owner: ThirdParty 54 | Version: 1 # There is a v2 that is recommended when using the GUI, so is there a newer way to do this in CF? 55 | Provider: GitHub 56 | OutputArtifacts: 57 | - Name: SourceCode 58 | Configuration: 59 | Owner: !Ref RepoOwner # could we figure this out by parsing a GitHub URL? 60 | Repo: !Ref RepoName 61 | Branch: main 62 | PollForSourceChanges: false 63 | OAuthToken: '{{resolve:secretsmanager:GitHubCloudformationDemo:SecretString:access_token}}' 64 | RunOrder: 1 65 | # Build and Deploy, etc., stages would follow. Here is an example 66 | - Name: Deploy 67 | Actions: 68 | - Name: CloudFormationDeploy 69 | ActionTypeId: 70 | Category: Deploy 71 | Owner: AWS 72 | Provider: CloudFormation 73 | Version: '1' 74 | InputArtifacts: 75 | - Name: SourceCode 76 | Configuration: 77 | ActionMode: CREATE_UPDATE 78 | Capabilities: CAPABILITY_IAM # this declares that the template being built will contain IAM changes. I think this triggers that assertion checkbox for th euser. 79 | # since we will not be running this manually, we need to provide 80 | # a role to permit the automated Cloudformation to do things 81 | RoleArn: !GetAtt CloudformationServiceRole.Arn 82 | StackName: MyGitHubAutomatedTemplate 83 | # this refers to the input artifact by name. Too bad we can't declare a 84 | # constant in the app, so we would not be repeating it in a hard-coded fashion. 85 | TemplatePath: !Sub 86 | - 'SourceCode::${template_path}' 87 | - template_path: !Ref TemplatePath 88 | RunOrder: 1 89 | 90 | MyGitHubWebhook: 91 | Type: 'AWS::CodePipeline::Webhook' 92 | Properties: 93 | Authentication: GITHUB_HMAC 94 | AuthenticationConfiguration: 95 | SecretToken: '{{resolve:secretsmanager:GitHubCloudformationDemo:SecretString:access_token}}' 96 | RegisterWithThirdParty: 'true' 97 | Filters: 98 | - JsonPath: "$.ref" 99 | MatchEquals: refs/heads/main 100 | TargetPipeline: !Ref MyPipeline 101 | TargetAction: Source 102 | TargetPipelineVersion: !GetAtt MyPipeline.Version 103 | 104 | CodePipelineServiceRole: 105 | Type: AWS::IAM::Role 106 | Properties: 107 | AssumeRolePolicyDocument: 108 | Version: "2012-10-17" 109 | Statement: 110 | - Effect: Allow 111 | Principal: 112 | Service: 113 | - codepipeline.amazonaws.com 114 | Action: 115 | - 'sts:AssumeRole' 116 | Description: Allows CodePipeline to call services on your behalf. 117 | Policies: 118 | - PolicyName: SuperAdminEverything 119 | PolicyDocument: 120 | Version: "2012-10-17" 121 | Statement: 122 | - Effect: Allow 123 | Action: '*' 124 | Resource: '*' 125 | RoleName: CodePipelineServiceRole 126 | 127 | CloudformationServiceRole: 128 | Type: AWS::IAM::Role 129 | Properties: 130 | AssumeRolePolicyDocument: 131 | Version: "2012-10-17" 132 | Statement: 133 | - Effect: Allow 134 | Principal: 135 | Service: 136 | - cloudformation.amazonaws.com 137 | Action: 138 | - 'sts:AssumeRole' 139 | Description: Allows Cloudformation to call services on your behalf. Slight change. 140 | ManagedPolicyArns: 141 | - arn:aws:iam::aws:policy/AmazonS3FullAccess 142 | - arn:aws:iam::aws:policy/AmazonEC2FullAccess # this should allow not only creating instances, but security groups as well 143 | RoleName: CloudformationServiceRole 144 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LinkedIn Learning Exercise Files License Agreement 2 | ================================================== 3 | 4 | This License Agreement (the "Agreement") is a binding legal agreement 5 | between you (as an individual or entity, as applicable) and LinkedIn 6 | Corporation (“LinkedIn”). By downloading or using the LinkedIn Learning 7 | exercise files in this repository (“Licensed Materials”), you agree to 8 | be bound by the terms of this Agreement. If you do not agree to these 9 | terms, do not download or use the Licensed Materials. 10 | 11 | 1. License. 12 | - a. Subject to the terms of this Agreement, LinkedIn hereby grants LinkedIn 13 | members during their LinkedIn Learning subscription a non-exclusive, 14 | non-transferable copyright license, for internal use only, to 1) make a 15 | reasonable number of copies of the Licensed Materials, and 2) make 16 | derivative works of the Licensed Materials for the sole purpose of 17 | practicing skills taught in LinkedIn Learning courses. 18 | - b. Distribution. Unless otherwise noted in the Licensed Materials, subject 19 | to the terms of this Agreement, LinkedIn hereby grants LinkedIn members 20 | with a LinkedIn Learning subscription a non-exclusive, non-transferable 21 | copyright license to distribute the Licensed Materials, except the 22 | Licensed Materials may not be included in any product or service (or 23 | otherwise used) to instruct or educate others. 24 | 25 | 2. Restrictions and Intellectual Property. 26 | - a. You may not to use, modify, copy, make derivative works of, publish, 27 | distribute, rent, lease, sell, sublicense, assign or otherwise transfer the 28 | Licensed Materials, except as expressly set forth above in Section 1. 29 | - b. Linkedin (and its licensors) retains its intellectual property rights 30 | in the Licensed Materials. Except as expressly set forth in Section 1, 31 | LinkedIn grants no licenses. 32 | - c. You indemnify LinkedIn and its licensors and affiliates for i) any 33 | alleged infringement or misappropriation of any intellectual property rights 34 | of any third party based on modifications you make to the Licensed Materials, 35 | ii) any claims arising from your use or distribution of all or part of the 36 | Licensed Materials and iii) a breach of this Agreement. You will defend, hold 37 | harmless, and indemnify LinkedIn and its affiliates (and our and their 38 | respective employees, shareholders, and directors) from any claim or action 39 | brought by a third party, including all damages, liabilities, costs and 40 | expenses, including reasonable attorneys’ fees, to the extent resulting from, 41 | alleged to have resulted from, or in connection with: (a) your breach of your 42 | obligations herein; or (b) your use or distribution of any Licensed Materials. 43 | 44 | 3. Open source. This code may include open source software, which may be 45 | subject to other license terms as provided in the files. 46 | 47 | 4. Warranty Disclaimer. LINKEDIN PROVIDES THE LICENSED MATERIALS ON AN “AS IS” 48 | AND “AS AVAILABLE” BASIS. LINKEDIN MAKES NO REPRESENTATION OR WARRANTY, 49 | WHETHER EXPRESS OR IMPLIED, ABOUT THE LICENSED MATERIALS, INCLUDING ANY 50 | REPRESENTATION THAT THE LICENSED MATERIALS WILL BE FREE OF ERRORS, BUGS OR 51 | INTERRUPTIONS, OR THAT THE LICENSED MATERIALS ARE ACCURATE, COMPLETE OR 52 | OTHERWISE VALID. TO THE FULLEST EXTENT PERMITTED BY LAW, LINKEDIN AND ITS 53 | AFFILIATES DISCLAIM ANY IMPLIED OR STATUTORY WARRANTY OR CONDITION, INCLUDING 54 | ANY IMPLIED WARRANTY OR CONDITION OF MERCHANTABILITY OR FITNESS FOR A 55 | PARTICULAR PURPOSE, AVAILABILITY, SECURITY, TITLE AND/OR NON-INFRINGEMENT. 56 | YOUR USE OF THE LICENSED MATERIALS IS AT YOUR OWN DISCRETION AND RISK, AND 57 | YOU WILL BE SOLELY RESPONSIBLE FOR ANY DAMAGE THAT RESULTS FROM USE OF THE 58 | LICENSED MATERIALS TO YOUR COMPUTER SYSTEM OR LOSS OF DATA. NO ADVICE OR 59 | INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM US OR THROUGH OR 60 | FROM THE LICENSED MATERIALS WILL CREATE ANY WARRANTY OR CONDITION NOT 61 | EXPRESSLY STATED IN THESE TERMS. 62 | 63 | 5. Limitation of Liability. LINKEDIN SHALL NOT BE LIABLE FOR ANY INDIRECT, 64 | INCIDENTAL, SPECIAL, PUNITIVE, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING 65 | BUT NOT LIMITED TO, DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER 66 | INTANGIBLE LOSSES . IN NO EVENT WILL LINKEDIN'S AGGREGATE LIABILITY TO YOU 67 | EXCEED $100. THIS LIMITATION OF LIABILITY SHALL: 68 | - i. APPLY REGARDLESS OF WHETHER (A) YOU BASE YOUR CLAIM ON CONTRACT, TORT, 69 | STATUTE, OR ANY OTHER LEGAL THEORY, (B) WE KNEW OR SHOULD HAVE KNOWN ABOUT 70 | THE POSSIBILITY OF SUCH DAMAGES, OR (C) THE LIMITED REMEDIES PROVIDED IN THIS 71 | SECTION FAIL OF THEIR ESSENTIAL PURPOSE; AND 72 | - ii. NOT APPLY TO ANY DAMAGE THAT LINKEDIN MAY CAUSE YOU INTENTIONALLY OR 73 | KNOWINGLY IN VIOLATION OF THESE TERMS OR APPLICABLE LAW, OR AS OTHERWISE 74 | MANDATED BY APPLICABLE LAW THAT CANNOT BE DISCLAIMED IN THESE TERMS. 75 | 76 | 6. Termination. This Agreement automatically terminates upon your breach of 77 | this Agreement or termination of your LinkedIn Learning subscription. On 78 | termination, all licenses granted under this Agreement will terminate 79 | immediately and you will delete the Licensed Materials. Sections 2-7 of this 80 | Agreement survive any termination of this Agreement. LinkedIn may discontinue 81 | the availability of some or all of the Licensed Materials at any time for any 82 | reason. 83 | 84 | 7. Miscellaneous. This Agreement will be governed by and construed in 85 | accordance with the laws of the State of California without regard to conflict 86 | of laws principles. The exclusive forum for any disputes arising out of or 87 | relating to this Agreement shall be an appropriate federal or state court 88 | sitting in the County of Santa Clara, State of California. If LinkedIn does 89 | not act to enforce a breach of this Agreement, that does not mean that 90 | LinkedIn has waived its right to enforce this Agreement. The Agreement does 91 | not create a partnership, agency relationship, or joint venture between the 92 | parties. Neither party has the power or authority to bind the other or to 93 | create any obligation or responsibility on behalf of the other. You may not, 94 | without LinkedIn’s prior written consent, assign or delegate any rights or 95 | obligations under these terms, including in connection with a change of 96 | control. Any purported assignment and delegation shall be ineffective. The 97 | Agreement shall bind and inure to the benefit of the parties, their respective 98 | successors and permitted assigns. If any provision of the Agreement is 99 | unenforceable, that provision will be modified to render it enforceable to the 100 | extent possible to give effect to the parties’ intentions and the remaining 101 | provisions will not be affected. This Agreement is the only agreement between 102 | you and LinkedIn regarding the Licensed Materials, and supersedes all prior 103 | agreements relating to the Licensed Materials. 104 | 105 | Last Updated: March 2019 106 | --------------------------------------------------------------------------------