├── .cfnlintrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── main.yml ├── .gitignore ├── AGENDA.md ├── Ch01 - Intro and Theory ├── L01 - A Quick Refresher │ └── README.md └── L05 - Tips & Watchouts │ └── settings.json ├── Ch02 - Custom Resources └── L02 - Lets Make One │ ├── !-solution │ ├── template.yaml │ └── usage.yaml │ ├── README.md │ ├── src │ ├── index.js │ ├── package.json │ └── yarn.lock │ ├── template.yaml │ ├── test │ ├── event-create.json │ ├── event-delete.json │ └── event-update.json │ └── usage.yaml ├── Ch03 - Macros & Transforms ├── L02 - StringFunctions │ ├── !-solution │ │ ├── template.yaml │ │ └── usage.yaml │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ ├── template.yaml │ ├── tests │ │ ├── event.json │ │ └── index.test.js │ ├── usage.yaml │ └── yarn.lock ├── L03 - CommonTags │ ├── !-solution │ │ ├── template.yaml │ │ └── usage.yaml │ ├── README.md │ ├── src │ │ ├── index.js │ │ └── resources_supporting_tags.json │ ├── template.yaml │ ├── tests │ │ └── event.json │ └── usage.yaml └── L04 - S3Objects │ ├── !-solution │ ├── template.yaml │ └── usage.yaml │ ├── README.md │ ├── src │ ├── index.js │ ├── node_modules │ │ ├── axios │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── UPGRADE_GUIDE.md │ │ │ ├── dist │ │ │ │ ├── axios.js │ │ │ │ ├── axios.map │ │ │ │ ├── axios.min.js │ │ │ │ └── axios.min.map │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── lib │ │ │ │ ├── adapters │ │ │ │ │ ├── README.md │ │ │ │ │ ├── http.js │ │ │ │ │ └── xhr.js │ │ │ │ ├── axios.js │ │ │ │ ├── cancel │ │ │ │ │ ├── Cancel.js │ │ │ │ │ ├── CancelToken.js │ │ │ │ │ └── isCancel.js │ │ │ │ ├── core │ │ │ │ │ ├── Axios.js │ │ │ │ │ ├── InterceptorManager.js │ │ │ │ │ ├── README.md │ │ │ │ │ ├── createError.js │ │ │ │ │ ├── dispatchRequest.js │ │ │ │ │ ├── enhanceError.js │ │ │ │ │ ├── mergeConfig.js │ │ │ │ │ ├── settle.js │ │ │ │ │ └── transformData.js │ │ │ │ ├── defaults.js │ │ │ │ ├── helpers │ │ │ │ │ ├── README.md │ │ │ │ │ ├── bind.js │ │ │ │ │ ├── buildURL.js │ │ │ │ │ ├── combineURLs.js │ │ │ │ │ ├── cookies.js │ │ │ │ │ ├── deprecatedMethod.js │ │ │ │ │ ├── isAbsoluteURL.js │ │ │ │ │ ├── isURLSameOrigin.js │ │ │ │ │ ├── normalizeHeaderName.js │ │ │ │ │ ├── parseHeaders.js │ │ │ │ │ └── spread.js │ │ │ │ └── utils.js │ │ │ └── package.json │ │ ├── clone │ │ │ ├── .npmignore │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── clone.iml │ │ │ ├── clone.js │ │ │ └── package.json │ │ ├── debug │ │ │ ├── .coveralls.yml │ │ │ ├── .eslintrc │ │ │ ├── .npmignore │ │ │ ├── .travis.yml │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── karma.conf.js │ │ │ ├── node.js │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── browser.js │ │ │ │ ├── debug.js │ │ │ │ ├── index.js │ │ │ │ └── node.js │ │ ├── follow-redirects │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── http.js │ │ │ ├── https.js │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── is-buffer │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── package.json │ │ └── ms │ │ │ ├── index.js │ │ │ ├── license.md │ │ │ ├── package.json │ │ │ └── readme.md │ ├── package.json │ ├── resource.js │ └── yarn.lock │ ├── template.yaml │ ├── tests │ ├── event-macro.json │ ├── event-resource-create.json │ └── event-resource-delete.json │ └── usage.yaml ├── Ch04 - Best Practices ├── L01-L02 - Nested Stacks │ ├── !-solution │ │ ├── FAIL │ │ │ ├── asg.yaml │ │ │ ├── compute.yaml │ │ │ ├── lc.yaml │ │ │ └── storage.yaml │ │ ├── RECOVER │ │ │ ├── asg.yaml │ │ │ ├── compute.yaml │ │ │ ├── lc.yaml │ │ │ └── storage.yaml │ │ ├── START │ │ │ ├── asg.yaml │ │ │ ├── compute.yaml │ │ │ ├── lc.yaml │ │ │ └── storage.yaml │ │ └── root.yaml │ ├── README.md │ ├── START │ │ ├── asg.yaml │ │ ├── compute.yaml │ │ ├── lc.yaml │ │ └── storage.yaml │ └── root.yaml ├── L03 - Working w Secrets │ ├── README.md │ ├── secrets.json │ └── template.yaml └── L05 - Template Storage and Revisions │ ├── README.md │ ├── main.yml │ ├── setup.yaml │ └── template.yaml ├── Ch05 - Mastering Stacks ├── L01 - Service Roles │ ├── README.md │ ├── setup.yaml │ └── template.yaml ├── L02 - Change Sets │ ├── README.md │ ├── changesetA.yaml │ ├── changesetB.yaml │ └── template.yaml ├── L03-L04 - Stack Sets │ ├── AWSCloudFormationStackSetAdministrationRole.yml │ ├── AWSCloudFormationStackSetExecutionRole.yml │ ├── README.md │ ├── setup.yaml │ └── template.yaml └── L05 - Stack Policies │ ├── !-solution │ └── stack-policy.json │ ├── README.md │ ├── stack-policy.json │ └── template.yaml ├── Ch06 - Working with EC2 Instances ├── L03 - Resource Policies │ ├── !-solution │ │ └── template.yaml │ ├── README.md │ ├── params.toml │ └── template.yaml └── L04 - cfn-hup │ ├── !-solution │ └── template.yaml │ ├── README.md │ ├── params.toml │ └── template.yaml ├── Ch07 - Work with Serverless ├── AWS SAR │ ├── README.md │ ├── hello-world │ │ ├── index.html │ │ └── template.yaml │ ├── sar-bucket-policy.json │ └── stack-proxy │ │ ├── app.js │ │ ├── package.json │ │ ├── src │ │ ├── commontags │ │ │ ├── index.js │ │ │ └── resources_supporting_tags.json │ │ ├── githubwebhook │ │ │ ├── index.js │ │ │ └── utils │ │ │ │ └── logger.js │ │ ├── macro-proxyentry │ │ │ └── index.js │ │ ├── originrequest │ │ │ └── index.js │ │ ├── originrequestproxy │ │ │ └── index.js │ │ ├── originresponse │ │ │ └── index.js │ │ └── proxyentry │ │ │ └── index.js │ │ ├── template.yaml │ │ ├── tests │ │ ├── github │ │ │ ├── env.json │ │ │ └── githubwebhook-event.json │ │ └── macro-stackproxy.json │ │ ├── usage.yaml │ │ ├── webpack.config.js │ │ └── yarn.lock └── JAMStack Deployment │ ├── README.md │ ├── backend │ ├── functions │ │ └── handler.js │ ├── makefile │ ├── package.json │ ├── scripts │ │ └── outputs.js │ ├── template.yaml │ ├── webpack.config.js │ └── yarn.lock │ ├── frontend │ ├── .eslintrc │ ├── .prettierrc │ ├── config │ │ ├── env.js │ │ ├── jest │ │ │ ├── cssTransform.js │ │ │ └── fileTransform.js │ │ ├── paths.js │ │ ├── webpack.config.dev.js │ │ ├── webpack.config.prod.js │ │ └── webpackDevServer.config.js │ ├── makefile │ ├── package.json │ ├── public │ │ ├── _redirects │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── scripts │ │ ├── build.js │ │ ├── deploy_static_files.sh │ │ ├── remove_static_files.sh │ │ ├── start.js │ │ └── test.js │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── Voter.js │ │ ├── chatWindow.js │ │ ├── config.js │ │ ├── images │ │ ├── baratheon.png │ │ ├── greyjoy.png │ │ ├── lannister.png │ │ ├── qr.png │ │ ├── stark.png │ │ ├── targaryen.png │ │ └── tully.png │ │ ├── index.css │ │ ├── index.js │ │ └── serviceWorker.js │ ├── main.yml │ ├── package.json │ └── yarn.lock ├── Ch08 - Putting it all together ├── README.md ├── backend │ ├── .eslintrc.json │ ├── README.md │ ├── functions │ │ ├── _common │ │ │ └── utils │ │ │ │ └── cache-service.js │ │ ├── cfn │ │ │ └── index.js │ │ └── github │ │ │ ├── index.js │ │ │ └── utils │ │ │ └── pageParser.js │ ├── makefile │ ├── package.json │ ├── scripts │ │ └── outputs.js │ ├── template.yaml │ ├── tests │ │ ├── cfn │ │ │ ├── allStacks.json │ │ │ └── getStack.json │ │ └── github │ │ │ ├── Repository.json │ │ │ ├── branches.json │ │ │ ├── createBranch.json │ │ │ ├── deleteBranch.json │ │ │ ├── env.json │ │ │ └── getRepo.json │ ├── webpack.config.js │ └── yarn.lock ├── frontend │ ├── .eslintignore │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── LICENSE │ ├── README.md │ ├── gatsby-browser.js │ ├── gatsby-config.js │ ├── gatsby-node.js │ ├── gatsby-ssr.js │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── BranchCreatorButton.js │ │ │ ├── ForkedMenu.js │ │ │ ├── ForkedMenuList.js │ │ │ ├── GroupTable.js │ │ │ ├── Repositories.js │ │ │ ├── StackRowHeader.js │ │ │ ├── StackRowOptions.js │ │ │ ├── StackStatusChip.js │ │ │ ├── Stacks.js │ │ │ ├── Stacks_old.js │ │ │ ├── header.js │ │ │ ├── image.js │ │ │ ├── layout.css │ │ │ ├── layout.js │ │ │ └── seo.js │ │ ├── config │ │ │ └── config.js │ │ ├── context │ │ │ └── context-theme.js │ │ ├── graphql │ │ │ ├── mutations │ │ │ │ ├── CreateBranch.js │ │ │ │ └── DeleteBranch.js │ │ │ └── queries │ │ │ │ ├── ListRepositories.js │ │ │ │ └── ListStacks.js │ │ ├── hooks │ │ │ └── useInstance.js │ │ ├── images │ │ │ ├── gatsby-astronaut.png │ │ │ └── gatsby-icon.png │ │ └── pages │ │ │ ├── 404.js │ │ │ ├── index.js │ │ │ └── page-2.js │ └── yarn.lock ├── package.json ├── secrets.example.json └── yarn.lock ├── Ch09 - Other Tools ├── L01 - Frameworks │ ├── cdk.ts │ ├── sam.yml │ ├── serverless.yml │ └── troposphere.py └── L02 - CFN Registry & CLI │ └── README.md ├── README.md ├── package.json ├── questions.yaml └── yarn.lock /.cfnlintrc: -------------------------------------------------------------------------------- 1 | templates: 2 | - "**/*.yaml" 3 | ignore_templates: 4 | - "**/codefresh.yaml" 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # .editorconfig 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Keeps CloudFormation YAML files standard 12 | [*.yaml] 13 | indent_style = space 14 | indent_size = 2 15 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "extends": [ 9 | "prettier" 10 | ], 11 | "plugins": [ 12 | "prettier" 13 | ], 14 | "parser": "babel-eslint", 15 | "rules": { 16 | "prettier/prettier": [ 17 | "error", 18 | { 19 | "singleQuote": true, 20 | "trailingComma": "all" 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: JAMStack Deployment 2 | 3 | on: 4 | push: 5 | branches: 6 | - feat-* 7 | 8 | jobs: 9 | build-deploy: 10 | name: Backend and Frontend Setup 11 | runs-on: ubuntu-latest 12 | defaults: 13 | run: 14 | shell: bash 15 | env: 16 | DEPLOY_BUCKET: acg-deploy-bucket 17 | AWS_DEFAULT_REGION: us-east-1 18 | steps: 19 | - name: Checkout Repo 20 | uses: actions/checkout@v1 21 | 22 | - name: Configure AWS CLI 23 | uses: aws-actions/configure-aws-credentials@v1 24 | with: 25 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 26 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 27 | aws-region: us-east-1 28 | 29 | - name: Build & Deploy 30 | working-directory: "${{ github.workspace }}/Ch07 - Work with Serverless/JAMStack Deployment" 31 | run: | 32 | npm config set scripts-prepend-node-path auto # https://bit.ly/3cKK2Uf 33 | yarn install 34 | yarn build:backend 35 | yarn deploy:backend \ 36 | STAGE=${GITHUB_REF##*/} \ 37 | STAGE_FLAG=dev \ 38 | REGION=$AWS_DEFAULT_REGION \ 39 | DEPLOY_BUCKET=$DEPLOY_BUCKET 40 | yarn build:frontend 41 | yarn deploy:frontend \ 42 | STAGE=${GITHUB_REF##*/} \ 43 | STAGE_FLAG=dev \ 44 | REGION=$AWS_DEFAULT_REGION 45 | # Other Ideas: 46 | # Lighthouse jakejarvis/lighthouse-action@master 47 | # Slack https://github.com/marketplace/actions/slack-notify 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # processed cfn templates 64 | packaged.yaml 65 | 66 | examples 67 | 68 | .serverless 69 | build/ 70 | 71 | .vscode 72 | 73 | # SAM 74 | .aws-sam 75 | 76 | # ignore secrets 77 | secrets.yaml 78 | secrets.json 79 | -------------------------------------------------------------------------------- /AGENDA.md: -------------------------------------------------------------------------------- 1 | 2 | ## Chapter 1 - Intro and Theory 3 | - Ch01_L01 – Introduction 4 | - Ch01_L02 – A Quick Refresher 5 | - Ch01_L03 – Template Anatomy 6 | - Ch01_L04 – Template Operations 7 | - Ch01_L05 – Tips & Watchouts 8 | 9 | ## Chapter 2 - Custom Resources 10 | - Ch02_L01 – Overview 11 | - Ch02_L02 – What We Are Building 12 | - Ch02_L03 – Let's Make one 13 | - Ch02_L04 – Let's Use It 14 | 15 | ## Chapter 3 - Macros & Transforms 16 | - Ch03_L01 – Overview 17 | - Ch03_L02 – Macro: String Operations 18 | - Ch03_L03 – Macro: Common Tags 19 | - Ch03_L04 – Macro: Custom Resource Types 20 | - Ch03_L05 – Unit Testing 21 | 22 | ## Chapter 4 - Best Practices 23 | - Ch04_L01 – Nested Stacks - Create 24 | - Ch04_L02 – Nested Stacks - Recover 25 | - Ch04_L03 – Working with Secrets 26 | - Ch04_L04 – Template Strategies 27 | - Ch04_L05 – Template Storage and Revisions 28 | 29 | ## Chapter 5 - Mastering Stacks 30 | - Ch05_L01 – Service Roles 31 | - Ch05_L02 – Change Sets 32 | - Ch05_L03 – StackSets - Part1 33 | - Ch05_L04 – StackSets - Part2 34 | - Ch05_L05 – Stack Policies 35 | 36 | ## Chapter 6 - Working with EC2 Instances 37 | - Ch06_L01 – CloudFormationInit 38 | - Ch06_L02 – configSets 39 | - Ch06_L03 – Resource Policies 40 | - Ch06_L04 – cfn-hup 41 | 42 | ## Chapter 7 - Working with Serverless 43 | - Ch07_L01 – AWS Serverless Application Repository - Part 1 44 | - Ch07_L02 – AWS Serverless Application Repository - Part 2 45 | - Ch07_L03 – AWS Serverless Application Repository - Part 3 46 | - Ch07_L04 – JAMStack Deployment - Part 1 47 | - Ch07_L05 – JAMStack Deployment - Part 2 48 | 49 | ## Chapter 8 - Putting it all together 50 | - Ch08_L01 – Programmatic CloudFormation 51 | - Ch08_L02 – Portal Code Walk-through 52 | - Ch08_L03 - Complete Course Clean-up 53 | 54 | ## Chapter 9 - Other Tools 55 | - Ch09_L01 - Frameworks 56 | - Troposphere 57 | - Serverless Framework 58 | - AWS SAM 59 | - AWS CDK 60 | - Ch09_L02 – CloudFormation Registry & CLI Discussion 61 | - Ch09_L03 – Conclusion 62 | -------------------------------------------------------------------------------- /Ch01 - Intro and Theory/L01 - A Quick Refresher/README.md: -------------------------------------------------------------------------------- 1 | 2 | ```shell 3 | PROFILE=cloudguru 4 | REGION=us-east-1 5 | CourseBucketParam=acg-deploy-bucket 6 | aws s3api create-bucket \ 7 | --bucket $CourseBucketParam \ 8 | --region $REGION \ 9 | --profile $PROFILE 10 | ``` 11 | -------------------------------------------------------------------------------- /Ch01 - Intro and Theory/L05 - Tips & Watchouts/settings.json: -------------------------------------------------------------------------------- 1 | // Custom tags for the parser to use 2 | "yaml.customTags": [ 3 | "!And sequence", 4 | "!If sequence", 5 | "!Not sequence", 6 | "!Equals sequence", 7 | "!Or sequence", 8 | "!FindInMap sequence", 9 | "!FindInMap scalar", 10 | "!Base64", 11 | "!Cidr", 12 | "!Ref", 13 | "!Sub sequence", 14 | "!Sub scalar", 15 | "!GetAtt", 16 | "!GetAZs", 17 | "!ImportValue", 18 | "!Select", 19 | "!Select sequence", 20 | "!Split sequence", 21 | "!Join sequence", 22 | "!Condition", 23 | "!Transform sequence" 24 | ], 25 | // Enable/disable default YAML formatter (requires restart) 26 | "yaml.format.enable": true, 27 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/!-solution/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Parameters: 4 | DomainParameter: 5 | Description: apex domain 6 | Type: String 7 | 8 | Resources: 9 | ProxyEntryLambdaFunction: 10 | Type: AWS::Serverless::Function 11 | Properties: 12 | Runtime: nodejs12.x 13 | CodeUri: src 14 | Handler: index.handler 15 | Role: !GetAtt ProxyEntryRole.Arn 16 | Environment: 17 | Variables: 18 | ORIGINMAP_TABLE: !Ref OriginMapTBL 19 | DOMAIN: !Ref DomainParameter 20 | 21 | ProxyEntryRole: 22 | Type: AWS::IAM::Role 23 | Properties: 24 | Path: / 25 | RoleName: stack-proxy-proxyentry-role 26 | AssumeRolePolicyDocument: 27 | Version: 2012-10-17 28 | Statement: 29 | - Effect: Allow 30 | Principal: 31 | Service: 32 | - lambda.amazonaws.com 33 | Action: sts:AssumeRole 34 | Policies: 35 | - PolicyName: stack-proxy-proxyentry-role 36 | PolicyDocument: 37 | Version: 2012-10-17 38 | Statement: 39 | - Effect: Allow 40 | Action: 41 | - logs:CreateLogGroup 42 | - logs:CreateLogStream 43 | - logs:PutLogEvents 44 | Resource: arn:aws:logs:*:*:* 45 | - Effect: Allow 46 | Action: 47 | - dynamodb:* 48 | Resource: 49 | - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/stack-proxy-* 50 | - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/stack-proxy-*/index/* 51 | 52 | OriginMapTBL: 53 | Type: AWS::DynamoDB::Table 54 | Properties: 55 | TableName: stack-proxy-domainmap 56 | AttributeDefinitions: 57 | - AttributeName: Host 58 | AttributeType: S 59 | KeySchema: 60 | - AttributeName: Host 61 | KeyType: HASH 62 | ProvisionedThroughput: 63 | ReadCapacityUnits: 10 64 | WriteCapacityUnits: 5 65 | 66 | Outputs: 67 | ProxyEntryLambdaARN: 68 | Description: ProxyEntry Lambda ARN 69 | Value: !GetAtt ProxyEntryLambdaFunction.Arn 70 | Export: 71 | Name: stack-proxy-proxyentry-lambda-arn 72 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/!-solution/usage.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | ServiceParameter: 3 | Type: String 4 | Description: Service Name 5 | StageParameter: 6 | Type: String 7 | Default: dev 8 | Description: Feature Branch Name 9 | Resources: 10 | WebsiteBucket: 11 | Type: AWS::S3::Bucket 12 | ProxyEntry: 13 | Type: Custom::ProxyEntry 14 | Version: 1.0 15 | Properties: 16 | ServiceToken: !ImportValue stack-proxy-proxyentry-lambda-arn 17 | Service: !Ref ServiceParameter 18 | Stage: !Ref StageParameter 19 | Origin: !GetAtt WebsiteBucket.DomainName 20 | 21 | Outputs: 22 | SiteURL: 23 | Description: URL for this Site 24 | Value: !Sub 25 | - https://${Domain} 26 | - { Domain: !GetAtt ProxyEntry.Host } 27 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### PACKAGE Custom Resource Template 3 | ```shell 4 | REGION=us-east-1 5 | PROFILE=cloudguru 6 | DEPLOY_BUCKET=acg-deploy-bucket 7 | aws cloudformation package \ 8 | --template-file template.yaml \ 9 | --s3-bucket $DEPLOY_BUCKET \ 10 | --output-template-file packaged.yaml \ 11 | --region $REGION \ 12 | --profile $PROFILE 13 | ``` 14 | 15 | ### DEPLOY Custom Resource Template 16 | ```shell 17 | STACKNAME=acg-custom-resource 18 | aws cloudformation deploy \ 19 | --stack-name $STACKNAME \ 20 | --template-file packaged.yaml \ 21 | --parameter-overrides DomainParameter=companyx.com \ 22 | --capabilities CAPABILITY_NAMED_IAM \ 23 | --region $REGION \ 24 | --profile $PROFILE 25 | ``` 26 | 27 | ### DEPLOY Template USING Custom Resource 28 | ```shell 29 | STACKNAME=projectx-feat-sls 30 | aws cloudformation deploy \ 31 | --stack-name $STACKNAME \ 32 | --template-file usage.yaml \ 33 | --parameter-overrides StageParameter=feat-sls ServiceParameter=projectx \ 34 | --region $REGION \ 35 | --profile $PROFILE 36 | ``` 37 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "axios": "^0.19.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/src/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | axios@^0.19.0: 6 | version "0.19.0" 7 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" 8 | integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== 9 | dependencies: 10 | follow-redirects "1.5.10" 11 | is-buffer "^2.0.2" 12 | 13 | debug@=3.1.0: 14 | version "3.1.0" 15 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 16 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 17 | dependencies: 18 | ms "2.0.0" 19 | 20 | follow-redirects@1.5.10: 21 | version "1.5.10" 22 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" 23 | integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== 24 | dependencies: 25 | debug "=3.1.0" 26 | 27 | is-buffer@^2.0.2: 28 | version "2.0.3" 29 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" 30 | integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== 31 | 32 | ms@2.0.0: 33 | version "2.0.0" 34 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 35 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 36 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/template.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch02 - Custom Resources/L02 - Lets Make One/template.yaml -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/test/event-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "RequestType" : "Create", 3 | "RequestId" : "unique id for this create request", 4 | "ResponseURL" : "pre-signed-url-for-create-response", 5 | "ResourceType" : "Custom::MyCustomResourceType", 6 | "LogicalResourceId" : "name of resource in template", 7 | "StackId" : "arn:aws:cloudformation:us-east-1:namespace:stack/stack-name/guid", 8 | "ResourceProperties" : { 9 | "Service" : "projectx", 10 | "Stage": "feat-sls", 11 | "Origin": "test-bucket" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/test/event-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "RequestType": "Delete", 3 | "RequestId": "unique id for this delete request", 4 | "ResponseURL": "pre-signed-url-for-delete-response", 5 | "ResourceType": "Custom::MyCustomResourceType", 6 | "LogicalResourceId": "name of resource in template", 7 | "StackId": "arn:aws:cloudformation:us-east-1:namespace:stack/stack-name/guid", 8 | "PhysicalResourceId": "feat-sls--projectx.companyx.com", 9 | "ResourceProperties": { 10 | "Service": "projectx", 11 | "Stage": "feat-sls", 12 | "Origin": "new-bucket" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/test/event-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "RequestType": "Update", 3 | "RequestId": "unique id for this update request", 4 | "ResponseURL": "pre-signed-url-for-update-response", 5 | "ResourceType": "Custom::MyCustomResourceType", 6 | "LogicalResourceId": "name of resource in template", 7 | "StackId": "arn:aws:cloudformation:us-east-1:namespace:stack/stack-name/guid", 8 | "PhysicalResourceId": "feat-sls--projectx.companyx.com", 9 | "ResourceProperties": { 10 | "Service": "projectx", 11 | "Stage": "feat-sls", 12 | "Origin": "new-bucket" 13 | }, 14 | "OldResourceProperties": { 15 | "Service": "projectx", 16 | "Stage": "feat-sls", 17 | "Origin": "test-bucket" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ch02 - Custom Resources/L02 - Lets Make One/usage.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch02 - Custom Resources/L02 - Lets Make One/usage.yaml -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/!-solution/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Description: Macro StringFunctions 4 | 5 | Resources: 6 | StringFunction: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | Runtime: nodejs12.x 10 | CodeUri: src 11 | Handler: index.handler 12 | 13 | Macro: 14 | Type: AWS::CloudFormation::Macro 15 | Properties: 16 | Name: String 17 | Description: String Processing Functions 18 | FunctionName: !GetAtt StringFunction.Arn 19 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/!-solution/usage.yaml: -------------------------------------------------------------------------------- 1 | Description: StringFunctions macro example template 2 | 3 | Parameters: 4 | InputString: 5 | Default: This is a test input string 6 | Type: String 7 | 8 | Resources: 9 | S3Bucket: 10 | Type: AWS::S3::Bucket 11 | Properties: 12 | Tags: 13 | - Key: Upper 14 | Value: 15 | "Fn::Transform": 16 | - Name: String 17 | Parameters: 18 | InputString: !Ref InputString 19 | Operation: Upper 20 | - Key: Lower 21 | Value: 22 | "Fn::Transform": 23 | - Name: String 24 | Parameters: 25 | InputString: !Ref InputString 26 | Operation: Lower 27 | - Key: Capitalize 28 | Value: 29 | "Fn::Transform": 30 | - Name: String 31 | Parameters: 32 | InputString: !Ref InputString 33 | Operation: Capitalize 34 | - Key: Title 35 | Value: 36 | "Fn::Transform": 37 | - Name: String 38 | Parameters: 39 | InputString: !Ref InputString 40 | Operation: Title 41 | - Key: Replace 42 | Value: 43 | "Fn::Transform": 44 | - Name: String 45 | Parameters: 46 | InputString: !Ref InputString 47 | Operation: Replace 48 | Old: " " 49 | New: "_" 50 | - Key: Strip 51 | Value: 52 | "Fn::Transform": 53 | - Name: String 54 | Parameters: 55 | InputString: !Ref InputString 56 | Operation: Strip 57 | Chars: Tgif 58 | - Key: ShortenLeft 59 | Value: 60 | "Fn::Transform": 61 | - Name: String 62 | Parameters: 63 | InputString: !Ref InputString 64 | Operation: MaxLength 65 | Length: 4 66 | StripFrom: Left 67 | - Key: ShortenRight 68 | Value: 69 | "Fn::Transform": 70 | - Name: String 71 | Parameters: 72 | InputString: !Ref InputString 73 | Operation: MaxLength 74 | Length: 4 75 | StripFrom: Right 76 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### PACKAGE Custom Resource Template 3 | ```shell 4 | REGION=us-east-1 5 | PROFILE=cloudguru 6 | DEPLOY_BUCKET=acg-deploy-bucket 7 | aws cloudformation package \ 8 | --template-file template.yaml \ 9 | --s3-bucket $DEPLOY_BUCKET \ 10 | --output-template-file packaged.yaml \ 11 | --region $REGION \ 12 | --profile $PROFILE 13 | ``` 14 | 15 | ### DEPLOY Custom Resource Template 16 | ```shell 17 | STACKNAME=macro-stringfunctions 18 | aws cloudformation deploy \ 19 | --stack-name $STACKNAME \ 20 | --template-file packaged.yaml \ 21 | --capabilities CAPABILITY_IAM \ 22 | --region $REGION \ 23 | --profile $PROFILE 24 | ``` 25 | 26 | ### DEPLOY Template USING Custom Resource 27 | ```shell 28 | STACKNAME=usage-stringfunctions 29 | aws cloudformation deploy \ 30 | --stack-name $STACKNAME \ 31 | --template-file usage.yaml \ 32 | --capabilities CAPABILITY_IAM \ 33 | --region $REGION \ 34 | --profile $PROFILE 35 | ``` 36 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test:lint": "eslint .", 4 | "test:unit": "jest --verbose", 5 | "test:unit:watch": "jest --verbose --watch", 6 | "test": "npm run test:lint && npm run test:unit", 7 | "jest": "jest" 8 | }, 9 | "jest": { 10 | "collectCoverageFrom": [ 11 | "**/src/**/*.test.js", 12 | "!node_modules" 13 | ], 14 | "testEnvironment": "node" 15 | }, 16 | "devDependencies": { 17 | "aws-sdk": "^2.521.0", 18 | "eslint": "^6.3.0", 19 | "eslint-config-prettier": "^6.1.0", 20 | "eslint-plugin-import": "^2.18.2", 21 | "eslint-plugin-prettier": "^3.1.0", 22 | "jest": "^24.9.0", 23 | "prettier": "^1.18.2", 24 | "prettier-eslint": "^9.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/src/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = (event, context, callback) => { 2 | console.log('event', JSON.stringify(event)); 3 | let fragment, 4 | status = 'success'; 5 | const { 6 | requestId, 7 | params: { Operation, InputString, Chars, Old, New, Length, StripFrom }, 8 | } = event; 9 | 10 | const noParamStringFuncs = [ 11 | 'Upper', 12 | 'Lower', 13 | 'Capitalize', 14 | 'Title', 15 | 'SwapCase', 16 | ]; 17 | 18 | if (noParamStringFuncs.includes(Operation)) { 19 | fragment = operations[Operation.toLowerCase()](InputString); 20 | } else if (Operation === 'Strip') { 21 | fragment = strip(InputString, Chars); 22 | } else if (Operation === 'Replace') { 23 | fragment = InputString.replace(new RegExp(Old, 'g'), New); 24 | } else if (Operation === 'MaxLength') { 25 | if (InputString <= Length) { 26 | fragment = InputString; 27 | } else if (StripFrom) { 28 | if (StripFrom === 'Left') { 29 | const strLength = InputString.length; 30 | fragment = InputString.slice(strLength - Length, strLength + 1); 31 | } else if (StripFrom === 'Right') { 32 | fragment = InputString.slice(0, Length); 33 | } else { 34 | response['status'] = 'failure'; 35 | } 36 | } 37 | } else { 38 | status = 'failure'; 39 | } 40 | 41 | const resp = { 42 | requestId, 43 | status, 44 | fragment, 45 | }; 46 | 47 | callback(null, resp); 48 | }; 49 | 50 | const upper = str => str.toUpperCase(); 51 | const lower = str => str.toLowerCase(); 52 | const capitalize = str => `${str.charAt(0).toUpperCase()}${str.slice(1)}`; 53 | const title = str => 54 | str 55 | .toLowerCase() 56 | .split(' ') 57 | .map(word => word.replace(word[0], word[0].toUpperCase())) 58 | .join(' '); 59 | const swapcase = str => 60 | str 61 | .split('') 62 | .map(c => (c === c.toUpperCase() ? lower(c) : upper(c))) 63 | .join(''); 64 | const strip = (s, c) => { 65 | if (!c) return s.trim(); 66 | if (c.indexOf(s[0]) > -1) return strip(s.substring(1), c); 67 | if (c.indexOf(s[s.length - 1]) > -1) 68 | return strip(s.substring(0, s.length - 1), c); 69 | return s; 70 | }; 71 | 72 | const operations = { 73 | upper, 74 | lower, 75 | capitalize, 76 | title, 77 | swapcase, 78 | }; 79 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Description: Macro StringFunctions 4 | 5 | Resources: 6 | StringFunction: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | Runtime: nodejs12.x 10 | CodeUri: src 11 | Handler: index.handler 12 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/tests/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east-1", 3 | "accountId": "104477223281", 4 | "fragment": {}, 5 | "transformId": "104477223281::String", 6 | "params": { 7 | "InputString": "this is a test input String", 8 | "Operation": "MaxLength", 9 | "Length": "4", 10 | "StripFrom": "Left" 11 | }, 12 | "requestId": "617b6d09-32cb-421d-ac8f-1507756e8ecd", 13 | "templateParameterValues": { 14 | "InputString": "This is a test input string" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L02 - StringFunctions/usage.yaml: -------------------------------------------------------------------------------- 1 | Description: StringFunctions macro example template 2 | 3 | Parameters: 4 | InputString: 5 | Default: This is a test input string 6 | Type: String 7 | 8 | Resources: 9 | S3Bucket: 10 | Type: AWS::S3::Bucket 11 | Properties: 12 | Tags: 13 | - Key: Upper 14 | Value: !Ref InputString 15 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L03 - CommonTags/!-solution/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Description: Macro CommonTags 4 | 5 | Resources: 6 | MacroFunction: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | Runtime: nodejs12.x 10 | CodeUri: src 11 | Handler: index.handler 12 | 13 | Macro: 14 | Type: AWS::CloudFormation::Macro 15 | Properties: 16 | Name: CommonTags 17 | FunctionName: !GetAtt MacroFunction.Arn 18 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L03 - CommonTags/!-solution/usage.yaml: -------------------------------------------------------------------------------- 1 | Transform: 2 | Name: CommonTags 3 | Parameters: 4 | Name: MySuperCoolApp 5 | CreatedBy: Me 6 | CostAllocation: MySuperCoolApp 7 | 8 | Description: CommonTags macro example template 9 | 10 | Resources: 11 | Bucket1: 12 | Type: AWS::S3::Bucket 13 | Properties: 14 | Tags: 15 | - Key: CostAllocation 16 | Value: override 17 | 18 | Bucket2: 19 | Type: AWS::S3::Bucket 20 | Properties: 21 | Tags: 22 | - Key: SomeOtherThing 23 | Value: stuff 24 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L03 - CommonTags/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### PACKAGE Custom Resource Template 3 | ```shell 4 | REGION=us-east-1 5 | PROFILE=cloudguru 6 | DEPLOY_BUCKET=acg-deploy-bucket 7 | aws cloudformation package \ 8 | --template-file template.yaml \ 9 | --s3-bucket $DEPLOY_BUCKET \ 10 | --output-template-file packaged.yaml \ 11 | --region $REGION \ 12 | --profile $PROFILE 13 | ``` 14 | 15 | ### DEPLOY Custom Resource Template 16 | ```shell 17 | STACKNAME=macro-commontags 18 | aws cloudformation deploy \ 19 | --stack-name $STACKNAME \ 20 | --template-file packaged.yaml \ 21 | --capabilities CAPABILITY_IAM \ 22 | --region $REGION \ 23 | --profile $PROFILE 24 | ``` 25 | 26 | ### DEPLOY Template USING Custom Resource 27 | ```shell 28 | STACKNAME=usage-commontags 29 | aws cloudformation deploy \ 30 | --stack-name $STACKNAME \ 31 | --template-file usage.yaml \ 32 | --capabilities CAPABILITY_IAM \ 33 | --region $REGION \ 34 | --profile $PROFILE 35 | ``` 36 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L03 - CommonTags/src/index.js: -------------------------------------------------------------------------------- 1 | const RESOURCES_SUPPORTING_TAGS = require('./resources_supporting_tags.json'); 2 | 3 | exports.handler = (event, context, callback) => { 4 | console.log('event', JSON.stringify(event), RESOURCES_SUPPORTING_TAGS); 5 | const { requestId, fragment, params: tags } = event; 6 | const resources = fragment.Resources; 7 | 8 | if (tags) tagResources(resources, tags); 9 | 10 | const resp = { 11 | requestId, 12 | status: 'success', 13 | fragment, 14 | }; 15 | 16 | callback(null, resp); 17 | }; 18 | 19 | const isTaggable = ({ Properties, Type }) => 20 | (Properties && Properties.Tags) || RESOURCES_SUPPORTING_TAGS.includes(Type); 21 | 22 | const isTaggedWithAlready = (tag, resourceTags) => { 23 | for (const tagObj in resourceTags) { 24 | if (resourceTags[tagObj].Key === tag) return true; 25 | } 26 | return false; 27 | }; 28 | 29 | const tagResources = (resources, tags) => { 30 | for (const key in resources) { 31 | tagResource(resources[key], tags); 32 | } 33 | }; 34 | 35 | const tagResource = (resource, tags) => { 36 | const resourceTags = (resource.Properties && resource.Properties.Tags) || []; 37 | console.log('tagResource', resourceTags); 38 | if (isTaggable(resource)) { 39 | for (const Key in tags) { 40 | if (!isTaggedWithAlready(Key, resourceTags)) { 41 | resourceTags.push({ 42 | Key, 43 | Value: tags[Key], 44 | }); 45 | } 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L03 - CommonTags/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Description: Macro CommonTags 4 | 5 | Resources: 6 | MacroFunction: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | Runtime: nodejs12.x 10 | CodeUri: src 11 | Handler: index.handler 12 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L03 - CommonTags/tests/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east-1", 3 | "accountId": "000000000000", 4 | "fragment": { 5 | "Description": "CommonTags macro example template", 6 | "Resources": { 7 | "Bucket1": { 8 | "Type": "AWS::S3::Bucket", 9 | "Properties": { 10 | "Tags": [ 11 | { 12 | "Key": "CostAllocation", 13 | "Value": "override" 14 | } 15 | ] 16 | } 17 | } 18 | } 19 | }, 20 | "transformId": "000000000000::CommonTags", 21 | "params": { 22 | "CreatedBy": "Me", 23 | "CostAllocation": "MySuperCoolApp", 24 | "Name": "MySuperCoolApp" 25 | }, 26 | "requestId": "04d681a2-906f-4d25-b0ef-72044ac95035", 27 | "templateParameterValues": {} 28 | } 29 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L03 - CommonTags/usage.yaml: -------------------------------------------------------------------------------- 1 | Description: CommonTags macro example template 2 | 3 | Resources: 4 | Bucket1: 5 | Type: AWS::S3::Bucket 6 | Properties: 7 | Tags: 8 | - Key: CostAllocation 9 | Value: override 10 | 11 | Bucket2: 12 | Type: AWS::S3::Bucket 13 | Properties: 14 | Tags: 15 | - Key: SomeOtherThing 16 | Value: stuff 17 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/!-solution/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | ResourceFunction: 5 | Type: AWS::Serverless::Function 6 | Properties: 7 | Runtime: nodejs12.x 8 | CodeUri: src 9 | Handler: resource.handler 10 | Policies: AmazonS3FullAccess 11 | 12 | MacroFunction: 13 | Type: AWS::Serverless::Function 14 | Properties: 15 | Runtime: nodejs12.x 16 | CodeUri: src 17 | Handler: index.handler 18 | Policies: AmazonS3FullAccess 19 | Environment: 20 | Variables: 21 | LAMBDA_ARN: !GetAtt ResourceFunction.Arn 22 | 23 | Macro: 24 | Type: AWS::CloudFormation::Macro 25 | Properties: 26 | Name: S3Objects 27 | FunctionName: !GetAtt MacroFunction.Arn 28 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/!-solution/usage.yaml: -------------------------------------------------------------------------------- 1 | Transform: S3Objects 2 | 3 | Resources: 4 | Bucket: 5 | Type: AWS::S3::Bucket 6 | 7 | Object1: 8 | Type: AWS::S3::Object 9 | Properties: 10 | Target: 11 | Bucket: !Ref Bucket 12 | Key: README.md 13 | ContentType: text/markdown 14 | Body: | 15 | # My text file 16 | 17 | This is my text file; 18 | there are many like it, 19 | but this one is mine. 20 | Object2: 21 | Type: AWS::S3::Object 22 | Properties: 23 | Target: 24 | Bucket: !Ref Bucket 25 | Key: 1-pixel.gif 26 | ACL: public-read 27 | ContentType: image/png 28 | Base64Body: R0lGODdhAQABAIABAP///0qIbCwAAAAAAQABAAACAkQBADs= 29 | Object3: 30 | Type: AWS::S3::Object 31 | Properties: 32 | Source: 33 | Bucket: !GetAtt Object1.Bucket 34 | Key: !GetAtt Object1.Key 35 | Target: 36 | Bucket: !Ref Bucket 37 | Key: README-copy.md 38 | ACL: public-read 39 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### PACKAGE Custom Resource Template 3 | ```shell 4 | REGION=us-east-1 5 | PROFILE=cloudguru 6 | DEPLOY_BUCKET=acg-deploy-bucket 7 | aws cloudformation package \ 8 | --template-file template.yaml \ 9 | --s3-bucket $DEPLOY_BUCKET \ 10 | --output-template-file packaged.yaml \ 11 | --region $REGION \ 12 | --profile $PROFILE 13 | ``` 14 | 15 | ### DEPLOY Custom Resource Template 16 | ```shell 17 | STACKNAME=macro-s3object 18 | aws cloudformation deploy \ 19 | --stack-name $STACKNAME \ 20 | --template-file packaged.yaml \ 21 | --capabilities CAPABILITY_IAM \ 22 | --region $REGION \ 23 | --profile $PROFILE 24 | ``` 25 | 26 | ### DEPLOY Template USING Custom Resource 27 | ```shell 28 | STACKNAME=usage-s3object 29 | aws cloudformation deploy \ 30 | --stack-name $STACKNAME \ 31 | --template-file usage.yaml \ 32 | --region $REGION \ 33 | --profile $PROFILE 34 | ``` 35 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/index.js: -------------------------------------------------------------------------------- 1 | const clone = require('clone'); 2 | 3 | const { LAMBDA_ARN } = process.env; 4 | 5 | const processTemplate = template => { 6 | let status = 'success'; 7 | let newResources = {}; 8 | const newTemplate = clone(template); 9 | const resources = newTemplate.Resources; 10 | 11 | for (let name in resources) { 12 | const resource = resources[name]; 13 | if (resource['Type'] === 'AWS::S3::Object') { 14 | const props = resource['Properties']; 15 | const Target = props['Target']; 16 | const bodyType = getBodyType(props); 17 | 18 | if (!bodyType.length === 1) 19 | throw 'You must specify exactly one of: Body, Base64Body, Source'; 20 | 21 | if (!Target['ACL']) Target['ACL'] = 'private'; 22 | 23 | const resource_props = { 24 | ServiceToken: LAMBDA_ARN, 25 | Target, 26 | [bodyType]: props[bodyType], 27 | }; 28 | 29 | newResources[name] = { 30 | Type: 'Custom::S3Object', 31 | Version: '1.0', 32 | Properties: resource_props, 33 | }; 34 | } 35 | } 36 | 37 | for (let name in newResources) { 38 | const resource = newResources[name]; 39 | resources[name] = resource; 40 | } 41 | 42 | return [status, newTemplate]; 43 | }; 44 | 45 | exports.handler = (event, context, callback) => { 46 | console.log('event', JSON.stringify(event)); 47 | const { requestId } = event; 48 | const [status, fragment] = processTemplate(event.fragment); 49 | const resp = { 50 | requestId, 51 | status, 52 | fragment, 53 | }; 54 | 55 | callback(null, resp); 56 | }; 57 | 58 | const getBodyType = props => 59 | Object.getOwnPropertyNames(props).filter(item => 60 | ['Body', 'Base64Body', 'Source'].includes(item), 61 | ); 62 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-present Matt Zabriskie 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/axios'); -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/adapters/README.md: -------------------------------------------------------------------------------- 1 | # axios // adapters 2 | 3 | The modules under `adapters/` are modules that handle dispatching a request and settling a returned `Promise` once a response is received. 4 | 5 | ## Example 6 | 7 | ```js 8 | var settle = require('./../core/settle'); 9 | 10 | module.exports = function myAdapter(config) { 11 | // At this point: 12 | // - config has been merged with defaults 13 | // - request transformers have already run 14 | // - request interceptors have already run 15 | 16 | // Make the request using config provided 17 | // Upon response settle the Promise 18 | 19 | return new Promise(function(resolve, reject) { 20 | 21 | var response = { 22 | data: responseData, 23 | status: request.status, 24 | statusText: request.statusText, 25 | headers: responseHeaders, 26 | config: config, 27 | request: request 28 | }; 29 | 30 | settle(resolve, reject, response); 31 | 32 | // From here: 33 | // - response transformers will run 34 | // - response interceptors will run 35 | }); 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/axios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./utils'); 4 | var bind = require('./helpers/bind'); 5 | var Axios = require('./core/Axios'); 6 | var mergeConfig = require('./core/mergeConfig'); 7 | var defaults = require('./defaults'); 8 | 9 | /** 10 | * Create an instance of Axios 11 | * 12 | * @param {Object} defaultConfig The default config for the instance 13 | * @return {Axios} A new instance of Axios 14 | */ 15 | function createInstance(defaultConfig) { 16 | var context = new Axios(defaultConfig); 17 | var instance = bind(Axios.prototype.request, context); 18 | 19 | // Copy axios.prototype to instance 20 | utils.extend(instance, Axios.prototype, context); 21 | 22 | // Copy context to instance 23 | utils.extend(instance, context); 24 | 25 | return instance; 26 | } 27 | 28 | // Create the default instance to be exported 29 | var axios = createInstance(defaults); 30 | 31 | // Expose Axios class to allow class inheritance 32 | axios.Axios = Axios; 33 | 34 | // Factory for creating new instances 35 | axios.create = function create(instanceConfig) { 36 | return createInstance(mergeConfig(axios.defaults, instanceConfig)); 37 | }; 38 | 39 | // Expose Cancel & CancelToken 40 | axios.Cancel = require('./cancel/Cancel'); 41 | axios.CancelToken = require('./cancel/CancelToken'); 42 | axios.isCancel = require('./cancel/isCancel'); 43 | 44 | // Expose all/spread 45 | axios.all = function all(promises) { 46 | return Promise.all(promises); 47 | }; 48 | axios.spread = require('./helpers/spread'); 49 | 50 | module.exports = axios; 51 | 52 | // Allow use of default import syntax in TypeScript 53 | module.exports.default = axios; 54 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/cancel/Cancel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * A `Cancel` is an object that is thrown when an operation is canceled. 5 | * 6 | * @class 7 | * @param {string=} message The message. 8 | */ 9 | function Cancel(message) { 10 | this.message = message; 11 | } 12 | 13 | Cancel.prototype.toString = function toString() { 14 | return 'Cancel' + (this.message ? ': ' + this.message : ''); 15 | }; 16 | 17 | Cancel.prototype.__CANCEL__ = true; 18 | 19 | module.exports = Cancel; 20 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/cancel/CancelToken.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Cancel = require('./Cancel'); 4 | 5 | /** 6 | * A `CancelToken` is an object that can be used to request cancellation of an operation. 7 | * 8 | * @class 9 | * @param {Function} executor The executor function. 10 | */ 11 | function CancelToken(executor) { 12 | if (typeof executor !== 'function') { 13 | throw new TypeError('executor must be a function.'); 14 | } 15 | 16 | var resolvePromise; 17 | this.promise = new Promise(function promiseExecutor(resolve) { 18 | resolvePromise = resolve; 19 | }); 20 | 21 | var token = this; 22 | executor(function cancel(message) { 23 | if (token.reason) { 24 | // Cancellation has already been requested 25 | return; 26 | } 27 | 28 | token.reason = new Cancel(message); 29 | resolvePromise(token.reason); 30 | }); 31 | } 32 | 33 | /** 34 | * Throws a `Cancel` if cancellation has been requested. 35 | */ 36 | CancelToken.prototype.throwIfRequested = function throwIfRequested() { 37 | if (this.reason) { 38 | throw this.reason; 39 | } 40 | }; 41 | 42 | /** 43 | * Returns an object that contains a new `CancelToken` and a function that, when called, 44 | * cancels the `CancelToken`. 45 | */ 46 | CancelToken.source = function source() { 47 | var cancel; 48 | var token = new CancelToken(function executor(c) { 49 | cancel = c; 50 | }); 51 | return { 52 | token: token, 53 | cancel: cancel 54 | }; 55 | }; 56 | 57 | module.exports = CancelToken; 58 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/cancel/isCancel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function isCancel(value) { 4 | return !!(value && value.__CANCEL__); 5 | }; 6 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/core/InterceptorManager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./../utils'); 4 | 5 | function InterceptorManager() { 6 | this.handlers = []; 7 | } 8 | 9 | /** 10 | * Add a new interceptor to the stack 11 | * 12 | * @param {Function} fulfilled The function to handle `then` for a `Promise` 13 | * @param {Function} rejected The function to handle `reject` for a `Promise` 14 | * 15 | * @return {Number} An ID used to remove interceptor later 16 | */ 17 | InterceptorManager.prototype.use = function use(fulfilled, rejected) { 18 | this.handlers.push({ 19 | fulfilled: fulfilled, 20 | rejected: rejected 21 | }); 22 | return this.handlers.length - 1; 23 | }; 24 | 25 | /** 26 | * Remove an interceptor from the stack 27 | * 28 | * @param {Number} id The ID that was returned by `use` 29 | */ 30 | InterceptorManager.prototype.eject = function eject(id) { 31 | if (this.handlers[id]) { 32 | this.handlers[id] = null; 33 | } 34 | }; 35 | 36 | /** 37 | * Iterate over all the registered interceptors 38 | * 39 | * This method is particularly useful for skipping over any 40 | * interceptors that may have become `null` calling `eject`. 41 | * 42 | * @param {Function} fn The function to call for each interceptor 43 | */ 44 | InterceptorManager.prototype.forEach = function forEach(fn) { 45 | utils.forEach(this.handlers, function forEachHandler(h) { 46 | if (h !== null) { 47 | fn(h); 48 | } 49 | }); 50 | }; 51 | 52 | module.exports = InterceptorManager; 53 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/core/README.md: -------------------------------------------------------------------------------- 1 | # axios // core 2 | 3 | The modules found in `core/` should be modules that are specific to the domain logic of axios. These modules would most likely not make sense to be consumed outside of the axios module, as their logic is too specific. Some examples of core modules are: 4 | 5 | - Dispatching requests 6 | - Managing interceptors 7 | - Handling config 8 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/core/createError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var enhanceError = require('./enhanceError'); 4 | 5 | /** 6 | * Create an Error with the specified message, config, error code, request and response. 7 | * 8 | * @param {string} message The error message. 9 | * @param {Object} config The config. 10 | * @param {string} [code] The error code (for example, 'ECONNABORTED'). 11 | * @param {Object} [request] The request. 12 | * @param {Object} [response] The response. 13 | * @returns {Error} The created error. 14 | */ 15 | module.exports = function createError(message, config, code, request, response) { 16 | var error = new Error(message); 17 | return enhanceError(error, config, code, request, response); 18 | }; 19 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/core/enhanceError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Update an Error with the specified config, error code, and response. 5 | * 6 | * @param {Error} error The error to update. 7 | * @param {Object} config The config. 8 | * @param {string} [code] The error code (for example, 'ECONNABORTED'). 9 | * @param {Object} [request] The request. 10 | * @param {Object} [response] The response. 11 | * @returns {Error} The error. 12 | */ 13 | module.exports = function enhanceError(error, config, code, request, response) { 14 | error.config = config; 15 | if (code) { 16 | error.code = code; 17 | } 18 | 19 | error.request = request; 20 | error.response = response; 21 | error.isAxiosError = true; 22 | 23 | error.toJSON = function() { 24 | return { 25 | // Standard 26 | message: this.message, 27 | name: this.name, 28 | // Microsoft 29 | description: this.description, 30 | number: this.number, 31 | // Mozilla 32 | fileName: this.fileName, 33 | lineNumber: this.lineNumber, 34 | columnNumber: this.columnNumber, 35 | stack: this.stack, 36 | // Axios 37 | config: this.config, 38 | code: this.code 39 | }; 40 | }; 41 | return error; 42 | }; 43 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/core/mergeConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | /** 6 | * Config-specific merge-function which creates a new config-object 7 | * by merging two configuration objects together. 8 | * 9 | * @param {Object} config1 10 | * @param {Object} config2 11 | * @returns {Object} New object resulting from merging config2 to config1 12 | */ 13 | module.exports = function mergeConfig(config1, config2) { 14 | // eslint-disable-next-line no-param-reassign 15 | config2 = config2 || {}; 16 | var config = {}; 17 | 18 | utils.forEach(['url', 'method', 'params', 'data'], function valueFromConfig2(prop) { 19 | if (typeof config2[prop] !== 'undefined') { 20 | config[prop] = config2[prop]; 21 | } 22 | }); 23 | 24 | utils.forEach(['headers', 'auth', 'proxy'], function mergeDeepProperties(prop) { 25 | if (utils.isObject(config2[prop])) { 26 | config[prop] = utils.deepMerge(config1[prop], config2[prop]); 27 | } else if (typeof config2[prop] !== 'undefined') { 28 | config[prop] = config2[prop]; 29 | } else if (utils.isObject(config1[prop])) { 30 | config[prop] = utils.deepMerge(config1[prop]); 31 | } else if (typeof config1[prop] !== 'undefined') { 32 | config[prop] = config1[prop]; 33 | } 34 | }); 35 | 36 | utils.forEach([ 37 | 'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer', 38 | 'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName', 39 | 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'maxContentLength', 40 | 'validateStatus', 'maxRedirects', 'httpAgent', 'httpsAgent', 'cancelToken', 41 | 'socketPath' 42 | ], function defaultToConfig2(prop) { 43 | if (typeof config2[prop] !== 'undefined') { 44 | config[prop] = config2[prop]; 45 | } else if (typeof config1[prop] !== 'undefined') { 46 | config[prop] = config1[prop]; 47 | } 48 | }); 49 | 50 | return config; 51 | }; 52 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/core/settle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createError = require('./createError'); 4 | 5 | /** 6 | * Resolve or reject a Promise based on response status. 7 | * 8 | * @param {Function} resolve A function that resolves the promise. 9 | * @param {Function} reject A function that rejects the promise. 10 | * @param {object} response The response. 11 | */ 12 | module.exports = function settle(resolve, reject, response) { 13 | var validateStatus = response.config.validateStatus; 14 | if (!validateStatus || validateStatus(response.status)) { 15 | resolve(response); 16 | } else { 17 | reject(createError( 18 | 'Request failed with status code ' + response.status, 19 | response.config, 20 | null, 21 | response.request, 22 | response 23 | )); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/core/transformData.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./../utils'); 4 | 5 | /** 6 | * Transform the data for a request or a response 7 | * 8 | * @param {Object|String} data The data to be transformed 9 | * @param {Array} headers The headers for the request or response 10 | * @param {Array|Function} fns A single function or Array of functions 11 | * @returns {*} The resulting transformed data 12 | */ 13 | module.exports = function transformData(data, headers, fns) { 14 | /*eslint no-param-reassign:0*/ 15 | utils.forEach(fns, function transform(fn) { 16 | data = fn(data, headers); 17 | }); 18 | 19 | return data; 20 | }; 21 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/README.md: -------------------------------------------------------------------------------- 1 | # axios // helpers 2 | 3 | The modules found in `helpers/` should be generic modules that are _not_ specific to the domain logic of axios. These modules could theoretically be published to npm on their own and consumed by other modules or apps. Some examples of generic modules are things like: 4 | 5 | - Browser polyfills 6 | - Managing cookies 7 | - Parsing HTTP headers 8 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/bind.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function bind(fn, thisArg) { 4 | return function wrap() { 5 | var args = new Array(arguments.length); 6 | for (var i = 0; i < args.length; i++) { 7 | args[i] = arguments[i]; 8 | } 9 | return fn.apply(thisArg, args); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/buildURL.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./../utils'); 4 | 5 | function encode(val) { 6 | return encodeURIComponent(val). 7 | replace(/%40/gi, '@'). 8 | replace(/%3A/gi, ':'). 9 | replace(/%24/g, '$'). 10 | replace(/%2C/gi, ','). 11 | replace(/%20/g, '+'). 12 | replace(/%5B/gi, '['). 13 | replace(/%5D/gi, ']'); 14 | } 15 | 16 | /** 17 | * Build a URL by appending params to the end 18 | * 19 | * @param {string} url The base of the url (e.g., http://www.google.com) 20 | * @param {object} [params] The params to be appended 21 | * @returns {string} The formatted url 22 | */ 23 | module.exports = function buildURL(url, params, paramsSerializer) { 24 | /*eslint no-param-reassign:0*/ 25 | if (!params) { 26 | return url; 27 | } 28 | 29 | var serializedParams; 30 | if (paramsSerializer) { 31 | serializedParams = paramsSerializer(params); 32 | } else if (utils.isURLSearchParams(params)) { 33 | serializedParams = params.toString(); 34 | } else { 35 | var parts = []; 36 | 37 | utils.forEach(params, function serialize(val, key) { 38 | if (val === null || typeof val === 'undefined') { 39 | return; 40 | } 41 | 42 | if (utils.isArray(val)) { 43 | key = key + '[]'; 44 | } else { 45 | val = [val]; 46 | } 47 | 48 | utils.forEach(val, function parseValue(v) { 49 | if (utils.isDate(v)) { 50 | v = v.toISOString(); 51 | } else if (utils.isObject(v)) { 52 | v = JSON.stringify(v); 53 | } 54 | parts.push(encode(key) + '=' + encode(v)); 55 | }); 56 | }); 57 | 58 | serializedParams = parts.join('&'); 59 | } 60 | 61 | if (serializedParams) { 62 | var hashmarkIndex = url.indexOf('#'); 63 | if (hashmarkIndex !== -1) { 64 | url = url.slice(0, hashmarkIndex); 65 | } 66 | 67 | url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; 68 | } 69 | 70 | return url; 71 | }; 72 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/combineURLs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Creates a new URL by combining the specified URLs 5 | * 6 | * @param {string} baseURL The base URL 7 | * @param {string} relativeURL The relative URL 8 | * @returns {string} The combined URL 9 | */ 10 | module.exports = function combineURLs(baseURL, relativeURL) { 11 | return relativeURL 12 | ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') 13 | : baseURL; 14 | }; 15 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/cookies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./../utils'); 4 | 5 | module.exports = ( 6 | utils.isStandardBrowserEnv() ? 7 | 8 | // Standard browser envs support document.cookie 9 | (function standardBrowserEnv() { 10 | return { 11 | write: function write(name, value, expires, path, domain, secure) { 12 | var cookie = []; 13 | cookie.push(name + '=' + encodeURIComponent(value)); 14 | 15 | if (utils.isNumber(expires)) { 16 | cookie.push('expires=' + new Date(expires).toGMTString()); 17 | } 18 | 19 | if (utils.isString(path)) { 20 | cookie.push('path=' + path); 21 | } 22 | 23 | if (utils.isString(domain)) { 24 | cookie.push('domain=' + domain); 25 | } 26 | 27 | if (secure === true) { 28 | cookie.push('secure'); 29 | } 30 | 31 | document.cookie = cookie.join('; '); 32 | }, 33 | 34 | read: function read(name) { 35 | var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); 36 | return (match ? decodeURIComponent(match[3]) : null); 37 | }, 38 | 39 | remove: function remove(name) { 40 | this.write(name, '', Date.now() - 86400000); 41 | } 42 | }; 43 | })() : 44 | 45 | // Non standard browser env (web workers, react-native) lack needed support. 46 | (function nonStandardBrowserEnv() { 47 | return { 48 | write: function write() {}, 49 | read: function read() { return null; }, 50 | remove: function remove() {} 51 | }; 52 | })() 53 | ); 54 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/deprecatedMethod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /*eslint no-console:0*/ 4 | 5 | /** 6 | * Supply a warning to the developer that a method they are using 7 | * has been deprecated. 8 | * 9 | * @param {string} method The name of the deprecated method 10 | * @param {string} [instead] The alternate method to use if applicable 11 | * @param {string} [docs] The documentation URL to get further details 12 | */ 13 | module.exports = function deprecatedMethod(method, instead, docs) { 14 | try { 15 | console.warn( 16 | 'DEPRECATED method `' + method + '`.' + 17 | (instead ? ' Use `' + instead + '` instead.' : '') + 18 | ' This method will be removed in a future release.'); 19 | 20 | if (docs) { 21 | console.warn('For more information about usage see ' + docs); 22 | } 23 | } catch (e) { /* Ignore */ } 24 | }; 25 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/isAbsoluteURL.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Determines whether the specified URL is absolute 5 | * 6 | * @param {string} url The URL to test 7 | * @returns {boolean} True if the specified URL is absolute, otherwise false 8 | */ 9 | module.exports = function isAbsoluteURL(url) { 10 | // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). 11 | // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed 12 | // by any combination of letters, digits, plus, period, or hyphen. 13 | return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); 14 | }; 15 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/normalizeHeaderName.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | module.exports = function normalizeHeaderName(headers, normalizedName) { 6 | utils.forEach(headers, function processHeader(value, name) { 7 | if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { 8 | headers[normalizedName] = value; 9 | delete headers[name]; 10 | } 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/parseHeaders.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./../utils'); 4 | 5 | // Headers whose duplicates are ignored by node 6 | // c.f. https://nodejs.org/api/http.html#http_message_headers 7 | var ignoreDuplicateOf = [ 8 | 'age', 'authorization', 'content-length', 'content-type', 'etag', 9 | 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 10 | 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 11 | 'referer', 'retry-after', 'user-agent' 12 | ]; 13 | 14 | /** 15 | * Parse headers into an object 16 | * 17 | * ``` 18 | * Date: Wed, 27 Aug 2014 08:58:49 GMT 19 | * Content-Type: application/json 20 | * Connection: keep-alive 21 | * Transfer-Encoding: chunked 22 | * ``` 23 | * 24 | * @param {String} headers Headers needing to be parsed 25 | * @returns {Object} Headers parsed into an object 26 | */ 27 | module.exports = function parseHeaders(headers) { 28 | var parsed = {}; 29 | var key; 30 | var val; 31 | var i; 32 | 33 | if (!headers) { return parsed; } 34 | 35 | utils.forEach(headers.split('\n'), function parser(line) { 36 | i = line.indexOf(':'); 37 | key = utils.trim(line.substr(0, i)).toLowerCase(); 38 | val = utils.trim(line.substr(i + 1)); 39 | 40 | if (key) { 41 | if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { 42 | return; 43 | } 44 | if (key === 'set-cookie') { 45 | parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); 46 | } else { 47 | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 48 | } 49 | } 50 | }); 51 | 52 | return parsed; 53 | }; 54 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/axios/lib/helpers/spread.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Syntactic sugar for invoking a function and expanding an array for arguments. 5 | * 6 | * Common use case would be to use `Function.prototype.apply`. 7 | * 8 | * ```js 9 | * function f(x, y, z) {} 10 | * var args = [1, 2, 3]; 11 | * f.apply(null, args); 12 | * ``` 13 | * 14 | * With `spread` this example can be re-written. 15 | * 16 | * ```js 17 | * spread(function(x, y, z) {})([1, 2, 3]); 18 | * ``` 19 | * 20 | * @param {Function} callback 21 | * @returns {Function} 22 | */ 23 | module.exports = function spread(callback) { 24 | return function wrap(arr) { 25 | return callback.apply(null, arr); 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/clone/.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /test.js 3 | /.travis.yml 4 | *.html 5 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/clone/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2011-2015 Paul Vorbach 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, OUT OF OR IN CONNECTION WITH THE 18 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/clone/clone.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/clone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clone", 3 | "description": "deep cloning of objects and arrays", 4 | "tags": [ 5 | "clone", 6 | "object", 7 | "array", 8 | "function", 9 | "date" 10 | ], 11 | "version": "2.1.2", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/pvorb/node-clone.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/pvorb/node-clone/issues" 18 | }, 19 | "main": "clone.js", 20 | "author": "Paul Vorbach (http://paul.vorba.ch/)", 21 | "contributors": [ 22 | "Blake Miner (http://www.blakeminer.com/)", 23 | "Tian You (http://blog.axqd.net/)", 24 | "George Stagas (http://stagas.com/)", 25 | "Tobiasz Cudnik (https://github.com/TobiaszCudnik)", 26 | "Pavel Lang (https://github.com/langpavel)", 27 | "Dan MacTough (http://yabfog.com/)", 28 | "w1nk (https://github.com/w1nk)", 29 | "Hugh Kennedy (http://twitter.com/hughskennedy)", 30 | "Dustin Diaz (http://dustindiaz.com)", 31 | "Ilya Shaisultanov (https://github.com/diversario)", 32 | "Nathan MacInnes (http://macinn.es/)", 33 | "Benjamin E. Coe (https://twitter.com/benjamincoe)", 34 | "Nathan Zadoks (https://github.com/nathan7)", 35 | "Róbert Oroszi (https://github.com/oroce)", 36 | "Aurélio A. Heckert (http://softwarelivre.org/aurium)", 37 | "Guy Ellis (http://www.guyellisrocks.com/)", 38 | "fscherwi (https://fscherwi.github.io)", 39 | "rictic (https://github.com/rictic)", 40 | "Martin Jurča (https://github.com/jurca)", 41 | "Misery Lee (https://github.com/miserylee)", 42 | "Clemens Wolff (https://github.com/c-w)" 43 | ], 44 | "license": "MIT", 45 | "engines": { 46 | "node": ">=0.8" 47 | }, 48 | "dependencies": {}, 49 | "devDependencies": { 50 | "nodeunit": "~0.9.0" 51 | }, 52 | "optionalDependencies": {}, 53 | "scripts": { 54 | "test": "nodeunit test.js" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: SIAeZjKYlHK74rbcFvNHMUzjRiMpflxve 2 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "globals": { 7 | "chrome": true 8 | }, 9 | "rules": { 10 | "no-console": 0, 11 | "no-empty": [1, { "allowEmptyCatch": true }] 12 | }, 13 | "extends": "eslint:recommended" 14 | } 15 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | example 5 | *.sock 6 | dist 7 | yarn.lock 8 | coverage 9 | bower.json 10 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "4" 7 | - "6" 8 | - "8" 9 | 10 | install: 11 | - make install 12 | 13 | script: 14 | - make lint 15 | - make test 16 | 17 | matrix: 18 | include: 19 | - node_js: '8' 20 | env: BROWSER=1 21 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 TJ Holowaychuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | and associated documentation files (the 'Software'), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 15 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 18 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/Makefile: -------------------------------------------------------------------------------- 1 | # get Makefile directory name: http://stackoverflow.com/a/5982798/376773 2 | THIS_MAKEFILE_PATH:=$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) 3 | THIS_DIR:=$(shell cd $(dir $(THIS_MAKEFILE_PATH));pwd) 4 | 5 | # BIN directory 6 | BIN := $(THIS_DIR)/node_modules/.bin 7 | 8 | # Path 9 | PATH := node_modules/.bin:$(PATH) 10 | SHELL := /bin/bash 11 | 12 | # applications 13 | NODE ?= $(shell which node) 14 | YARN ?= $(shell which yarn) 15 | PKG ?= $(if $(YARN),$(YARN),$(NODE) $(shell which npm)) 16 | BROWSERIFY ?= $(NODE) $(BIN)/browserify 17 | 18 | install: node_modules 19 | 20 | browser: dist/debug.js 21 | 22 | node_modules: package.json 23 | @NODE_ENV= $(PKG) install 24 | @touch node_modules 25 | 26 | dist/debug.js: src/*.js node_modules 27 | @mkdir -p dist 28 | @$(BROWSERIFY) \ 29 | --standalone debug \ 30 | . > dist/debug.js 31 | 32 | lint: 33 | @eslint *.js src/*.js 34 | 35 | test-node: 36 | @istanbul cover node_modules/mocha/bin/_mocha -- test/**.js 37 | @cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 38 | 39 | test-browser: 40 | @$(MAKE) browser 41 | @karma start --single-run 42 | 43 | test-all: 44 | @concurrently \ 45 | "make test-node" \ 46 | "make test-browser" 47 | 48 | test: 49 | @if [ "x$(BROWSER)" = "x" ]; then \ 50 | $(MAKE) test-node; \ 51 | else \ 52 | $(MAKE) test-browser; \ 53 | fi 54 | 55 | clean: 56 | rimraf dist coverage 57 | 58 | .PHONY: browser install clean lint test test-all test-node test-browser 59 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Dec 16 2016 13:09:51 GMT+0000 (UTC) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha', 'chai', 'sinon'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'dist/debug.js', 19 | 'test/*spec.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | 'src/node.js' 26 | ], 27 | 28 | 29 | // preprocess matching files before serving them to the browser 30 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 31 | preprocessors: { 32 | }, 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress' 36 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 37 | reporters: ['progress'], 38 | 39 | 40 | // web server port 41 | port: 9876, 42 | 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_INFO, 51 | 52 | 53 | // enable / disable watching file and executing tests whenever any file changes 54 | autoWatch: true, 55 | 56 | 57 | // start these browsers 58 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 59 | browsers: ['PhantomJS'], 60 | 61 | 62 | // Continuous Integration mode 63 | // if true, Karma captures browsers, runs the tests and exits 64 | singleRun: false, 65 | 66 | // Concurrency level 67 | // how many browser should be started simultaneous 68 | concurrency: Infinity 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/node.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/node'); 2 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debug", 3 | "version": "3.1.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/visionmedia/debug.git" 7 | }, 8 | "description": "small debugging utility", 9 | "keywords": [ 10 | "debug", 11 | "log", 12 | "debugger" 13 | ], 14 | "author": "TJ Holowaychuk ", 15 | "contributors": [ 16 | "Nathan Rajlich (http://n8.io)", 17 | "Andrew Rhyne " 18 | ], 19 | "license": "MIT", 20 | "dependencies": { 21 | "ms": "2.0.0" 22 | }, 23 | "devDependencies": { 24 | "browserify": "14.4.0", 25 | "chai": "^3.5.0", 26 | "concurrently": "^3.1.0", 27 | "coveralls": "^2.11.15", 28 | "eslint": "^3.12.1", 29 | "istanbul": "^0.4.5", 30 | "karma": "^1.3.0", 31 | "karma-chai": "^0.1.0", 32 | "karma-mocha": "^1.3.0", 33 | "karma-phantomjs-launcher": "^1.0.2", 34 | "karma-sinon": "^1.0.5", 35 | "mocha": "^3.2.0", 36 | "mocha-lcov-reporter": "^1.2.0", 37 | "rimraf": "^2.5.4", 38 | "sinon": "^1.17.6", 39 | "sinon-chai": "^2.8.0" 40 | }, 41 | "main": "./src/index.js", 42 | "browser": "./src/browser.js" 43 | } 44 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/debug/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Detect Electron renderer process, which is node, but we should 3 | * treat as a browser. 4 | */ 5 | 6 | if (typeof process === 'undefined' || process.type === 'renderer') { 7 | module.exports = require('./browser.js'); 8 | } else { 9 | module.exports = require('./node.js'); 10 | } 11 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/follow-redirects/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014–present Olivier Lalonde , James Talmage , Ruben Verborgh 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 18 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/follow-redirects/http.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./").http; 2 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/follow-redirects/https.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./").https; 2 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/follow-redirects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "follow-redirects", 3 | "version": "1.5.10", 4 | "description": "HTTP and HTTPS modules that follow redirects.", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=4.0" 8 | }, 9 | "scripts": { 10 | "test": "npm run lint && npm run mocha", 11 | "lint": "eslint *.js test", 12 | "mocha": "nyc mocha" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git@github.com:follow-redirects/follow-redirects.git" 17 | }, 18 | "homepage": "https://github.com/follow-redirects/follow-redirects", 19 | "bugs": { 20 | "url": "https://github.com/follow-redirects/follow-redirects/issues" 21 | }, 22 | "keywords": [ 23 | "http", 24 | "https", 25 | "url", 26 | "redirect", 27 | "client", 28 | "location", 29 | "utility" 30 | ], 31 | "author": "Ruben Verborgh (https://ruben.verborgh.org/)", 32 | "contributors": [ 33 | "Olivier Lalonde (http://www.syskall.com)", 34 | "James Talmage " 35 | ], 36 | "files": [ 37 | "index.js", 38 | "create.js", 39 | "http.js", 40 | "https.js" 41 | ], 42 | "dependencies": { 43 | "debug": "=3.1.0" 44 | }, 45 | "devDependencies": { 46 | "concat-stream": "^1.6.0", 47 | "coveralls": "^3.0.2", 48 | "eslint": "^4.19.1", 49 | "express": "^4.16.2", 50 | "mocha": "^5.0.0", 51 | "nyc": "^11.8.0" 52 | }, 53 | "license": "MIT", 54 | "nyc": { 55 | "reporter": [ 56 | "lcov", 57 | "text" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/is-buffer/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/is-buffer/README.md: -------------------------------------------------------------------------------- 1 | # is-buffer [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] 2 | 3 | [travis-image]: https://img.shields.io/travis/feross/is-buffer/master.svg 4 | [travis-url]: https://travis-ci.org/feross/is-buffer 5 | [npm-image]: https://img.shields.io/npm/v/is-buffer.svg 6 | [npm-url]: https://npmjs.org/package/is-buffer 7 | [downloads-image]: https://img.shields.io/npm/dm/is-buffer.svg 8 | [downloads-url]: https://npmjs.org/package/is-buffer 9 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 10 | [standard-url]: https://standardjs.com 11 | 12 | #### Determine if an object is a [`Buffer`](http://nodejs.org/api/buffer.html) (including the [browserify Buffer](https://github.com/feross/buffer)) 13 | 14 | [![saucelabs][saucelabs-image]][saucelabs-url] 15 | 16 | [saucelabs-image]: https://saucelabs.com/browser-matrix/is-buffer.svg 17 | [saucelabs-url]: https://saucelabs.com/u/is-buffer 18 | 19 | ## Why not use `Buffer.isBuffer`? 20 | 21 | This module lets you check if an object is a `Buffer` without using `Buffer.isBuffer` (which includes the whole [buffer](https://github.com/feross/buffer) module in [browserify](http://browserify.org/)). 22 | 23 | It's future-proof and works in node too! 24 | 25 | ## install 26 | 27 | ```bash 28 | npm install is-buffer 29 | ``` 30 | 31 | [Get supported is-buffer with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-is-buffer?utm_source=npm-is-buffer&utm_medium=referral&utm_campaign=readme) 32 | 33 | ## usage 34 | 35 | ```js 36 | var isBuffer = require('is-buffer') 37 | 38 | isBuffer(new Buffer(4)) // true 39 | isBuffer(Buffer.alloc(4)) //true 40 | 41 | isBuffer(undefined) // false 42 | isBuffer(null) // false 43 | isBuffer('') // false 44 | isBuffer(true) // false 45 | isBuffer(false) // false 46 | isBuffer(0) // false 47 | isBuffer(1) // false 48 | isBuffer(1.0) // false 49 | isBuffer('string') // false 50 | isBuffer({}) // false 51 | isBuffer(function foo () {}) // false 52 | ``` 53 | 54 | ## license 55 | 56 | MIT. Copyright (C) [Feross Aboukhadijeh](http://feross.org). 57 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/is-buffer/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Determine if an object is a Buffer 3 | * 4 | * @author Feross Aboukhadijeh 5 | * @license MIT 6 | */ 7 | 8 | module.exports = function isBuffer (obj) { 9 | return obj != null && obj.constructor != null && 10 | typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) 11 | } 12 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/is-buffer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "is-buffer", 3 | "description": "Determine if an object is a Buffer", 4 | "version": "2.0.3", 5 | "author": { 6 | "name": "Feross Aboukhadijeh", 7 | "email": "feross@feross.org", 8 | "url": "https://feross.org" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/feross/is-buffer/issues" 12 | }, 13 | "dependencies": {}, 14 | "devDependencies": { 15 | "airtap": "0.0.7", 16 | "standard": "*", 17 | "tape": "^4.0.0" 18 | }, 19 | "engines": { 20 | "node": ">=4" 21 | }, 22 | "keywords": [ 23 | "arraybuffer", 24 | "browser", 25 | "browser buffer", 26 | "browserify", 27 | "buffer", 28 | "buffers", 29 | "core buffer", 30 | "dataview", 31 | "float32array", 32 | "float64array", 33 | "int16array", 34 | "int32array", 35 | "type", 36 | "typed array", 37 | "uint32array" 38 | ], 39 | "license": "MIT", 40 | "main": "index.js", 41 | "repository": { 42 | "type": "git", 43 | "url": "git://github.com/feross/is-buffer.git" 44 | }, 45 | "scripts": { 46 | "test": "standard && npm run test-node && npm run test-browser", 47 | "test-browser": "airtap -- test/*.js", 48 | "test-browser-local": "airtap --local -- test/*.js", 49 | "test-node": "tape test/*.js" 50 | }, 51 | "testling": { 52 | "files": "test/*.js" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/ms/license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Zeit, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/ms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ms", 3 | "version": "2.0.0", 4 | "description": "Tiny milisecond conversion utility", 5 | "repository": "zeit/ms", 6 | "main": "./index", 7 | "files": [ 8 | "index.js" 9 | ], 10 | "scripts": { 11 | "precommit": "lint-staged", 12 | "lint": "eslint lib/* bin/*", 13 | "test": "mocha tests.js" 14 | }, 15 | "eslintConfig": { 16 | "extends": "eslint:recommended", 17 | "env": { 18 | "node": true, 19 | "es6": true 20 | } 21 | }, 22 | "lint-staged": { 23 | "*.js": [ 24 | "npm run lint", 25 | "prettier --single-quote --write", 26 | "git add" 27 | ] 28 | }, 29 | "license": "MIT", 30 | "devDependencies": { 31 | "eslint": "3.19.0", 32 | "expect.js": "0.3.1", 33 | "husky": "0.13.3", 34 | "lint-staged": "3.4.1", 35 | "mocha": "3.4.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/node_modules/ms/readme.md: -------------------------------------------------------------------------------- 1 | # ms 2 | 3 | [![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms) 4 | [![Slack Channel](http://zeit-slackin.now.sh/badge.svg)](https://zeit.chat/) 5 | 6 | Use this package to easily convert various time formats to milliseconds. 7 | 8 | ## Examples 9 | 10 | ```js 11 | ms('2 days') // 172800000 12 | ms('1d') // 86400000 13 | ms('10h') // 36000000 14 | ms('2.5 hrs') // 9000000 15 | ms('2h') // 7200000 16 | ms('1m') // 60000 17 | ms('5s') // 5000 18 | ms('1y') // 31557600000 19 | ms('100') // 100 20 | ``` 21 | 22 | ### Convert from milliseconds 23 | 24 | ```js 25 | ms(60000) // "1m" 26 | ms(2 * 60000) // "2m" 27 | ms(ms('10 hours')) // "10h" 28 | ``` 29 | 30 | ### Time format written-out 31 | 32 | ```js 33 | ms(60000, { long: true }) // "1 minute" 34 | ms(2 * 60000, { long: true }) // "2 minutes" 35 | ms(ms('10 hours'), { long: true }) // "10 hours" 36 | ``` 37 | 38 | ## Features 39 | 40 | - Works both in [node](https://nodejs.org) and in the browser. 41 | - If a number is supplied to `ms`, a string with a unit is returned. 42 | - If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`). 43 | - If you pass a string with a number and a valid unit, the number of equivalent ms is returned. 44 | 45 | ## Caught a bug? 46 | 47 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device 48 | 2. Link the package to the global module directory: `npm link` 49 | 3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, node will now use your clone of ms! 50 | 51 | As always, you can run the tests using: `npm test` 52 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "axios": "^0.19.0", 4 | "clone": "^2.1.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/src/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | axios@^0.19.0: 6 | version "0.19.0" 7 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" 8 | integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== 9 | dependencies: 10 | follow-redirects "1.5.10" 11 | is-buffer "^2.0.2" 12 | 13 | clone@^2.1.2: 14 | version "2.1.2" 15 | resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" 16 | integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= 17 | 18 | debug@=3.1.0: 19 | version "3.1.0" 20 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 21 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 22 | dependencies: 23 | ms "2.0.0" 24 | 25 | follow-redirects@1.5.10: 26 | version "1.5.10" 27 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" 28 | integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== 29 | dependencies: 30 | debug "=3.1.0" 31 | 32 | is-buffer@^2.0.2: 33 | version "2.0.3" 34 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" 35 | integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== 36 | 37 | ms@2.0.0: 38 | version "2.0.0" 39 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 40 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 41 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | ResourceFunction: 5 | Type: AWS::Serverless::Function 6 | Properties: 7 | Runtime: nodejs12.x 8 | CodeUri: src 9 | Handler: resource.handler 10 | Policies: AmazonS3FullAccess 11 | 12 | MacroFunction: 13 | Type: AWS::Serverless::Function 14 | Properties: 15 | Runtime: nodejs12.x 16 | CodeUri: src 17 | Handler: index.handler 18 | -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/tests/event-macro.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east-1", 3 | "accountId": "104477223281", 4 | "fragment": { 5 | "Resources": { 6 | "Bucket": { 7 | "Type": "AWS::S3::Bucket" 8 | }, 9 | "Object1": { 10 | "Type": "AWS::S3::Object", 11 | "Properties": { 12 | "Target": { 13 | "Bucket": { 14 | "Ref": "Bucket" 15 | }, 16 | "Key": "README.md", 17 | "ContentType": "text/markdown" 18 | }, 19 | "Body": "# My text file\n\nThis is my text file;\nthere are many like it,\nbut this one is mine.\n" 20 | } 21 | }, 22 | "Object2": { 23 | "Type": "AWS::S3::Object", 24 | "Properties": { 25 | "Target": { 26 | "Bucket": { 27 | "Ref": "Bucket" 28 | }, 29 | "Key": "1-pixel.gif", 30 | "ACL": "public-read", 31 | "ContentType": "image/png" 32 | }, 33 | "Base64Body": "R0lGODdhAQABAIABAP///0qIbCwAAAAAAQABAAACAkQBADs=" 34 | } 35 | }, 36 | "Object3": { 37 | "Type": "AWS::S3::Object", 38 | "Properties": { 39 | "Source": { 40 | "Bucket": { 41 | "Fn::GetAtt": "Object1.Bucket" 42 | }, 43 | "Key": { 44 | "Fn::GetAtt": "Object1.Key" 45 | } 46 | }, 47 | "Target": { 48 | "Bucket": { 49 | "Ref": "Bucket" 50 | }, 51 | "Key": "README-copy.md", 52 | "ACL": "public-read" 53 | } 54 | } 55 | } 56 | } 57 | }, 58 | "transformId": "104477223281::S3Objects", 59 | "params": {}, 60 | "requestId": "8f5e08f3-6431-4bb6-970f-dd625a1efa12", 61 | "templateParameterValues": {} 62 | } -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/tests/event-resource-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "RequestType": "Create", 3 | "ServiceToken": "arn:aws:lambda:us-east-1:104477223281:function:macro-s3objects-ResourceFunction-1KHRIK0HPKT2M", 4 | "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A104477223281%3Astack/s3objects3/2daf9010-946c-11e9-abd3-1235152b041e%7CObject1%7Cd15252b8-7d81-4212-9e56-13116bcc9e44?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190621T213346Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7200&X-Amz-Credential=AKIA6L7Q4OWTSAJFC3R7%2F20190621%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=b9660d9c338129072b60e4b788c67da9ac42f0d8050056b41da48301f77e6557", 5 | "StackId": "arn:aws:cloudformation:us-east-1:104477223281:stack/s3objects3/2daf9010-946c-11e9-abd3-1235152b041e", 6 | "RequestId": "d15252b8-7d81-4212-9e56-13116bcc9e44", 7 | "LogicalResourceId": "Object1", 8 | "ResourceType": "Custom::S3Object", 9 | "ResourceProperties": { 10 | "ServiceToken": "arn:aws:lambda:us-east-1:104477223281:function:macro-s3objects-ResourceFunction-1KHRIK0HPKT2M", 11 | "Target": { 12 | "ContentType": "text/markdown", 13 | "Bucket": "s3objects3-bucket-bq97nmcgjagh", 14 | "ACL": "private", 15 | "Key": "README.md" 16 | }, 17 | "Body": "# My text file\n\nThis is my text file;\nthere are many like it,\nbut this one is mine.\n" 18 | } 19 | } -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/tests/event-resource-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "RequestType": "Delete", 3 | "ServiceToken": "arn:aws:lambda:us-east-1:104477223281:function:macro-s3objects-ResourceFunction-1KHRIK0HPKT2M", 4 | "ResponseURL": "https://cloudformation-custom-resource-response-useast1.s3.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-east-1%3A104477223281%3Astack/s3objects2/49537040-946a-11e9-9057-0af1b2bb447e%7CObject2%7C7406ab27-5179-4be2-9951-76ec47084801?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20190621T213325Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7200&X-Amz-Credential=AKIA6L7Q4OWTSAJFC3R7%2F20190621%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=85c4635904aee08d804d8fe2a8acb92a64d5b54780e28d14a37eef1011b638ec", 5 | "StackId": "arn:aws:cloudformation:us-east-1:104477223281:stack/s3objects2/49537040-946a-11e9-9057-0af1b2bb447e", 6 | "RequestId": "7406ab27-5179-4be2-9951-76ec47084801", 7 | "LogicalResourceId": "Object2", 8 | "PhysicalResourceId": "s3objects2-Object2-QDFNQ51R1KY0", 9 | "ResourceType": "Custom::S3Object", 10 | "ResourceProperties": { 11 | "ServiceToken": "arn:aws:lambda:us-east-1:104477223281:function:macro-s3objects-ResourceFunction-1KHRIK0HPKT2M", 12 | "Target": { 13 | "ContentType": "image/png", 14 | "Bucket": "s3objects2-bucket-14eu2k3d8bj56", 15 | "ACL": "public-read", 16 | "Key": "1-pixel.gif" 17 | }, 18 | "Base64Body": "R0lGODdhAQABAIABAP///0qIbCwAAAAAAQABAAACAkQBADs=" 19 | } 20 | } -------------------------------------------------------------------------------- /Ch03 - Macros & Transforms/L04 - S3Objects/usage.yaml: -------------------------------------------------------------------------------- 1 | Transform: S3Objects 2 | 3 | Resources: 4 | Bucket: 5 | Type: AWS::S3::Bucket 6 | 7 | Object1: 8 | Type: AWS::S3::Object 9 | Properties: 10 | Target: 11 | Bucket: !Ref Bucket 12 | Key: README.md 13 | ContentType: text/markdown 14 | Body: | 15 | # CHANGED TITLE 16 | 17 | This is my text file; 18 | there are many like it, 19 | but this one is mine. 20 | 21 | Object2: 22 | Type: AWS::S3::Object 23 | Properties: 24 | Target: 25 | Bucket: !Ref Bucket 26 | Key: 1-pixel.gif 27 | ACL: public-read 28 | ContentType: image/png 29 | Base64Body: R0lGODdhAQABAIABAP///0qIbCwAAAAAAQABAAACAkQBADs= 30 | 31 | Object3: 32 | Type: AWS::S3::Object 33 | Properties: 34 | Source: 35 | Bucket: !GetAtt Object1.Bucket 36 | Key: !GetAtt Object1.Key 37 | Target: 38 | Bucket: !Ref Bucket 39 | Key: README-copy.md 40 | ACL: public-read 41 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/FAIL/asg.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | LCNameParam: 3 | Type: String 4 | Description: Launch Configuration Name 5 | Resources: 6 | ASG: 7 | Type: AWS::AutoScaling::AutoScalingGroup 8 | Properties: 9 | AvailabilityZones: 10 | - !Select 11 | - 0 12 | - Fn::GetAZs: !Ref AWS::Region 13 | DesiredCapacity: "1" 14 | LaunchConfigurationName: !Ref LCNameParam 15 | MaxSize: "1" 16 | MinSize: "1" 17 | 18 | # Added this Wait condition to cause FAIL and ROLLBACK 19 | WaitCondition: 20 | Type: AWS::CloudFormation::WaitCondition 21 | DependsOn: ASG 22 | CreationPolicy: 23 | ResourceSignal: 24 | Count: 1 25 | Timeout: PT1M 26 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/FAIL/compute.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | BucketURLParam: 3 | Type: String 4 | Description: Course bucket path 5 | Resources: 6 | LCStack: 7 | Type: AWS::CloudFormation::Stack 8 | Properties: 9 | TemplateURL: !Sub ${BucketURLParam}/lc.yaml 10 | 11 | ASGStack: 12 | Type: AWS::CloudFormation::Stack 13 | Properties: 14 | Parameters: 15 | LCNameParam: !GetAtt LCStack.Outputs.LCName 16 | TemplateURL: !Sub ${BucketURLParam}/asg.yaml 17 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/FAIL/lc.yaml: -------------------------------------------------------------------------------- 1 | Mappings: 2 | RegionArch2AMI: 3 | ap-northeast-1: 4 | HVM64: ami-0f63c02167ca94956 5 | ap-northeast-2: 6 | HVM64: ami-069c1055fab7b32e5 7 | ap-northeast-3: 8 | HVM64: ami-084e4de48dc0834e8 9 | ap-south-1: 10 | HVM64: ami-092e1fd695ed0e93c 11 | ap-southeast-1: 12 | HVM64: ami-0393b4f16793f7f12 13 | ap-southeast-2: 14 | HVM64: ami-0deda1f8bbb52aac7 15 | ca-central-1: 16 | HVM64: ami-008c2d1a8ad81bc10 17 | eu-central-1: 18 | HVM64: ami-0cf8fa6a01bb07363 19 | eu-north-1: 20 | HVM64: ami-73d65f0d 21 | eu-west-1: 22 | HVM64: ami-0286372f78291e588 23 | eu-west-2: 24 | HVM64: ami-04b69fa254407c8ee 25 | eu-west-3: 26 | HVM64: ami-0e82c2554d8492095 27 | sa-east-1: 28 | HVM64: ami-05a01ab93a59b45de 29 | us-east-1: 30 | HVM64: ami-012fd5eb46f56731f 31 | us-east-2: 32 | HVM64: ami-06e2e609dbf389341 33 | us-west-1: 34 | HVM64: ami-0bf3d63a666665438 35 | us-west-2: 36 | HVM64: ami-082fd9a18128c9e8c 37 | cn-north-1: 38 | HVM64: ami-05596fb52c3802012 39 | cn-northwest-1: 40 | HVM64: ami-03f7db8b059795736 41 | Resources: 42 | LC: 43 | Type: AWS::AutoScaling::LaunchConfiguration 44 | Properties: 45 | ImageId: !FindInMap [RegionArch2AMI, !Ref "AWS::Region", HVM64] 46 | InstanceType: t2.large # <- Changed from t2.micro 47 | 48 | Outputs: 49 | LCName: 50 | Description: Launch Configuration Name 51 | Value: !Ref LC 52 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/FAIL/storage.yaml: -------------------------------------------------------------------------------- 1 | Resources: 2 | Bucket: 3 | Type: AWS::S3::Bucket 4 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/RECOVER/asg.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | LCNameParam: 3 | Type: String 4 | Description: Launch Configuration Name 5 | Resources: 6 | ASG: 7 | Type: AWS::AutoScaling::AutoScalingGroup 8 | Properties: 9 | AvailabilityZones: 10 | - !Select 11 | - 0 12 | - Fn::GetAZs: !Ref AWS::Region 13 | DesiredCapacity: "1" 14 | LaunchConfigurationName: !Ref LCNameParam 15 | MaxSize: "1" 16 | MinSize: "1" 17 | # Wait Condition Removed 18 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/RECOVER/compute.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | BucketURLParam: 3 | Type: String 4 | Description: Course bucket path 5 | Resources: 6 | LCStack: 7 | Type: AWS::CloudFormation::Stack 8 | Properties: 9 | TemplateURL: !Sub ${BucketURLParam}/lc.yaml 10 | 11 | ASGStack: 12 | Type: AWS::CloudFormation::Stack 13 | Properties: 14 | Parameters: 15 | LCNameParam: !GetAtt LCStack.Outputs.LCName 16 | TemplateURL: !Sub ${BucketURLParam}/asg.yaml 17 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/RECOVER/lc.yaml: -------------------------------------------------------------------------------- 1 | Mappings: 2 | RegionArch2AMI: 3 | ap-northeast-1: 4 | HVM64: ami-0f63c02167ca94956 5 | ap-northeast-2: 6 | HVM64: ami-069c1055fab7b32e5 7 | ap-northeast-3: 8 | HVM64: ami-084e4de48dc0834e8 9 | ap-south-1: 10 | HVM64: ami-092e1fd695ed0e93c 11 | ap-southeast-1: 12 | HVM64: ami-0393b4f16793f7f12 13 | ap-southeast-2: 14 | HVM64: ami-0deda1f8bbb52aac7 15 | ca-central-1: 16 | HVM64: ami-008c2d1a8ad81bc10 17 | eu-central-1: 18 | HVM64: ami-0cf8fa6a01bb07363 19 | eu-north-1: 20 | HVM64: ami-73d65f0d 21 | eu-west-1: 22 | HVM64: ami-0286372f78291e588 23 | eu-west-2: 24 | HVM64: ami-04b69fa254407c8ee 25 | eu-west-3: 26 | HVM64: ami-0e82c2554d8492095 27 | sa-east-1: 28 | HVM64: ami-05a01ab93a59b45de 29 | us-east-1: 30 | HVM64: ami-012fd5eb46f56731f 31 | us-east-2: 32 | HVM64: ami-06e2e609dbf389341 33 | us-west-1: 34 | HVM64: ami-0bf3d63a666665438 35 | us-west-2: 36 | HVM64: ami-082fd9a18128c9e8c 37 | cn-north-1: 38 | HVM64: ami-05596fb52c3802012 39 | cn-northwest-1: 40 | HVM64: ami-03f7db8b059795736 41 | Resources: 42 | LC2: # <-- Caused a NEW LC to be created under new Logicial ID 43 | Type: AWS::AutoScaling::LaunchConfiguration 44 | Properties: 45 | ImageId: !FindInMap [RegionArch2AMI, !Ref "AWS::Region", HVM64] 46 | InstanceType: t2.large # <-- Kept the original change from t2.micro 47 | 48 | Outputs: 49 | LCName: 50 | Description: Launch Configuration Name 51 | Value: !Ref LC2 # <-- Updated all (only) reference to the Logical ID 52 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/RECOVER/storage.yaml: -------------------------------------------------------------------------------- 1 | Resources: 2 | Bucket: 3 | Type: AWS::S3::Bucket 4 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/START/asg.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | LCNameParam: 3 | Type: String 4 | Description: Launch Configuration Name 5 | Resources: 6 | ASG: 7 | Type: AWS::AutoScaling::AutoScalingGroup 8 | Properties: 9 | AvailabilityZones: 10 | - !Select 11 | - 0 12 | - Fn::GetAZs: !Ref AWS::Region 13 | DesiredCapacity: "1" 14 | LaunchConfigurationName: !Ref LCNameParam 15 | MaxSize: "1" 16 | MinSize: "1" 17 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/START/compute.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | BucketURLParam: 3 | Type: String 4 | Description: Course bucket path 5 | Resources: 6 | LCStack: 7 | Type: AWS::CloudFormation::Stack 8 | Properties: 9 | TemplateURL: !Sub ${BucketURLParam}/lc.yaml 10 | 11 | ASGStack: 12 | Type: AWS::CloudFormation::Stack 13 | Properties: 14 | Parameters: 15 | LCNameParam: !GetAtt LCStack.Outputs.LCName 16 | TemplateURL: !Sub ${BucketURLParam}/asg.yaml 17 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/START/lc.yaml: -------------------------------------------------------------------------------- 1 | Mappings: 2 | RegionArch2AMI: 3 | ap-northeast-1: 4 | HVM64: ami-0f63c02167ca94956 5 | ap-northeast-2: 6 | HVM64: ami-069c1055fab7b32e5 7 | ap-northeast-3: 8 | HVM64: ami-084e4de48dc0834e8 9 | ap-south-1: 10 | HVM64: ami-092e1fd695ed0e93c 11 | ap-southeast-1: 12 | HVM64: ami-0393b4f16793f7f12 13 | ap-southeast-2: 14 | HVM64: ami-0deda1f8bbb52aac7 15 | ca-central-1: 16 | HVM64: ami-008c2d1a8ad81bc10 17 | eu-central-1: 18 | HVM64: ami-0cf8fa6a01bb07363 19 | eu-north-1: 20 | HVM64: ami-73d65f0d 21 | eu-west-1: 22 | HVM64: ami-0286372f78291e588 23 | eu-west-2: 24 | HVM64: ami-04b69fa254407c8ee 25 | eu-west-3: 26 | HVM64: ami-0e82c2554d8492095 27 | sa-east-1: 28 | HVM64: ami-05a01ab93a59b45de 29 | us-east-1: 30 | HVM64: ami-012fd5eb46f56731f 31 | us-east-2: 32 | HVM64: ami-06e2e609dbf389341 33 | us-west-1: 34 | HVM64: ami-0bf3d63a666665438 35 | us-west-2: 36 | HVM64: ami-082fd9a18128c9e8c 37 | cn-north-1: 38 | HVM64: ami-05596fb52c3802012 39 | cn-northwest-1: 40 | HVM64: ami-03f7db8b059795736 41 | Resources: 42 | LC: 43 | Type: AWS::AutoScaling::LaunchConfiguration 44 | Properties: 45 | ImageId: !FindInMap [RegionArch2AMI, !Ref "AWS::Region", HVM64] 46 | InstanceType: t2.micro 47 | Outputs: 48 | LCName: 49 | Description: Launch Configuration Name 50 | Value: !Ref LC 51 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/START/storage.yaml: -------------------------------------------------------------------------------- 1 | Resources: 2 | Bucket: 3 | Type: AWS::S3::Bucket 4 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/!-solution/root.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | CourseBucketParam: 3 | Type: String 4 | Description: Course bucket we setup in first chapter 5 | BucketPathParam: 6 | Type: String 7 | Default: /coursefiles/nestedstacks 8 | Description: Path to template files 9 | ActionParam: 10 | Type: String 11 | Description: START/FAIL/RECOVER for example 12 | AllowedValues: 13 | - START 14 | - FAIL 15 | - RECOVER 16 | Resources: 17 | ComputeStack: 18 | Type: AWS::CloudFormation::Stack 19 | Properties: 20 | Parameters: 21 | BucketURLParam: !Sub https://${CourseBucketParam}.s3.amazonaws.com${BucketPathParam}/${ActionParam} 22 | TemplateURL: !Sub https://${CourseBucketParam}.s3.amazonaws.com${BucketPathParam}/${ActionParam}/compute.yaml 23 | StorageStack: 24 | Type: AWS::CloudFormation::Stack 25 | Properties: 26 | TemplateURL: !Sub https://${CourseBucketParam}.s3.amazonaws.com${BucketPathParam}/${ActionParam}/storage.yaml 27 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/START/asg.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch04 - Best Practices/L01-L02 - Nested Stacks/START/asg.yaml -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/START/compute.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch04 - Best Practices/L01-L02 - Nested Stacks/START/compute.yaml -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/START/lc.yaml: -------------------------------------------------------------------------------- 1 | Mappings: 2 | RegionArch2AMI: 3 | ap-northeast-1: 4 | HVM64: ami-0f63c02167ca94956 5 | ap-northeast-2: 6 | HVM64: ami-069c1055fab7b32e5 7 | ap-northeast-3: 8 | HVM64: ami-084e4de48dc0834e8 9 | ap-south-1: 10 | HVM64: ami-092e1fd695ed0e93c 11 | ap-southeast-1: 12 | HVM64: ami-0393b4f16793f7f12 13 | ap-southeast-2: 14 | HVM64: ami-0deda1f8bbb52aac7 15 | ca-central-1: 16 | HVM64: ami-008c2d1a8ad81bc10 17 | eu-central-1: 18 | HVM64: ami-0cf8fa6a01bb07363 19 | eu-north-1: 20 | HVM64: ami-73d65f0d 21 | eu-west-1: 22 | HVM64: ami-0286372f78291e588 23 | eu-west-2: 24 | HVM64: ami-04b69fa254407c8ee 25 | eu-west-3: 26 | HVM64: ami-0e82c2554d8492095 27 | sa-east-1: 28 | HVM64: ami-05a01ab93a59b45de 29 | us-east-1: 30 | HVM64: ami-012fd5eb46f56731f 31 | us-east-2: 32 | HVM64: ami-06e2e609dbf389341 33 | us-west-1: 34 | HVM64: ami-0bf3d63a666665438 35 | us-west-2: 36 | HVM64: ami-082fd9a18128c9e8c 37 | cn-north-1: 38 | HVM64: ami-05596fb52c3802012 39 | cn-northwest-1: 40 | HVM64: ami-03f7db8b059795736 41 | Resources: 42 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/START/storage.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch04 - Best Practices/L01-L02 - Nested Stacks/START/storage.yaml -------------------------------------------------------------------------------- /Ch04 - Best Practices/L01-L02 - Nested Stacks/root.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch04 - Best Practices/L01-L02 - Nested Stacks/root.yaml -------------------------------------------------------------------------------- /Ch04 - Best Practices/L03 - Working w Secrets/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Create SSM Param with encrypted JSON file as value 4 | ```shell 5 | STACKNAME=acg-secrets 6 | REGION=us-east-1 7 | PROFILE=cloudguru 8 | aws ssm put-parameter \ 9 | --name /acg/master-cfn/secrets \ 10 | --type SecureString \ 11 | --value "$(cat secrets.json)" \ 12 | --region $REGION \ 13 | --profile $PROFILE 14 | ``` 15 | 16 | ### Deploy Secrets Example Template 17 | ```shell 18 | aws cloudformation deploy \ 19 | --stack-name $STACKNAME \ 20 | --template-file template.yaml \ 21 | --capabilities CAPABILITY_NAMED_IAM \ 22 | --region $REGION \ 23 | --profile $PROFILE 24 | ``` 25 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L03 - Working w Secrets/secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "meaningoflife": "love", 3 | "aliencrashsite": "41°24'12.2\"N 2°10'26.5\"E", 4 | "whoshotfirst": "han" 5 | } 6 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L05 - Template Storage and Revisions/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Deploy Setup Template 3 | ```bash 4 | PROFILE=cloudguru 5 | REGION=us-east-1 6 | CourseBucketParam=acg-deploy-bucket 7 | STACKNAME=template-storage-setup 8 | aws cloudformation deploy \ 9 | --stack-name $STACKNAME \ 10 | --template-file setup.yaml \ 11 | --parameter-overrides CourseBucketParam=$CourseBucketParam \ 12 | --region $REGION \ 13 | --profile $PROFILE \ 14 | --capabilities CAPABILITY_NAMED_IAM 15 | ``` 16 | 17 | ## Helpful Links 18 | [GitHub Action Variables](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables) 19 | [GitHub Action Workflows](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions) 20 | [Awesome Actions](https://github.com/sdras/awesome-actions) 21 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L05 - Template Storage and Revisions/main.yml: -------------------------------------------------------------------------------- 1 | name: Lint CloudFormation Templates 2 | 3 | on: [push] 4 | 5 | jobs: 6 | lint-templates: 7 | name: Lint Templates 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v1 12 | 13 | - name: Lint Templates 14 | uses: ScottBrenner/cfn-lint-action@1.6.0 15 | with: 16 | args: "**/*.yaml" 17 | 18 | upload-templates: 19 | needs: lint-templates 20 | if: github.ref == 'refs/heads/master' 21 | name: Upload Templates to S3 22 | runs-on: ubuntu-latest 23 | env: 24 | TEMPLATE_LOCATION: ./Ch04 - Best Practices/L05 - Template Storage and Revisions 25 | DEPLOY_BUCKET: acg-deploy-bucket 26 | AWS_DEFAULT_REGION: us-east-1 27 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 28 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v1 32 | 33 | - name: Package to S3 34 | run: | 35 | aws cloudformation package \ 36 | --template-file "$TEMPLATE_LOCATION/template.yaml" \ 37 | --s3-bucket $DEPLOY_BUCKET \ 38 | --output-template-file "$TEMPLATE_LOCATION/packaged.yaml" \ 39 | --region $AWS_DEFAULT_REGION 40 | 41 | - name: Deploy Template to S3 42 | run: | 43 | aws s3 sync \ 44 | "$TEMPLATE_LOCATION" s3://$DEPLOY_BUCKET/coursefiles/templatestorage/${GITHUB_SHA::8} \ 45 | --exclude "*" \ 46 | --include "packaged.yaml" \ 47 | --region $AWS_DEFAULT_REGION 48 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L05 - Template Storage and Revisions/setup.yaml: -------------------------------------------------------------------------------- 1 | Parameters: 2 | CourseBucketParam: 3 | Type: String 4 | Description: Course bucket we setup in first chapter 5 | Default: acg-deploy-bucket 6 | Resources: 7 | iamUser: 8 | Type: AWS::IAM::User 9 | Properties: 10 | UserName: !Sub acg-template-editor-${AWS::Region} 11 | Policies: 12 | - PolicyName: acg-template-editor-policy 13 | PolicyDocument: 14 | Version: 2012-10-17 15 | Statement: 16 | - Effect: Allow 17 | Action: 18 | - s3:PutObject 19 | - s3:GetObject 20 | - s3:ListBucket 21 | - s3:DeleteObject 22 | Resource: 23 | - !Sub arn:aws:s3:::${CourseBucketParam} 24 | - !Sub arn:aws:s3:::${CourseBucketParam}/* 25 | iamKeys: 26 | Type: AWS::IAM::AccessKey 27 | Properties: 28 | UserName: !Ref iamUser 29 | Outputs: 30 | AccessKeyID: 31 | Value: !Ref iamKeys 32 | AccessKeySecret: 33 | Value: !GetAtt iamKeys.SecretAccessKey 34 | -------------------------------------------------------------------------------- /Ch04 - Best Practices/L05 - Template Storage and Revisions/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | LambdaFunction: 5 | Type: AWS::Serverless::Function 6 | Properties: 7 | Runtime: nodejs12.x 8 | InlineCode: | 9 | const { BUCKET } = process.env; 10 | module.exports.handler = (event, context, callback) => { 11 | console.log('BUCKET:', BUCKET); 12 | callback(null, 'I GOT THE BUCKET!!!'); 13 | }; 14 | Handler: index.handler 15 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L01 - Service Roles/setup.yaml: -------------------------------------------------------------------------------- 1 | Resources: 2 | CFStackRole: 3 | Type: AWS::IAM::Role 4 | Properties: 5 | AssumeRolePolicyDocument: 6 | Version: 2012-10-17 7 | Statement: 8 | - Effect: Allow 9 | Principal: 10 | Service: 11 | - cloudformation.amazonaws.com 12 | Action: 13 | - sts:AssumeRole 14 | Policies: 15 | - PolicyName: acg-cfstack-role 16 | PolicyDocument: 17 | Version: 2012-10-17 18 | Statement: 19 | - Effect: Allow 20 | Action: "*" 21 | Resource: "*" 22 | SupportUser: 23 | Type: AWS::IAM::User 24 | Properties: 25 | UserName: !Sub acg-support-team-${AWS::Region} 26 | Policies: 27 | - PolicyName: acg-support-team-policy 28 | PolicyDocument: 29 | Version: 2012-10-17 30 | Statement: 31 | - Effect: Allow 32 | Action: 33 | - cloudformation:CreateUploadBucket 34 | - cloudformation:GetTemplateSummary 35 | - cloudformation:DeleteStack 36 | - cloudformation:DescribeStacks 37 | - cloudformation:UpdateStack 38 | - cloudformation:ListStacks 39 | - cloudformation:ListStackResources 40 | - cloudformation:DescribeStackEvents 41 | - cloudformation:CreateChangeSet 42 | - cloudformation:GetTemplate 43 | - cloudformation:GetStackPolicy 44 | - cloudformation:ListChangeSets 45 | - cloudformation:DescribeChangeSet 46 | Resource: 47 | - "*" 48 | SupportUserKeys: 49 | Type: AWS::IAM::AccessKey 50 | Properties: 51 | UserName: !Ref SupportUser 52 | Outputs: 53 | CFStackRoleArn: 54 | Value: !GetAtt CFStackRole.Arn 55 | SupportUserKeyID: 56 | Value: !Ref SupportUserKeys 57 | SupportUserKeySecret: 58 | Value: !GetAtt SupportUserKeys.SecretAccessKey 59 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L01 - Service Roles/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | TestBucket: 5 | Type: AWS::S3::Bucket 6 | TestFunction: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | Runtime: nodejs12.x 10 | InlineCode: | 11 | exports.handler = (event, context, callback) => { 12 | callback(null, 'SUCCESS!'); 13 | }; 14 | Handler: index.handler 15 | TestTBL: 16 | Type: AWS::DynamoDB::Table 17 | Properties: 18 | TableName: !Sub ${AWS::StackName}-testtable 19 | AttributeDefinitions: 20 | - AttributeName: Id 21 | AttributeType: S 22 | KeySchema: 23 | - AttributeName: Id 24 | KeyType: HASH 25 | ProvisionedThroughput: 26 | ReadCapacityUnits: 10 27 | WriteCapacityUnits: 5 28 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L02 - Change Sets/README.md: -------------------------------------------------------------------------------- 1 | # Steps: 2 | - Deploy Base Template 3 | - Create ChangeSet A 4 | - Create ChangeSet B 5 | - Describe ChangeSet A 6 | - Execute ChangeSet A 7 | 8 | # Deploy Base Template 9 | ```shell 10 | STACKNAME=acg-changeset 11 | REGION=us-east-1 12 | PROFILE=cloudguru 13 | aws cloudformation deploy \ 14 | --stack-name $STACKNAME \ 15 | --template-file template.yaml \ 16 | --capabilities CAPABILITY_NAMED_IAM \ 17 | --region $REGION \ 18 | --profile $PROFILE 19 | ``` 20 | 21 | ## Create ChangeSet A 22 | ```shell 23 | aws cloudformation create-change-set \ 24 | --stack-name $STACKNAME \ 25 | --change-set-name changeSetA \ 26 | --template-body file://changesetA.yaml \ 27 | --capabilities CAPABILITY_NAMED_IAM \ 28 | --region $REGION \ 29 | --profile $PROFILE 30 | ``` 31 | ## Create ChangeSet B 32 | ```shell 33 | aws cloudformation create-change-set \ 34 | --stack-name $STACKNAME \ 35 | --change-set-name changeSetB \ 36 | --template-body file://changesetB.yaml \ 37 | --capabilities CAPABILITY_NAMED_IAM \ 38 | --region $REGION \ 39 | --profile $PROFILE 40 | ``` 41 | 42 | ## Describe ChangeSet A 43 | ```shell 44 | aws cloudformation describe-change-set \ 45 | --stack-name $STACKNAME \ 46 | --change-set-name changeSetA \ 47 | --region $REGION \ 48 | --profile $PROFILE 49 | ``` 50 | 51 | ## Execute ChangeSet A 52 | ```shell 53 | aws cloudformation execute-change-set \ 54 | --stack-name $STACKNAME \ 55 | --change-set-name changeSetA \ 56 | --region $REGION \ 57 | --profile $PROFILE 58 | ``` 59 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L02 - Change Sets/changesetA.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | S3Bucket: 5 | Type: AWS::S3::Bucket 6 | Properties: 7 | BucketName: !Sub ${AWS::StackName}-s3bucket 8 | AccessControl: Private 9 | BucketEncryption: 10 | ServerSideEncryptionConfiguration: 11 | - ServerSideEncryptionByDefault: 12 | SSEAlgorithm: AES256 13 | DynamoTable: 14 | Type: AWS::DynamoDB::Table 15 | Properties: 16 | TableName: !Sub ${AWS::StackName}-table 17 | AttributeDefinitions: 18 | - AttributeName: Host 19 | AttributeType: S 20 | KeySchema: 21 | - AttributeName: Host 22 | KeyType: HASH 23 | ProvisionedThroughput: 24 | ReadCapacityUnits: 300 25 | WriteCapacityUnits: 5 26 | Lambda: 27 | Type: AWS::Serverless::Function 28 | Properties: 29 | Runtime: nodejs12.x 30 | InlineCode: | 31 | exports.handler = (event, context, callback) => { 32 | callback(null, 'Hello!'); 33 | }; 34 | Handler: index.handler 35 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L02 - Change Sets/changesetB.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | S3Bucket: 5 | Type: AWS::S3::Bucket 6 | Properties: 7 | BucketName: !Sub ${AWS::StackName}-bucket 8 | AccessControl: Private 9 | BucketEncryption: 10 | ServerSideEncryptionConfiguration: 11 | - ServerSideEncryptionByDefault: 12 | SSEAlgorithm: AES256 13 | DynamoTable: 14 | Type: AWS::DynamoDB::Table 15 | Properties: 16 | TableName: !Sub ${AWS::StackName}-table2 17 | AttributeDefinitions: 18 | - AttributeName: Host 19 | AttributeType: S 20 | KeySchema: 21 | - AttributeName: Host 22 | KeyType: HASH 23 | ProvisionedThroughput: 24 | ReadCapacityUnits: 300 25 | WriteCapacityUnits: 5 26 | SQSQueue: 27 | Type: AWS::SQS::Queue 28 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L02 - Change Sets/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | S3Bucket: 5 | Type: AWS::S3::Bucket 6 | Properties: 7 | BucketName: !Sub ${AWS::StackName}-bucket 8 | AccessControl: Private 9 | BucketEncryption: 10 | ServerSideEncryptionConfiguration: 11 | - ServerSideEncryptionByDefault: 12 | SSEAlgorithm: AES256 13 | DynamoTable: 14 | Type: AWS::DynamoDB::Table 15 | Properties: 16 | TableName: !Sub ${AWS::StackName}-table 17 | AttributeDefinitions: 18 | - AttributeName: Host 19 | AttributeType: S 20 | KeySchema: 21 | - AttributeName: Host 22 | KeyType: HASH 23 | ProvisionedThroughput: 24 | ReadCapacityUnits: 300 25 | WriteCapacityUnits: 5 26 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L03-L04 - 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: !Sub ${AWS::StackName}-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: !Sub ${AWS::StackName}-AssumeRole-AWSCloudFormationStackSetExecutionRole 20 | PolicyDocument: 21 | Version: 2012-10-17 22 | Statement: 23 | - Effect: Allow 24 | Action: 25 | - sts:AssumeRole 26 | Resource: 27 | - arn:aws:iam::*:role/AWSCloudFormationStackSetExecutionRole 28 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L03-L04 - 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: !Sub ${AWS::StackName}-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 | - arn:aws:iam::aws:policy/AdministratorAccess 28 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L03-L04 - Stack Sets/setup.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | AccountGateFunction: 5 | Type: AWS::Serverless::Function 6 | Properties: 7 | FunctionName: AWSCloudFormationStackSetAccountGate 8 | Runtime: nodejs12.x 9 | InlineCode: | 10 | exports.handler = (event, context, callback) => { 11 | const Status = 'FAILED'; 12 | callback(null, { Status }); 13 | }; 14 | Handler: index.handler 15 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L03-L04 - Stack Sets/template.yaml: -------------------------------------------------------------------------------- 1 | Resources: 2 | S3Bucket: 3 | Type: AWS::S3::Bucket 4 | # Properties: 5 | # BucketName: !Sub acg-stackset-bucket-${AWS::AccountId}-${AWS::Region} 6 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L05 - Stack Policies/!-solution/stack-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Statement" : [ 3 | { 4 | "Effect" : "Allow", 5 | "Action" : "Update:*", 6 | "Principal": "*", 7 | "Resource" : "*" 8 | }, 9 | { 10 | "Effect" : "Deny", 11 | "Action" : "Update:*", 12 | "Principal": "*", 13 | "Resource" : "*", 14 | "Condition" : { 15 | "StringEquals" : { 16 | "ResourceType" : [ 17 | "AWS::DynamoDB::Table", 18 | "AWS::S3::Bucket" 19 | ] 20 | } 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L05 - Stack Policies/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Steps 3 | - Create Stack 4 | - Deploy Template Deleting DDB Table 5 | - Deploy Template Deleting Lambda Function 6 | 7 | # Create Stack 8 | ```shell 9 | PROFILE=cloudguru 10 | STACKNAME=acg-stackpolicies 11 | REGION=us-east-1 12 | aws cloudformation create-stack \ 13 | --stack-name $STACKNAME \ 14 | --template-body file://./template.yaml \ 15 | --stack-policy-body file://./stack-policy.json \ 16 | --capabilities CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND \ 17 | --region $REGION \ 18 | --profile $PROFILE 19 | ``` 20 | 21 | # Deploy Template Deleting DDB Table 22 | ```shell 23 | aws cloudformation deploy \ 24 | --stack-name $STACKNAME \ 25 | --template-file template.yaml \ 26 | --capabilities CAPABILITY_NAMED_IAM \ 27 | --region $REGION \ 28 | --profile $PROFILE 29 | ``` 30 | 31 | # Deploy Template Deleting Lambda Function 32 | ```shell 33 | aws cloudformation deploy \ 34 | --stack-name $STACKNAME \ 35 | --template-file template.yaml \ 36 | --capabilities CAPABILITY_NAMED_IAM \ 37 | --region $REGION \ 38 | --profile $PROFILE 39 | ``` 40 | -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L05 - Stack Policies/stack-policy.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch05 - Mastering Stacks/L05 - Stack Policies/stack-policy.json -------------------------------------------------------------------------------- /Ch05 - Mastering Stacks/L05 - Stack Policies/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Resources: 4 | TestBucket: 5 | Type: AWS::S3::Bucket 6 | TestFunction: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | Runtime: nodejs12.x 10 | MemorySize: 128 11 | InlineCode: | 12 | exports.handler = (event, context, callback) => { 13 | callback(null, 'SUCCESS!'); 14 | }; 15 | Handler: index.handler 16 | TestTBL: 17 | Type: AWS::DynamoDB::Table 18 | Properties: 19 | TableName: !Sub ${AWS::StackName}-testtable 20 | AttributeDefinitions: 21 | - AttributeName: Id 22 | AttributeType: S 23 | KeySchema: 24 | - AttributeName: Id 25 | KeyType: HASH 26 | ProvisionedThroughput: 27 | ReadCapacityUnits: 10 28 | WriteCapacityUnits: 5 29 | -------------------------------------------------------------------------------- /Ch06 - Working with EC2 Instances/L03 - Resource Policies/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## DEMO: Working with EC2 3 | 4 | ### Steps 5 | 1. Update params in `params.toml` 6 | 2. Update template with cfn-signal 7 | 3. Deploy `template.yaml` 8 | 9 | ### Deploy Template 10 | ```shell 11 | STACKNAME=acg-ghostblog 12 | REGION=us-east-1 13 | PROFILE=cloudguru 14 | aws cloudformation deploy \ 15 | --stack-name $STACKNAME \ 16 | --template-file template.yaml \ 17 | --parameter-overrides $(cat params.toml) \ 18 | --capabilities CAPABILITY_NAMED_IAM \ 19 | --region $REGION \ 20 | --profile $PROFILE 21 | ``` 22 | 23 | ## Helpful Info 24 | 25 | ### Helpful commands 26 | 27 | *Create Default VPC* 28 | ```shell 29 | REGION=us-east-1 30 | PROFILE=cloudguru 31 | aws ec2 create-default-vpc \ 32 | --region $REGION \ 33 | --profile $PROFILE 34 | ``` 35 | 36 | *Get the EC2's Public DNS Name:* 37 | `curl -s http://169.254.169.254/latest/meta-data/public-hostname` 38 | 39 | *Connecting to EC2:* 40 | ```shell 41 | ssh -i /path/my-key-pair.pem ec2-user@ec2-198-51-100-1.compute-1.amazonaws.com 42 | ``` 43 | 44 | *Location of Log Files:* 45 | ``` 46 | /var/log/cfn-init.log 47 | /var/log/cfn-init-cmd.log 48 | ``` 49 | -------------------------------------------------------------------------------- /Ch06 - Working with EC2 Instances/L03 - Resource Policies/params.toml: -------------------------------------------------------------------------------- 1 | KeyName=ghostblog 2 | DbPassword=SomepasswordHere 3 | DbRootPassword=SomepasswordHere 4 | DbUser=ghostuser 5 | WhitelistedIP=00.00.00.00/32 6 | EnvironmentSize=SMALL 7 | -------------------------------------------------------------------------------- /Ch06 - Working with EC2 Instances/L04 - cfn-hup/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## DEMO: Working with EC2 3 | 4 | ### Steps 5 | 1. Update params in `params.toml` 6 | 2. Edit `template.yaml` to include cfn-hup code 7 | 3. `Deploy Template` to course bucket 8 | 9 | ### CMDs for UserData cfn-hup Setup 10 | ```bash 11 | cp /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup 12 | chmod +x /etc/init.d/cfn-hup 13 | update-rc.d cfn-hup defaults 14 | ``` 15 | 16 | ### Deploy Template 17 | ```shell 18 | STACKNAME=acg-ghostblog 19 | REGION=us-east-1 20 | PROFILE=cloudguru 21 | aws cloudformation deploy \ 22 | --stack-name $STACKNAME \ 23 | --template-file template.yaml \ 24 | --parameter-overrides $(cat params.toml) \ 25 | --capabilities CAPABILITY_NAMED_IAM \ 26 | --region $REGION \ 27 | --profile $PROFILE 28 | ``` 29 | 30 | ## Helpful Info 31 | 32 | *Get the EC2's Public DNS Name:* 33 | `curl -s http://169.254.169.254/latest/meta-data/public-hostname` 34 | 35 | *Connecting to EC2:* 36 | ```shell 37 | ssh -i /path/my-key-pair.pem ec2-user@ec2-123-123-123-1.compute-1.amazonaws.com 38 | ``` 39 | 40 | *List all services:* 41 | ```shell 42 | service --status-all 43 | ``` 44 | 45 | *Location of Log Files:* 46 | ``` 47 | /var/log/cfn-init.log 48 | /var/log/cfn-init-cmd.log 49 | ``` 50 | 51 | *Live tail changes to cfn-hup.log* 52 | `tail -f /var/log/cfn-init.log` 53 | -------------------------------------------------------------------------------- /Ch06 - Working with EC2 Instances/L04 - cfn-hup/params.toml: -------------------------------------------------------------------------------- 1 | KeyName=ghostblog 2 | DbPassword=SomepasswordHere 3 | DbRootPassword=SomepasswordHere 4 | DbUser=ghostuser 5 | WhitelistedIP=00.00.00.00/32 6 | EnvironmentSize=SMALL 7 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/hello-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello World!

