├── .eslintrc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ └── upgrade-main.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .mergify.yml ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.js ├── API.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cdk.json ├── images └── AlbFargateServices.svg ├── package.json ├── services ├── CommonService │ ├── Dockerfile │ ├── go.mod │ ├── go.sum │ └── main.go └── OrderService │ ├── Dockerfile │ ├── go.mod │ ├── go.sum │ └── main.go ├── src ├── alb-fargate-svcs.ts ├── cdknag.ts ├── common │ └── common-functions.ts ├── index.ts ├── integ.default.ts └── main.ts ├── test ├── __snapshots__ │ └── default.test.ts.snap └── default.test.ts ├── tsconfig.dev.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true, 4 | "node": true 5 | }, 6 | "root": true, 7 | "plugins": [ 8 | "@typescript-eslint", 9 | "import" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": 2018, 14 | "sourceType": "module", 15 | "project": "./tsconfig.dev.json" 16 | }, 17 | "extends": [ 18 | "plugin:import/typescript" 19 | ], 20 | "settings": { 21 | "import/parsers": { 22 | "@typescript-eslint/parser": [ 23 | ".ts", 24 | ".tsx" 25 | ] 26 | }, 27 | "import/resolver": { 28 | "node": {}, 29 | "typescript": { 30 | "project": "./tsconfig.dev.json", 31 | "alwaysTryTypes": true 32 | } 33 | } 34 | }, 35 | "ignorePatterns": [ 36 | "*.js", 37 | "!.projenrc.js", 38 | "*.d.ts", 39 | "node_modules/", 40 | "*.generated.ts", 41 | "coverage" 42 | ], 43 | "rules": { 44 | "indent": [ 45 | "off" 46 | ], 47 | "@typescript-eslint/indent": [ 48 | "error", 49 | 2 50 | ], 51 | "quotes": [ 52 | "error", 53 | "single", 54 | { 55 | "avoidEscape": true 56 | } 57 | ], 58 | "comma-dangle": [ 59 | "error", 60 | "always-multiline" 61 | ], 62 | "comma-spacing": [ 63 | "error", 64 | { 65 | "before": false, 66 | "after": true 67 | } 68 | ], 69 | "no-multi-spaces": [ 70 | "error", 71 | { 72 | "ignoreEOLComments": false 73 | } 74 | ], 75 | "array-bracket-spacing": [ 76 | "error", 77 | "never" 78 | ], 79 | "array-bracket-newline": [ 80 | "error", 81 | "consistent" 82 | ], 83 | "object-curly-spacing": [ 84 | "error", 85 | "always" 86 | ], 87 | "object-curly-newline": [ 88 | "error", 89 | { 90 | "multiline": true, 91 | "consistent": true 92 | } 93 | ], 94 | "object-property-newline": [ 95 | "error", 96 | { 97 | "allowAllPropertiesOnSameLine": true 98 | } 99 | ], 100 | "keyword-spacing": [ 101 | "error" 102 | ], 103 | "brace-style": [ 104 | "error", 105 | "1tbs", 106 | { 107 | "allowSingleLine": true 108 | } 109 | ], 110 | "space-before-blocks": [ 111 | "error" 112 | ], 113 | "curly": [ 114 | "error", 115 | "multi-line", 116 | "consistent" 117 | ], 118 | "@typescript-eslint/member-delimiter-style": [ 119 | "error" 120 | ], 121 | "semi": [ 122 | "error", 123 | "always" 124 | ], 125 | "max-len": [ 126 | "error", 127 | { 128 | "code": 150, 129 | "ignoreUrls": true, 130 | "ignoreStrings": true, 131 | "ignoreTemplateLiterals": true, 132 | "ignoreComments": true, 133 | "ignoreRegExpLiterals": true 134 | } 135 | ], 136 | "quote-props": [ 137 | "error", 138 | "consistent-as-needed" 139 | ], 140 | "@typescript-eslint/no-require-imports": [ 141 | "error" 142 | ], 143 | "import/no-extraneous-dependencies": [ 144 | "error", 145 | { 146 | "devDependencies": [ 147 | "**/test/**", 148 | "**/build-tools/**" 149 | ], 150 | "optionalDependencies": false, 151 | "peerDependencies": true 152 | } 153 | ], 154 | "import/no-unresolved": [ 155 | "error" 156 | ], 157 | "import/order": [ 158 | "warn", 159 | { 160 | "groups": [ 161 | "builtin", 162 | "external" 163 | ], 164 | "alphabetize": { 165 | "order": "asc", 166 | "caseInsensitive": true 167 | } 168 | } 169 | ], 170 | "no-duplicate-imports": [ 171 | "error" 172 | ], 173 | "no-shadow": [ 174 | "off" 175 | ], 176 | "@typescript-eslint/no-shadow": [ 177 | "error" 178 | ], 179 | "key-spacing": [ 180 | "error" 181 | ], 182 | "no-multiple-empty-lines": [ 183 | "error" 184 | ], 185 | "@typescript-eslint/no-floating-promises": [ 186 | "error" 187 | ], 188 | "no-return-await": [ 189 | "off" 190 | ], 191 | "@typescript-eslint/return-await": [ 192 | "error" 193 | ], 194 | "no-trailing-spaces": [ 195 | "error" 196 | ], 197 | "dot-notation": [ 198 | "error" 199 | ], 200 | "no-bitwise": [ 201 | "error" 202 | ], 203 | "@typescript-eslint/member-ordering": [ 204 | "error", 205 | { 206 | "default": [ 207 | "public-static-field", 208 | "public-static-method", 209 | "protected-static-field", 210 | "protected-static-method", 211 | "private-static-field", 212 | "private-static-method", 213 | "field", 214 | "constructor", 215 | "method" 216 | ] 217 | } 218 | ] 219 | }, 220 | "overrides": [ 221 | { 222 | "files": [ 223 | ".projenrc.js" 224 | ], 225 | "rules": { 226 | "@typescript-eslint/no-require-imports": "off", 227 | "import/no-extraneous-dependencies": "off" 228 | } 229 | } 230 | ] 231 | } 232 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | *.snap linguist-generated 4 | /.eslintrc.json linguist-generated 5 | /.gitattributes linguist-generated 6 | /.github/pull_request_template.md linguist-generated 7 | /.github/workflows/auto-approve.yml linguist-generated 8 | /.github/workflows/build.yml linguist-generated 9 | /.github/workflows/pull-request-lint.yml linguist-generated 10 | /.github/workflows/release.yml linguist-generated 11 | /.github/workflows/upgrade-main.yml linguist-generated 12 | /.gitignore linguist-generated 13 | /.gitpod.yml linguist-generated 14 | /.mergify.yml linguist-generated 15 | /.npmignore linguist-generated 16 | /.projen/** linguist-generated 17 | /.projen/deps.json linguist-generated 18 | /.projen/files.json linguist-generated 19 | /.projen/tasks.json linguist-generated 20 | /API.md linguist-generated 21 | /package.json linguist-generated 22 | /tsconfig.dev.json linguist-generated 23 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'pahud') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | outputs: 13 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 14 | env: 15 | CI: "true" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Install dependencies 23 | run: yarn install --check-files 24 | - name: build 25 | run: npx projen build 26 | - name: Find mutations 27 | id: self_mutation 28 | run: |- 29 | git add . 30 | git diff --staged --patch --exit-code > .repo.patch || echo "::set-output name=self_mutation_happened::true" 31 | - name: Upload patch 32 | if: steps.self_mutation.outputs.self_mutation_happened 33 | uses: actions/upload-artifact@v2 34 | with: 35 | name: .repo.patch 36 | path: .repo.patch 37 | - name: Fail build on mutation 38 | if: steps.self_mutation.outputs.self_mutation_happened 39 | run: |- 40 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 41 | cat .repo.patch 42 | exit 1 43 | - name: Backup artifact permissions 44 | run: cd dist && getfacl -R . > permissions-backup.acl 45 | continue-on-error: true 46 | - name: Upload artifact 47 | uses: actions/upload-artifact@v2.1.1 48 | with: 49 | name: build-artifact 50 | path: dist 51 | container: 52 | image: jsii/superchain:1-buster-slim-node14 53 | self-mutation: 54 | needs: build 55 | runs-on: ubuntu-latest 56 | permissions: 57 | contents: write 58 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v3 62 | with: 63 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 64 | ref: ${{ github.event.pull_request.head.ref }} 65 | repository: ${{ github.event.pull_request.head.repo.full_name }} 66 | - name: Download patch 67 | uses: actions/download-artifact@v3 68 | with: 69 | name: .repo.patch 70 | path: ${{ runner.temp }} 71 | - name: Apply patch 72 | run: '[ -s ${{ runner.temp }}/.repo.patch ] && git apply ${{ runner.temp }}/.repo.patch || echo "Empty patch. Skipping."' 73 | - name: Set git identity 74 | run: |- 75 | git config user.name "github-actions" 76 | git config user.email "github-actions@github.com" 77 | - name: Push changes 78 | run: |2- 79 | git add . 80 | git commit -s -m "chore: self mutation" 81 | git push origin HEAD:${{ github.event.pull_request.head.ref }} 82 | package-js: 83 | needs: build 84 | runs-on: ubuntu-latest 85 | permissions: {} 86 | if: "! needs.build.outputs.self_mutation_happened" 87 | steps: 88 | - uses: actions/setup-node@v3 89 | with: 90 | node-version: 14.x 91 | - name: Download build artifacts 92 | uses: actions/download-artifact@v3 93 | with: 94 | name: build-artifact 95 | path: dist 96 | - name: Restore build artifact permissions 97 | run: cd dist && setfacl --restore=permissions-backup.acl 98 | continue-on-error: true 99 | - name: Prepare Repository 100 | run: mv dist .repo 101 | - name: Install Dependencies 102 | run: cd .repo && yarn install --check-files --frozen-lockfile 103 | - name: Create js artifact 104 | run: cd .repo && npx projen package:js 105 | - name: Collect js Artifact 106 | run: mv .repo/dist dist 107 | package-python: 108 | needs: build 109 | runs-on: ubuntu-latest 110 | permissions: {} 111 | if: "! needs.build.outputs.self_mutation_happened" 112 | steps: 113 | - uses: actions/setup-node@v3 114 | with: 115 | node-version: 14.x 116 | - uses: actions/setup-python@v3 117 | with: 118 | python-version: 3.x 119 | - name: Download build artifacts 120 | uses: actions/download-artifact@v3 121 | with: 122 | name: build-artifact 123 | path: dist 124 | - name: Restore build artifact permissions 125 | run: cd dist && setfacl --restore=permissions-backup.acl 126 | continue-on-error: true 127 | - name: Prepare Repository 128 | run: mv dist .repo 129 | - name: Install Dependencies 130 | run: cd .repo && yarn install --check-files --frozen-lockfile 131 | - name: Create python artifact 132 | run: cd .repo && npx projen package:python 133 | - name: Collect python Artifact 134 | run: mv .repo/dist dist 135 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | jobs: 14 | validate: 15 | name: Validate PR title 16 | runs-on: ubuntu-latest 17 | permissions: 18 | pull-requests: write 19 | steps: 20 | - uses: amannn/action-semantic-pull-request@v4.5.0 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | types: |- 25 | feat 26 | fix 27 | chore 28 | requireScope: false 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | outputs: 15 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 16 | env: 17 | CI: "true" 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 23 | - name: Set git identity 24 | run: |- 25 | git config user.name "github-actions" 26 | git config user.email "github-actions@github.com" 27 | - name: Install dependencies 28 | run: yarn install --check-files --frozen-lockfile 29 | - name: release 30 | run: npx projen release 31 | - name: Check for new commits 32 | id: git_remote 33 | run: echo ::set-output name=latest_commit::"$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" 34 | - name: Backup artifact permissions 35 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 36 | run: cd dist && getfacl -R . > permissions-backup.acl 37 | continue-on-error: true 38 | - name: Upload artifact 39 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 40 | uses: actions/upload-artifact@v2.1.1 41 | with: 42 | name: build-artifact 43 | path: dist 44 | container: 45 | image: jsii/superchain:1-buster-slim-node14 46 | release_github: 47 | name: Publish to GitHub Releases 48 | needs: release 49 | runs-on: ubuntu-latest 50 | permissions: 51 | contents: write 52 | if: needs.release.outputs.latest_commit == github.sha 53 | steps: 54 | - uses: actions/setup-node@v3 55 | with: 56 | node-version: 14.x 57 | - name: Download build artifacts 58 | uses: actions/download-artifact@v3 59 | with: 60 | name: build-artifact 61 | path: dist 62 | - name: Restore build artifact permissions 63 | run: cd dist && setfacl --restore=permissions-backup.acl 64 | continue-on-error: true 65 | - name: Prepare Repository 66 | run: mv dist .repo 67 | - name: Collect GitHub Metadata 68 | run: mv .repo/dist dist 69 | - name: Release 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | GITHUB_REPOSITORY: ${{ github.repository }} 73 | GITHUB_REF: ${{ github.ref }} 74 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_REF 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 75 | release_npm: 76 | name: Publish to npm 77 | needs: release 78 | runs-on: ubuntu-latest 79 | permissions: 80 | contents: read 81 | if: needs.release.outputs.latest_commit == github.sha 82 | steps: 83 | - uses: actions/setup-node@v3 84 | with: 85 | node-version: 14.x 86 | - name: Download build artifacts 87 | uses: actions/download-artifact@v3 88 | with: 89 | name: build-artifact 90 | path: dist 91 | - name: Restore build artifact permissions 92 | run: cd dist && setfacl --restore=permissions-backup.acl 93 | continue-on-error: true 94 | - name: Prepare Repository 95 | run: mv dist .repo 96 | - name: Install Dependencies 97 | run: cd .repo && yarn install --check-files --frozen-lockfile 98 | - name: Create js artifact 99 | run: cd .repo && npx projen package:js 100 | - name: Collect js Artifact 101 | run: mv .repo/dist dist 102 | - name: Release 103 | env: 104 | NPM_DIST_TAG: latest 105 | NPM_REGISTRY: registry.npmjs.org 106 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 107 | run: npx -p publib@latest publib-npm 108 | release_pypi: 109 | name: Publish to PyPI 110 | needs: release 111 | runs-on: ubuntu-latest 112 | permissions: 113 | contents: read 114 | if: needs.release.outputs.latest_commit == github.sha 115 | steps: 116 | - uses: actions/setup-node@v3 117 | with: 118 | node-version: 14.x 119 | - uses: actions/setup-python@v3 120 | with: 121 | python-version: 3.x 122 | - name: Download build artifacts 123 | uses: actions/download-artifact@v3 124 | with: 125 | name: build-artifact 126 | path: dist 127 | - name: Restore build artifact permissions 128 | run: cd dist && setfacl --restore=permissions-backup.acl 129 | continue-on-error: true 130 | - name: Prepare Repository 131 | run: mv dist .repo 132 | - name: Install Dependencies 133 | run: cd .repo && yarn install --check-files --frozen-lockfile 134 | - name: Create python artifact 135 | run: cd .repo && npx projen package:python 136 | - name: Collect python Artifact 137 | run: mv .repo/dist dist 138 | - name: Release 139 | env: 140 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 141 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 142 | run: npx -p publib@latest publib-pypi 143 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 * * * 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | ref: main 21 | - name: Install dependencies 22 | run: yarn install --check-files --frozen-lockfile 23 | - name: Upgrade dependencies 24 | run: npx projen upgrade 25 | - name: Find mutations 26 | id: create_patch 27 | run: |- 28 | git add . 29 | git diff --staged --patch --exit-code > .repo.patch || echo "::set-output name=patch_created::true" 30 | - name: Upload patch 31 | if: steps.create_patch.outputs.patch_created 32 | uses: actions/upload-artifact@v2 33 | with: 34 | name: .repo.patch 35 | path: .repo.patch 36 | container: 37 | image: jsii/superchain:1-buster-slim-node14 38 | pr: 39 | name: Create Pull Request 40 | needs: upgrade 41 | runs-on: ubuntu-latest 42 | permissions: 43 | contents: write 44 | pull-requests: write 45 | if: ${{ needs.upgrade.outputs.patch_created }} 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@v3 49 | with: 50 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 51 | ref: main 52 | - name: Download patch 53 | uses: actions/download-artifact@v3 54 | with: 55 | name: .repo.patch 56 | path: ${{ runner.temp }} 57 | - name: Apply patch 58 | run: '[ -s ${{ runner.temp }}/.repo.patch ] && git apply ${{ runner.temp }}/.repo.patch || echo "Empty patch. Skipping."' 59 | - name: Set git identity 60 | run: |- 61 | git config user.name "github-actions" 62 | git config user.email "github-actions@github.com" 63 | - name: Create Pull Request 64 | id: create-pr 65 | uses: peter-evans/create-pull-request@v3 66 | with: 67 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 68 | commit-message: |- 69 | chore(deps): upgrade dependencies 70 | 71 | Upgrades project dependencies. See details in [workflow run]. 72 | 73 | [Workflow Run]: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} 74 | 75 | ------ 76 | 77 | *Automatically created by projen via the "upgrade-main" workflow* 78 | branch: github-actions/upgrade-main 79 | title: "chore(deps): upgrade dependencies" 80 | labels: auto-approve,auto-merge 81 | body: |- 82 | Upgrades project dependencies. See details in [workflow run]. 83 | 84 | [Workflow Run]: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} 85 | 86 | ------ 87 | 88 | *Automatically created by projen via the "upgrade-main" workflow* 89 | author: github-actions 90 | committer: github-actions 91 | signoff: true 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/.npmignore 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | lib-cov 22 | coverage 23 | *.lcov 24 | .nyc_output 25 | build/Release 26 | node_modules/ 27 | jspm_packages/ 28 | *.tsbuildinfo 29 | .eslintcache 30 | *.tgz 31 | .yarn-integrity 32 | .cache 33 | !/.projenrc.js 34 | /test-reports/ 35 | junit.xml 36 | /coverage/ 37 | !/.github/workflows/build.yml 38 | /dist/changelog.md 39 | /dist/version.txt 40 | !/.github/workflows/release.yml 41 | !/.mergify.yml 42 | !/.github/workflows/upgrade-main.yml 43 | !/.github/pull_request_template.md 44 | !/test/ 45 | !/tsconfig.dev.json 46 | !/src/ 47 | /lib 48 | /dist/ 49 | !/.eslintrc.json 50 | .jsii 51 | tsconfig.json 52 | !/API.md 53 | !/.gitpod.yml 54 | cdk.out 55 | cdk.context.json 56 | yarn-error.log 57 | dependabot.yml 58 | website/public 59 | .vscode 60 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jsii/superchain:node14 2 | 3 | RUN mv $(which aws) /usr/local/bin/awscliv1 4 | 5 | RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip" \ 6 | && unzip /tmp/awscliv2.zip -d /tmp \ 7 | && /tmp/aws/install -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | image: public.ecr.aws/pahudnet/gitpod-workspace:latest 4 | tasks: 5 | - command: npx projen upgrade 6 | init: yarn gitpod:prebuild 7 | github: 8 | prebuilds: 9 | addCheck: true 10 | addBadge: true 11 | addLabel: true 12 | branches: true 13 | pullRequests: true 14 | pullRequestsFromForks: true 15 | vscode: 16 | extensions: 17 | - dbaeumer.vscode-eslint 18 | - ms-azuretools.vscode-docker 19 | - AmazonWebServices.aws-toolkit-vscode 20 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | conditions: 6 | - "#approved-reviews-by>=1" 7 | - -label~=(do-not-merge) 8 | - status-success=build 9 | - status-success=package-js 10 | - status-success=package-python 11 | pull_request_rules: 12 | - name: Automatic merge on approval and successful build 13 | actions: 14 | delete_head_branch: {} 15 | queue: 16 | method: squash 17 | name: default 18 | commit_message_template: |- 19 | {{ title }} (#{{ number }}) 20 | 21 | {{ body }} 22 | conditions: 23 | - "#approved-reviews-by>=1" 24 | - -label~=(do-not-merge) 25 | - status-success=build 26 | - status-success=package-js 27 | - status-success=package-python 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /.mergify.yml 10 | /test/ 11 | /tsconfig.dev.json 12 | /src/ 13 | !/lib/ 14 | !/lib/**/*.js 15 | !/lib/**/*.d.ts 16 | dist 17 | /tsconfig.json 18 | /.github/ 19 | /.vscode/ 20 | /.idea/ 21 | /.projenrc.js 22 | tsconfig.tsbuildinfo 23 | /.eslintrc.json 24 | !.jsii 25 | cdk.out 26 | cdk.context.json 27 | yarn-error.log 28 | dependabot.yml 29 | website/public 30 | .vscode 31 | images 32 | docs 33 | website 34 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@types/jest", 5 | "version": "^27", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@types/node", 10 | "version": "^14", 11 | "type": "build" 12 | }, 13 | { 14 | "name": "@typescript-eslint/eslint-plugin", 15 | "version": "^5", 16 | "type": "build" 17 | }, 18 | { 19 | "name": "@typescript-eslint/parser", 20 | "version": "^5", 21 | "type": "build" 22 | }, 23 | { 24 | "name": "aws-cdk-lib", 25 | "version": "2.11.0", 26 | "type": "build" 27 | }, 28 | { 29 | "name": "constructs", 30 | "version": "10.0.5", 31 | "type": "build" 32 | }, 33 | { 34 | "name": "eslint-import-resolver-node", 35 | "type": "build" 36 | }, 37 | { 38 | "name": "eslint-import-resolver-typescript", 39 | "type": "build" 40 | }, 41 | { 42 | "name": "eslint-plugin-import", 43 | "type": "build" 44 | }, 45 | { 46 | "name": "eslint", 47 | "version": "^8", 48 | "type": "build" 49 | }, 50 | { 51 | "name": "jest-junit", 52 | "version": "^13", 53 | "type": "build" 54 | }, 55 | { 56 | "name": "jest", 57 | "version": "^27", 58 | "type": "build" 59 | }, 60 | { 61 | "name": "jsii", 62 | "type": "build" 63 | }, 64 | { 65 | "name": "jsii-diff", 66 | "type": "build" 67 | }, 68 | { 69 | "name": "jsii-docgen", 70 | "type": "build" 71 | }, 72 | { 73 | "name": "jsii-pacmak", 74 | "type": "build" 75 | }, 76 | { 77 | "name": "json-schema", 78 | "type": "build" 79 | }, 80 | { 81 | "name": "npm-check-updates", 82 | "version": "^15", 83 | "type": "build" 84 | }, 85 | { 86 | "name": "projen", 87 | "type": "build" 88 | }, 89 | { 90 | "name": "standard-version", 91 | "version": "^9", 92 | "type": "build" 93 | }, 94 | { 95 | "name": "ts-jest", 96 | "version": "^27", 97 | "type": "build" 98 | }, 99 | { 100 | "name": "typescript", 101 | "type": "build" 102 | }, 103 | { 104 | "name": "cdk-nag", 105 | "version": "^2.0.0", 106 | "type": "bundled" 107 | }, 108 | { 109 | "name": "@types/prettier", 110 | "version": "2.6.0", 111 | "type": "override" 112 | }, 113 | { 114 | "name": "@types/responselike", 115 | "version": "1.0.0", 116 | "type": "override" 117 | }, 118 | { 119 | "name": "got", 120 | "version": "12.3.1", 121 | "type": "override" 122 | }, 123 | { 124 | "name": "aws-cdk-lib", 125 | "version": "^2.11.0", 126 | "type": "peer" 127 | }, 128 | { 129 | "name": "constructs", 130 | "version": "^10.0.5", 131 | "type": "peer" 132 | } 133 | ], 134 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 135 | } 136 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/build.yml", 8 | ".github/workflows/pull-request-lint.yml", 9 | ".github/workflows/release.yml", 10 | ".github/workflows/upgrade-main.yml", 11 | ".gitignore", 12 | ".gitpod.yml", 13 | ".mergify.yml", 14 | ".projen/deps.json", 15 | ".projen/files.json", 16 | ".projen/tasks.json", 17 | "tsconfig.dev.json" 18 | ], 19 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 20 | } 21 | -------------------------------------------------------------------------------- /.projen/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "name": "build", 5 | "description": "Full release build", 6 | "steps": [ 7 | { 8 | "spawn": "default" 9 | }, 10 | { 11 | "spawn": "pre-compile" 12 | }, 13 | { 14 | "spawn": "compile" 15 | }, 16 | { 17 | "spawn": "post-compile" 18 | }, 19 | { 20 | "spawn": "test" 21 | }, 22 | { 23 | "spawn": "package" 24 | } 25 | ] 26 | }, 27 | "bump": { 28 | "name": "bump", 29 | "description": "Bumps version based on latest git tag and generates a changelog entry", 30 | "env": { 31 | "OUTFILE": "package.json", 32 | "CHANGELOG": "dist/changelog.md", 33 | "BUMPFILE": "dist/version.txt", 34 | "RELEASETAG": "dist/releasetag.txt", 35 | "RELEASE_TAG_PREFIX": "" 36 | }, 37 | "steps": [ 38 | { 39 | "builtin": "release/bump-version" 40 | } 41 | ], 42 | "condition": "! git log --oneline -1 | grep -q \"chore(release):\"" 43 | }, 44 | "clobber": { 45 | "name": "clobber", 46 | "description": "hard resets to HEAD of origin and cleans the local repo", 47 | "env": { 48 | "BRANCH": "$(git branch --show-current)" 49 | }, 50 | "steps": [ 51 | { 52 | "exec": "git checkout -b scratch", 53 | "name": "save current HEAD in \"scratch\" branch" 54 | }, 55 | { 56 | "exec": "git checkout $BRANCH" 57 | }, 58 | { 59 | "exec": "git fetch origin", 60 | "name": "fetch latest changes from origin" 61 | }, 62 | { 63 | "exec": "git reset --hard origin/$BRANCH", 64 | "name": "hard reset to origin commit" 65 | }, 66 | { 67 | "exec": "git clean -fdx", 68 | "name": "clean all untracked files" 69 | }, 70 | { 71 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 72 | } 73 | ], 74 | "condition": "git diff --exit-code > /dev/null" 75 | }, 76 | "compat": { 77 | "name": "compat", 78 | "description": "Perform API compatibility check against latest version", 79 | "steps": [ 80 | { 81 | "exec": "jsii-diff npm:$(node -p \"require('./package.json').name\") -k --ignore-file .compatignore || (echo \"\nUNEXPECTED BREAKING CHANGES: add keys such as 'removed:constructs.Node.of' to .compatignore to skip.\n\" && exit 1)" 82 | } 83 | ] 84 | }, 85 | "compile": { 86 | "name": "compile", 87 | "description": "Only compile", 88 | "steps": [ 89 | { 90 | "exec": "jsii --silence-warnings=reserved-word" 91 | } 92 | ] 93 | }, 94 | "default": { 95 | "name": "default", 96 | "description": "Synthesize project files", 97 | "steps": [ 98 | { 99 | "exec": "node .projenrc.js" 100 | } 101 | ] 102 | }, 103 | "docgen": { 104 | "name": "docgen", 105 | "description": "Generate API.md from .jsii manifest", 106 | "steps": [ 107 | { 108 | "exec": "jsii-docgen -o API.md" 109 | } 110 | ] 111 | }, 112 | "eject": { 113 | "name": "eject", 114 | "description": "Remove projen from the project", 115 | "env": { 116 | "PROJEN_EJECTING": "true" 117 | }, 118 | "steps": [ 119 | { 120 | "spawn": "default" 121 | } 122 | ] 123 | }, 124 | "eslint": { 125 | "name": "eslint", 126 | "description": "Runs eslint against the codebase", 127 | "steps": [ 128 | { 129 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test build-tools .projenrc.js" 130 | } 131 | ] 132 | }, 133 | "gitpod:prebuild": { 134 | "name": "gitpod:prebuild", 135 | "description": "Prebuild setup for Gitpod", 136 | "steps": [ 137 | { 138 | "exec": "yarn install --frozen-lockfile --check-files" 139 | }, 140 | { 141 | "exec": "npx projen compile" 142 | } 143 | ] 144 | }, 145 | "package": { 146 | "name": "package", 147 | "description": "Creates the distribution package", 148 | "steps": [ 149 | { 150 | "exec": "if [ ! -z ${CI} ]; then rsync -a . .repo --exclude .git --exclude node_modules && rm -rf dist && mv .repo dist; else npx projen package-all; fi" 151 | } 152 | ] 153 | }, 154 | "package-all": { 155 | "name": "package-all", 156 | "description": "Packages artifacts for all target languages", 157 | "steps": [ 158 | { 159 | "spawn": "package:js" 160 | }, 161 | { 162 | "spawn": "package:python" 163 | } 164 | ] 165 | }, 166 | "package:js": { 167 | "name": "package:js", 168 | "description": "Create js language bindings", 169 | "steps": [ 170 | { 171 | "exec": "jsii-pacmak -v --target js" 172 | } 173 | ] 174 | }, 175 | "package:python": { 176 | "name": "package:python", 177 | "description": "Create python language bindings", 178 | "steps": [ 179 | { 180 | "exec": "jsii-pacmak -v --target python" 181 | } 182 | ] 183 | }, 184 | "post-compile": { 185 | "name": "post-compile", 186 | "description": "Runs after successful compilation", 187 | "steps": [ 188 | { 189 | "spawn": "docgen" 190 | } 191 | ] 192 | }, 193 | "post-upgrade": { 194 | "name": "post-upgrade", 195 | "description": "Runs after upgrading dependencies" 196 | }, 197 | "pre-compile": { 198 | "name": "pre-compile", 199 | "description": "Prepare the project for compilation" 200 | }, 201 | "release": { 202 | "name": "release", 203 | "description": "Prepare a release from \"main\" branch", 204 | "env": { 205 | "RELEASE": "true" 206 | }, 207 | "steps": [ 208 | { 209 | "exec": "rm -fr dist" 210 | }, 211 | { 212 | "spawn": "bump" 213 | }, 214 | { 215 | "spawn": "build" 216 | }, 217 | { 218 | "spawn": "unbump" 219 | }, 220 | { 221 | "exec": "git diff --ignore-space-at-eol --exit-code" 222 | } 223 | ] 224 | }, 225 | "test": { 226 | "name": "test", 227 | "description": "Run tests", 228 | "steps": [ 229 | { 230 | "exec": "jest --passWithNoTests --all --updateSnapshot" 231 | }, 232 | { 233 | "spawn": "eslint" 234 | } 235 | ] 236 | }, 237 | "test:watch": { 238 | "name": "test:watch", 239 | "description": "Run jest in watch mode", 240 | "steps": [ 241 | { 242 | "exec": "jest --watch" 243 | } 244 | ] 245 | }, 246 | "unbump": { 247 | "name": "unbump", 248 | "description": "Restores version to 0.0.0", 249 | "env": { 250 | "OUTFILE": "package.json", 251 | "CHANGELOG": "dist/changelog.md", 252 | "BUMPFILE": "dist/version.txt", 253 | "RELEASETAG": "dist/releasetag.txt", 254 | "RELEASE_TAG_PREFIX": "" 255 | }, 256 | "steps": [ 257 | { 258 | "builtin": "release/reset-version" 259 | } 260 | ] 261 | }, 262 | "upgrade": { 263 | "name": "upgrade", 264 | "description": "upgrade dependencies", 265 | "env": { 266 | "CI": "0" 267 | }, 268 | "steps": [ 269 | { 270 | "exec": "yarn upgrade npm-check-updates" 271 | }, 272 | { 273 | "exec": "npm-check-updates --dep dev --upgrade --target=minor --reject='aws-cdk-lib,constructs'" 274 | }, 275 | { 276 | "exec": "npm-check-updates --dep optional --upgrade --target=minor --reject='aws-cdk-lib,constructs'" 277 | }, 278 | { 279 | "exec": "npm-check-updates --dep peer --upgrade --target=minor --reject='aws-cdk-lib,constructs'" 280 | }, 281 | { 282 | "exec": "npm-check-updates --dep prod --upgrade --target=minor --reject='aws-cdk-lib,constructs'" 283 | }, 284 | { 285 | "exec": "npm-check-updates --dep bundle --upgrade --target=minor --reject='aws-cdk-lib,constructs'" 286 | }, 287 | { 288 | "exec": "yarn install --check-files" 289 | }, 290 | { 291 | "exec": "yarn upgrade" 292 | }, 293 | { 294 | "exec": "npx projen" 295 | }, 296 | { 297 | "spawn": "post-upgrade" 298 | } 299 | ] 300 | }, 301 | "watch": { 302 | "name": "watch", 303 | "description": "Watch & compile in the background", 304 | "steps": [ 305 | { 306 | "exec": "jsii -w --silence-warnings=reserved-word" 307 | } 308 | ] 309 | } 310 | }, 311 | "env": { 312 | "PATH": "$(npx -c \"node -e \\\"console.log(process.env.PATH)\\\"\")" 313 | }, 314 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 315 | } 316 | -------------------------------------------------------------------------------- /.projenrc.js: -------------------------------------------------------------------------------- 1 | const { 2 | awscdk, 3 | DevEnvironmentDockerImage, 4 | Gitpod, 5 | } = require('projen'); 6 | 7 | const project = new awscdk.AwsCdkConstructLibrary({ 8 | author: 'Pahud Hsieh', 9 | authorAddress: 'hunhsieh@amazon.com', 10 | cdkVersion: '2.11.0', 11 | defaultReleaseBranch: 'main', 12 | licensed: false, // leave the LICENSE file as is. 13 | name: 'serverless-container-constructs', 14 | description: 'CDK patterns for modern application with serverless containers on AWS', 15 | repositoryUrl: 'https://github.com/aws-samples/serverless-container-constructs', 16 | bundledDeps: [ 17 | 'cdk-nag@^2.0.0', 18 | ], 19 | depsUpgradeOptions: { 20 | ignoreProjen: false, 21 | workflowOptions: { 22 | labels: ['auto-approve', 'auto-merge'], 23 | }, 24 | }, 25 | autoApproveOptions: { 26 | secret: 'GITHUB_TOKEN', 27 | allowedUsernames: ['pahud'], 28 | }, 29 | publishToPypi: { 30 | distName: 'serverless-container-constructs', 31 | module: 'serverless_container_constructs', 32 | }, 33 | catalog: { 34 | announce: false, 35 | twitter: 'pahudnet', 36 | }, 37 | keywords: [ 38 | 'cdk', 39 | 'fargate', 40 | 'serverless', 41 | 'aws', 42 | ], 43 | }); 44 | 45 | project.package.addField('resolutions', { 46 | 'pac-resolver': '^5.0.0', 47 | 'set-value': '^4.0.1', 48 | 'ansi-regex': '^5.0.1', 49 | }); 50 | 51 | 52 | const gitpodPrebuild = project.addTask('gitpod:prebuild', { 53 | description: 'Prebuild setup for Gitpod', 54 | }); 55 | // install and compile only, do not test or package. 56 | gitpodPrebuild.exec('yarn install --frozen-lockfile --check-files'); 57 | gitpodPrebuild.exec('npx projen compile'); 58 | 59 | let gitpod = new Gitpod(project, { 60 | dockerImage: DevEnvironmentDockerImage.fromImage('public.ecr.aws/pahudnet/gitpod-workspace:latest'), 61 | prebuilds: { 62 | addCheck: true, 63 | addBadge: true, 64 | addLabel: true, 65 | branches: true, 66 | pullRequests: true, 67 | pullRequestsFromForks: true, 68 | }, 69 | }); 70 | 71 | gitpod.addCustomTask({ 72 | init: 'yarn gitpod:prebuild', 73 | // always upgrade after init 74 | command: 'npx projen upgrade', 75 | }); 76 | 77 | gitpod.addVscodeExtensions( 78 | 'dbaeumer.vscode-eslint', 79 | 'ms-azuretools.vscode-docker', 80 | 'AmazonWebServices.aws-toolkit-vscode', 81 | ); 82 | 83 | const common_exclude = ['cdk.out', 'cdk.context.json', 'yarn-error.log', 'dependabot.yml', 'website/public', '.vscode']; 84 | project.npmignore.exclude(...common_exclude, 'images', 'docs', 'website'); 85 | project.gitignore.exclude(...common_exclude); 86 | 87 | project.synth(); 88 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### AlbFargateServices 6 | 7 | #### Initializers 8 | 9 | ```typescript 10 | import { AlbFargateServices } from 'serverless-container-constructs' 11 | 12 | new AlbFargateServices(scope: Construct, id: string, props: AlbFargateServicesProps) 13 | ``` 14 | 15 | ##### `scope`Required 16 | 17 | - *Type:* [`constructs.Construct`](#constructs.Construct) 18 | 19 | --- 20 | 21 | ##### `id`Required 22 | 23 | - *Type:* `string` 24 | 25 | --- 26 | 27 | ##### `props`Required 28 | 29 | - *Type:* [`serverless-container-constructs.AlbFargateServicesProps`](#serverless-container-constructs.AlbFargateServicesProps) 30 | 31 | --- 32 | 33 | 34 | 35 | #### Properties 36 | 37 | ##### `externalAlb`Optional 38 | 39 | ```typescript 40 | public readonly externalAlb: ApplicationLoadBalancer; 41 | ``` 42 | 43 | - *Type:* [`aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer`](#aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer) 44 | 45 | --- 46 | 47 | ##### `internalAlb`Optional 48 | 49 | ```typescript 50 | public readonly internalAlb: ApplicationLoadBalancer; 51 | ``` 52 | 53 | - *Type:* [`aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer`](#aws-cdk-lib.aws_elasticloadbalancingv2.ApplicationLoadBalancer) 54 | 55 | --- 56 | 57 | 58 | ### BaseFargateServices 59 | 60 | #### Initializers 61 | 62 | ```typescript 63 | import { BaseFargateServices } from 'serverless-container-constructs' 64 | 65 | new BaseFargateServices(scope: Construct, id: string, props: BaseFargateServicesProps) 66 | ``` 67 | 68 | ##### `scope`Required 69 | 70 | - *Type:* [`constructs.Construct`](#constructs.Construct) 71 | 72 | --- 73 | 74 | ##### `id`Required 75 | 76 | - *Type:* `string` 77 | 78 | --- 79 | 80 | ##### `props`Required 81 | 82 | - *Type:* [`serverless-container-constructs.BaseFargateServicesProps`](#serverless-container-constructs.BaseFargateServicesProps) 83 | 84 | --- 85 | 86 | 87 | 88 | #### Properties 89 | 90 | ##### `service`Required 91 | 92 | ```typescript 93 | public readonly service: FargateService[]; 94 | ``` 95 | 96 | - *Type:* [`aws-cdk-lib.aws_ecs.FargateService`](#aws-cdk-lib.aws_ecs.FargateService)[] 97 | 98 | The service(s) created from the task(s). 99 | 100 | --- 101 | 102 | ##### `vpc`Required 103 | 104 | ```typescript 105 | public readonly vpc: IVpc; 106 | ``` 107 | 108 | - *Type:* [`aws-cdk-lib.aws_ec2.IVpc`](#aws-cdk-lib.aws_ec2.IVpc) 109 | 110 | --- 111 | 112 | 113 | ## Structs 114 | 115 | ### AlbFargateServicesProps 116 | 117 | #### Initializer 118 | 119 | ```typescript 120 | import { AlbFargateServicesProps } from 'serverless-container-constructs' 121 | 122 | const albFargateServicesProps: AlbFargateServicesProps = { ... } 123 | ``` 124 | 125 | ##### `tasks`Required 126 | 127 | ```typescript 128 | public readonly tasks: FargateTaskProps[]; 129 | ``` 130 | 131 | - *Type:* [`serverless-container-constructs.FargateTaskProps`](#serverless-container-constructs.FargateTaskProps)[] 132 | 133 | --- 134 | 135 | ##### `enableExecuteCommand`Optional 136 | 137 | ```typescript 138 | public readonly enableExecuteCommand: boolean; 139 | ``` 140 | 141 | - *Type:* `boolean` 142 | - *Default:* false 143 | 144 | Whether to enable ECS Exec support. 145 | 146 | > https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html 147 | 148 | --- 149 | 150 | ##### `route53Ops`Optional 151 | 152 | ```typescript 153 | public readonly route53Ops: Route53Options; 154 | ``` 155 | 156 | - *Type:* [`serverless-container-constructs.Route53Options`](#serverless-container-constructs.Route53Options) 157 | 158 | --- 159 | 160 | ##### `spot`Optional 161 | 162 | ```typescript 163 | public readonly spot: boolean; 164 | ``` 165 | 166 | - *Type:* `boolean` 167 | - *Default:* false 168 | 169 | create a FARGATE_SPOT only cluster. 170 | 171 | --- 172 | 173 | ##### `vpc`Optional 174 | 175 | ```typescript 176 | public readonly vpc: IVpc; 177 | ``` 178 | 179 | - *Type:* [`aws-cdk-lib.aws_ec2.IVpc`](#aws-cdk-lib.aws_ec2.IVpc) 180 | 181 | --- 182 | 183 | ##### `vpcSubnets`Optional 184 | 185 | ```typescript 186 | public readonly vpcSubnets: SubnetSelection; 187 | ``` 188 | 189 | - *Type:* [`aws-cdk-lib.aws_ec2.SubnetSelection`](#aws-cdk-lib.aws_ec2.SubnetSelection) 190 | - *Default:* { 191 | subnetType: ec2.SubnetType.PRIVATE, 192 | } 193 | 194 | The subnets to associate with the service. 195 | 196 | --- 197 | 198 | ### BaseFargateServicesProps 199 | 200 | #### Initializer 201 | 202 | ```typescript 203 | import { BaseFargateServicesProps } from 'serverless-container-constructs' 204 | 205 | const baseFargateServicesProps: BaseFargateServicesProps = { ... } 206 | ``` 207 | 208 | ##### `tasks`Required 209 | 210 | ```typescript 211 | public readonly tasks: FargateTaskProps[]; 212 | ``` 213 | 214 | - *Type:* [`serverless-container-constructs.FargateTaskProps`](#serverless-container-constructs.FargateTaskProps)[] 215 | 216 | --- 217 | 218 | ##### `enableExecuteCommand`Optional 219 | 220 | ```typescript 221 | public readonly enableExecuteCommand: boolean; 222 | ``` 223 | 224 | - *Type:* `boolean` 225 | - *Default:* false 226 | 227 | Whether to enable ECS Exec support. 228 | 229 | > https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html 230 | 231 | --- 232 | 233 | ##### `route53Ops`Optional 234 | 235 | ```typescript 236 | public readonly route53Ops: Route53Options; 237 | ``` 238 | 239 | - *Type:* [`serverless-container-constructs.Route53Options`](#serverless-container-constructs.Route53Options) 240 | 241 | --- 242 | 243 | ##### `spot`Optional 244 | 245 | ```typescript 246 | public readonly spot: boolean; 247 | ``` 248 | 249 | - *Type:* `boolean` 250 | - *Default:* false 251 | 252 | create a FARGATE_SPOT only cluster. 253 | 254 | --- 255 | 256 | ##### `vpc`Optional 257 | 258 | ```typescript 259 | public readonly vpc: IVpc; 260 | ``` 261 | 262 | - *Type:* [`aws-cdk-lib.aws_ec2.IVpc`](#aws-cdk-lib.aws_ec2.IVpc) 263 | 264 | --- 265 | 266 | ##### `vpcSubnets`Optional 267 | 268 | ```typescript 269 | public readonly vpcSubnets: SubnetSelection; 270 | ``` 271 | 272 | - *Type:* [`aws-cdk-lib.aws_ec2.SubnetSelection`](#aws-cdk-lib.aws_ec2.SubnetSelection) 273 | - *Default:* { 274 | subnetType: ec2.SubnetType.PRIVATE, 275 | } 276 | 277 | The subnets to associate with the service. 278 | 279 | --- 280 | 281 | ### FargateTaskProps 282 | 283 | #### Initializer 284 | 285 | ```typescript 286 | import { FargateTaskProps } from 'serverless-container-constructs' 287 | 288 | const fargateTaskProps: FargateTaskProps = { ... } 289 | ``` 290 | 291 | ##### `listenerPort`Required 292 | 293 | ```typescript 294 | public readonly listenerPort: number; 295 | ``` 296 | 297 | - *Type:* `number` 298 | 299 | --- 300 | 301 | ##### `task`Required 302 | 303 | ```typescript 304 | public readonly task: FargateTaskDefinition; 305 | ``` 306 | 307 | - *Type:* [`aws-cdk-lib.aws_ecs.FargateTaskDefinition`](#aws-cdk-lib.aws_ecs.FargateTaskDefinition) 308 | 309 | --- 310 | 311 | ##### `accessibility`Optional 312 | 313 | ```typescript 314 | public readonly accessibility: LoadBalancerAccessibility; 315 | ``` 316 | 317 | - *Type:* [`serverless-container-constructs.LoadBalancerAccessibility`](#serverless-container-constructs.LoadBalancerAccessibility) 318 | - *Default:* both 319 | 320 | Register the service to internal ELB, external ELB or both. 321 | 322 | --- 323 | 324 | ##### `capacityProviderStrategy`Optional 325 | 326 | ```typescript 327 | public readonly capacityProviderStrategy: CapacityProviderStrategy[]; 328 | ``` 329 | 330 | - *Type:* [`aws-cdk-lib.aws_ecs.CapacityProviderStrategy`](#aws-cdk-lib.aws_ecs.CapacityProviderStrategy)[] 331 | 332 | --- 333 | 334 | ##### `desiredCount`Optional 335 | 336 | ```typescript 337 | public readonly desiredCount: number; 338 | ``` 339 | 340 | - *Type:* `number` 341 | - *Default:* 1 342 | 343 | desired number of tasks for the service. 344 | 345 | --- 346 | 347 | ##### `scalingPolicy`Optional 348 | 349 | ```typescript 350 | public readonly scalingPolicy: ServiceScalingPolicy; 351 | ``` 352 | 353 | - *Type:* [`serverless-container-constructs.ServiceScalingPolicy`](#serverless-container-constructs.ServiceScalingPolicy) 354 | - *Default:* { maxCapacity: 10, targetCpuUtilization: 50, requestsPerTarget: 1000 } 355 | 356 | service autoscaling policy. 357 | 358 | --- 359 | 360 | ### Route53Options 361 | 362 | #### Initializer 363 | 364 | ```typescript 365 | import { Route53Options } from 'serverless-container-constructs' 366 | 367 | const route53Options: Route53Options = { ... } 368 | ``` 369 | 370 | ##### `externalElbRecordName`Optional 371 | 372 | ```typescript 373 | public readonly externalElbRecordName: string; 374 | ``` 375 | 376 | - *Type:* `string` 377 | - *Default:* external 378 | 379 | the external ELB record name. 380 | 381 | --- 382 | 383 | ##### `internalElbRecordName`Optional 384 | 385 | ```typescript 386 | public readonly internalElbRecordName: string; 387 | ``` 388 | 389 | - *Type:* `string` 390 | - *Default:* internal 391 | 392 | the internal ELB record name. 393 | 394 | --- 395 | 396 | ##### `zoneName`Optional 397 | 398 | ```typescript 399 | public readonly zoneName: string; 400 | ``` 401 | 402 | - *Type:* `string` 403 | - *Default:* svc.local 404 | 405 | private zone name. 406 | 407 | --- 408 | 409 | ### ServiceScalingPolicy 410 | 411 | #### Initializer 412 | 413 | ```typescript 414 | import { ServiceScalingPolicy } from 'serverless-container-constructs' 415 | 416 | const serviceScalingPolicy: ServiceScalingPolicy = { ... } 417 | ``` 418 | 419 | ##### `maxCapacity`Optional 420 | 421 | ```typescript 422 | public readonly maxCapacity: number; 423 | ``` 424 | 425 | - *Type:* `number` 426 | - *Default:* 10 427 | 428 | max capacity for the service autoscaling. 429 | 430 | --- 431 | 432 | ##### `requestPerTarget`Optional 433 | 434 | ```typescript 435 | public readonly requestPerTarget: number; 436 | ``` 437 | 438 | - *Type:* `number` 439 | - *Default:* 1000 440 | 441 | request per target. 442 | 443 | --- 444 | 445 | ##### `targetCpuUtilization`Optional 446 | 447 | ```typescript 448 | public readonly targetCpuUtilization: number; 449 | ``` 450 | 451 | - *Type:* `number` 452 | - *Default:* 50 453 | 454 | target cpu utilization. 455 | 456 | --- 457 | 458 | 459 | 460 | ## Enums 461 | 462 | ### LoadBalancerAccessibility 463 | 464 | The load balancer accessibility. 465 | 466 | #### `EXTERNAL_ONLY` 467 | 468 | register to external load balancer only. 469 | 470 | --- 471 | 472 | 473 | #### `INTERNAL_ONLY` 474 | 475 | register to internal load balancer only. 476 | 477 | --- 478 | 479 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [0.1.0](https://github.com/aws-samples/serverless-container-constructs/compare/v0.0.0...v0.1.0) (2021-09-13) 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-container-constructs 2 | 3 | CDK patterns for modern application with serverless containers on AWS 4 | 5 | # `AlbFargateServices` 6 | 7 | Inspired by _Vijay Menon_ from the [AWS blog post](https://aws.amazon.com/blogs/containers/how-to-use-multiple-load-balancer-target-group-support-for-amazon-ecs-to-access-internal-and-external-service-endpoint-using-the-same-dns-name/) introduced in 2019, `AlbFargateServices` allows you to create one or many fargate services with both internet-facing ALB and internal ALB associated with all services. With this pattern, fargate services will be allowed to intercommunicat via internal ALB while external inbound traffic will be spread across the same service tasks through internet-facing ALB. 8 | 9 | 10 | The sample below will create 3 fargate services associated with both external and internal ALBs. The internal ALB will have an alias(`internal.svc.local`) auto-configured from Route 53 so services can communite through the private ALB endpoint. 11 | 12 | 13 | ```ts 14 | import { AlbFargateServices } from 'serverless-container-constructs'; 15 | 16 | new AlbFargateServices(stack, 'Service', { 17 | spot: true, // FARGATE_SPOT only cluster 18 | tasks: [ 19 | { 20 | listenerPort: 80, 21 | task: orderTask, 22 | desiredCount: 2, 23 | // customize the service autoscaling policy 24 | scalingPolicy: { 25 | maxCapacity: 20, 26 | requestPerTarget: 1000, 27 | targetCpuUtilization: 50, 28 | }, 29 | }, 30 | { listenerPort: 8080, task: customerTask, desiredCount: 2 }, 31 | { listenerPort: 9090, task: productTask, desiredCount: 2 }, 32 | ], 33 | route53Ops: { 34 | zoneName, // svc.local 35 | externalAlbRecordName, // external.svc.local 36 | internalAlbRecordName, // internal.svc.local 37 | }, 38 | }); 39 | ``` 40 | 41 | ## Fargate Spot Support 42 | 43 | By enabling the `spot` property, 100% fargate spot tasks will be provisioned to help you save up to 70%. Check more details about [Fargate Spot](https://aws.amazon.com/about-aws/whats-new/2019/12/aws-launches-fargate-spot-save-up-to-70-for-fault-tolerant-applications/?nc1=h_ls). This is a handy catch-all flag to force all tasks to be `FARGATE_SPOT` only. 44 | 45 | To specify mixed strategy with partial `FARGATE` and partial `FARGATE_SPOT`, specify the `capacityProviderStrategy` for individual tasks like 46 | 47 | ```ts 48 | new AlbFargateServices(stack, 'Service', { 49 | tasks: [ 50 | { 51 | listenerPort: 8080, 52 | task: customerTask, 53 | desiredCount: 2, 54 | capacityProviderStrategy: [ 55 | { 56 | capacityProvider: 'FARGATE', 57 | base: 1, 58 | weight: 1, 59 | }, 60 | { 61 | capacityProvider: 'FARGATE_SPOT', 62 | base: 0, 63 | weight: 3, 64 | }, 65 | ], 66 | }, 67 | ], 68 | }); 69 | ``` 70 | The custom capacity provider strategy will be applied if `capacityProviderStretegy` is specified, otherwise, 100% spot will be used when `spot: true`. The default policy is 100% Fargate on-demand. 71 | 72 | ## ECS Exec 73 | 74 | Simply turn on the `enableExecuteCommand` property to enable the [ECS Exec](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html) support for all services. 75 | 76 | ## Internal or External Only 77 | 78 | By default, all task(s) defined in the `AlbFargateServices` will be registered to both external and internal ALBs. 79 | Set `accessibility` to make it internal only, external only, or both. 80 | 81 | ```ts 82 | new AlbFargateServices(stack, 'Service', { 83 | tasks: [ 84 | // this task is internal only 85 | { listenerPort: 8080, task: task1, accessibility: LoadBalancerAccessibility.INTERNAL_ONLY}, 86 | // this task is external only 87 | { listenerPort: 8081, task: task2, accessibility: LoadBalancerAccessibility.EXTERNAL_ONLY}, 88 | // this task is both external and internal facing(default behavior) 89 | { listenerPort: 8082, task: task3 }, 90 | ], 91 | }); 92 | ``` 93 | 94 | Please note if all tasks are defined as `INTERNAL_ONLY`, no external ALB will be created. Similarly, no internal ALB 95 | will be created if all defined as `EXTERNAL_ONLY`. 96 | 97 | ## VPC Subnets 98 | 99 | By default, all tasks will be deployed in the private subnets. You will need the NAT gateway for the default route associated with the private subnets to ensure the task can successfully pull the container images. 100 | 101 | However, you are allowed to specify `vpcSubnets` to customize the subnet selection. 102 | 103 | To deploy all tasks in public subnets, one per AZ: 104 | 105 | ```ts 106 | new AlbFargateServices(stack, 'Service', { 107 | vpcSubnets: { 108 | subnetType: ec2.SubnetType.PUBLIC, 109 | onePerAz: true, 110 | }, 111 | ... 112 | }); 113 | ``` 114 | This will implicitly enable the `auto assign public IP` for each fargate task so the task can successfully pull the container images from external registry. However, the ingress traffic will still be balanced via the external ALB. 115 | 116 | To deploy all tasks in specific subnets: 117 | 118 | ```ts 119 | new AlbFargateServices(stack, 'Service', { 120 | vpcSubnets: { 121 | subnets: [ 122 | ec2.Subnet.fromSubnetId(stack, 'sub-1a', 'subnet-0e9460dbcfc4cf6ee'), 123 | ec2.Subnet.fromSubnetId(stack, 'sub-1b', 'subnet-0562f666bdf5c29af'), 124 | ec2.Subnet.fromSubnetId(stack, 'sub-1c', 'subnet-00ab15c0022872f06'), 125 | ], 126 | }, 127 | ... 128 | }); 129 | ``` 130 | 131 | 132 | ## Sample Application 133 | 134 | This repository comes with a sample applicaiton with 3 services in Golang. On deployment, the `Order` service will be exposed externally on external ALB port `80` and all requests to the `Order` service will trigger sub-requests internally to another other two services(`product` and `customer`) through the internal ALB and eventually aggregate the response back to the client. 135 | 136 | 137 | ![](images/AlbFargateServices.svg) 138 | 139 | ## Deploy 140 | 141 | To deploy the sample application in you default VPC: 142 | 143 | ```sh 144 | // install first 145 | yarn install 146 | npx cdk diff -c use_default_vpc=1 147 | npx cdk deploy -c use_default_vpc=1 148 | ``` 149 | 150 | On deployment complete, you will see the external ALB endpoint in the CDK output. `cURL` the external HTTP endpoint and you should be able to see the aggregated response. 151 | 152 | 153 | ```sh 154 | $ curl http://demo-Servi-EH1OINYDWDU9-1397122594.ap-northeast-1.elb.amazonaws.com 155 | 156 | {"service":"order", "version":"1.0"} 157 | {"service":"product","version":"1.0"} 158 | {"service":"customer","version":"1.0"} 159 | ``` 160 | 161 | ## `cdk-nag` with `AwsSolutions` check 162 | 163 | This construct follows the best practices from the [AWS Solutoins](https://github.com/cdklabs/cdk-nag/blob/main/RULES.md#awssolutions) with [cdk-nag](https://github.com/cdklabs/cdk-nag). Enable the `AWS_SOLUTIONS_CHECK` context variable to check aginst the cdk-nag rules. 164 | 165 | ```sh 166 | npx cdk diff -c AWS_SOLUTIONS_CHECK=1 167 | or 168 | npx cdk synth -c AWS_SOLUTIONS_CHECK=1 169 | ``` 170 | 171 | 172 | ## Security 173 | 174 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 175 | 176 | ## License 177 | 178 | This library is licensed under the MIT-0 License. See the LICENSE file. 179 | 180 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts src/integ.default.ts" 3 | } 4 | -------------------------------------------------------------------------------- /images/AlbFargateServices.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Product
Product
Order
Order
Customer
Customer
External ALB
External ALB
Internal ALB
Internal ALB
http://internal.svc.local
http:/...
HTTP:9090
HTTP:9090
HTTP:80
HTTP:80
HTTP:8080
HTTP:8080
HTTPS:443
HTTPS:443
HTTP:9090
HTTP:9090
HTTP:8080
HTTP:8080
Viewer does not support full SVG 1.1
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-container-constructs", 3 | "description": "CDK patterns for modern application with serverless containers on AWS", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/aws-samples/serverless-container-constructs" 7 | }, 8 | "scripts": { 9 | "build": "npx projen build", 10 | "bump": "npx projen bump", 11 | "clobber": "npx projen clobber", 12 | "compat": "npx projen compat", 13 | "compile": "npx projen compile", 14 | "default": "npx projen default", 15 | "docgen": "npx projen docgen", 16 | "eject": "npx projen eject", 17 | "eslint": "npx projen eslint", 18 | "gitpod:prebuild": "npx projen gitpod:prebuild", 19 | "package": "npx projen package", 20 | "package-all": "npx projen package-all", 21 | "package:js": "npx projen package:js", 22 | "package:python": "npx projen package:python", 23 | "post-compile": "npx projen post-compile", 24 | "post-upgrade": "npx projen post-upgrade", 25 | "pre-compile": "npx projen pre-compile", 26 | "release": "npx projen release", 27 | "test": "npx projen test", 28 | "test:watch": "npx projen test:watch", 29 | "unbump": "npx projen unbump", 30 | "upgrade": "npx projen upgrade", 31 | "watch": "npx projen watch", 32 | "projen": "npx projen" 33 | }, 34 | "author": { 35 | "name": "Pahud Hsieh", 36 | "email": "hunhsieh@amazon.com", 37 | "organization": false 38 | }, 39 | "devDependencies": { 40 | "@types/jest": "^27", 41 | "@types/node": "^14", 42 | "@typescript-eslint/eslint-plugin": "^5", 43 | "@typescript-eslint/parser": "^5", 44 | "aws-cdk-lib": "2.11.0", 45 | "constructs": "10.0.5", 46 | "eslint": "^8", 47 | "eslint-import-resolver-node": "^0.3.6", 48 | "eslint-import-resolver-typescript": "^2.7.1", 49 | "eslint-plugin-import": "^2.26.0", 50 | "jest": "^27", 51 | "jest-junit": "^13", 52 | "jsii": "^1.69.0", 53 | "jsii-diff": "^1.69.0", 54 | "jsii-docgen": "^3.8.31", 55 | "jsii-pacmak": "^1.69.0", 56 | "json-schema": "^0.4.0", 57 | "npm-check-updates": "^15", 58 | "projen": "^0.63.6", 59 | "standard-version": "^9", 60 | "ts-jest": "^27", 61 | "typescript": "^4.8.4" 62 | }, 63 | "peerDependencies": { 64 | "aws-cdk-lib": "^2.11.0", 65 | "constructs": "^10.0.5" 66 | }, 67 | "dependencies": { 68 | "cdk-nag": "^2.0.0" 69 | }, 70 | "bundledDependencies": [ 71 | "cdk-nag" 72 | ], 73 | "keywords": [ 74 | "aws", 75 | "cdk", 76 | "fargate", 77 | "serverless" 78 | ], 79 | "main": "lib/index.js", 80 | "license": "UNLICENSED", 81 | "version": "0.0.0", 82 | "jest": { 83 | "testMatch": [ 84 | "/src/**/__tests__/**/*.ts?(x)", 85 | "/(test|src)/**/*(*.)@(spec|test).ts?(x)" 86 | ], 87 | "clearMocks": true, 88 | "collectCoverage": true, 89 | "coverageReporters": [ 90 | "json", 91 | "lcov", 92 | "clover", 93 | "cobertura", 94 | "text" 95 | ], 96 | "coverageDirectory": "coverage", 97 | "coveragePathIgnorePatterns": [ 98 | "/node_modules/" 99 | ], 100 | "testPathIgnorePatterns": [ 101 | "/node_modules/" 102 | ], 103 | "watchPathIgnorePatterns": [ 104 | "/node_modules/" 105 | ], 106 | "reporters": [ 107 | "default", 108 | [ 109 | "jest-junit", 110 | { 111 | "outputDirectory": "test-reports" 112 | } 113 | ] 114 | ], 115 | "preset": "ts-jest", 116 | "globals": { 117 | "ts-jest": { 118 | "tsconfig": "tsconfig.dev.json" 119 | } 120 | } 121 | }, 122 | "types": "lib/index.d.ts", 123 | "stability": "stable", 124 | "jsii": { 125 | "outdir": "dist", 126 | "targets": { 127 | "python": { 128 | "distName": "serverless-container-constructs", 129 | "module": "serverless_container_constructs" 130 | } 131 | }, 132 | "tsc": { 133 | "outDir": "lib", 134 | "rootDir": "src" 135 | } 136 | }, 137 | "awscdkio": { 138 | "twitter": "pahudnet", 139 | "announce": false 140 | }, 141 | "resolutions": { 142 | "pac-resolver": "^5.0.0", 143 | "set-value": "^4.0.1", 144 | "ansi-regex": "^5.0.1", 145 | "@types/responselike": "1.0.0", 146 | "got": "12.3.1", 147 | "@types/prettier": "2.6.0" 148 | }, 149 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 150 | } 151 | -------------------------------------------------------------------------------- /services/CommonService/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/lambda/provided:al2 AS builder 2 | 3 | RUN yum update -y && yum install -y golang 4 | 5 | WORKDIR /go/src/myapp.github.com 6 | 7 | COPY . . 8 | 9 | RUN GOOS=linux GOARCH=amd64 go build 10 | 11 | FROM public.ecr.aws/lambda/provided:al2 12 | 13 | WORKDIR /root/ 14 | 15 | COPY --from=builder /go/src/myapp.github.com/myapp /root/ 16 | 17 | RUN ls /root/ 18 | 19 | EXPOSE 8080 20 | 21 | ENTRYPOINT [] 22 | 23 | CMD ["./myapp"] -------------------------------------------------------------------------------- /services/CommonService/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pahud/myapp/v2 2 | 3 | go 1.16 4 | 5 | require github.com/gin-gonic/gin v1.7.2 // indirect 6 | -------------------------------------------------------------------------------- /services/CommonService/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 4 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 5 | github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= 6 | github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 7 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 8 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 9 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 10 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 11 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 12 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 13 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 14 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 15 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 16 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 17 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 18 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 19 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 20 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 21 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 22 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 23 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 24 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 25 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 26 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 29 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 30 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 31 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 32 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 33 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 34 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 35 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 36 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 37 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 38 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 39 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 42 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 44 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 45 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 47 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 48 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 49 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 50 | -------------------------------------------------------------------------------- /services/CommonService/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func main() { 10 | serviceName, versionNum := "undefined", "undefined" 11 | 12 | if v, ok := os.LookupEnv("serviceName"); ok { 13 | serviceName = v 14 | } 15 | 16 | if v, ok := os.LookupEnv("versionNum"); ok { 17 | versionNum = v 18 | } 19 | 20 | r := gin.Default() 21 | r.GET("/", func(c *gin.Context) { 22 | c.JSON(200, gin.H{ 23 | "service": serviceName, 24 | "version": versionNum, 25 | }) 26 | }) 27 | r.Run() // listen and serve on 0.0.0.0:8080 28 | } 29 | -------------------------------------------------------------------------------- /services/OrderService/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/lambda/provided:al2 AS builder 2 | 3 | RUN yum update -y && yum install -y golang 4 | 5 | WORKDIR /go/src/myapp.github.com 6 | 7 | COPY . . 8 | 9 | RUN GOOS=linux GOARCH=amd64 go build 10 | 11 | FROM public.ecr.aws/lambda/provided:al2 12 | 13 | WORKDIR /root/ 14 | 15 | COPY --from=builder /go/src/myapp.github.com/myapp /root/ 16 | 17 | RUN ls /root/ 18 | 19 | EXPOSE 8080 20 | 21 | ENTRYPOINT [] 22 | 23 | CMD ["./myapp"] -------------------------------------------------------------------------------- /services/OrderService/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pahud/myapp/v2 2 | 3 | go 1.16 4 | 5 | require github.com/gin-gonic/gin v1.7.2 // indirect 6 | -------------------------------------------------------------------------------- /services/OrderService/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 4 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 5 | github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= 6 | github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 7 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 8 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 9 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 10 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 11 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 12 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 13 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 14 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 15 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 16 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 17 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 18 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 19 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 20 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 21 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 22 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 23 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 24 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 25 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 26 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 29 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 30 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 31 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 32 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 33 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 34 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 35 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 36 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 37 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 38 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 39 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 42 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 44 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 45 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 47 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 48 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 49 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 50 | -------------------------------------------------------------------------------- /services/OrderService/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func HttpGet(url string, c chan<- string) { 15 | resp, err := http.Get(url) 16 | if err != nil { 17 | c <- "error" 18 | log.Println(err) 19 | } else { 20 | defer resp.Body.Close() 21 | body, _ := ioutil.ReadAll(resp.Body) 22 | c <- string(body) 23 | } 24 | 25 | } 26 | 27 | func fetchServices() []string { 28 | // fetch other microservices result 29 | ch := make(chan string, 2) 30 | 31 | microservices := []string{} 32 | buf := []string{} 33 | 34 | if v, ok := os.LookupEnv("PRODUCT_SVC_URL"); ok { 35 | fmt.Println("got PRODUCT_SVC_URL") 36 | PRODUCT_SVC_URL := v 37 | microservices = append(microservices, PRODUCT_SVC_URL) 38 | // go HttpGet(PRODUCT_SVC_URL, ch) 39 | } 40 | if v, ok := os.LookupEnv("CUSTOMER_SVC_URL"); ok { 41 | fmt.Println("got CUSTOMER_SVC_URL") 42 | CUSTOMER_SVC_URL := v 43 | microservices = append(microservices, CUSTOMER_SVC_URL) 44 | // go HttpGet(CUSTOMER_SVC_URL, ch) 45 | } 46 | 47 | for _, url := range microservices { 48 | fmt.Printf("fetching %s\n", url) 49 | go HttpGet(url, ch) 50 | } 51 | 52 | for range microservices { 53 | o := <-ch 54 | buf = append(buf, o) 55 | } 56 | return buf 57 | } 58 | 59 | func main() { 60 | 61 | r := gin.Default() 62 | r.GET("/", func(c *gin.Context) { 63 | serviceName, versionNum := "undefined", "undefined" 64 | 65 | if v, ok := os.LookupEnv("serviceName"); ok { 66 | serviceName = v 67 | } 68 | 69 | if v, ok := os.LookupEnv("versionNum"); ok { 70 | versionNum = v 71 | } 72 | buf := fetchServices() 73 | c.String(http.StatusOK, "{\"service\":%s, \"version\":%s}\n%s\n", serviceName, versionNum, strings.Join(buf, "\n")) 74 | 75 | }) 76 | r.Run() // listen and serve on 0.0.0.0:8080 77 | } 78 | -------------------------------------------------------------------------------- /src/alb-fargate-svcs.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CfnOutput, Stack, 3 | aws_ec2 as ec2, 4 | aws_ecs as ecs, 5 | aws_elasticloadbalancingv2 as elbv2, 6 | aws_iam as iam, 7 | aws_route53 as route53, 8 | aws_route53_targets as targets, 9 | } from 'aws-cdk-lib'; 10 | import { Construct } from 'constructs'; 11 | import * as cdknag from './cdknag'; 12 | import { BaseFargateServices, BaseFargateServicesProps, LoadBalancerAccessibility } from './main'; 13 | 14 | export interface AlbFargateServicesProps extends BaseFargateServicesProps {} 15 | 16 | export class AlbFargateServices extends BaseFargateServices { 17 | readonly externalAlb?: elbv2.ApplicationLoadBalancer; 18 | readonly internalAlb?: elbv2.ApplicationLoadBalancer; 19 | constructor(scope: Construct, id: string, props: AlbFargateServicesProps) { 20 | super(scope, id, props); 21 | 22 | // create the access log bucket 23 | const accessLogBucket = new cdknag.AccessLogDeliveryBucket(this, 'AccessLogBucket').bucket; 24 | 25 | if (this.hasExternalLoadBalancer) { 26 | this.externalAlb = new elbv2.ApplicationLoadBalancer(this, 'ExternalAlb', { 27 | vpc: this.vpc, 28 | internetFacing: true, 29 | }); 30 | this.externalAlb.logAccessLogs(accessLogBucket, `${id}-extalblog`); 31 | } 32 | 33 | if (this.hasInternalLoadBalancer) { 34 | this.internalAlb = new elbv2.ApplicationLoadBalancer(this, 'InternalAlb', { 35 | vpc: this.vpc, 36 | internetFacing: false, 37 | }); 38 | this.internalAlb.logAccessLogs(accessLogBucket, `${id}-intalblog`); 39 | } 40 | 41 | props.tasks.forEach((t, index )=> { 42 | const defaultContainerName = t.task.defaultContainer?.containerName; 43 | 44 | // default scaling policy 45 | const scaling = this.service[index].autoScaleTaskCount({ maxCapacity: t.scalingPolicy?.maxCapacity ?? 10 }); 46 | scaling.scaleOnCpuUtilization('CpuScaling', { 47 | targetUtilizationPercent: t.scalingPolicy?.targetCpuUtilization ?? 50, 48 | }); 49 | 50 | if (t.accessibility != LoadBalancerAccessibility.INTERNAL_ONLY) { 51 | const exttg = new elbv2.ApplicationTargetGroup(this, `${defaultContainerName}ExtTG`, { 52 | protocol: elbv2.ApplicationProtocol.HTTP, 53 | vpc: this.vpc, 54 | }); 55 | // listener for the external ALB 56 | new elbv2.ApplicationListener(this, `ExtAlbListener${t.listenerPort}`, { 57 | loadBalancer: this.externalAlb!, 58 | open: true, 59 | port: t.listenerPort, 60 | protocol: elbv2.ApplicationProtocol.HTTP, 61 | defaultTargetGroups: [exttg], 62 | }); 63 | scaling.scaleOnRequestCount('RequestScaling', { 64 | requestsPerTarget: t.scalingPolicy?.requestPerTarget ?? 1000, 65 | targetGroup: exttg, 66 | }); 67 | exttg.addTarget(this.service[index]); 68 | } 69 | 70 | if (t.accessibility != LoadBalancerAccessibility.EXTERNAL_ONLY) { 71 | const inttg = new elbv2.ApplicationTargetGroup(this, `${defaultContainerName}IntTG`, { 72 | protocol: elbv2.ApplicationProtocol.HTTP, 73 | vpc: this.vpc, 74 | }); 75 | 76 | // listener for the internal ALB 77 | new elbv2.ApplicationListener(this, `IntAlbListener${t.listenerPort}`, { 78 | loadBalancer: this.internalAlb!, 79 | open: true, 80 | port: t.listenerPort, 81 | protocol: elbv2.ApplicationProtocol.HTTP, 82 | defaultTargetGroups: [inttg], 83 | }); 84 | 85 | // extra scaling policy 86 | scaling.scaleOnRequestCount('RequestScaling2', { 87 | requestsPerTarget: t.scalingPolicy?.requestPerTarget ?? 1000, 88 | targetGroup: inttg, 89 | }); 90 | inttg.addTarget(this.service[index]); 91 | } 92 | 93 | }); 94 | 95 | // Route53 96 | const externalAlbRecordName = props.route53Ops?.externalElbRecordName ?? 'external'; 97 | const internalAlbRecordName = props.route53Ops?.internalElbRecordName ?? 'internal'; 98 | const zone = new route53.PrivateHostedZone(this, 'HostedZone', { 99 | zoneName: this.zoneName, 100 | vpc: this.vpc, 101 | }); 102 | 103 | if (this.hasInternalLoadBalancer) { 104 | new route53.ARecord(this, 'InternalAlbAlias', { 105 | zone, 106 | recordName: internalAlbRecordName, 107 | target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(this.internalAlb!)), 108 | }); 109 | } 110 | 111 | 112 | if (this.hasExternalLoadBalancer) { 113 | new route53.ARecord(this, 'ExternalAlbAlias', { 114 | zone, 115 | recordName: externalAlbRecordName, 116 | target: route53.RecordTarget.fromAlias(new targets.LoadBalancerTarget(this.externalAlb!)), 117 | }); 118 | } 119 | 120 | if (this.hasExternalLoadBalancer) { 121 | new CfnOutput(this, 'ExternalEndpoint', { value: `http://${this.externalAlb!.loadBalancerDnsName}` }); 122 | new CfnOutput(this, 'ExternalEndpointPrivate', { value: `http://${externalAlbRecordName}.${this.zoneName}` }); 123 | } 124 | if (this.hasInternalLoadBalancer) { 125 | new CfnOutput(this, 'InternalEndpoint', { value: `http://${this.internalAlb!.loadBalancerDnsName}` }); 126 | new CfnOutput(this, 'InternalEndpointPrivate', { value: `http://${internalAlbRecordName}.${this.zoneName}` }); 127 | } 128 | 129 | // ensure the dependency 130 | const cp = this.node.tryFindChild('Cluster') as ecs.CfnClusterCapacityProviderAssociations; 131 | this.service.forEach(s => { 132 | s.node.addDependency(cp); 133 | }); 134 | 135 | // add solution ID for the stack 136 | if (!Stack.of(this).templateOptions.description) { 137 | Stack.of(this).templateOptions.description = '(SO8030) - AWS CDK stack with serverless-container-constructs'; 138 | } 139 | 140 | /** 141 | * suppress the cdk-nag rules 142 | */ 143 | if (this.externalAlb) { 144 | let sg: ec2.CfnSecurityGroup; 145 | sg = this.externalAlb.node.tryFindChild('SecurityGroup') as ec2.CfnSecurityGroup; 146 | cdknag.Suppress.securityGroup(sg, [ 147 | { 148 | id: 'AwsSolutions-EC23', 149 | reason: 'public ALB requires 0.0.0.0/0 inbound access', 150 | }, 151 | ]); 152 | } 153 | if (this.internalAlb) { 154 | let sg: ec2.CfnSecurityGroup; 155 | sg = this.internalAlb.node.tryFindChild('SecurityGroup') as ec2.CfnSecurityGroup; 156 | cdknag.Suppress.securityGroup(sg, [ 157 | { 158 | id: 'AwsSolutions-EC23', 159 | reason: 'internal ALB requires 0.0.0.0/0 inbound access', 160 | }, 161 | ]); 162 | } 163 | props.tasks.forEach(t => { 164 | let cfnPolicy = t.task.executionRole?.node.tryFindChild('DefaultPolicy') as iam.Policy; 165 | cdknag.Suppress.iamPolicy(cfnPolicy, [ 166 | { 167 | id: 'AwsSolutions-IAM5', 168 | reason: 'ecr:GetAuthorizationToken requires wildcard resource', 169 | }, 170 | ]); 171 | cfnPolicy = t.task.taskRole?.node.tryFindChild('DefaultPolicy') as iam.Policy; 172 | cdknag.Suppress.iamPolicy(cfnPolicy, [ 173 | { 174 | id: 'AwsSolutions-IAM5', 175 | reason: 'task role with ECS exec support requires wildcard resource for ssmmessages. see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html', 176 | }, 177 | ]); 178 | }); 179 | 180 | } 181 | } -------------------------------------------------------------------------------- /src/cdknag.ts: -------------------------------------------------------------------------------- 1 | import { 2 | aws_ec2 as ec2, 3 | aws_iam as iam, 4 | aws_s3 as s3, 5 | } from 'aws-cdk-lib'; 6 | import { Construct } from 'constructs'; 7 | 8 | interface ruleToSuppress { 9 | readonly id: string; 10 | readonly reason: string; 11 | } 12 | 13 | export class Suppress { 14 | static bucket(construct: Construct, rulesToSuppress: ruleToSuppress[] ) { 15 | const cfnResource = construct?.node.defaultChild as s3.CfnBucket; 16 | cfnResource?.addMetadata('cdk_nag', { rules_to_suppress: rulesToSuppress }); 17 | } 18 | static securityGroup(construct: Construct, rulesToSuppress: ruleToSuppress[]) { 19 | const cfnResource = construct?.node.defaultChild as ec2.CfnSecurityGroup; 20 | cfnResource?.addMetadata('cdk_nag', { rules_to_suppress: rulesToSuppress }); 21 | } 22 | static iamPolicy(construct: Construct, rulesToSuppress: ruleToSuppress[]) { 23 | const cfnResource = construct?.node.defaultChild as iam.CfnPolicy; 24 | cfnResource?.addMetadata('cdk_nag', { rules_to_suppress: rulesToSuppress }); 25 | } 26 | } 27 | 28 | 29 | export interface BucketProps { 30 | readonly serverAccessLogsPrefix?: string; 31 | } 32 | 33 | /** 34 | * The generic access log and log delivery bucket. 35 | */ 36 | export class AccessLogDeliveryBucket extends Construct { 37 | readonly bucket: s3.Bucket; 38 | constructor(scope: Construct, id: string, props: BucketProps = {}) { 39 | super(scope, id); 40 | this.bucket = new s3.Bucket(this, id, { 41 | encryption: s3.BucketEncryption.S3_MANAGED, 42 | accessControl: s3.BucketAccessControl.LOG_DELIVERY_WRITE, 43 | serverAccessLogsPrefix: props.serverAccessLogsPrefix ?? 'access-log-', 44 | blockPublicAccess: { 45 | blockPublicAcls: true, 46 | blockPublicPolicy: true, 47 | ignorePublicAcls: true, 48 | restrictPublicBuckets: true, 49 | }, 50 | }); 51 | Suppress.bucket(this.bucket, [ 52 | { id: 'AwsSolutions-S1', reason: 'implicitly set current bucket as the acces log bucket' }, 53 | ]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/common/common-functions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | aws_ec2 as ec2, 3 | } from 'aws-cdk-lib'; 4 | import { Construct } from 'constructs'; 5 | 6 | export function getOrCreateVpc(scope: Construct): ec2.IVpc { 7 | // use an existing vpc or create a new one 8 | return scope.node.tryGetContext('use_default_vpc') === '1' 9 | || process.env.CDK_USE_DEFAULT_VPC === '1' ? ec2.Vpc.fromLookup(scope, 'Vpc', { isDefault: true }) : 10 | scope.node.tryGetContext('use_vpc_id') ? 11 | ec2.Vpc.fromLookup(scope, 'Vpc', { vpcId: scope.node.tryGetContext('use_vpc_id') }) : 12 | new ec2.Vpc(scope, 'Vpc', { 13 | maxAzs: 3, 14 | natGateways: 1, 15 | flowLogs: { flowLogs: {} }, 16 | }); 17 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './main'; 2 | export * from './alb-fargate-svcs'; 3 | export * from './common/common-functions'; -------------------------------------------------------------------------------- /src/integ.default.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { 3 | Stack, App, Aspects, 4 | aws_ec2 as ec2, 5 | aws_ecs as ecs, 6 | } from 'aws-cdk-lib'; 7 | import { AwsSolutionsChecks } from 'cdk-nag'; 8 | import { AlbFargateServices } from './index'; 9 | import { LoadBalancerAccessibility } from './main'; 10 | 11 | const app = new App(); 12 | 13 | const env = { 14 | region: process.env.CDK_DEFAULT_REGION, 15 | account: process.env.CDK_DEFAULT_ACCOUNT, 16 | }; 17 | 18 | const stack = new Stack(app, 'demo-stack', { env }); 19 | 20 | if (stack.node.tryGetContext('AWS_SOLUTIONS_CHECK')) { 21 | Aspects.of(app).add(new AwsSolutionsChecks()); 22 | } 23 | 24 | const orderTask = new ecs.FargateTaskDefinition(stack, 'orderTask', { 25 | cpu: 256, 26 | memoryLimitMiB: 512, 27 | }); 28 | 29 | const zoneName = 'svc.local'; 30 | const internalAlbRecordName = 'internal'; 31 | const externalAlbRecordName = 'external'; 32 | const internalALBEndpoint = `http://${internalAlbRecordName}.${zoneName}`; 33 | 34 | orderTask.addContainer('order', { 35 | image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../services/OrderService')), 36 | portMappings: [ 37 | { containerPort: 8080 }, 38 | ], 39 | environment: { 40 | PRODUCT_SVC_URL: `${internalALBEndpoint}:9090`, 41 | CUSTOMER_SVC_URL: `${internalALBEndpoint}:8080`, 42 | serviceName: 'order', 43 | versionNum: '1.0', 44 | }, 45 | logging: new ecs.AwsLogDriver({ streamPrefix: 'order-' }), 46 | }); 47 | 48 | const customerTask = new ecs.FargateTaskDefinition(stack, 'customerTask', { 49 | cpu: 256, 50 | memoryLimitMiB: 512, 51 | }); 52 | 53 | customerTask.addContainer('customer', { 54 | image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../services/CommonService')), 55 | portMappings: [ 56 | { containerPort: 8080 }, 57 | ], 58 | environment: { 59 | PRODUCT_SVC_URL: `${internalALBEndpoint}:9090`, 60 | CUSTOMER_SVC_URL: `${internalALBEndpoint}:8080`, 61 | serviceName: 'customer', 62 | versionNum: '1.0', 63 | }, 64 | }); 65 | 66 | const productTask = new ecs.FargateTaskDefinition(stack, 'productTask', { 67 | cpu: 256, 68 | memoryLimitMiB: 512, 69 | }); 70 | 71 | productTask.addContainer('product', { 72 | image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../services/CommonService')), 73 | portMappings: [ 74 | { containerPort: 8080 }, 75 | ], 76 | environment: { 77 | PRODUCT_SVC_URL: `${internalALBEndpoint}:9090`, 78 | CUSTOMER_SVC_URL: `${internalALBEndpoint}:8080`, 79 | serviceName: 'product', 80 | versionNum: '1.0', 81 | }, 82 | }); 83 | 84 | const svc = new AlbFargateServices(stack, 'Service', { 85 | spot: true, // FARGATE_SPOT only cluster 86 | enableExecuteCommand: true, 87 | tasks: [ 88 | // The order service with both external/internal access 89 | { 90 | listenerPort: 80, 91 | accessibility: LoadBalancerAccessibility.EXTERNAL_ONLY, 92 | task: orderTask, 93 | desiredCount: 2, 94 | // customize the service autoscaling policy 95 | scalingPolicy: { 96 | maxCapacity: 20, 97 | requestPerTarget: 1000, 98 | targetCpuUtilization: 50, 99 | }, 100 | }, 101 | { 102 | // The customer service(internal only) 103 | accessibility: LoadBalancerAccessibility.INTERNAL_ONLY, 104 | listenerPort: 8080, 105 | task: customerTask, 106 | desiredCount: 1, 107 | capacityProviderStrategy: [ 108 | { 109 | capacityProvider: 'FARGATE', 110 | base: 1, 111 | weight: 1, 112 | }, 113 | { 114 | capacityProvider: 'FARGATE_SPOT', 115 | base: 0, 116 | weight: 3, 117 | }, 118 | ], 119 | }, 120 | // The produce service(internal only) 121 | { listenerPort: 9090, task: productTask, desiredCount: 1, accessibility: LoadBalancerAccessibility.INTERNAL_ONLY }, 122 | ], 123 | route53Ops: { 124 | zoneName, // svc.local 125 | externalElbRecordName: externalAlbRecordName, // external.svc.local 126 | internalElbRecordName: internalAlbRecordName, // internal.svc.local 127 | }, 128 | }); 129 | 130 | 131 | // create a dummy sg 132 | const dummySg = new ec2.SecurityGroup(stack, 'DummySG', { 133 | vpc: svc.vpc, 134 | }); 135 | 136 | // allow all traffic from dummy sg to all the services 137 | for (let i = 0; i < svc.service.length; i++) { 138 | svc.service[i].connections.allowFrom(dummySg, ec2.Port.allTraffic()); 139 | } 140 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Stack, 3 | aws_ec2 as ec2, 4 | aws_ecs as ecs, 5 | aws_iam as iam, 6 | } from 'aws-cdk-lib'; 7 | import { Construct } from 'constructs'; 8 | import * as cdknag from './cdknag'; 9 | import { getOrCreateVpc } from './common/common-functions'; 10 | 11 | export interface BaseFargateServicesProps { 12 | readonly vpc?: ec2.IVpc; 13 | readonly tasks: FargateTaskProps[]; 14 | readonly route53Ops?: Route53Options; 15 | /** 16 | * create a FARGATE_SPOT only cluster 17 | * @default false 18 | */ 19 | readonly spot?: boolean; 20 | /** 21 | * Whether to enable ECS Exec support 22 | * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html 23 | * @default false 24 | */ 25 | readonly enableExecuteCommand?: boolean; 26 | /** 27 | * The subnets to associate with the service. 28 | * @default - 29 | * { 30 | * subnetType: ec2.SubnetType.PRIVATE, 31 | * } 32 | */ 33 | readonly vpcSubnets?: ec2.SubnetSelection; 34 | } 35 | 36 | /** 37 | * The load balancer accessibility. 38 | */ 39 | export enum LoadBalancerAccessibility { 40 | /** 41 | * register to external load balancer only 42 | */ 43 | EXTERNAL_ONLY = 'EXTERNAL_ONLY', 44 | /** 45 | * register to internal load balancer only 46 | */ 47 | INTERNAL_ONLY = 'INTERNAL_ONLY', 48 | } 49 | 50 | export interface FargateTaskProps { 51 | readonly task: ecs.FargateTaskDefinition; 52 | readonly listenerPort: number; 53 | /** 54 | * desired number of tasks for the service 55 | * @default 1 56 | */ 57 | readonly desiredCount?: number; 58 | /** 59 | * service autoscaling policy 60 | * @default - { maxCapacity: 10, targetCpuUtilization: 50, requestsPerTarget: 1000 } 61 | */ 62 | readonly scalingPolicy?: ServiceScalingPolicy; 63 | readonly capacityProviderStrategy?: ecs.CapacityProviderStrategy[]; 64 | /** 65 | * Register the service to internal ELB, external ELB or both. 66 | * @default both 67 | */ 68 | readonly accessibility?: LoadBalancerAccessibility; 69 | } 70 | 71 | 72 | export interface ServiceScalingPolicy { 73 | /** 74 | * max capacity for the service autoscaling 75 | * @default 10 76 | */ 77 | readonly maxCapacity?: number; 78 | /** 79 | * target cpu utilization 80 | * @default 50 81 | */ 82 | readonly targetCpuUtilization?: number; 83 | /** 84 | * request per target 85 | * @default 1000 86 | */ 87 | readonly requestPerTarget?: number; 88 | } 89 | 90 | export interface Route53Options { 91 | /** 92 | * private zone name 93 | * @default svc.local 94 | */ 95 | readonly zoneName?: string; 96 | /** 97 | * the external ELB record name 98 | * @default external 99 | */ 100 | readonly externalElbRecordName?: string; 101 | /** 102 | * the internal ELB record name 103 | * @default internal 104 | */ 105 | readonly internalElbRecordName?: string; 106 | } 107 | 108 | export abstract class BaseFargateServices extends Construct { 109 | readonly vpc: ec2.IVpc; 110 | /** 111 | * The service(s) created from the task(s) 112 | */ 113 | readonly service: ecs.FargateService[]; 114 | protected zoneName: string = ''; 115 | protected hasExternalLoadBalancer: boolean = false; 116 | protected hasInternalLoadBalancer: boolean = false; 117 | protected vpcSubnets: ec2.SubnetSelection = { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }; 118 | /** 119 | * determine if vpcSubnets are all public ones 120 | */ 121 | private isPublicSubnets: boolean = false; 122 | constructor(scope: Construct, id: string, props: BaseFargateServicesProps) { 123 | super(scope, id); 124 | 125 | this.vpc = props.vpc ?? getOrCreateVpc(this), 126 | this.service = []; 127 | if (props.vpcSubnets) { 128 | this.vpcSubnets = props.vpcSubnets; 129 | this.validateSubnets(this.vpc, this.vpcSubnets); 130 | } 131 | 132 | 133 | // determine whether we need the external LB 134 | props.tasks.forEach(t => { 135 | // determine the accessibility 136 | if (t.accessibility != LoadBalancerAccessibility.INTERNAL_ONLY ) { 137 | this.hasExternalLoadBalancer = true; 138 | } 139 | if (t.accessibility != LoadBalancerAccessibility.EXTERNAL_ONLY) { 140 | this.hasInternalLoadBalancer = true; 141 | } 142 | }); 143 | 144 | const cluster = new ecs.Cluster(this, 'Cluster', { 145 | vpc: this.vpc, 146 | enableFargateCapacityProviders: true, 147 | containerInsights: true, 148 | executeCommandConfiguration: { 149 | logging: ecs.ExecuteCommandLogging.DEFAULT, 150 | }, 151 | }); 152 | 153 | const spotOnlyStrategy = [ 154 | { 155 | capacityProvider: 'FARGATE_SPOT', 156 | base: 0, 157 | weight: 1, 158 | }, 159 | { 160 | capacityProvider: 'FARGATE', 161 | base: 0, 162 | weight: 0, 163 | }, 164 | ]; 165 | 166 | props.tasks.forEach(t => { 167 | const defaultContainerName = t.task.defaultContainer?.containerName; 168 | const svc = new ecs.FargateService(this, `${defaultContainerName}Service`, { 169 | taskDefinition: t.task, 170 | cluster, 171 | capacityProviderStrategies: t.capacityProviderStrategy ?? ( props.spot ? spotOnlyStrategy : undefined ), 172 | desiredCount: t.desiredCount, 173 | enableExecuteCommand: props.enableExecuteCommand ?? false, 174 | vpcSubnets: this.vpcSubnets, 175 | assignPublicIp: this.isPublicSubnets, 176 | }); 177 | this.service.push(svc); 178 | }); 179 | 180 | // Route53 181 | this.zoneName = props.route53Ops?.zoneName ?? 'svc.local'; 182 | 183 | // ensure the dependency 184 | const cp = this.node.tryFindChild('Cluster') as ecs.CfnClusterCapacityProviderAssociations; 185 | this.service.forEach(s => { 186 | s.node.addDependency(cp); 187 | }); 188 | 189 | // add solution ID for the stack 190 | if (!Stack.of(this).templateOptions.description) { 191 | Stack.of(this).templateOptions.description = '(SO8030) - AWS CDK stack with serverless-container-constructs'; 192 | } 193 | 194 | props.tasks.forEach(t => { 195 | let cfnPolicy = t.task.executionRole?.node.tryFindChild('DefaultPolicy') as iam.Policy; 196 | cdknag.Suppress.iamPolicy(cfnPolicy, [ 197 | { 198 | id: 'AwsSolutions-IAM5', 199 | reason: 'ecr:GetAuthorizationToken requires wildcard resource', 200 | }, 201 | ]); 202 | cfnPolicy = t.task.taskRole?.node.tryFindChild('DefaultPolicy') as iam.Policy; 203 | cdknag.Suppress.iamPolicy(cfnPolicy, [ 204 | { 205 | id: 'AwsSolutions-IAM5', 206 | reason: 'task role with ECS exec support requires wildcard resource for ssmmessages. see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html', 207 | }, 208 | ]); 209 | }); 210 | 211 | } 212 | 213 | private validateSubnets(vpc: ec2.IVpc, vpcSubnets: ec2.SubnetSelection) { 214 | const subnets = vpc.selectSubnets(vpcSubnets); 215 | // get all subnets in the VPC 216 | const allsubnetIds = vpc.publicSubnets.concat(vpc.privateSubnets).concat(vpc.isolatedSubnets).map(x => x.subnetId); 217 | // validate the given subnets 218 | subnets.subnetIds.forEach(s => { 219 | if (!allsubnetIds.includes(s)) { 220 | throw new Error(`${s} does not exist in the VPC`); 221 | } 222 | if (vpc.isolatedSubnets.map(i => i.subnetId).includes(s)) { 223 | throw new Error(`Isolated subnet ${s} is not allowed`); 224 | } 225 | }); 226 | const hasPublic = subnets.subnetIds.some(s => new Set(vpc.publicSubnets.map(x => x.subnetId)).has(s)); 227 | const hasPrivate = subnets.subnetIds.some(s => new Set(vpc.privateSubnets.map(x => x.subnetId)).has(s)); 228 | if (hasPublic && hasPrivate) { 229 | throw new Error('You should provide either all public or all private subnets, not both.'); 230 | } 231 | this.isPublicSubnets = subnets.subnetIds.some(s => new Set(vpc.publicSubnets.map(x => x.subnetId)).has(s)); 232 | } 233 | } -------------------------------------------------------------------------------- /test/default.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { 3 | App, Stack, 4 | aws_ec2 as ec2, 5 | aws_ecs as ecs, 6 | } from 'aws-cdk-lib'; 7 | import { Template, Match } from 'aws-cdk-lib/assertions'; 8 | import { AlbFargateServices, LoadBalancerAccessibility } from '../src/index'; 9 | 10 | let app: App; 11 | let env: { region: string; account: string }; 12 | let stack: Stack; 13 | 14 | 15 | beforeEach(() => { 16 | app = new App(); 17 | env = { 18 | region: 'us-east-1', 19 | account: '123456789012', 20 | }; 21 | stack = new Stack(app, 'demo-stack', { env }); 22 | }); 23 | 24 | 25 | // match snapshot 26 | test('Snapshot', () => { 27 | const orderTask = new ecs.FargateTaskDefinition(stack, 'orderTask', { 28 | cpu: 256, 29 | memoryLimitMiB: 512, 30 | }); 31 | 32 | const zoneName = 'svc.local'; 33 | const internalAlbRecordName = 'internal'; 34 | const externalAlbRecordName = 'external'; 35 | const internalALBEndpoint = `http://${internalAlbRecordName}.${zoneName}`; 36 | 37 | orderTask.addContainer('order', { 38 | image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../services/OrderService')), 39 | portMappings: [ 40 | { containerPort: 8080 }, 41 | ], 42 | environment: { 43 | PRODUCT_SVC_URL: `${internalALBEndpoint}:9090`, 44 | CUSTOMER_SVC_URL: `${internalALBEndpoint}:8080`, 45 | serviceName: 'order', 46 | versionNum: '1.0', 47 | }, 48 | }); 49 | 50 | const customerTask = new ecs.FargateTaskDefinition(stack, 'customerTask', { 51 | cpu: 256, 52 | memoryLimitMiB: 512, 53 | }); 54 | 55 | customerTask.addContainer('customer', { 56 | image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../services/CommonService')), 57 | portMappings: [ 58 | { containerPort: 8080 }, 59 | ], 60 | environment: { 61 | PRODUCT_SVC_URL: `${internalALBEndpoint}:9090`, 62 | CUSTOMER_SVC_URL: `${internalALBEndpoint}:8080`, 63 | serviceName: 'customer', 64 | versionNum: '1.0', 65 | }, 66 | }); 67 | 68 | const productTask = new ecs.FargateTaskDefinition(stack, 'productTask', { 69 | cpu: 256, 70 | memoryLimitMiB: 512, 71 | }); 72 | 73 | productTask.addContainer('product', { 74 | image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../services/CommonService')), 75 | portMappings: [ 76 | { containerPort: 8080 }, 77 | ], 78 | environment: { 79 | PRODUCT_SVC_URL: `${internalALBEndpoint}:9090`, 80 | CUSTOMER_SVC_URL: `${internalALBEndpoint}:8080`, 81 | serviceName: 'product', 82 | versionNum: '1.0', 83 | }, 84 | }); 85 | 86 | new AlbFargateServices(stack, 'Service', { 87 | spot: true, // FARGATE_SPOT only cluster 88 | tasks: [ 89 | { 90 | listenerPort: 80, 91 | task: orderTask, 92 | desiredCount: 2, 93 | // customize the service autoscaling policy 94 | scalingPolicy: { 95 | maxCapacity: 20, 96 | requestPerTarget: 1000, 97 | targetCpuUtilization: 50, 98 | }, 99 | }, 100 | { listenerPort: 8080, task: customerTask, desiredCount: 2 }, 101 | { listenerPort: 9090, task: productTask, desiredCount: 2 }, 102 | ], 103 | route53Ops: { 104 | zoneName, // svc.local 105 | externalElbRecordName: externalAlbRecordName, // external.svc.local 106 | internalElbRecordName: internalAlbRecordName, // internal.svc.local 107 | }, 108 | }); 109 | expect(app.synth().getStackArtifact(stack.artifactId).template).toMatchSnapshot(); 110 | }); 111 | 112 | test('AlbFargateServices - minimal setup', () => { 113 | // GIVEN 114 | // WHEN 115 | const task = new ecs.FargateTaskDefinition(stack, 'task', { 116 | cpu: 256, 117 | memoryLimitMiB: 512, 118 | }); 119 | 120 | task.addContainer('nginx', { 121 | image: ecs.ContainerImage.fromRegistry('nginx'), 122 | portMappings: [{ containerPort: 80 }], 123 | }); 124 | 125 | new AlbFargateServices(stack, 'Service', { 126 | tasks: [{ listenerPort: 80, task }], 127 | }); 128 | 129 | // THEN 130 | // We should have two ALBs 131 | // the external one 132 | 133 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 134 | Scheme: 'internet-facing', 135 | Type: 'application', 136 | }); 137 | // the internal one 138 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 139 | Scheme: 'internal', 140 | Type: 'application', 141 | }); 142 | // We should have fargate service 143 | Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { 144 | LaunchType: 'FARGATE', 145 | }); 146 | }); 147 | 148 | test('AlbFargateServices - internal only', () => { 149 | // GIVEN 150 | // WHEN 151 | const task = new ecs.FargateTaskDefinition(stack, 'task', { 152 | cpu: 256, 153 | memoryLimitMiB: 512, 154 | }); 155 | 156 | task.addContainer('nginx', { 157 | image: ecs.ContainerImage.fromRegistry('nginx'), 158 | portMappings: [{ containerPort: 80 }], 159 | }); 160 | 161 | new AlbFargateServices(stack, 'Service', { 162 | tasks: [{ listenerPort: 80, task, accessibility: LoadBalancerAccessibility.INTERNAL_ONLY }], 163 | }); 164 | 165 | // THEN 166 | // we should NOT have the external ALB 167 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 168 | Scheme: Match.not('internet-facing'), 169 | Type: 'application', 170 | }); 171 | // we should have the internal ALB 172 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 173 | Scheme: 'internal', 174 | Type: 'application', 175 | }); 176 | // We should have fargate service 177 | Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { 178 | LaunchType: 'FARGATE', 179 | }); 180 | }); 181 | 182 | test('AlbFargateServices - external only', () => { 183 | // GIVEN 184 | // WHEN 185 | const task = new ecs.FargateTaskDefinition(stack, 'task', { 186 | cpu: 256, 187 | memoryLimitMiB: 512, 188 | }); 189 | 190 | task.addContainer('nginx', { 191 | image: ecs.ContainerImage.fromRegistry('nginx'), 192 | portMappings: [{ containerPort: 80 }], 193 | }); 194 | 195 | new AlbFargateServices(stack, 'Service', { 196 | tasks: [{ listenerPort: 80, task, accessibility: LoadBalancerAccessibility.EXTERNAL_ONLY }], 197 | }); 198 | 199 | // THEN 200 | // we should NOT have the internal ALB 201 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 202 | Scheme: Match.not('internal'), 203 | }); 204 | // we should have the external ALB 205 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 206 | Scheme: 'internet-facing', 207 | Type: 'application', 208 | }); 209 | // We should have fargate service 210 | Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { 211 | LaunchType: 'FARGATE', 212 | }); 213 | }); 214 | 215 | 216 | test('AlbFargateServices - partial internal only', () => { 217 | // GIVEN 218 | // WHEN 219 | const task = new ecs.FargateTaskDefinition(stack, 'task', { 220 | cpu: 256, 221 | memoryLimitMiB: 512, 222 | }); 223 | 224 | task.addContainer('nginx', { 225 | image: ecs.ContainerImage.fromRegistry('nginx'), 226 | portMappings: [{ containerPort: 80 }], 227 | }); 228 | 229 | const task2 = new ecs.FargateTaskDefinition(stack, 'task2', { 230 | cpu: 256, 231 | memoryLimitMiB: 512, 232 | }); 233 | 234 | task2.addContainer('caddy', { 235 | image: ecs.ContainerImage.fromRegistry('caddy'), 236 | portMappings: [{ containerPort: 2015 }], 237 | }); 238 | 239 | new AlbFargateServices(stack, 'Service', { 240 | tasks: [ 241 | { listenerPort: 80, task, accessibility: LoadBalancerAccessibility.INTERNAL_ONLY }, 242 | { listenerPort: 8080, task: task2 }, 243 | ], 244 | }); 245 | 246 | // THEN 247 | // we should still have the external ALB 248 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 249 | Scheme: 'internet-facing', 250 | Type: 'application', 251 | }); 252 | // we should have the internal ALB 253 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 254 | Scheme: 'internal', 255 | Type: 'application', 256 | }); 257 | // We should have fargate service 258 | Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { 259 | LaunchType: 'FARGATE', 260 | }); 261 | }); 262 | 263 | test('AlbFargateServices - partial external only', () => { 264 | // GIVEN 265 | // WHEN 266 | const task = new ecs.FargateTaskDefinition(stack, 'task', { 267 | cpu: 256, 268 | memoryLimitMiB: 512, 269 | }); 270 | 271 | task.addContainer('nginx', { 272 | image: ecs.ContainerImage.fromRegistry('nginx'), 273 | portMappings: [{ containerPort: 80 }], 274 | }); 275 | 276 | const task2 = new ecs.FargateTaskDefinition(stack, 'task2', { 277 | cpu: 256, 278 | memoryLimitMiB: 512, 279 | }); 280 | 281 | task2.addContainer('caddy', { 282 | image: ecs.ContainerImage.fromRegistry('caddy'), 283 | portMappings: [{ containerPort: 2015 }], 284 | }); 285 | 286 | new AlbFargateServices(stack, 'Service', { 287 | tasks: [ 288 | { listenerPort: 80, task, accessibility: LoadBalancerAccessibility.EXTERNAL_ONLY }, 289 | { listenerPort: 8080, task: task2 }, 290 | ], 291 | }); 292 | 293 | // THEN 294 | // we should still have the external ALB 295 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 296 | Scheme: 'internet-facing', 297 | Type: 'application', 298 | }); 299 | // we should have the internal ALB 300 | Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { 301 | Scheme: 'internal', 302 | Type: 'application', 303 | }); 304 | // We should have fargate service 305 | Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { 306 | LaunchType: 'FARGATE', 307 | }); 308 | }); 309 | 310 | 311 | test('AlbFargateServices - vpc subnet select default select private subnet', () => { 312 | // GIVEN 313 | // WHEN 314 | const task = new ecs.FargateTaskDefinition(stack, 'task', { 315 | cpu: 256, 316 | memoryLimitMiB: 512, 317 | }); 318 | 319 | task.addContainer('nginx', { 320 | image: ecs.ContainerImage.fromRegistry('nginx'), 321 | portMappings: [{ containerPort: 80 }], 322 | }); 323 | 324 | new AlbFargateServices(stack, 'Service', { 325 | tasks: [ 326 | { listenerPort: 8080, task }, 327 | ], 328 | }); 329 | 330 | // THEN 331 | // we should still have the assgin public Ip. 332 | Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { 333 | NetworkConfiguration: { 334 | AwsvpcConfiguration: { 335 | AssignPublicIp: 'DISABLED', 336 | SecurityGroups: [ 337 | { 338 | 'Fn::GetAtt': [ 339 | 'ServicenginxServiceSecurityGroupD362365D', 340 | 'GroupId', 341 | ], 342 | }, 343 | ], 344 | Subnets: [ 345 | { 346 | Ref: 'ServiceVpcPrivateSubnet1Subnet5DB98340', 347 | }, 348 | { 349 | Ref: 'ServiceVpcPrivateSubnet2Subnet0A0B778B', 350 | }, 351 | { 352 | Ref: 'ServiceVpcPrivateSubnet3SubnetFED5903C', 353 | }, 354 | ], 355 | }, 356 | }, 357 | }); 358 | }); 359 | 360 | 361 | test('AlbFargateServices - vpc subnet select test select public subnet', () => { 362 | // GIVEN 363 | // WHEN 364 | const task = new ecs.FargateTaskDefinition(stack, 'task', { 365 | cpu: 256, 366 | memoryLimitMiB: 512, 367 | }); 368 | 369 | task.addContainer('nginx', { 370 | image: ecs.ContainerImage.fromRegistry('nginx'), 371 | portMappings: [{ containerPort: 80 }], 372 | }); 373 | 374 | new AlbFargateServices(stack, 'Service', { 375 | tasks: [ 376 | { listenerPort: 8080, task }, 377 | ], 378 | vpcSubnets: { 379 | subnetType: ec2.SubnetType.PUBLIC, 380 | }, 381 | }); 382 | 383 | // THEN 384 | // we should still have the assgin public Ip. 385 | Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { 386 | NetworkConfiguration: { 387 | AwsvpcConfiguration: { 388 | AssignPublicIp: 'ENABLED', 389 | SecurityGroups: [ 390 | { 391 | 'Fn::GetAtt': [ 392 | 'ServicenginxServiceSecurityGroupD362365D', 393 | 'GroupId', 394 | ], 395 | }, 396 | ], 397 | Subnets: [ 398 | { 399 | Ref: 'ServiceVpcPublicSubnet1Subnet7B418339', 400 | }, 401 | { 402 | Ref: 'ServiceVpcPublicSubnet2SubnetDE1A00CE', 403 | }, 404 | { 405 | Ref: 'ServiceVpcPublicSubnet3SubnetDDA2D85D', 406 | }, 407 | ], 408 | }, 409 | }, 410 | }); 411 | }); 412 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "experimentalDecorators": true, 7 | "inlineSourceMap": true, 8 | "inlineSources": true, 9 | "lib": [ 10 | "es2019" 11 | ], 12 | "module": "CommonJS", 13 | "noEmitOnError": false, 14 | "noFallthroughCasesInSwitch": true, 15 | "noImplicitAny": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "resolveJsonModule": true, 21 | "strict": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": true, 24 | "stripInternal": true, 25 | "target": "ES2019" 26 | }, 27 | "include": [ 28 | ".projenrc.js", 29 | "src/**/*.ts", 30 | "test/**/*.ts" 31 | ], 32 | "exclude": [ 33 | "node_modules" 34 | ], 35 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 36 | } 37 | --------------------------------------------------------------------------------