From 4a8c15ce738285d0d34e91024e364c83a3770f38 Mon Sep 17 00:00:00 2001 From: Mike Roberts Date: Tue, 2 Aug 2022 17:48:05 -0400 Subject: [PATCH] First public commit --- .github/actions/deploy/action.yaml | 25 + .github/workflows/deploy-pr-to-test.yaml | 27 + .github/workflows/deploy-to-prod.yaml | 32 + .gitignore | 2 + README.md | 47 ++ cdk.json | 3 + deploy.sh | 9 + package-lock.json | 758 +++++++++++++++++++++++ package.json | 18 + src/cdk/cdkApp.ts | 30 + src/cdk/envProps.ts | 76 +++ src/cloudfront/preProcessFunction.js | 51 ++ src/site/foo/index.html | 5 + src/site/index.html | 5 + tsconfig.json | 9 + 15 files changed, 1097 insertions(+) create mode 100644 .github/actions/deploy/action.yaml create mode 100644 .github/workflows/deploy-pr-to-test.yaml create mode 100644 .github/workflows/deploy-to-prod.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cdk.json create mode 100755 deploy.sh create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/cdk/cdkApp.ts create mode 100644 src/cdk/envProps.ts create mode 100644 src/cloudfront/preProcessFunction.js create mode 100644 src/site/foo/index.html create mode 100644 src/site/index.html create mode 100644 tsconfig.json diff --git a/.github/actions/deploy/action.yaml b/.github/actions/deploy/action.yaml new file mode 100644 index 0000000..58a35a9 --- /dev/null +++ b/.github/actions/deploy/action.yaml @@ -0,0 +1,25 @@ +name: Deploy + +description: Deploy + +inputs: + aws-role-to-assume: + description: Which Role to assume for Github OIDC + required: true + +runs: + using: 'composite' + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ inputs.aws-role-to-assume }} + aws-region: us-east-1 + + # If you had a build step for your website you would include it here + # For this example we're just using the content in the source repo as-is + + - run: > + npm install && + ./deploy.sh + shell: bash diff --git a/.github/workflows/deploy-pr-to-test.yaml b/.github/workflows/deploy-pr-to-test.yaml new file mode 100644 index 0000000..b1d3481 --- /dev/null +++ b/.github/workflows/deploy-pr-to-test.yaml @@ -0,0 +1,27 @@ +name: Deploy PR to Test + +on: + pull_request: + +# All PRs share the same test stack, so only run this workflow once at any time +concurrency: ${{ github.workflow }} + +# Required because we are using OIDC to connect to AWS +permissions: + id-token: write + contents: read + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + # This will automatically checkout the correct branch + - uses: actions/checkout@v3 + + # Call the custom action this workflow shares with the release workflow + - uses: ./.github/actions/deploy + with: + # You would likely change the name of this secret + aws-role-to-assume: ${{ secrets.TEST_ACTIONS_ROLE_ARN }} \ No newline at end of file diff --git a/.github/workflows/deploy-to-prod.yaml b/.github/workflows/deploy-to-prod.yaml new file mode 100644 index 0000000..181700f --- /dev/null +++ b/.github/workflows/deploy-to-prod.yaml @@ -0,0 +1,32 @@ +name: Deploy Production + +on: + push: + branches: + - main + workflow_dispatch: + +# Only allow one run at a time for this workflow +# See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency +# This workflow will only run once per this workflow name, and per ref (which is the branch or tag) +concurrency: ${{ github.workflow }}-${{ github.ref }} + +# Required because we are using OIDC to connect to AWS +permissions: + id-token: write + contents: read + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + + steps: + # This will automatically checkout the correct branch + - uses: actions/checkout@v3 + + # Call the custom action this workflow shares with the PR workflow + - uses: ./.github/actions/deploy + with: + # You would likely change the name of this secret + aws-role-to-assume: ${{ secrets.OPENSOURCE_ACTIONS_ROLE_ARN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b8e2c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cdk.out +node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5cebb91 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Coffee Store Web Demo - real example of a deployed website on AWS + +_"Production-ready websites on AWS with slightly less faff"_ + +This is a "real" instance of the Coffee Store Web repo (NB - https://github.com/symphoniacloud/coffee-store-web is currently still an old version - it will be updated soon.) + +This repository is deployed using the included Github Actions workflows to https://www.coffeestorewebdemo.symphonia.io/ . + +This demo shows various techniques: + +* Deploying a website on AWS with CDK using Symphonia's [`cdk-website`](https://github.com/symphoniacloud/cdk-website) construct + * Use multiple custom domain names in production (apex domain, and `www.` domain), and single custom domains elsewhere. + * Use caching behavior and invalidations in production; use a pass-through cache without invalidations in development and test. +* Use a CloudFront Function for URL manipulation and redirects +* Use the target AWS account ID to drive CDK configuration +* Github Actions Workflows to automatically deploy to production account from `main`, and to test account from Pull Requests + * Use Github OIDC for AWS access + * Use a custom action for shared behavior between workflows + +## How to use + +This is not a "200 level" example. :) I'm assuming that if you're going to use this example then you understand CDK, deploying to AWS, and the basics of websites, certificates, and DNS. + +Overall I recommend you review the entire contents of this repo. Particularly relevant files are: + +* [src/cdk/*](src/cdk) - The CDK definition files for the application +* [src/cloudfront/preProcessFunction.js](src/cloudfront/preProcessFunction.js) - A CloudFront Function that will be run for every request +* [src/site/*](src/site) - Sample site content - you will want to replace this with your own, or generate content at build time +* [.github/*](.github) - Github Actions configuration for PR (test) and production deployment + +Many of the specific elements in this repo are custom, since it is an actual deployed site. Elements that you will likely want to change if you are copying it are: + +* The [per-environment properties](src/cdk/envProps.ts) will drastically change. + * Note the main `CoffeeStoreWebDemoStackPropsPerEnv` constant in that file is keyed by account ID, so these will have to change. + * Also you'll want to change custom domain names, certificate ARN import details, and hosted zone names (assuming you want to automatically update DNS) + * For more on this, see the [documentation for the `cdk-website` construct](https://github.com/symphoniacloud/cdk-website) +* You will want to change `DEFAULT_STACK_NAME` in [cdkApp.ts](src/cdk/cdkApp.ts) . You may also want to be able to change the stack name outside of that file, but that's beyond the scope of this demo +* If you are generating any content before deployment you will either want to update [deploy.sh](deploy.sh), or update the [custom Github Actions action](.github/actions/deploy/action.yaml) to also build and not just deploy. + * You'll probably also want to change `content` -> `path` in [cdkApp.ts](src/cdk/cdkApp.ts) +* If you are just using static content in the repo, then change the contents of [src/site](src/site) +* Consider the ["pre process" CloudFront function](src/cloudfront/preProcessFunction.js): + * Either delete it, and remove the `preProcessFunctionCode` property in [cdkApp.ts](src/cdk/cdkApp.ts) ... + * ... or update the `manualRedirects` value in [preProcessFunction.js](src/cloudfront/preProcessFunction.js) +* If you want to use the Github Actions workflows: + * You'll need to deploy the Github OIDC resources if you haven't done so already. See [here](https://github.com/symphoniacloud/coffee-store-v2/tree/main/github-actions-prereqs) for an example. + * You'll also want to save the relevant role ARNs as secrets in Github Actions, and change `OPENSOURCE_ACTIONS_ROLE_ARN` in [deploy-to-prod.yaml](.github/workflows/deploy-to-prod.yaml) and `TEST_ACTIONS_ROLE_ARN` in [deploy-pr-to-test.yaml](.github/workflows/deploy-pr-to-test.yaml) +* If you don't want to use Github Actions, then delete the [.github](.github) directory. \ No newline at end of file diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..07bb7d9 --- /dev/null +++ b/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node --prefer-ts-exts src/cdk/cdkApp.ts" +} diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..3b56bc8 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -euo pipefail + +# Assumes CDK has been bootstrapped in the current account + region + +# As is, this example uses the default stack name (DEFAULT_STACK_NAME) in the CDK App source +# If you want to override the stack name, then add `--context stackName=YOUR_STACK_NAME` here +npx cdk deploy --require-approval never diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..79dd852 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,758 @@ +{ + "name": "cdk-website-basic-example", + "version": "2022.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cdk-website-basic-example", + "version": "2022.1.0", + "devDependencies": { + "@symphoniacloud/cdk-website": "0.0.6", + "@tsconfig/node16": "1.x", + "@types/node": "^16.x", + "aws-cdk": "2.x", + "aws-cdk-lib": "2.x", + "constructs": "10.x", + "ts-node": "10.x", + "typescript": "4.x" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@symphoniacloud/cdk-website": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@symphoniacloud/cdk-website/-/cdk-website-0.0.6.tgz", + "integrity": "sha512-6Kpmmeqh6LBwn7vmuEIwQwWzhL+xmXKTWqJsU7MrCLJ12oalN2r+ZlcC9nmEecqQjC2aOMqRGy3K3mdKeQyJYg==", + "dev": true, + "peerDependencies": { + "aws-cdk-lib": "^2.30.0", + "constructs": "^10.0.5" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.11.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.46.tgz", + "integrity": "sha512-x+sfpb2dMrhCQPL4NAGs64Z9hh0t72aP0dg+PuZidmPr/0Gj5ELQTjD/t46dq3DF/8ZvSHOaIyDIbAsdPshyVQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/aws-cdk": { + "version": "2.33.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.33.0.tgz", + "integrity": "sha512-pwqqXzdkyBraKARdyZ6OqMIbEHlnM0x5LJ08zFYQGcrC+jk/e3Y3XWGJrapC6sAKNwQNF78dcJbTSNBjjLXMdQ==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.33.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.33.0.tgz", + "integrity": "sha512-+aV6+P3RROFndkw9/mtXCciL1RL2tHssju6kgwmml0XIqcnjJ8qyCR23fE8MEq49Q+6dQ8sBN2HtrdKHw/sgnw==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "yaml" + ], + "dev": true, + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^9.1.0", + "ignore": "^5.2.0", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.1.1", + "semver": "^7.3.7", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/at-least-node": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "dev": true, + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.10", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.3.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constructs": { + "version": "10.1.59", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.1.59.tgz", + "integrity": "sha512-dzU21oJthIjjFmEm07Mew1CNRseHrdIufbIEBSqa9x5qPtrqEJHl5Z8XstrPrb4jDQAJ8qwzG/XcOEAjOIwIrw==", + "dev": true, + "engines": { + "node": ">= 14.17.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@symphoniacloud/cdk-website": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@symphoniacloud/cdk-website/-/cdk-website-0.0.6.tgz", + "integrity": "sha512-6Kpmmeqh6LBwn7vmuEIwQwWzhL+xmXKTWqJsU7MrCLJ12oalN2r+ZlcC9nmEecqQjC2aOMqRGy3K3mdKeQyJYg==", + "dev": true, + "requires": {} + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "@types/node": { + "version": "16.11.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.46.tgz", + "integrity": "sha512-x+sfpb2dMrhCQPL4NAGs64Z9hh0t72aP0dg+PuZidmPr/0Gj5ELQTjD/t46dq3DF/8ZvSHOaIyDIbAsdPshyVQ==", + "dev": true + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "aws-cdk": { + "version": "2.33.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.33.0.tgz", + "integrity": "sha512-pwqqXzdkyBraKARdyZ6OqMIbEHlnM0x5LJ08zFYQGcrC+jk/e3Y3XWGJrapC6sAKNwQNF78dcJbTSNBjjLXMdQ==", + "dev": true, + "requires": { + "fsevents": "2.3.2" + } + }, + "aws-cdk-lib": { + "version": "2.33.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.33.0.tgz", + "integrity": "sha512-+aV6+P3RROFndkw9/mtXCciL1RL2tHssju6kgwmml0XIqcnjJ8qyCR23fE8MEq49Q+6dQ8sBN2HtrdKHw/sgnw==", + "dev": true, + "requires": { + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^9.1.0", + "ignore": "^5.2.0", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.1.1", + "semver": "^7.3.7", + "yaml": "1.10.2" + }, + "dependencies": { + "@balena/dockerignore": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "case": { + "version": "1.6.3", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "bundled": true, + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "bundled": true, + "dev": true + }, + "ignore": { + "version": "5.2.0", + "bundled": true, + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonschema": { + "version": "1.4.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "punycode": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "7.3.7", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yaml": { + "version": "1.10.2", + "bundled": true, + "dev": true + } + } + }, + "constructs": { + "version": "10.1.59", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.1.59.tgz", + "integrity": "sha512-dzU21oJthIjjFmEm07Mew1CNRseHrdIufbIEBSqa9x5qPtrqEJHl5Z8XstrPrb4jDQAJ8qwzG/XcOEAjOIwIrw==", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..582f040 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "cdk-website-basic-example", + "version": "2022.1.0", + "private": true, + "devDependencies": { + "@tsconfig/node16": "1.x", + "@types/node": "^16.x", + "aws-cdk": "2.x", + "aws-cdk-lib": "2.x", + "@symphoniacloud/cdk-website": "0.0.6", + "constructs": "10.x", + "ts-node": "10.x", + "typescript": "4.x" + }, + "scripts": { + "deploy": "npx cdk deploy --require-approval never" + } +} diff --git a/src/cdk/cdkApp.ts b/src/cdk/cdkApp.ts new file mode 100644 index 0000000..e1b9a8d --- /dev/null +++ b/src/cdk/cdkApp.ts @@ -0,0 +1,30 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import {App, Stack} from 'aws-cdk-lib'; +import {Construct} from 'constructs'; +import {Website} from "@symphoniacloud/cdk-website"; +import {CoffeeStoreWebDemoStackProps, getPropsForDefaultAWSEnvironment} from "./envProps"; + +const DEFAULT_STACK_NAME = 'coffee-store-web-demo' + +class CoffeeStoreWebDemo extends Stack { + constructor(scope: Construct, id: string, props: CoffeeStoreWebDemoStackProps) { + super(scope, id, props); + + new Website(this, 'Website', { + customDomain: props.customDomain, + content: { + // If you build your site before deployment then change this path to that of your build output + path: 'src/site', + performCacheInvalidation: props.performCacheInvalidation + }, + preProcessFunctionCode: {fromPath: 'src/cloudfront/preProcessFunction.js'}, + additionalDefaultBehaviorOptions: props.additionalDefaultBehaviorOptions + }) + } +} + +const app = new App(); +const stackName = app.node.tryGetContext('stackName') || DEFAULT_STACK_NAME + +new CoffeeStoreWebDemo(app, 'CoffeeStoreWebDemo', getPropsForDefaultAWSEnvironment(stackName)); \ No newline at end of file diff --git a/src/cdk/envProps.ts b/src/cdk/envProps.ts new file mode 100644 index 0000000..0562c89 --- /dev/null +++ b/src/cdk/envProps.ts @@ -0,0 +1,76 @@ +import {Environment, Fn, StackProps} from "aws-cdk-lib"; +import {BehaviorOptions, CachePolicy} from "aws-cdk-lib/aws-cloudfront"; +import {WebsiteCustomDomain} from "@symphoniacloud/cdk-website"; + +export interface CoffeeStoreWebDemoStackProps extends StackProps { + customDomain: WebsiteCustomDomain + performCacheInvalidation?: boolean + additionalDefaultBehaviorOptions?: Omit; +} + +const CoffeeStoreWebDemoStackPropsPerEnv: Record = { + // Opensource account - this is this demo's "prod" account + '073101298092': { + customDomain: { + // For this demo we want to serve in production on both the "apex" domain, and "www." + domains: [{ + domainName: 'coffeestorewebdemo.symphonia.io', + hostedZone: {fromDomainName: 'coffeestorewebdemo.symphonia.io'}, + }, { + domainName: 'www.coffeestorewebdemo.symphonia.io', + hostedZone: {fromDomainName: 'coffeestorewebdemo.symphonia.io'}, + }], + // The certificate is deployed in a different stack, and the ARN is exported, which we import here + certificate: {fromArn: Fn.importValue('CoffeeStoreWebDemoCertificate')} + }, + // In production we want to invalidate the CloudFront cache in this demo - you might not want the same + // behavior since for frequent deployments this may cost money + performCacheInvalidation: true + }, + // Test + '443780941070': { + // For test and development we only serve on one domain name because of a shared DNS hosted zone + customDomain: { + domainName: 'coffeestorewebdemo.test.symphonia.io', + hostedZone: {fromDomainName: 'test.symphonia.io'}, + certificate: {fromArn: Fn.importValue('StandardCertificate')} + }, + // In test and dev we don't invalidate, but we also turn off all caching behavior + additionalDefaultBehaviorOptions: { + cachePolicy: CachePolicy.CACHING_DISABLED + } + }, + // Mike's dev account - this models an "account per development" strategy + '397589511426': { + customDomain: { + domainName: 'coffeestorewebdemo.mike.symphonia.io', + hostedZone: {fromDomainName: 'mike.symphonia.io'}, + certificate: {fromArn: Fn.importValue('StandardCertificate')}, + }, + additionalDefaultBehaviorOptions: { + cachePolicy: CachePolicy.CACHING_DISABLED + } + }, +} + +export function getPropsForDefaultAWSEnvironment(stackName: string): CoffeeStoreWebDemoStackProps { + const env = calcEnvironment() + const appProps = CoffeeStoreWebDemoStackPropsPerEnv[env.account] + + if (!appProps) + throw new Error(`No env props for ${JSON.stringify(env)}`) + + return { + env, stackName, ...appProps + } +} + +export function calcEnvironment(): Required { + const account = process.env.CDK_DEFAULT_ACCOUNT + const region = process.env.CDK_DEFAULT_REGION + + if (account && region) + return {account, region} + + throw new Error('Unable to read CDK_DEFAULT_ACCOUNT or CDK_DEFAULT_REGION') +} \ No newline at end of file diff --git a/src/cloudfront/preProcessFunction.js b/src/cloudfront/preProcessFunction.js new file mode 100644 index 0000000..7f12577 --- /dev/null +++ b/src/cloudfront/preProcessFunction.js @@ -0,0 +1,51 @@ +// Modified version of https://github.com/CloudUnder/lambda-edge-nice-urls +// noinspection ES6ConvertVarToLetConst + +// Using vars because CloudFront Functions' Javascript runtime doesn't support consts + +// Replace this map with your own (or set to an empty map) +var manualRedirects = { + "/symphonia":"https://symphonia.io", +} + +// Change this if you want to use a different default object name +var defaultObject = 'index.html' + +// See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/writing-function-code.html +function handler(event) { + var request = event.request; + var uri = request.uri; + + // If the request matches an entry in our "manualRedirects" map, then REDIRECT to that + var manualRedirect = manualRedirects[uri]; + if (manualRedirect) + return createRedirect(manualRedirect); + + // For non-root directory requests ending in "/", then REDIRECT to same URI but without trailing slash + // E.g. redirect https://coffeestorewebdemo.symphonia.io/foo/ to https://coffeestorewebdemo.symphonia.io/foo + if (uri.match(/.+\/$/)) + return createRedirect(uri.slice(0, -1)) + + // CloudFront doesn't allow default objects other than at the root directory + // So, if a path has no extension (and is not the root) then treat as a subdirectory and MODIFY REQUEST (**don't** redirect) + // to add "/index.html" to end + if (uri.match(/\/[^/.]+$/)) { + request.uri = uri + '/' + defaultObject; + return request; + } + + // If nothing matches, return request unchanged + return request; +} + +function createRedirect(location) { + return { + headers: { + location: { + value: location + } + }, + statusCode: 301, + statusDescription: 'Moved Permanently' + }; +} diff --git a/src/site/foo/index.html b/src/site/foo/index.html new file mode 100644 index 0000000..6d2714b --- /dev/null +++ b/src/site/foo/index.html @@ -0,0 +1,5 @@ + + +

Hello Coffee World! This demonstrates getting content in a sub-directory

+ + \ No newline at end of file diff --git a/src/site/index.html b/src/site/index.html new file mode 100644 index 0000000..f05eac9 --- /dev/null +++ b/src/site/index.html @@ -0,0 +1,5 @@ + + +

Hello Coffee World!

+ + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7d7877a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@tsconfig/node16/tsconfig.json", + "compilerOptions": { + "noEmit": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "strictNullChecks": true + } +} \ No newline at end of file