4 | 5 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/hello-world/template.yaml: -------------------------------------------------------------------------------- 1 | Transform: ACG-StackProxy-2020-02-16 2 | 3 | Parameters: 4 | StageParameter: 5 | Type: String 6 | Default: dev 7 | Description: Feature Branch Name 8 | Resources: 9 | ProxyEntry: 10 | Type: DVB::StackProxy::ProxyEntry 11 | Properties: 12 | Service: !Ref AWS::StackName 13 | Stage: !Ref StageParameter 14 | Origin: !GetAtt WebsiteBucket.DomainName 15 | WebsiteBucket: 16 | Type: AWS::S3::Bucket 17 | BucketPolicy: 18 | Type: AWS::S3::BucketPolicy 19 | Properties: 20 | Bucket: !Ref WebsiteBucket 21 | PolicyDocument: 22 | Version: 2012-10-17 23 | Statement: 24 | Effect: Allow 25 | Principal: 26 | CanonicalUser: 27 | - !GetAtt ProxyEntry.CloudfrontOAI 28 | AWS: 29 | - !GetAtt ProxyEntry.OriginRequestProxyRoleARN 30 | Action: 31 | - s3:ListBucket 32 | - s3:GetObject 33 | Resource: 34 | - !Sub arn:aws:s3:::${WebsiteBucket} 35 | - !Sub arn:aws:s3:::${WebsiteBucket}/* 36 | 37 | Outputs: 38 | SiteURL: 39 | Description: URL for this site 40 | Value: !Sub https://${ProxyEntry.Host} 41 | WebsiteBucket: 42 | Description: S3 Bucket for this site 43 | Value: !Ref WebsiteBucket 44 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/sar-bucket-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Principal": { 7 | "Service": "serverlessrepo.amazonaws.com" 8 | }, 9 | "Condition": { 10 | "StringEquals": { 11 | "aws:SourceAccount": "AWS::AccountId" 12 | } 13 | }, 14 | "Action": "s3:GetObject", 15 | "Resource": "arn:aws:s3:::acg-deploy-bucket/*" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "aws-sdk": "^2.619.0", 4 | "axios": "^0.19.2", 5 | "bluebird": "^3.5.4", 6 | "cfn-lambda": "^4.0.0", 7 | "clone": "^2.1.2", 8 | "crypto": "^1.0.1", 9 | "debug": "^4.1.1", 10 | "graphql": "^0.13.2", 11 | "lodash.chunk": "^4.2.0", 12 | "lodash.filter": "^4.6.0", 13 | "lodash.find": "^4.6.0", 14 | "lodash.map": "^4.6.0", 15 | "middy": "^0.33.2", 16 | "middy-reroute": "^2.1.4" 17 | }, 18 | "scripts": { 19 | "build": "webpack-cli", 20 | "clean": "rimraf .aws-sam .vscode", 21 | "prebuild": "rimraf .aws-sam .vscode", 22 | "prewatch": "rimraf .aws-sam .vscode", 23 | "watch": "webpack-cli -w", 24 | "deploy": "make deploy" 25 | }, 26 | "devDependencies": { 27 | "@babel/cli": "^7.8.4", 28 | "@babel/core": "^7.8.4", 29 | "aws-sam-webpack-plugin": "^0.5.1", 30 | "babel-loader": "^8.0.6", 31 | "rimraf": "^3.0.2", 32 | "webpack": "^4.41.6", 33 | "webpack-cli": "^3.3.11" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/src/commontags/index.js: -------------------------------------------------------------------------------- 1 | const RESOURCES_SUPPORTING_TAGS = require('./resources_supporting_tags.json'); 2 | 3 | exports.handler = (event, context, callback) => { 4 | console.log('event', JSON.stringify(event), RESOURCES_SUPPORTING_TAGS); 5 | const { requestId, fragment, params: tags } = event; 6 | const resources = fragment.Resources; 7 | 8 | if (tags) tagResources(resources, tags); 9 | 10 | const resp = { 11 | requestId, 12 | status: 'success', 13 | fragment, 14 | }; 15 | 16 | callback(null, resp); 17 | }; 18 | 19 | const isTaggable = ({ Properties, Type }) => 20 | (Properties && Properties.Tags) || RESOURCES_SUPPORTING_TAGS.includes(Type); 21 | 22 | const isTaggedWithAlready = (tag, resourceTags) => { 23 | for (const tagObj in resourceTags) { 24 | if (resourceTags[tagObj].Key === tag) return true; 25 | } 26 | return false; 27 | }; 28 | 29 | const tagResources = (resources, tags) => { 30 | for (const key in resources) { 31 | tagResource(resources[key], tags); 32 | } 33 | }; 34 | 35 | const tagResource = (resource, tags) => { 36 | const resourceTags = (resource.Properties && resource.Properties.Tags) || []; 37 | console.log('tagResource', resourceTags); 38 | if (isTaggable(resource)) { 39 | for (const Key in tags) { 40 | if (!isTaggedWithAlready(Key, resourceTags)) { 41 | resourceTags.push({ 42 | Key, 43 | Value: tags[Key], 44 | }); 45 | } 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/src/githubwebhook/utils/logger.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug'); 2 | const log = debug('reroute:log'); 3 | log.log = console.log.bind(console); 4 | 5 | module.exports = log; 6 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/src/macro-proxyentry/index.js: -------------------------------------------------------------------------------- 1 | const clone = require('clone'); 2 | 3 | const { PROXYENTRY_ARN } = process.env; 4 | 5 | const processTemplate = template => { 6 | let status = 'success'; 7 | let newResources = {}; 8 | const newTemplate = clone(template); 9 | const resources = newTemplate.Resources; 10 | 11 | for (let name in resources) { 12 | const resource = resources[name]; 13 | if (resource['Type'] === 'DVB::StackProxy::ProxyEntry') { 14 | const { Version } = resource; 15 | const { Service, Stage, Origin } = resource['Properties']; 16 | 17 | if (!(Service && Stage && Origin)) 18 | throw 'You must specify a Service, Stage and Origin'; 19 | 20 | newResources[name] = { 21 | Type: 'Custom::StackProxyEntry', 22 | Version, 23 | Properties: { 24 | ServiceToken: PROXYENTRY_ARN, 25 | Service, 26 | Stage, 27 | Origin, 28 | }, 29 | }; 30 | } 31 | } 32 | 33 | for (let name in newResources) { 34 | const resource = newResources[name]; 35 | resources[name] = resource; 36 | } 37 | 38 | return [status, newTemplate]; 39 | }; 40 | 41 | exports.handler = (event, context, callback) => { 42 | console.log('event', JSON.stringify(event)); 43 | const { requestId } = event; 44 | const [status, fragment] = processTemplate(event.fragment); 45 | const resp = { 46 | requestId, 47 | status, 48 | fragment, 49 | }; 50 | 51 | callback(null, resp); 52 | }; 53 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/src/originrequest/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const middy = require('middy'); 4 | const { reroute } = require('middy-reroute'); 5 | 6 | const handler = middy((event, context, cb) => { 7 | const request = !!event.Records ? event.Records[0].cf.request : event; 8 | cb(null, request); 9 | }).use( 10 | reroute({ 11 | cacheTtl: 2, 12 | }), 13 | ); 14 | 15 | module.exports = { handler }; 16 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/src/originrequestproxy/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.DEBUG = 'reroute:log'; 4 | 5 | const middy = require('middy'); 6 | const { reroute, rerouteOrigin } = require('middy-reroute'); 7 | const functionSuffix = '-originrequestproxy'; 8 | 9 | const handler = middy((event, context, cb) => { 10 | const request = !!event.Records ? event.Records[0].cf.request : event; 11 | cb(null, request); 12 | }) 13 | .use(rerouteOrigin({ functionSuffix })) 14 | .use( 15 | reroute({ 16 | cacheTtl: 2, 17 | }), 18 | ); 19 | 20 | module.exports = { handler }; 21 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/src/originresponse/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const middy = require('middy'); 4 | 5 | const handler = middy((event, context, cb) => { 6 | console.log('event: ', JSON.stringify(event)); 7 | const { response } = event.Records[0].cf; 8 | cb(null, response); 9 | }); 10 | 11 | module.exports = { handler }; 12 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/tests/github/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "GithubWebhookFunction": { 3 | "AWS_REGION": "us-east-1", 4 | "GITHUB_WEBHOOK_SECRET": "2JGEJPgPBKMJKg", 5 | "PROD_CI_ROLE_ARN": "arn:aws:iam::645655324390:role/stack-proxy-StackProxy-VHXCBANH4AR6-deployer-role", 6 | "DEV_CI_ROLE_ARN": "arn:aws:iam::645655324390:role/stack-proxy-StackProxy-VHXCBANH4AR6-deployer-role" 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/tests/macro-stackproxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east-1", 3 | "accountId": "104477223281", 4 | "fragment": { 5 | "Resources": { 6 | "Bucket": { 7 | "Type": "AWS::S3::Bucket" 8 | }, 9 | "ProxyEntry": { 10 | "Type": "DVB::StackProxy::ProxyEntry", 11 | "Properties": { 12 | "Service": "someproject", 13 | "Stage": "feat-thing", 14 | "Origin": "bucketname-something-something" 15 | } 16 | } 17 | } 18 | }, 19 | "transformId": "104477223281::S3Objects", 20 | "params": {}, 21 | "requestId": "8f5e08f3-6431-4bb6-970f-dd625a1efa12", 22 | "templateParameterValues": {} 23 | } 24 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/usage.yaml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | 3 | Parameters: 4 | ApplicationId: 5 | Type: String 6 | Description: StackProxy Application ID 7 | SemanticVersion: 8 | Type: String 9 | Description: WildCard SSL Certificate ARN 10 | DomainParam: 11 | Type: String 12 | Description: Domain to be extended with subdomains Eg. domain.com or sub.domain.com 13 | GitHubWebhookSecretParam: 14 | Type: String 15 | Description: GitHub Webhook Secret 16 | SSLCertARN: 17 | Type: String 18 | Description: WildCard SSL Certificate ARN 19 | 20 | Resources: 21 | StackProxy: 22 | Type: AWS::Serverless::Application 23 | Properties: 24 | Location: 25 | ApplicationId: !Ref ApplicationId 26 | SemanticVersion: !Ref SemanticVersion 27 | Parameters: 28 | DomainParam: !Ref DomainParam 29 | GitHubWebhookSecretParam: !Ref GitHubWebhookSecretParam 30 | SSLCertARN: !Ref SSLCertARN 31 | 32 | Outputs: 33 | GitHubWebhook: 34 | Description: Endpoint for the GitHub Webhook 35 | Value: !GetAtt StackProxy.Outputs.GitHubWebhook 36 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/AWS SAR/stack-proxy/webpack.config.js: -------------------------------------------------------------------------------- 1 | const AwsSamPlugin = require('aws-sam-webpack-plugin'); 2 | 3 | const awsSamPlugin = new AwsSamPlugin(); 4 | 5 | module.exports = { 6 | // Loads the entry object from the AWS::Serverless::Function resources in your 7 | // SAM config. Setting this to a function will 8 | entry: () => awsSamPlugin.entry(), 9 | 10 | // Write the output to the .aws-sam/build folder 11 | output: { 12 | filename: '[name]/app.js', 13 | libraryTarget: 'commonjs2', 14 | path: __dirname + '/.aws-sam/build/', 15 | }, 16 | 17 | // Create source maps 18 | devtool: 'source-map', 19 | 20 | // Resolve .ts and .js extensions 21 | resolve: { 22 | extensions: ['.ts', '.js'], 23 | }, 24 | 25 | // Target node 26 | target: 'node', 27 | 28 | // AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce 29 | // the size of your deployment package. If you want to always include it then comment out this line. It has 30 | // been included conditionally because the node10.x docker image used by SAM local doesn't include it. 31 | externals: process.env.NODE_ENV === 'development' ? [] : ['aws-sdk'], 32 | 33 | // Set the webpack mode 34 | mode: process.env.NODE_ENV || 'production', 35 | 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.js$/, 40 | exclude: /node_modules/, 41 | use: [ 42 | { 43 | loader: 'babel-loader', 44 | }, 45 | ], 46 | }, 47 | ], 48 | }, 49 | 50 | // Add the AWS SAM Webpack plugin 51 | plugins: [awsSamPlugin], 52 | }; 53 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Build & Deploy JAMStack App 3 | 4 | 1) [Setup Admin Deployer User](#Setup-Admin-Deployer-User) 5 | 2) [Add AWS Key & Secret to GitHub Secrets](#Add-AWS-Key-&-Secret-to-GitHub-Secrets) 6 | 3) [Update Workflow Env Vars](#Update-Workflow-Env-Vars) 7 | 4) [Setup GitHub Workflow folder](#Setup-GitHub-Workflow-folder) 8 | 5) [Add Webhook to GitHub Repo](#Add-Webhook-to-GitHub-Repo) 9 | 6) [Create Feature Branch](#Create-Feature-Branch) 10 | 11 | ### Setup Admin Deployer User 12 | Eg. `cloudguru-deployer` 13 | 14 | ### Add AWS Key & Secret to GitHub Secrets 15 | Eg. `AWS_ACCESS_KEY_ID` & `AWS_SECRET_ACCESS_KEY` 16 | 17 | ### Update Workflow Env Vars 18 | Eg. `TEMPLATE_LOCATION, DEPLOY_BUCKET, AWS_DEFAULT_REGION` 19 | 20 | ### Setup GitHub Workflow folder 21 | `.github/workflows/main.yml` 22 | 23 | ### Add Webhook to GitHub Repo 24 | - Get hook from StackProxy Outputs 25 | 26 | ### Create Feature Branch 27 | Eg. `feat-test` 28 | 29 | 30 | ## Code Review 31 | - [Backend Template](https://github.com/iDVB/course-mastering-aws-cloudformation/blob/800402a6840c6f6f66d6b396977270f2ea95e5c0/Ch07%20-%20Work%20with%20Serverless/L02%20-%20JAMStack%20Deployment/backend/template.yaml#L218-L223) 32 | - [Embeded Git Hash](https://github.com/iDVB/course-mastering-aws-cloudformation/blob/800402a6840c6f6f66d6b396977270f2ea95e5c0/Ch07%20-%20Work%20with%20Serverless/L02%20-%20JAMStack%20Deployment/frontend/public/index.html#L2) 33 | - [Outputs Script](https://github.com/iDVB/course-mastering-aws-cloudformation/blob/800402a6840c6f6f66d6b396977270f2ea95e5c0/Ch07%20-%20Work%20with%20Serverless/L02%20-%20JAMStack%20Deployment/backend/scripts/outputs.js#L7) 34 | - [_redirects](https://github.com/iDVB/course-mastering-aws-cloudformation/blob/800402a6840c6f6f66d6b396977270f2ea95e5c0/Ch07%20-%20Work%20with%20Serverless/L02%20-%20JAMStack%20Deployment/frontend/public/_redirects#L2) + [middy-reroute](https://www.npmjs.com/package/middy-reroute) 35 | - [SAM webpack plugin](https://github.com/iDVB/course-mastering-aws-cloudformation/blob/800402a6840c6f6f66d6b396977270f2ea95e5c0/Ch07%20-%20Work%20with%20Serverless/L02%20-%20JAMStack%20Deployment/backend/package.json#L15) 36 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acg-votingapp-backend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bluebird": "^3.5.3", 7 | "lodash.omit": "^4.5.0", 8 | "lodash.reduce": "^4.6.0", 9 | "node-fetch": "^2.3.0", 10 | "node-jose": "^1.1.0" 11 | }, 12 | "devDependencies": { 13 | "@babel/cli": "^7.8.4", 14 | "@babel/core": "^7.9.0", 15 | "aws-sam-webpack-plugin": "^0.6.0", 16 | "aws-sdk": "^2.648.0", 17 | "babel-loader": "^8.1.0", 18 | "commander": "^5.0.0", 19 | "rimraf": "^3.0.2", 20 | "webpack": "^4.42.1", 21 | "webpack-cli": "^3.3.11" 22 | }, 23 | "scripts": { 24 | "build": "webpack-cli", 25 | "clean": "rimraf .aws-sam .vscode", 26 | "prebuild": "rimraf .aws-sam .vscode", 27 | "prewatch": "rimraf .aws-sam .vscode", 28 | "watch": "webpack-cli -w", 29 | "getenv": "make getenv", 30 | "deploy": "make deploy getenv" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/backend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const AwsSamPlugin = require('aws-sam-webpack-plugin'); 2 | 3 | const awsSamPlugin = new AwsSamPlugin(); 4 | 5 | module.exports = { 6 | // Loads the entry object from the AWS::Serverless::Function resources in your 7 | // SAM config. Setting this to a function will 8 | entry: () => awsSamPlugin.entry(), 9 | 10 | // Write the output to the .aws-sam/build folder 11 | output: { 12 | filename: '[name]/app.js', 13 | libraryTarget: 'commonjs2', 14 | path: __dirname + '/.aws-sam/build/', 15 | }, 16 | 17 | // Create source maps 18 | devtool: 'source-map', 19 | 20 | // Resolve .ts and .js extensions 21 | resolve: { 22 | extensions: ['.ts', '.js'], 23 | }, 24 | 25 | // Target node 26 | target: 'node', 27 | 28 | // AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce 29 | // the size of your deployment package. If you want to always include it then comment out this line. It has 30 | // been included conditionally because the node10.x docker image used by SAM local doesn't include it. 31 | externals: process.env.NODE_ENV === 'development' ? [] : ['aws-sdk'], 32 | 33 | // Set the webpack mode 34 | mode: process.env.NODE_ENV || 'production', 35 | 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.js$/, 40 | exclude: /node_modules/, 41 | use: [ 42 | { 43 | loader: 'babel-loader', 44 | }, 45 | ], 46 | }, 47 | ], 48 | }, 49 | 50 | // Add the AWS SAM Webpack plugin 51 | plugins: [awsSamPlugin], 52 | }; 53 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier", "prettier/react"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "react/jsx-filename-extension": [ 6 | 1, 7 | { 8 | "extensions": [".js", ".jsx"] 9 | } 10 | ], 11 | "react/prop-types": 0, 12 | "no-underscore-dangle": 0, 13 | "no-unused-expressions": 0, 14 | "no-unused-vars": ["warn"], 15 | "import/imports-first": ["error", "absolute-first"], 16 | "import/newline-after-import": "error", 17 | "no-console": 0 18 | }, 19 | "globals": { 20 | "window": true, 21 | "document": true, 22 | "localStorage": true, 23 | "FormData": true, 24 | "FileReader": true, 25 | "Blob": true, 26 | "navigator": true 27 | }, 28 | "parser": "babel-eslint" 29 | } 30 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | const assetFilename = JSON.stringify(path.basename(filename)); 11 | 12 | if (filename.match(/\.svg$/)) { 13 | return `module.exports = { 14 | __esModule: true, 15 | default: ${assetFilename}, 16 | ReactComponent: (props) => ({ 17 | $$typeof: Symbol.for('react.element'), 18 | type: 'svg', 19 | ref: null, 20 | key: null, 21 | props: Object.assign({}, props, { 22 | children: ${assetFilename} 23 | }) 24 | }), 25 | };`; 26 | } 27 | 28 | return `module.exports = ${assetFilename};`; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | .ONESHELL: 3 | 4 | SERVICE = $(eval SERVICE := $(shell cut -d "=" -f 2 <<< $(shell cd ../backend && npm run env | grep "npm_package_name")))$(SERVICE) 5 | WEBSITE_BUCKET = $(eval WEBSITE_BUCKET := $(call getBucket))$(WEBSITE_BUCKET) 6 | DISTRIBUTION_ID = $(eval DISTRIBUTION_ID := $(call getDistroID))$(DISTRIBUTION_ID) 7 | 8 | profile = $(if $(PROFILE:-=),--profile $(PROFILE),) 9 | 10 | deploy: check-env syncS3 invalidate 11 | 12 | syncS3: 13 | @echo "Deploying Frontend for Stack $(STACK_NAME)" 14 | @echo "Syncing to S3 Bucket: $(WEBSITE_BUCKET) ..." 15 | aws s3 sync ./build/ "s3://$(WEBSITE_BUCKET)/" \ 16 | --exclude "*.html" \ 17 | --cache-control 'max-age=31536000, public' \ 18 | --delete \ 19 | --region '${REGION}' \ 20 | $(profile) 21 | aws s3 sync ./build/ "s3://$(WEBSITE_BUCKET)/" \ 22 | --exclude "*" --include "*.html" \ 23 | --cache-control 'max-age=0, no-cache, no-store, must-revalidate' \ 24 | --content-type text/html \ 25 | --delete \ 26 | --region '${REGION}' \ 27 | $(profile) 28 | 29 | invalidate: 30 | ifeq (${DISTRIBUTION_ID},"None") 31 | @echo "Invalidating Cloudfront Distro: ${DISTRIBUTION_ID}..." 32 | aws configure set preview.cloudfront true 33 | aws cloudfront create-invalidation \ 34 | --distribution-id $(DISTRIBUTION_ID) \ 35 | --paths "/*" \ 36 | --region '${REGION}' \ 37 | $(profile) 38 | else 39 | @echo "No Cloudfront Distro to invalidate." 40 | endif 41 | 42 | check-env: 43 | ifeq ($(call ifndef_any_of,STAGE STAGE_FLAG REGION),) 44 | $(eval STACK_NAME=$(shell echo $(SERVICE)-$(STAGE))) 45 | else 46 | $(error STAGE, STAGE_FLAG, and REGION must be defined) 47 | endif 48 | 49 | ifndef_any_of = $(filter undefined,$(foreach v,$(1),$(origin $(v)))) 50 | ifdef_any_of = $(filter-out undefined,$(foreach v,$(1),$(origin $(v)))) 51 | 52 | getBucket = $(shell aws cloudformation describe-stacks \ 53 | --stack-name $(STACK_NAME) \ 54 | --query "Stacks[0].Outputs[?OutputKey=='WebsiteBucket'] | [0].OutputValue" \ 55 | --output text \ 56 | --region '${REGION}' \ 57 | $(profile)) 58 | 59 | getDistroID = $(shell aws cloudformation describe-stacks \ 60 | --stack-name $(STACK_NAME) \ 61 | --query "Stacks[0].Outputs[?OutputKey=='DistributionId'] | [0].OutputValue" \ 62 | --output text \ 63 | --region '${REGION}' \ 64 | $(profile)) 65 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/public/_redirects: -------------------------------------------------------------------------------- 1 | 2 | /whatnext https://acloud.guru/learn/lambda-edge 302 3 | /api/* https://api.github.com/:splat 200 4 | 5 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/public/favicon.ico -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 23 | React App 24 | 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/scripts/deploy_static_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | STACK="${1}" 5 | echo "Querying Stack: ${STACK}..." 6 | 7 | BUCKET_NAME=$(aws \ 8 | cloudformation describe-stacks \ 9 | --stack-name "${STACK}" \ 10 | --query "Stacks[0].Outputs[?OutputKey=='WebsiteBucket'] | [0].OutputValue" \ 11 | --output text) 12 | 13 | DISTRIBUTION_ID=$(aws \ 14 | cloudformation describe-stacks \ 15 | --stack-name "${STACK}" \ 16 | --query "Stacks[0].Outputs[?OutputKey=='DistributionId'] | [0].OutputValue" \ 17 | --output text) 18 | 19 | echo "Deploying static assets to Bucket: ${BUCKET_NAME}..." 20 | 21 | aws s3 sync ./build/ "s3://${BUCKET_NAME}/" --exclude "*.html" --cache-control 'max-age=31536000, public' --delete 22 | aws s3 sync ./build/ "s3://${BUCKET_NAME}/" --exclude "*" --include "*.html" --cache-control 'max-age=0, no-cache, no-store, must-revalidate' --content-type text/html --delete 23 | 24 | echo "Deploy COMPLETE" 25 | 26 | if [ "${DISTRIBUTION_ID}" != "None" ]; then 27 | echo "Invalidating Cloudfront Distro: ${DISTRIBUTION_ID}..." 28 | aws configure set preview.cloudfront true 29 | aws cloudfront create-invalidation --distribution-id ${DISTRIBUTION_ID} --paths "/*" 30 | else 31 | echo "No Cloudfront Distro to invalidate." 32 | fi -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/scripts/remove_static_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | STACK="${1}" 5 | echo "Deleting static assets from: ${STACK}..." 6 | 7 | BUCKET_NAME=$(aws \ 8 | cloudformation describe-stacks \ 9 | --stack-name "${STACK}" \ 10 | --query "Stacks[0].Outputs[?OutputKey=='WebsiteBucket'] | [0].OutputValue" \ 11 | --output text) 12 | 13 | mkdir /tmp/empty 14 | 15 | aws s3 sync --delete /tmp/empty/ "s3://${BUCKET_NAME}/" 16 | 17 | rmdir /tmp/empty 18 | 19 | echo "Bucket ${BUCKET_NAME} has been emptied" -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI, in coverage mode, or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--coverage') === -1 && 45 | argv.indexOf('--watchAll') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #eee; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: #444; 19 | } 20 | 21 | .App-link { 22 | color: #444; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { createGlobalStyle } from 'styled-components'; 3 | import { disableBodyScroll } from 'body-scroll-lock'; 4 | import Voter from './Voter'; 5 | 6 | const App = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | disableBodyScroll(); 16 | 17 | const GlobalStyle = createGlobalStyle` 18 | html, body, #root { 19 | height: 100%; 20 | } 21 | `; 22 | 23 | const StyledApp = styled.div` 24 | height: 100%; 25 | `; 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/chatWindow.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Launcher } from 'react-chat-window'; 3 | import Sockette from 'sockette'; 4 | 5 | let ws = null; 6 | 7 | const ChatWindow = props => { 8 | const [messageList, setMessageList] = useState([]); 9 | const [badge, setBadge] = useState(0); 10 | const [isOpen, setIsOpen] = useState(false); 11 | const { username } = 'DVB'; 12 | 13 | useEffect(() => { 14 | ws = new Sockette( 15 | 'wss://1rh5eh6qpj.execute-api.us-east-1.amazonaws.com/feat-socket/', 16 | { 17 | timeout: 5e3, 18 | maxAttempts: 1, 19 | onopen: e => console.log('connected:', e), 20 | onmessage: e => onMessageReceied(e), 21 | onreconnect: e => console.log('Reconnecting...', e), 22 | onmaximum: e => console.log('Stop Attempting!', e), 23 | onclose: e => console.log('Closed!', e), 24 | onerror: e => console.log('Error:', e), 25 | }, 26 | ); 27 | return function cleanup() { 28 | ws && ws.close(); 29 | ws = null; 30 | }; 31 | }, [messageList]); 32 | 33 | const handleClick = () => { 34 | setIsOpen(!isOpen); 35 | setBadge(0); 36 | }; 37 | 38 | const onMessageWasSent = message => { 39 | const newMessage = { ...message, author: username }; 40 | ws.json({ 41 | action: 'sendMessage', 42 | data: JSON.stringify(newMessage), 43 | }); 44 | }; 45 | 46 | const onMessageReceied = ({ data }) => { 47 | const { author, type, data: messageData } = JSON.parse(data); 48 | const isMe = username === author ? 'me' : 'them'; 49 | if (!isOpen) { 50 | setBadge(+badge + 1); 51 | } 52 | setMessageList([ 53 | ...messageList, 54 | { 55 | author: isMe, 56 | type, 57 | data: messageData, 58 | }, 59 | ]); 60 | }; 61 | return ( 62 |
63 | 76 |
77 | ); 78 | }; 79 | 80 | export default ChatWindow; 81 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | git: { 3 | hash: process.env.REACT_APP_GIT_VERSION, 4 | }, 5 | site: { 6 | api: process.env.REACT_APP_SERVICE_ENDPOINT_WEBSOCKET, 7 | url: process.env.REACT_APP_SITE_URL, 8 | }, 9 | }; 10 | 11 | console.log(config); 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/baratheon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/baratheon.png -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/greyjoy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/greyjoy.png -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/lannister.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/lannister.png -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/qr.png -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/stark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/stark.png -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/targaryen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/targaryen.png -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/tully.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/images/tully.png -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/main.yml: -------------------------------------------------------------------------------- 1 | name: JAMStack Deployment 2 | 3 | on: 4 | push: 5 | branches: 6 | - feat-* 7 | 8 | jobs: 9 | build-deploy: 10 | name: Backend and Frontend Setup 11 | runs-on: ubuntu-latest 12 | defaults: 13 | run: 14 | shell: bash 15 | env: 16 | DEPLOY_BUCKET: acg-deploy-bucket 17 | AWS_DEFAULT_REGION: us-east-1 18 | steps: 19 | - name: Checkout Repo 20 | uses: actions/checkout@v1 21 | 22 | - name: Configure AWS CLI 23 | uses: aws-actions/configure-aws-credentials@v1 24 | with: 25 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 26 | aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 27 | aws-region: us-east-1 28 | 29 | - name: Build & Deploy 30 | working-directory: "${{ github.workspace }}/Ch07 - Work with Serverless/JAMStack Deployment" 31 | run: | 32 | npm config set scripts-prepend-node-path auto # https://bit.ly/3cKK2Uf 33 | yarn install 34 | yarn build:backend 35 | yarn deploy:backend \ 36 | STAGE=${GITHUB_REF##*/} \ 37 | STAGE_FLAG=dev \ 38 | REGION=$AWS_DEFAULT_REGION \ 39 | DEPLOY_BUCKET=$DEPLOY_BUCKET 40 | yarn build:frontend 41 | yarn deploy:frontend \ 42 | STAGE=${GITHUB_REF##*/} \ 43 | STAGE_FLAG=dev \ 44 | REGION=$AWS_DEFAULT_REGION 45 | # Other Ideas: 46 | # Lighthouse jakejarvis/lighthouse-action@master 47 | # Slack https://github.com/marketplace/actions/slack-notify 48 | -------------------------------------------------------------------------------- /Ch07 - Work with Serverless/JAMStack Deployment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acg-votingapp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "workspaces": [ 6 | "frontend", 7 | "backend" 8 | ], 9 | "scripts": { 10 | "build:backend": "cd backend && yarn build", 11 | "deploy:backend": "cd backend && yarn deploy", 12 | "build:frontend": "cd frontend && yarn build", 13 | "deploy:frontend": "cd frontend && yarn deploy" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Deploy Custom Cloud Portal 3 | 4 | 1) [Setup Personal Access Token](https://github.com/settings/tokens) 5 | 2) [Create/Encrypt SSMParam Secrets](#Create/Encrypt-SSMParam-Secrets) 6 | 3) [Deploy Backend](#Deploy-Backend) 7 | 4) [Build & Run Local Frontend](#Build-&-Run-Local-Frontend) 8 | 9 | ### Create/Encrypt SSMParam Secrets 10 | ```bash 11 | REGION=us-east-1 12 | PROFILE=cloudguru 13 | aws ssm put-parameter \ 14 | --name /acg/portal/secrets/prod \ 15 | --type SecureString \ 16 | --value "$(cat secrets.json)" \ 17 | --overwrite \ 18 | --region $REGION \ 19 | --profile $PROFILE 20 | ``` 21 | 22 | ### Deploy Backend 23 | ```bash 24 | yarn install 25 | yarn build:backend 26 | yarn deploy:backend \ 27 | STAGE=prod \ 28 | STAGE_FLAG=prod \ 29 | REGION=us-east-1 \ 30 | PROFILE=cloudguru \ 31 | DEPLOY_BUCKET=acg-deploy-bucket \ 32 | GITHUB_USER=iDVB 33 | ``` 34 | 35 | ## Build & Run Local Frontend 36 | ```bash 37 | yarn start 38 | ``` 39 | 40 | ## Code Review 41 | 1) Stack-Repo Relation Tags 42 | 2) Feature: Create Branch 43 | 3) Feature: Delete Branch 44 | 45 | 46 | ## Improvement Ideas 47 | - Abstract Stacks, Repos and GitHub Actions into "Projects" 48 | - Have environment completion statuses take into account GitHub Action build status 49 | - Use DynamoDB to track relationships rather than just stack tags 50 | - Show all repos, and ablity to hide some from view 51 | - Add lighthouse test to build and be able to view historical results in the Cloud Portal 52 | - Add other stats to Cloud Portal page. Eg. GA, Epsigon, PagerDuty, Code Coverage etc 53 | 54 | ## Clean up 55 | - GitHub Access Token 56 | - GitHub Repo Secrets 57 | - SSM Params 58 | - Stacks 59 | - CloudWatch Logs 60 | - User, Roles 61 | - SSL Cert 62 | - KMS Key 63 | - Course Deployment bucket 64 | 65 | ### Delete All Log Groups 66 | `**WARNING** Be very careful, this will delete all the logs in your account. 67 | Be sure this is what you want to do before running.` 68 | ```bash 69 | REGION=us-east-1 70 | PROFILE=cloudguru 71 | aws logs describe-log-groups --query 'logGroups[*].logGroupName' --region $REGION --profile $PROFILE --output table | \ 72 | awk '{print $2}' | grep -v ^$ | while read x; do echo "deleting $x" ; aws logs delete-log-group --log-group-name $x --region $REGION --profile $PROFILE; done 73 | ``` 74 | [source](https://gist.github.com/pahud/1e875cb1252a622173cc2236be5c2963) 75 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "extends": [ 9 | "prettier" 10 | ], 11 | "plugins": [ 12 | "prettier" 13 | ], 14 | "parser": "babel-eslint", 15 | "rules": { 16 | "prettier/prettier": [ 17 | "error", 18 | { 19 | "singleQuote": true, 20 | "trailingComma": "all" 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/README.md: -------------------------------------------------------------------------------- 1 | 2 | ```bash 3 | REGION=us-east-1 4 | PROFILE=cloudguru 5 | sam local invoke \ 6 | -t .aws-sam/build/template.yaml \ 7 | -n tests/github/env.json \ 8 | -e tests/github/repository.json \ 9 | --region $REGION \ 10 | --profile $PROFILE \ 11 | LambdaGithub 12 | ``` 13 | 14 | ```bash 15 | make deploy STAGE=prod STAGE_FLAG=prod PROFILE=cloudguru 16 | ``` 17 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/functions/_common/utils/cache-service.js: -------------------------------------------------------------------------------- 1 | const NodeCache = require('node-cache'); 2 | 3 | class Cache { 4 | constructor(ttlSeconds) { 5 | this.cache = new NodeCache({ 6 | stdTTL: ttlSeconds, 7 | checkperiod: ttlSeconds * 0.2, 8 | useClones: false, 9 | }); 10 | 11 | this.ttl = ttlSeconds; 12 | } 13 | 14 | get(key, storeFunction) { 15 | if (this.ttl > 0) { 16 | const value = this.cache.get(key); 17 | if (value) { 18 | return Promise.resolve(value); 19 | } 20 | } 21 | return storeFunction().then(result => { 22 | this.ttl > 0 && this.cache.set(key, result, this.ttl); 23 | return result; 24 | }); 25 | } 26 | 27 | del(keys) { 28 | this.cache.del(keys); 29 | } 30 | 31 | setDefaultTtl(ttl) { 32 | this.ttl = ttl; 33 | this.ttl === 0 && this.flush(); 34 | } 35 | 36 | delStartWith(startStr = '') { 37 | if (!startStr) { 38 | return; 39 | } 40 | 41 | const keys = this.cache.keys(); 42 | for (const key of keys) { 43 | if (key.indexOf(startStr) === 0) { 44 | this.del(key); 45 | } 46 | } 47 | } 48 | 49 | flush() { 50 | this.cache.flushAll(); 51 | } 52 | } 53 | 54 | module.exports = Cache; 55 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/functions/github/utils/pageParser.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var octopage = {}; 3 | octopage.parser = parser; 4 | 5 | /** 6 | * @param {String} linkStr String from API's response in 'Link' field 7 | * @return {Object} 8 | */ 9 | function parser(linkStr) { 10 | return linkStr 11 | .split(',') 12 | .map(function(rel) { 13 | return rel.split(';').map(function(curr, idx) { 14 | if (idx === 0) return /[^_]page=(\d+)/.exec(curr)[1]; 15 | if (idx === 1) return /rel="(.+)"/.exec(curr)[1]; 16 | }); 17 | }) 18 | .reduce(function(obj, curr, i) { 19 | obj[curr[1]] = curr[0]; 20 | return obj; 21 | }, {}); 22 | } 23 | 24 | // Node.js / io.js 25 | if (typeof module !== 'undefined' && module.exports) { 26 | module.exports = octopage; 27 | } 28 | // AMD / RequireJS 29 | else if (typeof define !== 'undefined' && define.amd) { 30 | define([], function() { 31 | return octopage; 32 | }); 33 | } 34 | // browser side 35 | else { 36 | window.octopage = octopage; 37 | } 38 | })(this); 39 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acg-portal-api", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.19.2", 7 | "camelcase-keys": "^6.2.2", 8 | "commander": "^5.0.0", 9 | "lodash.find": "^4.6.0", 10 | "lodash.groupby": "^4.6.0", 11 | "node-cache": "^5.1.0", 12 | "tomlify-j0.4": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "@babel/cli": "^7.8.4", 16 | "@babel/core": "^7.9.0", 17 | "aws-sam-webpack-plugin": "^0.6.0", 18 | "aws-sdk": "^2.653.0", 19 | "babel-eslint": "^10.1.0", 20 | "babel-loader": "^8.1.0", 21 | "eslint": "^6.8.0", 22 | "eslint-config-prettier": "^6.10.1", 23 | "eslint-plugin-prettier": "^3.1.2", 24 | "prettier": "^2.0.4", 25 | "prettier-eslint": "^9.0.1", 26 | "rimraf": "^3.0.2", 27 | "webpack": "^4.42.1", 28 | "webpack-cli": "^3.3.11" 29 | }, 30 | "scripts": { 31 | "build": "webpack-cli", 32 | "clean": "rimraf .aws-sam .vscode", 33 | "prebuild": "rimraf .aws-sam .vscode", 34 | "prewatch": "rimraf .aws-sam .vscode", 35 | "watch": "webpack-cli -w", 36 | "getenv": "make getenv", 37 | "deploy": "make deploy getenv" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/cfn/allStacks.json: -------------------------------------------------------------------------------- 1 | { 2 | "field": "allStacks" 3 | } 4 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/cfn/getStack.json: -------------------------------------------------------------------------------- 1 | { 2 | "field": "getStack", 3 | "arguments": { 4 | "id": "arn:aws:cloudformation:us-east-1:645655324390:stack/acg-votingapp-backend-feat-demo/e868ea20-7c27-11ea-9099-0e58925d1f8e" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/github/Repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "field": "repository", 3 | "source": { 4 | "tags": [ 5 | { 6 | "key": "GIT_BRANCH", 7 | "value": "master" 8 | }, 9 | { 10 | "key": "SERVICE", 11 | "value": "acg-votingapp-backend" 12 | }, 13 | { 14 | "key": "GIT_REPOSITORY", 15 | "value": "course-mastering-aws-cloudformation" 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/github/branches.json: -------------------------------------------------------------------------------- 1 | { 2 | "field": "branches", 3 | "source": { 4 | "owner": { 5 | "login": "iDVB" 6 | }, 7 | "name": "middy-reroute" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/github/createBranch.json: -------------------------------------------------------------------------------- 1 | { 2 | "field": "createBranch", 3 | "arguments": { 4 | "repository": "course-mastering-aws-cloudformation", 5 | "name": "feat-grey", 6 | "sha": "bfd6f50ddc107dd838b4089a72e81fb2c40d6369" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/github/deleteBranch.json: -------------------------------------------------------------------------------- 1 | { 2 | "field": "deleteBranch", 3 | "arguments": { 4 | "repository": "course-mastering-aws-cloudformation", 5 | "name": "feat-cam" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/github/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "LambdaGithub": { 3 | "AWS_REGION": "us-east-1", 4 | "SSMSECRETS": "/acg/portal/secrets/prod", 5 | "GITHUB_USER": "iDVB" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/tests/github/getRepo.json: -------------------------------------------------------------------------------- 1 | { 2 | "field": "getRepo", 3 | "arguments": { 4 | "owner": "iDVB", 5 | "name": "middy-reroute" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/backend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const AwsSamPlugin = require('aws-sam-webpack-plugin'); 2 | 3 | const awsSamPlugin = new AwsSamPlugin(); 4 | 5 | module.exports = { 6 | // Loads the entry object from the AWS::Serverless::Function resources in your 7 | // SAM config. Setting this to a function will 8 | entry: () => awsSamPlugin.entry(), 9 | 10 | // Write the output to the .aws-sam/build folder 11 | output: { 12 | filename: '[name]/app.js', 13 | libraryTarget: 'commonjs2', 14 | path: __dirname + '/.aws-sam/build/', 15 | }, 16 | 17 | // Create source maps 18 | devtool: 'source-map', 19 | 20 | // Resolve .ts and .js extensions 21 | resolve: { 22 | extensions: ['.ts', '.js'], 23 | }, 24 | 25 | // Target node 26 | target: 'node', 27 | 28 | // AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce 29 | // the size of your deployment package. If you want to always include it then comment out this line. It has 30 | // been included conditionally because the node10.x docker image used by SAM local doesn't include it. 31 | externals: process.env.NODE_ENV === 'development' ? [] : ['aws-sdk'], 32 | 33 | // Set the webpack mode 34 | mode: process.env.NODE_ENV || 'production', 35 | 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.js$/, 40 | exclude: /node_modules/, 41 | use: [ 42 | { 43 | loader: 'babel-loader', 44 | }, 45 | ], 46 | }, 47 | ], 48 | }, 49 | 50 | // Add the AWS SAM Webpack plugin 51 | plugins: [awsSamPlugin], 52 | }; 53 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | **/ForkedMenu.js 2 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variable files 55 | .env* 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | package.json 3 | package-lock.json 4 | public 5 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/gatsby-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | module.exports = { 4 | siteMetadata: { 5 | title: `ACG Cloud Portal`, 6 | description: `Control your Cloud.`, 7 | author: `iDVB`, 8 | }, 9 | plugins: [ 10 | { 11 | resolve: `gatsby-plugin-material-ui`, 12 | options: { 13 | stylesProvider: { 14 | injectFirst: true, 15 | }, 16 | }, 17 | }, 18 | `gatsby-plugin-react-helmet`, 19 | { 20 | resolve: `gatsby-source-filesystem`, 21 | options: { 22 | name: `images`, 23 | path: `${__dirname}/src/images`, 24 | }, 25 | }, 26 | `gatsby-transformer-sharp`, 27 | `gatsby-plugin-sharp`, 28 | { 29 | resolve: `gatsby-plugin-manifest`, 30 | options: { 31 | name: `gatsby-starter-default`, 32 | short_name: `starter`, 33 | start_url: `/`, 34 | background_color: `#663399`, 35 | theme_color: `#663399`, 36 | display: `minimal-ui`, 37 | icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site. 38 | }, 39 | }, 40 | `gatsby-plugin-styled-components`, 41 | // this (optional) plugin enables Progressive Web App + Offline functionality 42 | // To learn more, visit: https://gatsby.dev/offline 43 | // `gatsby-plugin-offline`, 44 | ], 45 | } 46 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/gatsby-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Node APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/node-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | const React = require('react') 9 | const gitInfo = require('git-repo-info')() 10 | 11 | exports.onRenderBody = ({ setBodyAttributes }) => { 12 | setBodyAttributes({ 13 | 'data-version': gitInfo.abbreviatedSha, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acg-portal-frontend", 3 | "private": true, 4 | "description": "A simple starter to get up and developing quickly with Gatsby", 5 | "version": "0.1.0", 6 | "author": "Kyle Mathews ", 7 | "dependencies": { 8 | "@apollo/react-common": "^3.1.4", 9 | "@apollo/react-hooks": "^3.1.5", 10 | "@material-ui/core": "^4.9.10", 11 | "@material-ui/icons": "^4.9.1", 12 | "apollo-boost": "^0.4.7", 13 | "apollo-cache-inmemory": "^1.6.5", 14 | "apollo-client": "^2.6.8", 15 | "apollo-link": "^1.2.14", 16 | "aws-appsync-auth-link": "^2.0.2", 17 | "aws-appsync-subscription-link": "^2.1.0", 18 | "clsx": "^1.1.0", 19 | "gatsby": "^2.20.12", 20 | "gatsby-image": "^2.3.1", 21 | "gatsby-plugin-manifest": "^2.3.3", 22 | "gatsby-plugin-material-ui": "^2.1.6", 23 | "gatsby-plugin-offline": "^3.1.2", 24 | "gatsby-plugin-react-helmet": "^3.2.1", 25 | "gatsby-plugin-sharp": "^2.5.3", 26 | "gatsby-plugin-styled-components": "^3.2.1", 27 | "gatsby-source-filesystem": "^2.2.2", 28 | "gatsby-transformer-sharp": "^2.4.3", 29 | "git-repo-info": "^2.1.1", 30 | "lodash.find": "^4.6.0", 31 | "material-ui-popup-state": "^1.5.4", 32 | "prop-types": "^15.7.2", 33 | "react": "^16.12.0", 34 | "react-dom": "^16.13.1", 35 | "react-helmet": "^5.2.1", 36 | "react-is": "^16.13.1", 37 | "styled-components": "^5.1.0" 38 | }, 39 | "devDependencies": { 40 | "prettier": "^1.19.1" 41 | }, 42 | "keywords": [ 43 | "gatsby" 44 | ], 45 | "license": "MIT", 46 | "scripts": { 47 | "build": "gatsby build", 48 | "develop": "gatsby develop", 49 | "format": "prettier --write \"**/*.{js,jsx,json,md}\"", 50 | "start": "npm run develop", 51 | "serve": "gatsby serve", 52 | "clean": "gatsby clean", 53 | "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 58 | }, 59 | "bugs": { 60 | "url": "https://github.com/gatsbyjs/gatsby/issues" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/StackRowHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import TableCell from '@material-ui/core/TableCell' 4 | import TableRow from '@material-ui/core/TableRow' 5 | import IconButton from '@material-ui/core/IconButton' 6 | import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight' 7 | import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown' 8 | import GitHubIcon from '@material-ui/icons/GitHub' 9 | import BranchCreatorButton from './BranchCreatorButton' 10 | 11 | const StackRowHeader = ({ 12 | itemKey, 13 | groupedData: stackGroup, 14 | onToggleExpand, 15 | isExpanded, 16 | }) => { 17 | const group = stackGroup[itemKey] 18 | const repo = group[0].repository 19 | const repoUrl = repo && repo.html_url 20 | const isOther = itemKey === 'null' 21 | 22 | return ( 23 | 24 | 28 | 29 | {isExpanded ? : } 30 | 31 | {!isOther ? itemKey : 'Other'} 32 | 33 | 34 | {true && !isOther && } 35 | {repoUrl && ( 36 | 37 | 38 | 39 | 40 | 41 | )} 42 | 43 | 44 | ) 45 | } 46 | 47 | export default StackRowHeader 48 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/StackRowOptions.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import IconButton from '@material-ui/core/IconButton' 3 | import WebIcon from '@material-ui/icons/Web' 4 | import GitHubIcon from '@material-ui/icons/GitHub' 5 | import DeleteIcon from '@material-ui/icons/Delete' 6 | import { useMutation } from '@apollo/react-hooks' 7 | 8 | import DeleteBranch from '../graphql/mutations/DeleteBranch' 9 | 10 | const prohibitedBranches = ['master', 'prod'] 11 | 12 | const StackRowOptions = ({ cellItem: stack, ...rest }) => { 13 | const { siteUrl } = stack 14 | const { commitUrl } = stack.repository || {} 15 | const branch = stack.stage 16 | const [deleteBranch] = useMutation(DeleteBranch) 17 | 18 | const handleDelete = () => { 19 | const repository = stack.repository.name 20 | const name = branch 21 | deleteBranch({ variables: { repository, name } }) 22 | } 23 | 24 | const isNotProhibitedBranch = !prohibitedBranches.includes(branch) 25 | 26 | return ( 27 | 28 | {siteUrl && ( 29 | 30 | 31 | 32 | 33 | 34 | )} 35 | {commitUrl && ( 36 | 41 | 42 | 43 | 44 | 45 | )} 46 | {isNotProhibitedBranch && ( 47 | 48 | 49 | 50 | )} 51 | 52 | ) 53 | } 54 | 55 | export default StackRowOptions 56 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/StackStatusChip.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Button from '@material-ui/core/Button' 4 | import clsx from 'clsx' 5 | import { makeStyles } from '@material-ui/core/styles' 6 | 7 | import { STACK_STATUSES } from '../config/config' 8 | 9 | const StackStatusChip = ({ children, cellItem: stack }) => { 10 | const classes = useStyles() 11 | const statusType = STACK_STATUSES[stack.stackStatus] 12 | return ( 13 | 28 | ) 29 | } 30 | 31 | const useStyles = makeStyles(theme => ({ 32 | button: { 33 | '&$disabled': { 34 | color: 'white', 35 | boxShadow: 'none', 36 | opacity: 0.6, 37 | }, 38 | }, 39 | success: { 40 | '&$disabled': { 41 | background: theme.palette.success.dark, 42 | }, 43 | }, 44 | info: { 45 | '&$disabled': { 46 | background: theme.palette.info.dark, 47 | }, 48 | }, 49 | error: { 50 | '&$disabled': { 51 | background: theme.palette.error.dark, 52 | }, 53 | }, 54 | disabled: {}, 55 | })) 56 | 57 | StackStatusChip.propTypes = { 58 | children: PropTypes.string.isRequired, 59 | } 60 | 61 | export default StackStatusChip 62 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/header.js: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import PropTypes from 'prop-types' 3 | import React from 'react' 4 | 5 | const Header = ({ siteTitle }) => ( 6 |
12 |
19 |

20 | 27 | {siteTitle} 28 | 29 |

30 |
31 |
32 | ) 33 | 34 | Header.propTypes = { 35 | siteTitle: PropTypes.string, 36 | } 37 | 38 | Header.defaultProps = { 39 | siteTitle: ``, 40 | } 41 | 42 | export default Header 43 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/image.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { useStaticQuery, graphql } from "gatsby" 3 | import Img from "gatsby-image" 4 | 5 | /* 6 | * This component is built using `gatsby-image` to automatically serve optimized 7 | * images with lazy loading and reduced file sizes. The image is loaded using a 8 | * `useStaticQuery`, which allows us to load the image from directly within this 9 | * component, rather than having to pass the image data down from pages. 10 | * 11 | * For more information, see the docs: 12 | * - `gatsby-image`: https://gatsby.dev/gatsby-image 13 | * - `useStaticQuery`: https://www.gatsbyjs.org/docs/use-static-query/ 14 | */ 15 | 16 | const Image = () => { 17 | const data = useStaticQuery(graphql` 18 | query { 19 | placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) { 20 | childImageSharp { 21 | fluid(maxWidth: 300) { 22 | ...GatsbyImageSharpFluid 23 | } 24 | } 25 | } 26 | } 27 | `) 28 | 29 | return 30 | } 31 | 32 | export default Image 33 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/layout.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/layout.js: -------------------------------------------------------------------------------- 1 | // https://bit.ly/2RL1FuT - Migrate react-apollo from v2 to v3 in conjunction with AWS AppSync 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | import { useStaticQuery, graphql } from 'gatsby' 5 | 6 | import { createAuthLink } from 'aws-appsync-auth-link' 7 | import { createSubscriptionHandshakeLink } from 'aws-appsync-subscription-link' 8 | import { ApolloProvider } from '@apollo/react-common' 9 | import { ApolloLink } from 'apollo-link' 10 | import ApolloClient from 'apollo-client' 11 | import { InMemoryCache } from 'apollo-cache-inmemory' 12 | 13 | import ThemeProvider from '../context/context-theme' 14 | import Header from './header' 15 | import './layout.css' 16 | 17 | import { GRAPHQL_ENDPOINT, GRAPHQL_KEY, AWS_REGION } from '../config/config' 18 | 19 | const config = { 20 | url: GRAPHQL_ENDPOINT, 21 | region: AWS_REGION, 22 | auth: { 23 | type: 'API_KEY', 24 | apiKey: GRAPHQL_KEY, 25 | }, 26 | } 27 | 28 | const client = new ApolloClient({ 29 | link: ApolloLink.from([ 30 | createAuthLink(config), 31 | createSubscriptionHandshakeLink(config), 32 | ]), 33 | cache: new InMemoryCache(), 34 | defaultOptions: { 35 | watchQuery: { 36 | fetchPolicy: 'cache-and-network', 37 | }, 38 | }, 39 | }) 40 | 41 | const Layout = ({ children }) => { 42 | const data = useStaticQuery(graphql` 43 | query SiteTitleQuery { 44 | site { 45 | siteMetadata { 46 | title 47 | } 48 | } 49 | } 50 | `) 51 | return ( 52 | 53 | 54 |
55 |
62 |
{children}
63 |
64 | 65 | 66 | ) 67 | } 68 | 69 | Layout.propTypes = { 70 | children: PropTypes.node.isRequired, 71 | } 72 | 73 | export default Layout 74 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/components/seo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * Gatsby's useStaticQuery React hook 4 | * 5 | * See: https://www.gatsbyjs.org/docs/use-static-query/ 6 | */ 7 | 8 | import React from "react" 9 | import PropTypes from "prop-types" 10 | import Helmet from "react-helmet" 11 | import { useStaticQuery, graphql } from "gatsby" 12 | 13 | function SEO({ description, lang, meta, title }) { 14 | const { site } = useStaticQuery( 15 | graphql` 16 | query { 17 | site { 18 | siteMetadata { 19 | title 20 | description 21 | author 22 | } 23 | } 24 | } 25 | ` 26 | ) 27 | 28 | const metaDescription = description || site.siteMetadata.description 29 | 30 | return ( 31 | 72 | ) 73 | } 74 | 75 | SEO.defaultProps = { 76 | lang: `en`, 77 | meta: [], 78 | description: ``, 79 | } 80 | 81 | SEO.propTypes = { 82 | description: PropTypes.string, 83 | lang: PropTypes.string, 84 | meta: PropTypes.arrayOf(PropTypes.object), 85 | title: PropTypes.string.isRequired, 86 | } 87 | 88 | export default SEO 89 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/config/config.js: -------------------------------------------------------------------------------- 1 | export const GRAPHQL_ENDPOINT = process.env.GATSBY_GRAPH_Q_L 2 | export const GRAPHQL_KEY = process.env.GATSBY_A_P_I_KEY 3 | export const AWS_REGION = process.env.GATSBY_AWS_REGION 4 | export const STACK_STATUSES_SETTLED = { 5 | settled: { 6 | CREATE_COMPLETE: 'success', 7 | UPDATE_COMPLETE: 'success', 8 | IMPORT_COMPLETE: 'info', 9 | DELETE_COMPLETE: 'info', 10 | CREATE_FAILED: 'error', 11 | DELETE_FAILED: 'error', 12 | ROLLBACK_COMPLETE: 'error', 13 | ROLLBACK_FAILED: 'error', 14 | UPDATE_ROLLBACK_COMPLETE: 'error', 15 | UPDATE_ROLLBACK_FAILED: 'error', 16 | IMPORT_ROLLBACK_FAILED: 'error', 17 | IMPORT_ROLLBACK_COMPLETE: 'error', 18 | }, 19 | unsettled: { 20 | REVIEW_IN_PROGRESS: 'info', 21 | CREATE_IN_PROGRESS: 'info', 22 | UPDATE_COMPLETE_CLEANUP_IN_PROGRESS: 'info', 23 | UPDATE_IN_PROGRESS: 'info', 24 | DELETE_IN_PROGRESS: 'info', 25 | IMPORT_IN_PROGRESS: 'info', 26 | ROLLBACK_IN_PROGRESS: 'error', 27 | UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS: 'error', 28 | UPDATE_ROLLBACK_IN_PROGRESS: 'error', 29 | IMPORT_ROLLBACK_IN_PROGRESS: 'error', 30 | }, 31 | } 32 | 33 | export const STACK_STATUSES = { 34 | ...STACK_STATUSES_SETTLED.settled, 35 | ...STACK_STATUSES_SETTLED.unsettled, 36 | } 37 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/context/context-theme.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | createMuiTheme, 4 | responsiveFontSizes, 5 | ThemeProvider as MuiThemeProvider, 6 | } from '@material-ui/core/styles' 7 | 8 | const themeDark = responsiveFontSizes( 9 | createMuiTheme({ 10 | palette: { 11 | type: 'dark', 12 | }, 13 | overrides: { 14 | MuiFormLabel: { 15 | root: { 16 | color: '#fff', 17 | '&$focused, &$error': { 18 | color: '#fff', 19 | }, 20 | }, 21 | }, 22 | MuiInputBase: { 23 | input: { 24 | color: '#fff', 25 | }, 26 | }, 27 | MuiOutlinedInput: { 28 | root: { 29 | '& $notchedOutline': { 30 | borderColor: '#fff', 31 | }, 32 | '&$focused $notchedOutline': { 33 | borderColor: '#fff', 34 | }, 35 | '&$error $notchedOutline': { 36 | borderColor: '#f44336', 37 | }, 38 | }, 39 | }, 40 | MuiFormHelperText: { 41 | root: { 42 | '&$error': { 43 | color: '#fff', 44 | }, 45 | }, 46 | }, 47 | }, 48 | }), 49 | ) 50 | 51 | const ThemeProvider = ({ themeName = 'themeDark', children }) => { 52 | const theme = themes[themeName] 53 | return {children} 54 | } 55 | 56 | const themes = { 57 | themeDark: themeDark, 58 | } 59 | 60 | export default ThemeProvider 61 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/graphql/mutations/CreateBranch.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-boost' 2 | 3 | export default gql` 4 | mutation createBranch($repository: String!, $name: String!, $sha: String!) { 5 | createBranch(repository: $repository, name: $name, sha: $sha) { 6 | name 7 | sha 8 | url 9 | } 10 | } 11 | ` 12 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/graphql/mutations/DeleteBranch.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-boost' 2 | 3 | export default gql` 4 | mutation deleteBranch($repository: String!, $name: String!) { 5 | deleteBranch(repository: $repository, name: $name) { 6 | name 7 | url 8 | } 9 | } 10 | ` 11 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/graphql/queries/ListRepositories.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-boost' 2 | 3 | export default gql` 4 | query listRepositories { 5 | allRepos { 6 | id 7 | name 8 | html_url 9 | branches { 10 | name 11 | sha 12 | } 13 | stacks { 14 | stackName 15 | } 16 | } 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/graphql/queries/ListStacks.js: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-boost' 2 | 3 | export default gql` 4 | query listStacks { 5 | allStacks { 6 | stackId 7 | stackName 8 | description 9 | stackStatus 10 | service 11 | siteUrl 12 | serviceEndpoint 13 | serviceEndpointWebsocket 14 | websiteBucket 15 | stage 16 | stageFlag 17 | repository { 18 | id 19 | name 20 | commitUrl 21 | html_url 22 | branches { 23 | name 24 | sha 25 | } 26 | } 27 | creationTime 28 | lastUpdatedTime 29 | } 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/hooks/useInstance.js: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | 3 | const useInstance = (initialValueOrFunction = {}) => { 4 | const ref = useRef() 5 | if (!ref.current) { 6 | ref.current = 7 | typeof initialValueOrFunction === 'function' 8 | ? initialValueOrFunction() 9 | : initialValueOrFunction 10 | } 11 | return ref.current 12 | } 13 | 14 | export default useInstance 15 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/images/gatsby-astronaut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch08 - Putting it all together/frontend/src/images/gatsby-astronaut.png -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/images/gatsby-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch08 - Putting it all together/frontend/src/images/gatsby-icon.png -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Layout from "../components/layout" 4 | import SEO from "../components/seo" 5 | 6 | const NotFoundPage = () => ( 7 | 8 | 9 |

NOT FOUND

10 |

You just hit a route that doesn't exist... the sadness.

11 |
12 | ) 13 | 14 | export default NotFoundPage 15 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Layout from '../components/layout' 4 | import SEO from '../components/seo' 5 | import Stacks from '../components/Stacks' 6 | 7 | const IndexPage = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default IndexPage 17 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/frontend/src/pages/page-2.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Link } from "gatsby" 3 | 4 | import Layout from "../components/layout" 5 | import SEO from "../components/seo" 6 | 7 | const SecondPage = () => ( 8 | 9 | 10 |

Hi from the second page

11 |

Welcome to page 2

12 | Go back to the homepage 13 |
14 | ) 15 | 16 | export default SecondPage 17 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acg-portal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "workspaces": [ 6 | "frontend", 7 | "backend" 8 | ], 9 | "scripts": { 10 | "build:backend": "cd backend && yarn build", 11 | "deploy:backend": "cd backend && yarn deploy", 12 | "start": "cd frontend && yarn start" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch08 - Putting it all together/secrets.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "GITHUB_TOKEN": "BEST_KEY_EVER" 3 | } 4 | -------------------------------------------------------------------------------- /Ch09 - Other Tools/L01 - Frameworks/cdk.ts: -------------------------------------------------------------------------------- 1 | 2 | import cdk = require('@aws-cdk/core'); 3 | import { StaticSite } from './static-site'; 4 | 5 | class MyStaticSiteStack extends cdk.Stack { 6 | constructor(parent: cdk.App, name: string, props: cdk.StackProps) { 7 | super(parent, name, props); 8 | 9 | new StaticSite(this, 'StaticSite', { 10 | domainName: this.node.tryGetContext('domain'), 11 | siteSubDomain: this.node.tryGetContext('subdomain'), 12 | }); 13 | } 14 | } 15 | const app = new cdk.App(); 16 | new MyStaticSiteStack(app, 'MyStaticSite', { env: { 17 | region: 'us-east-1' 18 | }}); 19 | app.synth(); 20 | -------------------------------------------------------------------------------- /Ch09 - Other Tools/L01 - Frameworks/sam.yml: -------------------------------------------------------------------------------- 1 | Transform: AWS::Serverless-2016-10-31 2 | Resources: 3 | MyApi: 4 | Type: AWS::Serverless::Api 5 | Properties: 6 | StageName: Prod 7 | HelloWorldFunction: 8 | Type: AWS::Serverless::Function 9 | Properties: 10 | CodeUri: src/ 11 | Handler: index.handler 12 | Runtime: nodejs12.x 13 | Events: 14 | HelloWorld: 15 | Type: Api 16 | Properties: 17 | RestApiId: !Ref MyApi 18 | Path: / 19 | Method: get 20 | Outputs: 21 | HelloWorldUrl: 22 | Description: HelloWorldFunction Prod stage URL 23 | Value: !Sub https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ 24 | -------------------------------------------------------------------------------- /Ch09 - Other Tools/L01 - Frameworks/serverless.yml: -------------------------------------------------------------------------------- 1 | service: my-api 2 | 3 | provider: 4 | name: aws 5 | runtime: nodejs12.x 6 | 7 | functions: 8 | myFunction: 9 | handler: index.handler 10 | events: 11 | - http: 12 | path: api/public 13 | method: get 14 | cors: true 15 | -------------------------------------------------------------------------------- /Ch09 - Other Tools/L02 - CFN Registry & CLI/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACloudGuru-Resources/course-mastering-aws-cloudformation/dbbd92a04b75f828464364c88095412b1fb51bb6/Ch09 - Other Tools/L02 - CFN Registry & CLI/README.md -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acg-mastering-cloudformation", 3 | "version": "1.0.0", 4 | "description": "A Cloud Guru Course: Mastering CloudFormation", 5 | "main": "index.js", 6 | "scripts": { 7 | "test:lint": "eslint .", 8 | "test:unit": "jest --verbose", 9 | "test:unit:watch": "jest --verbose --watch", 10 | "test": "npm run test:lint && npm run test:unit", 11 | "jest": "jest" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/iDVB/acg-mastering-cloudformation.git" 16 | }, 17 | "author": "Dan Van Brunt (https://github.com/iDVB)", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/iDVB/acg-mastering-cloudformation/issues" 21 | }, 22 | "homepage": "https://github.com/iDVB/acg-mastering-cloudformation#readme", 23 | "dependencies": { 24 | "axios": "^0.19.0", 25 | "babel-eslint": "^10.0.3", 26 | "clone": "^2.1.2" 27 | }, 28 | "devDependencies": { 29 | "aws-sdk": "^2.479.0", 30 | "eslint": "^5.16.0", 31 | "eslint-config-prettier": "^5.0.0", 32 | "eslint-plugin-import": "^2.17.3", 33 | "eslint-plugin-prettier": "^3.1.0", 34 | "jest": "^24.8.0", 35 | "prettier": "^1.18.2", 36 | "prettier-eslint": "^9.0.0" 37 | }, 38 | "jest": { 39 | "collectCoverageFrom": [ 40 | "**/src/**/*.test.js", 41 | "!node_modules" 42 | ], 43 | "testEnvironment": "node" 44 | } 45 | } 46 | --------------------------------------------------------------------------------