├── .eslintrc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ └── upgrade-main.yml ├── .gitignore ├── .mergify.yml ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── API.md ├── API.md.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── package.json ├── src ├── codebuild-handler │ └── index.ts ├── destination.ts ├── docker-image-deployment.ts ├── index.ts ├── login.ts └── source.ts ├── test ├── assets │ ├── test1 │ │ └── Dockerfile │ └── test2 │ │ └── Dockerfile ├── docker-image-deploy.test.ts └── integ │ └── app.ts ├── tsconfig.dev.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import", 11 | "@stylistic" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module", 17 | "project": "./tsconfig.dev.json" 18 | }, 19 | "extends": [ 20 | "plugin:import/typescript" 21 | ], 22 | "settings": { 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [ 25 | ".ts", 26 | ".tsx" 27 | ] 28 | }, 29 | "import/resolver": { 30 | "node": {}, 31 | "typescript": { 32 | "project": "./tsconfig.dev.json", 33 | "alwaysTryTypes": true 34 | } 35 | } 36 | }, 37 | "ignorePatterns": [ 38 | "*.js", 39 | "*.d.ts", 40 | "node_modules/", 41 | "*.generated.ts", 42 | "coverage", 43 | "!.projenrc.ts", 44 | "!projenrc/**/*.ts" 45 | ], 46 | "rules": { 47 | "@stylistic/indent": [ 48 | "error", 49 | 2 50 | ], 51 | "@stylistic/quotes": [ 52 | "error", 53 | "single", 54 | { 55 | "avoidEscape": true 56 | } 57 | ], 58 | "@stylistic/comma-dangle": [ 59 | "error", 60 | "always-multiline" 61 | ], 62 | "@stylistic/comma-spacing": [ 63 | "error", 64 | { 65 | "before": false, 66 | "after": true 67 | } 68 | ], 69 | "@stylistic/no-multi-spaces": [ 70 | "error", 71 | { 72 | "ignoreEOLComments": false 73 | } 74 | ], 75 | "@stylistic/array-bracket-spacing": [ 76 | "error", 77 | "never" 78 | ], 79 | "@stylistic/array-bracket-newline": [ 80 | "error", 81 | "consistent" 82 | ], 83 | "@stylistic/object-curly-spacing": [ 84 | "error", 85 | "always" 86 | ], 87 | "@stylistic/object-curly-newline": [ 88 | "error", 89 | { 90 | "multiline": true, 91 | "consistent": true 92 | } 93 | ], 94 | "@stylistic/object-property-newline": [ 95 | "error", 96 | { 97 | "allowAllPropertiesOnSameLine": true 98 | } 99 | ], 100 | "@stylistic/keyword-spacing": [ 101 | "error" 102 | ], 103 | "@stylistic/brace-style": [ 104 | "error", 105 | "1tbs", 106 | { 107 | "allowSingleLine": true 108 | } 109 | ], 110 | "@stylistic/space-before-blocks": [ 111 | "error" 112 | ], 113 | "@stylistic/member-delimiter-style": [ 114 | "error" 115 | ], 116 | "@stylistic/semi": [ 117 | "error", 118 | "always" 119 | ], 120 | "@stylistic/max-len": [ 121 | "error", 122 | { 123 | "code": 150, 124 | "ignoreUrls": true, 125 | "ignoreStrings": true, 126 | "ignoreTemplateLiterals": true, 127 | "ignoreComments": true, 128 | "ignoreRegExpLiterals": true 129 | } 130 | ], 131 | "@stylistic/quote-props": [ 132 | "error", 133 | "consistent-as-needed" 134 | ], 135 | "@stylistic/key-spacing": [ 136 | "error" 137 | ], 138 | "@stylistic/no-multiple-empty-lines": [ 139 | "error" 140 | ], 141 | "@stylistic/no-trailing-spaces": [ 142 | "error" 143 | ], 144 | "curly": [ 145 | "error", 146 | "multi-line", 147 | "consistent" 148 | ], 149 | "@typescript-eslint/no-require-imports": "error", 150 | "import/no-extraneous-dependencies": [ 151 | "error", 152 | { 153 | "devDependencies": [ 154 | "**/test/**", 155 | "**/build-tools/**", 156 | ".projenrc.ts", 157 | "projenrc/**/*.ts" 158 | ], 159 | "optionalDependencies": false, 160 | "peerDependencies": true 161 | } 162 | ], 163 | "import/no-unresolved": [ 164 | "error" 165 | ], 166 | "import/order": [ 167 | "warn", 168 | { 169 | "groups": [ 170 | "builtin", 171 | "external" 172 | ], 173 | "alphabetize": { 174 | "order": "asc", 175 | "caseInsensitive": true 176 | } 177 | } 178 | ], 179 | "import/no-duplicates": [ 180 | "error" 181 | ], 182 | "no-shadow": [ 183 | "off" 184 | ], 185 | "@typescript-eslint/no-shadow": "error", 186 | "@typescript-eslint/no-floating-promises": "error", 187 | "no-return-await": [ 188 | "off" 189 | ], 190 | "@typescript-eslint/return-await": "error", 191 | "dot-notation": [ 192 | "error" 193 | ], 194 | "no-bitwise": [ 195 | "error" 196 | ], 197 | "@typescript-eslint/member-ordering": [ 198 | "error", 199 | { 200 | "default": [ 201 | "public-static-field", 202 | "public-static-method", 203 | "protected-static-field", 204 | "protected-static-method", 205 | "private-static-field", 206 | "private-static-method", 207 | "field", 208 | "constructor", 209 | "method" 210 | ] 211 | } 212 | ] 213 | }, 214 | "overrides": [ 215 | { 216 | "files": [ 217 | ".projenrc.ts" 218 | ], 219 | "rules": { 220 | "@typescript-eslint/no-require-imports": "off", 221 | "import/no-extraneous-dependencies": "off" 222 | } 223 | } 224 | ] 225 | } 226 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.eslintrc.json linguist-generated 6 | /.gitattributes linguist-generated 7 | /.github/pull_request_template.md linguist-generated 8 | /.github/workflows/auto-approve.yml linguist-generated 9 | /.github/workflows/build.yml linguist-generated 10 | /.github/workflows/pull-request-lint.yml linguist-generated 11 | /.github/workflows/release.yml linguist-generated 12 | /.github/workflows/upgrade-main.yml linguist-generated 13 | /.gitignore 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 | /LICENSE linguist-generated 22 | /package.json linguist-generated 23 | /tsconfig.dev.json linguist-generated 24 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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 == 'cdklabs-automation') 18 | steps: 19 | - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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@v5 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v5 24 | with: 25 | node-version: lts/* 26 | - name: Install dependencies 27 | run: yarn install --check-files 28 | - name: build 29 | run: npx projen build 30 | - name: Find mutations 31 | id: self_mutation 32 | run: |- 33 | git add . 34 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 35 | shell: bash 36 | working-directory: ./ 37 | - name: Upload patch 38 | if: steps.self_mutation.outputs.self_mutation_happened 39 | uses: actions/upload-artifact@v4.6.2 40 | with: 41 | name: repo.patch 42 | path: repo.patch 43 | overwrite: true 44 | - name: Fail build on mutation 45 | if: steps.self_mutation.outputs.self_mutation_happened 46 | run: |- 47 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 48 | cat repo.patch 49 | exit 1 50 | - name: Backup artifact permissions 51 | run: cd dist && getfacl -R . > permissions-backup.acl 52 | continue-on-error: true 53 | - name: Upload artifact 54 | uses: actions/upload-artifact@v4.6.2 55 | with: 56 | name: build-artifact 57 | path: dist 58 | overwrite: true 59 | self-mutation: 60 | needs: build 61 | runs-on: ubuntu-latest 62 | permissions: 63 | contents: write 64 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v5 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | ref: ${{ github.event.pull_request.head.ref }} 71 | repository: ${{ github.event.pull_request.head.repo.full_name }} 72 | - name: Download patch 73 | uses: actions/download-artifact@v5 74 | with: 75 | name: repo.patch 76 | path: ${{ runner.temp }} 77 | - name: Apply patch 78 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 79 | - name: Set git identity 80 | run: |- 81 | git config user.name "github-actions[bot]" 82 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 83 | - name: Push changes 84 | env: 85 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 86 | run: |- 87 | git add . 88 | git commit -s -m "chore: self mutation" 89 | git push origin "HEAD:$PULL_REQUEST_REF" 90 | package-js: 91 | needs: build 92 | runs-on: ubuntu-latest 93 | permissions: 94 | contents: read 95 | if: ${{ !needs.build.outputs.self_mutation_happened }} 96 | steps: 97 | - uses: actions/setup-node@v5 98 | with: 99 | node-version: lts/* 100 | - name: Download build artifacts 101 | uses: actions/download-artifact@v5 102 | with: 103 | name: build-artifact 104 | path: dist 105 | - name: Restore build artifact permissions 106 | run: cd dist && setfacl --restore=permissions-backup.acl 107 | continue-on-error: true 108 | - name: Checkout 109 | uses: actions/checkout@v5 110 | with: 111 | ref: ${{ github.event.pull_request.head.ref }} 112 | repository: ${{ github.event.pull_request.head.repo.full_name }} 113 | path: .repo 114 | - name: Install Dependencies 115 | run: cd .repo && yarn install --check-files --frozen-lockfile 116 | - name: Extract build artifact 117 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 118 | - name: Move build artifact out of the way 119 | run: mv dist dist.old 120 | - name: Create js artifact 121 | run: cd .repo && npx projen package:js 122 | - name: Collect js artifact 123 | run: mv .repo/dist dist 124 | package-java: 125 | needs: build 126 | runs-on: ubuntu-latest 127 | permissions: 128 | contents: read 129 | if: ${{ !needs.build.outputs.self_mutation_happened }} 130 | steps: 131 | - uses: actions/setup-java@v5 132 | with: 133 | distribution: corretto 134 | java-version: "11" 135 | - uses: actions/setup-node@v5 136 | with: 137 | node-version: lts/* 138 | - name: Download build artifacts 139 | uses: actions/download-artifact@v5 140 | with: 141 | name: build-artifact 142 | path: dist 143 | - name: Restore build artifact permissions 144 | run: cd dist && setfacl --restore=permissions-backup.acl 145 | continue-on-error: true 146 | - name: Checkout 147 | uses: actions/checkout@v5 148 | with: 149 | ref: ${{ github.event.pull_request.head.ref }} 150 | repository: ${{ github.event.pull_request.head.repo.full_name }} 151 | path: .repo 152 | - name: Install Dependencies 153 | run: cd .repo && yarn install --check-files --frozen-lockfile 154 | - name: Extract build artifact 155 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 156 | - name: Move build artifact out of the way 157 | run: mv dist dist.old 158 | - name: Create java artifact 159 | run: cd .repo && npx projen package:java 160 | - name: Collect java artifact 161 | run: mv .repo/dist dist 162 | package-python: 163 | needs: build 164 | runs-on: ubuntu-latest 165 | permissions: 166 | contents: read 167 | if: ${{ !needs.build.outputs.self_mutation_happened }} 168 | steps: 169 | - uses: actions/setup-node@v5 170 | with: 171 | node-version: lts/* 172 | - uses: actions/setup-python@v6 173 | with: 174 | python-version: 3.x 175 | - name: Download build artifacts 176 | uses: actions/download-artifact@v5 177 | with: 178 | name: build-artifact 179 | path: dist 180 | - name: Restore build artifact permissions 181 | run: cd dist && setfacl --restore=permissions-backup.acl 182 | continue-on-error: true 183 | - name: Checkout 184 | uses: actions/checkout@v5 185 | with: 186 | ref: ${{ github.event.pull_request.head.ref }} 187 | repository: ${{ github.event.pull_request.head.repo.full_name }} 188 | path: .repo 189 | - name: Install Dependencies 190 | run: cd .repo && yarn install --check-files --frozen-lockfile 191 | - name: Extract build artifact 192 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 193 | - name: Move build artifact out of the way 194 | run: mv dist dist.old 195 | - name: Create python artifact 196 | run: cd .repo && npx projen package:python 197 | - name: Collect python artifact 198 | run: mv .repo/dist dist 199 | package-dotnet: 200 | needs: build 201 | runs-on: ubuntu-latest 202 | permissions: 203 | contents: read 204 | if: ${{ !needs.build.outputs.self_mutation_happened }} 205 | steps: 206 | - uses: actions/setup-node@v5 207 | with: 208 | node-version: lts/* 209 | - uses: actions/setup-dotnet@v5 210 | with: 211 | dotnet-version: 6.x 212 | - name: Download build artifacts 213 | uses: actions/download-artifact@v5 214 | with: 215 | name: build-artifact 216 | path: dist 217 | - name: Restore build artifact permissions 218 | run: cd dist && setfacl --restore=permissions-backup.acl 219 | continue-on-error: true 220 | - name: Checkout 221 | uses: actions/checkout@v5 222 | with: 223 | ref: ${{ github.event.pull_request.head.ref }} 224 | repository: ${{ github.event.pull_request.head.repo.full_name }} 225 | path: .repo 226 | - name: Install Dependencies 227 | run: cd .repo && yarn install --check-files --frozen-lockfile 228 | - name: Extract build artifact 229 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 230 | - name: Move build artifact out of the way 231 | run: mv dist dist.old 232 | - name: Create dotnet artifact 233 | run: cd .repo && npx projen package:dotnet 234 | - name: Collect dotnet artifact 235 | run: mv .repo/dist dist 236 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v6 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | requireScope: false 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: false 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: write 17 | outputs: 18 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 19 | tag_exists: ${{ steps.check_tag_exists.outputs.exists }} 20 | env: 21 | CI: "true" 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v5 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions[bot]" 30 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v5 33 | with: 34 | node-version: lts/* 35 | - name: Install dependencies 36 | run: yarn install --check-files --frozen-lockfile 37 | - name: release 38 | run: npx projen release 39 | - name: Check if version has already been tagged 40 | id: check_tag_exists 41 | run: |- 42 | TAG=$(cat dist/releasetag.txt) 43 | ([ ! -z "$TAG" ] && git ls-remote -q --exit-code --tags origin $TAG && (echo "exists=true" >> $GITHUB_OUTPUT)) || (echo "exists=false" >> $GITHUB_OUTPUT) 44 | cat $GITHUB_OUTPUT 45 | - name: Check for new commits 46 | id: git_remote 47 | run: |- 48 | echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 49 | cat $GITHUB_OUTPUT 50 | shell: bash 51 | - name: Backup artifact permissions 52 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 53 | run: cd dist && getfacl -R . > permissions-backup.acl 54 | continue-on-error: true 55 | - name: Upload artifact 56 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 57 | uses: actions/upload-artifact@v4.6.2 58 | with: 59 | name: build-artifact 60 | path: dist 61 | overwrite: true 62 | release_github: 63 | name: Publish to GitHub Releases 64 | needs: 65 | - release 66 | - release_npm 67 | - release_maven 68 | - release_pypi 69 | - release_nuget 70 | runs-on: ubuntu-latest 71 | permissions: 72 | contents: write 73 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 74 | steps: 75 | - uses: actions/setup-node@v5 76 | with: 77 | node-version: lts/* 78 | - name: Download build artifacts 79 | uses: actions/download-artifact@v5 80 | with: 81 | name: build-artifact 82 | path: dist 83 | - name: Restore build artifact permissions 84 | run: cd dist && setfacl --restore=permissions-backup.acl 85 | continue-on-error: true 86 | - name: Release 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_SHA 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 90 | release_npm: 91 | name: Publish to npm 92 | needs: release 93 | runs-on: ubuntu-latest 94 | permissions: 95 | id-token: write 96 | contents: read 97 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 98 | steps: 99 | - uses: actions/setup-node@v5 100 | with: 101 | node-version: lts/* 102 | - name: Download build artifacts 103 | uses: actions/download-artifact@v5 104 | with: 105 | name: build-artifact 106 | path: dist 107 | - name: Restore build artifact permissions 108 | run: cd dist && setfacl --restore=permissions-backup.acl 109 | continue-on-error: true 110 | - name: Checkout 111 | uses: actions/checkout@v5 112 | with: 113 | path: .repo 114 | - name: Install Dependencies 115 | run: cd .repo && yarn install --check-files --frozen-lockfile 116 | - name: Extract build artifact 117 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 118 | - name: Move build artifact out of the way 119 | run: mv dist dist.old 120 | - name: Create js artifact 121 | run: cd .repo && npx projen package:js 122 | - name: Collect js artifact 123 | run: mv .repo/dist dist 124 | - name: Release 125 | env: 126 | NPM_DIST_TAG: latest 127 | NPM_REGISTRY: registry.npmjs.org 128 | NPM_CONFIG_PROVENANCE: "true" 129 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 130 | run: npx -p publib@latest publib-npm 131 | release_maven: 132 | name: Publish to Maven Central 133 | needs: release 134 | runs-on: ubuntu-latest 135 | permissions: 136 | contents: read 137 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 138 | steps: 139 | - uses: actions/setup-java@v5 140 | with: 141 | distribution: corretto 142 | java-version: "11" 143 | - uses: actions/setup-node@v5 144 | with: 145 | node-version: lts/* 146 | - name: Download build artifacts 147 | uses: actions/download-artifact@v5 148 | with: 149 | name: build-artifact 150 | path: dist 151 | - name: Restore build artifact permissions 152 | run: cd dist && setfacl --restore=permissions-backup.acl 153 | continue-on-error: true 154 | - name: Checkout 155 | uses: actions/checkout@v5 156 | with: 157 | path: .repo 158 | - name: Install Dependencies 159 | run: cd .repo && yarn install --check-files --frozen-lockfile 160 | - name: Extract build artifact 161 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 162 | - name: Move build artifact out of the way 163 | run: mv dist dist.old 164 | - name: Create java artifact 165 | run: cd .repo && npx projen package:java 166 | - name: Collect java artifact 167 | run: mv .repo/dist dist 168 | - name: Release 169 | env: 170 | MAVEN_SERVER_ID: central-ossrh 171 | MAVEN_GPG_PRIVATE_KEY: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} 172 | MAVEN_GPG_PRIVATE_KEY_PASSPHRASE: ${{ secrets.MAVEN_GPG_PRIVATE_KEY_PASSPHRASE }} 173 | MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} 174 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 175 | MAVEN_STAGING_PROFILE_ID: ${{ secrets.MAVEN_STAGING_PROFILE_ID }} 176 | run: npx -p publib@latest publib-maven 177 | release_pypi: 178 | name: Publish to PyPI 179 | needs: release 180 | runs-on: ubuntu-latest 181 | permissions: 182 | contents: read 183 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 184 | steps: 185 | - uses: actions/setup-node@v5 186 | with: 187 | node-version: lts/* 188 | - uses: actions/setup-python@v6 189 | with: 190 | python-version: 3.x 191 | - name: Download build artifacts 192 | uses: actions/download-artifact@v5 193 | with: 194 | name: build-artifact 195 | path: dist 196 | - name: Restore build artifact permissions 197 | run: cd dist && setfacl --restore=permissions-backup.acl 198 | continue-on-error: true 199 | - name: Checkout 200 | uses: actions/checkout@v5 201 | with: 202 | path: .repo 203 | - name: Install Dependencies 204 | run: cd .repo && yarn install --check-files --frozen-lockfile 205 | - name: Extract build artifact 206 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 207 | - name: Move build artifact out of the way 208 | run: mv dist dist.old 209 | - name: Create python artifact 210 | run: cd .repo && npx projen package:python 211 | - name: Collect python artifact 212 | run: mv .repo/dist dist 213 | - name: Release 214 | env: 215 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 216 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 217 | run: npx -p publib@latest publib-pypi 218 | release_nuget: 219 | name: Publish to NuGet Gallery 220 | needs: release 221 | runs-on: ubuntu-latest 222 | permissions: 223 | contents: read 224 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 225 | steps: 226 | - uses: actions/setup-node@v5 227 | with: 228 | node-version: lts/* 229 | - uses: actions/setup-dotnet@v5 230 | with: 231 | dotnet-version: 6.x 232 | - name: Download build artifacts 233 | uses: actions/download-artifact@v5 234 | with: 235 | name: build-artifact 236 | path: dist 237 | - name: Restore build artifact permissions 238 | run: cd dist && setfacl --restore=permissions-backup.acl 239 | continue-on-error: true 240 | - name: Checkout 241 | uses: actions/checkout@v5 242 | with: 243 | path: .repo 244 | - name: Install Dependencies 245 | run: cd .repo && yarn install --check-files --frozen-lockfile 246 | - name: Extract build artifact 247 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 248 | - name: Move build artifact out of the way 249 | run: mv dist dist.old 250 | - name: Create dotnet artifact 251 | run: cd .repo && npx projen package:dotnet 252 | - name: Collect dotnet artifact 253 | run: mv .repo/dist dist 254 | - name: Release 255 | env: 256 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 257 | run: npx -p publib@latest publib-nuget 258 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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@v5 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade 29 | - name: Find mutations 30 | id: create_patch 31 | run: |- 32 | git add . 33 | git diff --staged --patch --exit-code > repo.patch || echo "patch_created=true" >> $GITHUB_OUTPUT 34 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-main" workflow* 81 | branch: github-actions/upgrade-main 82 | title: "chore(deps): upgrade dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | /cdk.out 35 | /test-reports/ 36 | junit.xml 37 | /coverage/ 38 | !/.github/workflows/build.yml 39 | /dist/changelog.md 40 | /dist/version.txt 41 | !/.github/workflows/release.yml 42 | !/.mergify.yml 43 | !/.github/workflows/upgrade-main.yml 44 | !/.github/pull_request_template.md 45 | !/test/ 46 | !/tsconfig.dev.json 47 | !/src/ 48 | /lib 49 | /dist/ 50 | !/.eslintrc.json 51 | .jsii 52 | tsconfig.json 53 | !/API.md 54 | !/.projenrc.ts 55 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | queue_conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | - status-success=package-js 11 | - status-success=package-java 12 | - status-success=package-python 13 | - status-success=package-dotnet 14 | merge_method: squash 15 | commit_message_template: |- 16 | {{ title }} (#{{ number }}) 17 | 18 | {{ body }} 19 | pull_request_rules: 20 | - name: Automatic merge on approval and successful build 21 | actions: 22 | delete_head_branch: {} 23 | queue: 24 | name: default 25 | conditions: 26 | - "#approved-reviews-by>=1" 27 | - -label~=(do-not-merge) 28 | - status-success=build 29 | - status-success=package-js 30 | - status-success=package-java 31 | - status-success=package-python 32 | - status-success=package-dotnet 33 | merge_queue: 34 | max_parallel_checks: 1 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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 | /.gitattributes 26 | /.projenrc.ts 27 | /projenrc 28 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@stylistic/eslint-plugin", 5 | "version": "^2", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@types/jest", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@types/node", 14 | "type": "build" 15 | }, 16 | { 17 | "name": "@typescript-eslint/eslint-plugin", 18 | "version": "^8", 19 | "type": "build" 20 | }, 21 | { 22 | "name": "@typescript-eslint/parser", 23 | "version": "^8", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "commit-and-tag-version", 28 | "version": "^12", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "esbuild", 33 | "type": "build" 34 | }, 35 | { 36 | "name": "eslint-import-resolver-typescript", 37 | "type": "build" 38 | }, 39 | { 40 | "name": "eslint-plugin-import", 41 | "type": "build" 42 | }, 43 | { 44 | "name": "eslint", 45 | "version": "^9", 46 | "type": "build" 47 | }, 48 | { 49 | "name": "jest", 50 | "type": "build" 51 | }, 52 | { 53 | "name": "jest-junit", 54 | "version": "^16", 55 | "type": "build" 56 | }, 57 | { 58 | "name": "jsii-diff", 59 | "type": "build" 60 | }, 61 | { 62 | "name": "jsii-docgen", 63 | "version": "^10.5.0", 64 | "type": "build" 65 | }, 66 | { 67 | "name": "jsii-pacmak", 68 | "type": "build" 69 | }, 70 | { 71 | "name": "jsii-rosetta", 72 | "version": "~5.8.0", 73 | "type": "build" 74 | }, 75 | { 76 | "name": "jsii", 77 | "version": "~5.8.0", 78 | "type": "build" 79 | }, 80 | { 81 | "name": "projen", 82 | "type": "build" 83 | }, 84 | { 85 | "name": "ts-jest", 86 | "type": "build" 87 | }, 88 | { 89 | "name": "ts-node", 90 | "type": "build" 91 | }, 92 | { 93 | "name": "typescript", 94 | "type": "build" 95 | }, 96 | { 97 | "name": "@types/aws-lambda", 98 | "type": "bundled" 99 | }, 100 | { 101 | "name": "aws-sdk", 102 | "type": "bundled" 103 | }, 104 | { 105 | "name": "aws-cdk-lib", 106 | "version": "^2.24.0", 107 | "type": "peer" 108 | }, 109 | { 110 | "name": "constructs", 111 | "version": "^10.0.5", 112 | "type": "peer" 113 | } 114 | ], 115 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 116 | } 117 | -------------------------------------------------------------------------------- /.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 | ".mergify.yml", 13 | ".projen/deps.json", 14 | ".projen/files.json", 15 | ".projen/tasks.json", 16 | "LICENSE", 17 | "tsconfig.dev.json" 18 | ], 19 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts 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 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 37 | }, 38 | "steps": [ 39 | { 40 | "builtin": "release/bump-version" 41 | } 42 | ], 43 | "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" 44 | }, 45 | "clobber": { 46 | "name": "clobber", 47 | "description": "hard resets to HEAD of origin and cleans the local repo", 48 | "env": { 49 | "BRANCH": "$(git branch --show-current)" 50 | }, 51 | "steps": [ 52 | { 53 | "exec": "git checkout -b scratch", 54 | "name": "save current HEAD in \"scratch\" branch" 55 | }, 56 | { 57 | "exec": "git checkout $BRANCH" 58 | }, 59 | { 60 | "exec": "git fetch origin", 61 | "name": "fetch latest changes from origin" 62 | }, 63 | { 64 | "exec": "git reset --hard origin/$BRANCH", 65 | "name": "hard reset to origin commit" 66 | }, 67 | { 68 | "exec": "git clean -fdx", 69 | "name": "clean all untracked files" 70 | }, 71 | { 72 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 73 | } 74 | ], 75 | "condition": "git diff --exit-code > /dev/null" 76 | }, 77 | "compat": { 78 | "name": "compat", 79 | "description": "Perform API compatibility check against latest version", 80 | "steps": [ 81 | { 82 | "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)" 83 | } 84 | ] 85 | }, 86 | "compile": { 87 | "name": "compile", 88 | "description": "Only compile", 89 | "steps": [ 90 | { 91 | "exec": "jsii --silence-warnings=reserved-word" 92 | } 93 | ] 94 | }, 95 | "default": { 96 | "name": "default", 97 | "description": "Synthesize project files", 98 | "steps": [ 99 | { 100 | "exec": "ts-node --project tsconfig.dev.json .projenrc.ts" 101 | } 102 | ] 103 | }, 104 | "docgen": { 105 | "name": "docgen", 106 | "description": "Generate API.md from .jsii manifest", 107 | "steps": [ 108 | { 109 | "exec": "jsii-docgen -o API.md" 110 | } 111 | ] 112 | }, 113 | "eject": { 114 | "name": "eject", 115 | "description": "Remove projen from the project", 116 | "env": { 117 | "PROJEN_EJECTING": "true" 118 | }, 119 | "steps": [ 120 | { 121 | "spawn": "default" 122 | } 123 | ] 124 | }, 125 | "eslint": { 126 | "name": "eslint", 127 | "description": "Runs eslint against the codebase", 128 | "env": { 129 | "ESLINT_USE_FLAT_CONFIG": "false", 130 | "NODE_NO_WARNINGS": "1" 131 | }, 132 | "steps": [ 133 | { 134 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools projenrc .projenrc.ts", 135 | "receiveArgs": true 136 | } 137 | ] 138 | }, 139 | "install": { 140 | "name": "install", 141 | "description": "Install project dependencies and update lockfile (non-frozen)", 142 | "steps": [ 143 | { 144 | "exec": "yarn install --check-files" 145 | } 146 | ] 147 | }, 148 | "install:ci": { 149 | "name": "install:ci", 150 | "description": "Install project dependencies using frozen lockfile", 151 | "steps": [ 152 | { 153 | "exec": "yarn install --check-files --frozen-lockfile" 154 | } 155 | ] 156 | }, 157 | "package": { 158 | "name": "package", 159 | "description": "Creates the distribution package", 160 | "steps": [ 161 | { 162 | "spawn": "package:js", 163 | "condition": "node -e \"if (!process.env.CI) process.exit(1)\"" 164 | }, 165 | { 166 | "spawn": "package-all", 167 | "condition": "node -e \"if (process.env.CI) process.exit(1)\"" 168 | } 169 | ] 170 | }, 171 | "package-all": { 172 | "name": "package-all", 173 | "description": "Packages artifacts for all target languages", 174 | "steps": [ 175 | { 176 | "spawn": "package:js" 177 | }, 178 | { 179 | "spawn": "package:java" 180 | }, 181 | { 182 | "spawn": "package:python" 183 | }, 184 | { 185 | "spawn": "package:dotnet" 186 | } 187 | ] 188 | }, 189 | "package:dotnet": { 190 | "name": "package:dotnet", 191 | "description": "Create dotnet language bindings", 192 | "steps": [ 193 | { 194 | "exec": "jsii-pacmak -v --target dotnet" 195 | } 196 | ] 197 | }, 198 | "package:java": { 199 | "name": "package:java", 200 | "description": "Create java language bindings", 201 | "steps": [ 202 | { 203 | "exec": "jsii-pacmak -v --target java" 204 | } 205 | ] 206 | }, 207 | "package:js": { 208 | "name": "package:js", 209 | "description": "Create js language bindings", 210 | "steps": [ 211 | { 212 | "exec": "jsii-pacmak -v --target js" 213 | } 214 | ] 215 | }, 216 | "package:python": { 217 | "name": "package:python", 218 | "description": "Create python language bindings", 219 | "steps": [ 220 | { 221 | "exec": "jsii-pacmak -v --target python" 222 | } 223 | ] 224 | }, 225 | "post-compile": { 226 | "name": "post-compile", 227 | "description": "Runs after successful compilation", 228 | "steps": [ 229 | { 230 | "spawn": "docgen" 231 | } 232 | ] 233 | }, 234 | "post-upgrade": { 235 | "name": "post-upgrade", 236 | "description": "Runs after upgrading dependencies" 237 | }, 238 | "pre-compile": { 239 | "name": "pre-compile", 240 | "description": "Prepare the project for compilation" 241 | }, 242 | "release": { 243 | "name": "release", 244 | "description": "Prepare a release from \"main\" branch", 245 | "env": { 246 | "RELEASE": "true" 247 | }, 248 | "steps": [ 249 | { 250 | "exec": "rm -fr dist" 251 | }, 252 | { 253 | "spawn": "bump" 254 | }, 255 | { 256 | "spawn": "build" 257 | }, 258 | { 259 | "spawn": "unbump" 260 | }, 261 | { 262 | "exec": "git diff --ignore-space-at-eol --exit-code" 263 | } 264 | ] 265 | }, 266 | "test": { 267 | "name": "test", 268 | "description": "Run tests", 269 | "steps": [ 270 | { 271 | "exec": "jest --passWithNoTests --updateSnapshot", 272 | "receiveArgs": true 273 | }, 274 | { 275 | "spawn": "eslint" 276 | } 277 | ] 278 | }, 279 | "test:watch": { 280 | "name": "test:watch", 281 | "description": "Run jest in watch mode", 282 | "steps": [ 283 | { 284 | "exec": "jest --watch" 285 | } 286 | ] 287 | }, 288 | "unbump": { 289 | "name": "unbump", 290 | "description": "Restores version to 0.0.0", 291 | "env": { 292 | "OUTFILE": "package.json", 293 | "CHANGELOG": "dist/changelog.md", 294 | "BUMPFILE": "dist/version.txt", 295 | "RELEASETAG": "dist/releasetag.txt", 296 | "RELEASE_TAG_PREFIX": "", 297 | "BUMP_PACKAGE": "commit-and-tag-version@^12" 298 | }, 299 | "steps": [ 300 | { 301 | "builtin": "release/reset-version" 302 | } 303 | ] 304 | }, 305 | "upgrade": { 306 | "name": "upgrade", 307 | "description": "upgrade dependencies", 308 | "env": { 309 | "CI": "0" 310 | }, 311 | "steps": [ 312 | { 313 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev,peer,prod,optional --filter=@types/jest,@types/node,esbuild,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,jsii-pacmak,projen,ts-jest,ts-node,typescript,@types/aws-lambda,aws-sdk" 314 | }, 315 | { 316 | "exec": "yarn install --check-files" 317 | }, 318 | { 319 | "exec": "yarn upgrade @stylistic/eslint-plugin @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser commit-and-tag-version esbuild eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii projen ts-jest ts-node typescript @types/aws-lambda aws-sdk aws-cdk-lib constructs" 320 | }, 321 | { 322 | "exec": "npx projen" 323 | }, 324 | { 325 | "spawn": "post-upgrade" 326 | } 327 | ] 328 | }, 329 | "watch": { 330 | "name": "watch", 331 | "description": "Watch & compile in the background", 332 | "steps": [ 333 | { 334 | "exec": "jsii -w --silence-warnings=reserved-word" 335 | } 336 | ] 337 | } 338 | }, 339 | "env": { 340 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 341 | }, 342 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 343 | } 344 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import { awscdk } from 'projen'; 2 | const project = new awscdk.AwsCdkConstructLibrary({ 3 | author: 'Parker Scanlon', 4 | authorAddress: 'https://aws.amazon.com/', 5 | cdkVersion: '2.24.0', // needed for node16 6 | defaultReleaseBranch: 'main', 7 | name: 'cdk-docker-image-deployment', 8 | projenrcTs: true, 9 | repositoryUrl: 'https://github.com/cdklabs/cdk-docker-image-deployment.git', 10 | homepage: 'https://github.com/cdklabs/cdk-docker-image-deployment#readme', 11 | autoApproveUpgrades: true, 12 | autoApproveOptions: { 13 | allowedUsernames: ['cdklabs-automation'], 14 | secret: 'GITHUB_TOKEN', 15 | }, 16 | gitignore: ['/cdk.out'], 17 | description: 'This module allows you to copy docker image assets to a repository you control. This can be necessary if you want to build a Docker image in one CDK app and consume it in a different app or outside the CDK.', 18 | bundledDeps: ['@types/aws-lambda', 'aws-sdk'], 19 | devDeps: ['esbuild'], 20 | publishToPypi: { 21 | distName: 'cdk-docker-image-deployment', 22 | module: 'cdk_docker_image_deployment', 23 | }, 24 | publishToMaven: { 25 | javaPackage: 'io.github.cdklabs.cdk.docker.image.deployment', 26 | mavenGroupId: 'io.github.cdklabs', 27 | mavenArtifactId: 'cdk-docker-image-deployment', 28 | mavenServerId: 'central-ossrh', 29 | }, 30 | publishToNuget: { 31 | dotNetNamespace: 'Cdklabs.CdkDockerImageDeployment', 32 | packageId: 'Cdklabs.CdkDockerImageDeployment', 33 | }, 34 | }); 35 | 36 | project.synth(); 37 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### DockerImageDeployment 6 | 7 | `DockerImageDeployment` pushes an image from a local or external source to a specified external destination. 8 | 9 | #### Initializers 10 | 11 | ```typescript 12 | import { DockerImageDeployment } from 'cdk-docker-image-deployment' 13 | 14 | new DockerImageDeployment(scope: Construct, id: string, props: DockerImageDeploymentProps) 15 | ``` 16 | 17 | | **Name** | **Type** | **Description** | 18 | | --- | --- | --- | 19 | | scope | constructs.Construct | *No description.* | 20 | | id | string | *No description.* | 21 | | props | DockerImageDeploymentProps | *No description.* | 22 | 23 | --- 24 | 25 | ##### `scope`Required 26 | 27 | - *Type:* constructs.Construct 28 | 29 | --- 30 | 31 | ##### `id`Required 32 | 33 | - *Type:* string 34 | 35 | --- 36 | 37 | ##### `props`Required 38 | 39 | - *Type:* DockerImageDeploymentProps 40 | 41 | --- 42 | 43 | #### Methods 44 | 45 | | **Name** | **Description** | 46 | | --- | --- | 47 | | toString | Returns a string representation of this construct. | 48 | 49 | --- 50 | 51 | ##### `toString` 52 | 53 | ```typescript 54 | public toString(): string 55 | ``` 56 | 57 | Returns a string representation of this construct. 58 | 59 | #### Static Functions 60 | 61 | | **Name** | **Description** | 62 | | --- | --- | 63 | | isConstruct | Checks if `x` is a construct. | 64 | 65 | --- 66 | 67 | ##### ~~`isConstruct`~~ 68 | 69 | ```typescript 70 | import { DockerImageDeployment } from 'cdk-docker-image-deployment' 71 | 72 | DockerImageDeployment.isConstruct(x: any) 73 | ``` 74 | 75 | Checks if `x` is a construct. 76 | 77 | ###### `x`Required 78 | 79 | - *Type:* any 80 | 81 | Any object. 82 | 83 | --- 84 | 85 | #### Properties 86 | 87 | | **Name** | **Type** | **Description** | 88 | | --- | --- | --- | 89 | | node | constructs.Node | The tree node. | 90 | 91 | --- 92 | 93 | ##### `node`Required 94 | 95 | ```typescript 96 | public readonly node: Node; 97 | ``` 98 | 99 | - *Type:* constructs.Node 100 | 101 | The tree node. 102 | 103 | --- 104 | 105 | 106 | ## Structs 107 | 108 | ### DestinationConfig 109 | 110 | Destination information. 111 | 112 | #### Initializer 113 | 114 | ```typescript 115 | import { DestinationConfig } from 'cdk-docker-image-deployment' 116 | 117 | const destinationConfig: DestinationConfig = { ... } 118 | ``` 119 | 120 | #### Properties 121 | 122 | | **Name** | **Type** | **Description** | 123 | | --- | --- | --- | 124 | | destinationUri | string | The URI of the destination repository to deploy to. | 125 | | loginConfig | LoginConfig | The login command and region. | 126 | | destinationTag | string | The tag of the deployed image. | 127 | 128 | --- 129 | 130 | ##### `destinationUri`Required 131 | 132 | ```typescript 133 | public readonly destinationUri: string; 134 | ``` 135 | 136 | - *Type:* string 137 | 138 | The URI of the destination repository to deploy to. 139 | 140 | --- 141 | 142 | ##### `loginConfig`Required 143 | 144 | ```typescript 145 | public readonly loginConfig: LoginConfig; 146 | ``` 147 | 148 | - *Type:* LoginConfig 149 | 150 | The login command and region. 151 | 152 | --- 153 | 154 | ##### `destinationTag`Optional 155 | 156 | ```typescript 157 | public readonly destinationTag: string; 158 | ``` 159 | 160 | - *Type:* string 161 | - *Default:* the tag of the source 162 | 163 | The tag of the deployed image. 164 | 165 | --- 166 | 167 | ### DockerImageDeploymentProps 168 | 169 | #### Initializer 170 | 171 | ```typescript 172 | import { DockerImageDeploymentProps } from 'cdk-docker-image-deployment' 173 | 174 | const dockerImageDeploymentProps: DockerImageDeploymentProps = { ... } 175 | ``` 176 | 177 | #### Properties 178 | 179 | | **Name** | **Type** | **Description** | 180 | | --- | --- | --- | 181 | | destination | Destination | Destination repository to deploy the image to. | 182 | | source | Source | Source of the image to deploy. | 183 | 184 | --- 185 | 186 | ##### `destination`Required 187 | 188 | ```typescript 189 | public readonly destination: Destination; 190 | ``` 191 | 192 | - *Type:* Destination 193 | 194 | Destination repository to deploy the image to. 195 | 196 | --- 197 | 198 | ##### `source`Required 199 | 200 | ```typescript 201 | public readonly source: Source; 202 | ``` 203 | 204 | - *Type:* Source 205 | 206 | Source of the image to deploy. 207 | 208 | --- 209 | 210 | ### EcrSourceOptions 211 | 212 | Properties needed for Source.ecr. 213 | 214 | #### Initializer 215 | 216 | ```typescript 217 | import { EcrSourceOptions } from 'cdk-docker-image-deployment' 218 | 219 | const ecrSourceOptions: EcrSourceOptions = { ... } 220 | ``` 221 | 222 | #### Properties 223 | 224 | | **Name** | **Type** | **Description** | 225 | | --- | --- | --- | 226 | | tag | string | Tag of deployed image. | 227 | 228 | --- 229 | 230 | ##### `tag`Optional 231 | 232 | ```typescript 233 | public readonly tag: string; 234 | ``` 235 | 236 | - *Type:* string 237 | - *Default:* tag of source 238 | 239 | Tag of deployed image. 240 | 241 | --- 242 | 243 | ### LoginConfig 244 | 245 | Login commands for specified registry. 246 | 247 | #### Initializer 248 | 249 | ```typescript 250 | import { LoginConfig } from 'cdk-docker-image-deployment' 251 | 252 | const loginConfig: LoginConfig = { ... } 253 | ``` 254 | 255 | #### Properties 256 | 257 | | **Name** | **Type** | **Description** | 258 | | --- | --- | --- | 259 | | loginCommand | string | Command to run in codebuild to login. | 260 | | region | string | Region of ECR repository. | 261 | 262 | --- 263 | 264 | ##### `loginCommand`Required 265 | 266 | ```typescript 267 | public readonly loginCommand: string; 268 | ``` 269 | 270 | - *Type:* string 271 | 272 | Command to run in codebuild to login. 273 | 274 | Formatted `docker login ...`. 275 | 276 | --- 277 | 278 | ##### `region`Optional 279 | 280 | ```typescript 281 | public readonly region: string; 282 | ``` 283 | 284 | - *Type:* string 285 | - *Default:* undefined if not an ECR repository 286 | 287 | Region of ECR repository. 288 | 289 | --- 290 | 291 | ### SourceConfig 292 | 293 | Source information. 294 | 295 | #### Initializer 296 | 297 | ```typescript 298 | import { SourceConfig } from 'cdk-docker-image-deployment' 299 | 300 | const sourceConfig: SourceConfig = { ... } 301 | ``` 302 | 303 | #### Properties 304 | 305 | | **Name** | **Type** | **Description** | 306 | | --- | --- | --- | 307 | | imageTag | string | The source tag. | 308 | | imageUri | string | The source image URI. | 309 | | loginConfig | LoginConfig | The login command and region. | 310 | 311 | --- 312 | 313 | ##### `imageTag`Required 314 | 315 | ```typescript 316 | public readonly imageTag: string; 317 | ``` 318 | 319 | - *Type:* string 320 | 321 | The source tag. 322 | 323 | --- 324 | 325 | ##### `imageUri`Required 326 | 327 | ```typescript 328 | public readonly imageUri: string; 329 | ``` 330 | 331 | - *Type:* string 332 | 333 | The source image URI. 334 | 335 | --- 336 | 337 | ##### `loginConfig`Required 338 | 339 | ```typescript 340 | public readonly loginConfig: LoginConfig; 341 | ``` 342 | 343 | - *Type:* LoginConfig 344 | 345 | The login command and region. 346 | 347 | --- 348 | 349 | ### SourceContext 350 | 351 | Bind context for Source. 352 | 353 | #### Initializer 354 | 355 | ```typescript 356 | import { SourceContext } from 'cdk-docker-image-deployment' 357 | 358 | const sourceContext: SourceContext = { ... } 359 | ``` 360 | 361 | #### Properties 362 | 363 | | **Name** | **Type** | **Description** | 364 | | --- | --- | --- | 365 | | handlerRole | aws-cdk-lib.aws_iam.IRole | The role for the handler. | 366 | 367 | --- 368 | 369 | ##### `handlerRole`Required 370 | 371 | ```typescript 372 | public readonly handlerRole: IRole; 373 | ``` 374 | 375 | - *Type:* aws-cdk-lib.aws_iam.IRole 376 | 377 | The role for the handler. 378 | 379 | --- 380 | 381 | ## Classes 382 | 383 | ### Destination 384 | 385 | Specifies docker image deployment destination. 386 | 387 | Usage: 388 | 389 | ```ts 390 | declare const repo: ecr.IRepository; 391 | const destinationEcr = dockerDeploy.Destination.ecr(repository, { 392 | tag: 'tag', 393 | }); 394 | ``` 395 | 396 | #### Initializers 397 | 398 | ```typescript 399 | import { Destination } from 'cdk-docker-image-deployment' 400 | 401 | new Destination() 402 | ``` 403 | 404 | | **Name** | **Type** | **Description** | 405 | | --- | --- | --- | 406 | 407 | --- 408 | 409 | #### Methods 410 | 411 | | **Name** | **Description** | 412 | | --- | --- | 413 | | bind | Bind grants the CodeBuild role permissions to pull and push to a repository if necessary. | 414 | 415 | --- 416 | 417 | ##### `bind` 418 | 419 | ```typescript 420 | public bind(role: IGrantable): DestinationConfig 421 | ``` 422 | 423 | Bind grants the CodeBuild role permissions to pull and push to a repository if necessary. 424 | 425 | Bind should be invoked by the caller to get the DestinationConfig. 426 | 427 | ###### `role`Required 428 | 429 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 430 | 431 | --- 432 | 433 | #### Static Functions 434 | 435 | | **Name** | **Description** | 436 | | --- | --- | 437 | | ecr | Uses an ECR repository in the same account as the stack as the destination for the image. | 438 | 439 | --- 440 | 441 | ##### `ecr` 442 | 443 | ```typescript 444 | import { Destination } from 'cdk-docker-image-deployment' 445 | 446 | Destination.ecr(repository: IRepository, options?: EcrSourceOptions) 447 | ``` 448 | 449 | Uses an ECR repository in the same account as the stack as the destination for the image. 450 | 451 | ###### `repository`Required 452 | 453 | - *Type:* aws-cdk-lib.aws_ecr.IRepository 454 | 455 | --- 456 | 457 | ###### `options`Optional 458 | 459 | - *Type:* EcrSourceOptions 460 | 461 | --- 462 | 463 | 464 | 465 | ### Source 466 | 467 | Specifies docker image deployment source. 468 | 469 | Usage: 470 | 471 | ```ts 472 | import * as path from 'path'; 473 | const path = path.join(__dirname, 'path/to/directory'); 474 | const sourceDirectory = Source.directory(path); 475 | ``` 476 | or with additional `assetOptions` 477 | ```ts 478 | import * as path from 'path'; 479 | const path = path.join(__dirname, 'path/to/directory'); 480 | const sourceDirectory = Source.directory(path, { 481 | file: 'Dockerfile.api', 482 | buildArgs: { 483 | HTTP_PROXY: 'http://10.20.30.2:1234' 484 | } 485 | }) 486 | ``` 487 | 488 | #### Initializers 489 | 490 | ```typescript 491 | import { Source } from 'cdk-docker-image-deployment' 492 | 493 | new Source() 494 | ``` 495 | 496 | | **Name** | **Type** | **Description** | 497 | | --- | --- | --- | 498 | 499 | --- 500 | 501 | #### Methods 502 | 503 | | **Name** | **Description** | 504 | | --- | --- | 505 | | bind | Bind grants the CodeBuild role permissions to pull from a repository if necessary. | 506 | 507 | --- 508 | 509 | ##### `bind` 510 | 511 | ```typescript 512 | public bind(scope: Construct, context: SourceContext): SourceConfig 513 | ``` 514 | 515 | Bind grants the CodeBuild role permissions to pull from a repository if necessary. 516 | 517 | Bind should be invoked by the caller to get the SourceConfig. 518 | 519 | ###### `scope`Required 520 | 521 | - *Type:* constructs.Construct 522 | 523 | --- 524 | 525 | ###### `context`Required 526 | 527 | - *Type:* SourceContext 528 | 529 | --- 530 | 531 | #### Static Functions 532 | 533 | | **Name** | **Description** | 534 | | --- | --- | 535 | | directory | Uses a local image built from a Dockerfile in a local directory as the source. | 536 | 537 | --- 538 | 539 | ##### `directory` 540 | 541 | ```typescript 542 | import { Source } from 'cdk-docker-image-deployment' 543 | 544 | Source.directory(path: string, assetOptions?: DockerImageAssetOptions) 545 | ``` 546 | 547 | Uses a local image built from a Dockerfile in a local directory as the source. 548 | 549 | ###### `path`Required 550 | 551 | - *Type:* string 552 | 553 | path to the directory containing your Dockerfile (not a path to a file). 554 | 555 | --- 556 | 557 | ###### `assetOptions`Optional 558 | 559 | - *Type:* aws-cdk-lib.aws_ecr_assets.DockerImageAssetOptions 560 | 561 | specify any additional [DockerImageAssetOptions](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecr_assets.DockerImageAssetOptions.html) (except `path`). 562 | 563 | --- 564 | 565 | 566 | 567 | 568 | -------------------------------------------------------------------------------- /API.md.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### DockerImageDeployment 6 | 7 | `DockerImageDeployment` pushes an image from a local or external source to a specified external destination. 8 | 9 | #### Initializers 10 | 11 | ```typescript 12 | import { DockerImageDeployment } from 'cdk-docker-image-deployment' 13 | 14 | new DockerImageDeployment(scope: Construct, id: string, props: DockerImageDeploymentProps) 15 | ``` 16 | 17 | | **Name** | **Type** | **Description** | 18 | | --- | --- | --- | 19 | | scope | constructs.Construct | *No description.* | 20 | | id | string | *No description.* | 21 | | props | DockerImageDeploymentProps | *No description.* | 22 | 23 | --- 24 | 25 | ##### `scope`Required 26 | 27 | - *Type:* constructs.Construct 28 | 29 | --- 30 | 31 | ##### `id`Required 32 | 33 | - *Type:* string 34 | 35 | --- 36 | 37 | ##### `props`Required 38 | 39 | - *Type:* DockerImageDeploymentProps 40 | 41 | --- 42 | 43 | #### Methods 44 | 45 | | **Name** | **Description** | 46 | | --- | --- | 47 | | toString | Returns a string representation of this construct. | 48 | 49 | --- 50 | 51 | ##### `toString` 52 | 53 | ```typescript 54 | public toString(): string 55 | ``` 56 | 57 | Returns a string representation of this construct. 58 | 59 | #### Static Functions 60 | 61 | | **Name** | **Description** | 62 | | --- | --- | 63 | | isConstruct | Checks if `x` is a construct. | 64 | 65 | --- 66 | 67 | ##### ~~`isConstruct`~~ 68 | 69 | ```typescript 70 | import { DockerImageDeployment } from 'cdk-docker-image-deployment' 71 | 72 | DockerImageDeployment.isConstruct(x: any) 73 | ``` 74 | 75 | Checks if `x` is a construct. 76 | 77 | ###### `x`Required 78 | 79 | - *Type:* any 80 | 81 | Any object. 82 | 83 | --- 84 | 85 | #### Properties 86 | 87 | | **Name** | **Type** | **Description** | 88 | | --- | --- | --- | 89 | | node | constructs.Node | The tree node. | 90 | 91 | --- 92 | 93 | ##### `node`Required 94 | 95 | ```typescript 96 | public readonly node: Node; 97 | ``` 98 | 99 | - *Type:* constructs.Node 100 | 101 | The tree node. 102 | 103 | --- 104 | 105 | 106 | ## Structs 107 | 108 | ### DestinationConfig 109 | 110 | Destination information. 111 | 112 | #### Initializer 113 | 114 | ```typescript 115 | import { DestinationConfig } from 'cdk-docker-image-deployment' 116 | 117 | const destinationConfig: DestinationConfig = { ... } 118 | ``` 119 | 120 | #### Properties 121 | 122 | | **Name** | **Type** | **Description** | 123 | | --- | --- | --- | 124 | | destinationUri | string | The URI of the destination repository to deploy to. | 125 | | loginConfig | LoginConfig | The login command and region. | 126 | | destinationTag | string | The tag of the deployed image. | 127 | 128 | --- 129 | 130 | ##### `destinationUri`Required 131 | 132 | ```typescript 133 | public readonly destinationUri: string; 134 | ``` 135 | 136 | - *Type:* string 137 | 138 | The URI of the destination repository to deploy to. 139 | 140 | --- 141 | 142 | ##### `loginConfig`Required 143 | 144 | ```typescript 145 | public readonly loginConfig: LoginConfig; 146 | ``` 147 | 148 | - *Type:* LoginConfig 149 | 150 | The login command and region. 151 | 152 | --- 153 | 154 | ##### `destinationTag`Optional 155 | 156 | ```typescript 157 | public readonly destinationTag: string; 158 | ``` 159 | 160 | - *Type:* string 161 | - *Default:* the tag of the source 162 | 163 | The tag of the deployed image. 164 | 165 | --- 166 | 167 | ### DockerImageDeploymentProps 168 | 169 | #### Initializer 170 | 171 | ```typescript 172 | import { DockerImageDeploymentProps } from 'cdk-docker-image-deployment' 173 | 174 | const dockerImageDeploymentProps: DockerImageDeploymentProps = { ... } 175 | ``` 176 | 177 | #### Properties 178 | 179 | | **Name** | **Type** | **Description** | 180 | | --- | --- | --- | 181 | | destination | Destination | Destination repository to deploy the image to. | 182 | | source | Source | Source of the image to deploy. | 183 | 184 | --- 185 | 186 | ##### `destination`Required 187 | 188 | ```typescript 189 | public readonly destination: Destination; 190 | ``` 191 | 192 | - *Type:* Destination 193 | 194 | Destination repository to deploy the image to. 195 | 196 | --- 197 | 198 | ##### `source`Required 199 | 200 | ```typescript 201 | public readonly source: Source; 202 | ``` 203 | 204 | - *Type:* Source 205 | 206 | Source of the image to deploy. 207 | 208 | --- 209 | 210 | ### EcrSourceOptions 211 | 212 | Properties needed for Source.ecr. 213 | 214 | #### Initializer 215 | 216 | ```typescript 217 | import { EcrSourceOptions } from 'cdk-docker-image-deployment' 218 | 219 | const ecrSourceOptions: EcrSourceOptions = { ... } 220 | ``` 221 | 222 | #### Properties 223 | 224 | | **Name** | **Type** | **Description** | 225 | | --- | --- | --- | 226 | | tag | string | Tag of deployed image. | 227 | 228 | --- 229 | 230 | ##### `tag`Optional 231 | 232 | ```typescript 233 | public readonly tag: string; 234 | ``` 235 | 236 | - *Type:* string 237 | - *Default:* tag of source 238 | 239 | Tag of deployed image. 240 | 241 | --- 242 | 243 | ### LoginConfig 244 | 245 | Login commands for specified registry. 246 | 247 | #### Initializer 248 | 249 | ```typescript 250 | import { LoginConfig } from 'cdk-docker-image-deployment' 251 | 252 | const loginConfig: LoginConfig = { ... } 253 | ``` 254 | 255 | #### Properties 256 | 257 | | **Name** | **Type** | **Description** | 258 | | --- | --- | --- | 259 | | loginCommand | string | Command to run in codebuild to login. | 260 | | region | string | Region of ECR repository. | 261 | 262 | --- 263 | 264 | ##### `loginCommand`Required 265 | 266 | ```typescript 267 | public readonly loginCommand: string; 268 | ``` 269 | 270 | - *Type:* string 271 | 272 | Command to run in codebuild to login. 273 | 274 | Formatted `docker login ...`. 275 | 276 | --- 277 | 278 | ##### `region`Optional 279 | 280 | ```typescript 281 | public readonly region: string; 282 | ``` 283 | 284 | - *Type:* string 285 | - *Default:* undefined if not an ECR repository 286 | 287 | Region of ECR repository. 288 | 289 | --- 290 | 291 | ### SourceConfig 292 | 293 | Source information. 294 | 295 | #### Initializer 296 | 297 | ```typescript 298 | import { SourceConfig } from 'cdk-docker-image-deployment' 299 | 300 | const sourceConfig: SourceConfig = { ... } 301 | ``` 302 | 303 | #### Properties 304 | 305 | | **Name** | **Type** | **Description** | 306 | | --- | --- | --- | 307 | | imageTag | string | The source tag. | 308 | | imageUri | string | The source image URI. | 309 | | loginConfig | LoginConfig | The login command and region. | 310 | 311 | --- 312 | 313 | ##### `imageTag`Required 314 | 315 | ```typescript 316 | public readonly imageTag: string; 317 | ``` 318 | 319 | - *Type:* string 320 | 321 | The source tag. 322 | 323 | --- 324 | 325 | ##### `imageUri`Required 326 | 327 | ```typescript 328 | public readonly imageUri: string; 329 | ``` 330 | 331 | - *Type:* string 332 | 333 | The source image URI. 334 | 335 | --- 336 | 337 | ##### `loginConfig`Required 338 | 339 | ```typescript 340 | public readonly loginConfig: LoginConfig; 341 | ``` 342 | 343 | - *Type:* LoginConfig 344 | 345 | The login command and region. 346 | 347 | --- 348 | 349 | ### SourceContext 350 | 351 | Bind context for Source. 352 | 353 | #### Initializer 354 | 355 | ```typescript 356 | import { SourceContext } from 'cdk-docker-image-deployment' 357 | 358 | const sourceContext: SourceContext = { ... } 359 | ``` 360 | 361 | #### Properties 362 | 363 | | **Name** | **Type** | **Description** | 364 | | --- | --- | --- | 365 | | handlerRole | aws-cdk-lib.aws_iam.IRole | The role for the handler. | 366 | 367 | --- 368 | 369 | ##### `handlerRole`Required 370 | 371 | ```typescript 372 | public readonly handlerRole: IRole; 373 | ``` 374 | 375 | - *Type:* aws-cdk-lib.aws_iam.IRole 376 | 377 | The role for the handler. 378 | 379 | --- 380 | 381 | ## Classes 382 | 383 | ### Destination 384 | 385 | Specifies docker image deployment destination. 386 | 387 | Usage: 388 | 389 | ```ts 390 | declare const repo: ecr.IRepository; 391 | const destinationEcr = dockerDeploy.Destination.ecr(repository, { 392 | tag: 'tag', 393 | }); 394 | ``` 395 | 396 | #### Initializers 397 | 398 | ```typescript 399 | import { Destination } from 'cdk-docker-image-deployment' 400 | 401 | new Destination() 402 | ``` 403 | 404 | | **Name** | **Type** | **Description** | 405 | | --- | --- | --- | 406 | 407 | --- 408 | 409 | #### Methods 410 | 411 | | **Name** | **Description** | 412 | | --- | --- | 413 | | bind | Bind grants the CodeBuild role permissions to pull and push to a repository if necessary. | 414 | 415 | --- 416 | 417 | ##### `bind` 418 | 419 | ```typescript 420 | public bind(role: IGrantable): DestinationConfig 421 | ``` 422 | 423 | Bind grants the CodeBuild role permissions to pull and push to a repository if necessary. 424 | 425 | Bind should be invoked by the caller to get the DestinationConfig. 426 | 427 | ###### `role`Required 428 | 429 | - *Type:* aws-cdk-lib.aws_iam.IGrantable 430 | 431 | --- 432 | 433 | #### Static Functions 434 | 435 | | **Name** | **Description** | 436 | | --- | --- | 437 | | ecr | Uses an ECR repository in the same account as the stack as the destination for the image. | 438 | 439 | --- 440 | 441 | ##### `ecr` 442 | 443 | ```typescript 444 | import { Destination } from 'cdk-docker-image-deployment' 445 | 446 | Destination.ecr(repository: IRepository, options?: EcrSourceOptions) 447 | ``` 448 | 449 | Uses an ECR repository in the same account as the stack as the destination for the image. 450 | 451 | ###### `repository`Required 452 | 453 | - *Type:* aws-cdk-lib.aws_ecr.IRepository 454 | 455 | --- 456 | 457 | ###### `options`Optional 458 | 459 | - *Type:* EcrSourceOptions 460 | 461 | --- 462 | 463 | 464 | 465 | ### Source 466 | 467 | Specifies docker image deployment source. 468 | 469 | Usage: 470 | 471 | ```ts 472 | import * as path from 'path'; 473 | const path = path.join(__dirname, 'path/to/directory'); 474 | const sourceDirectory = Source.directory(path); 475 | ``` 476 | 477 | #### Initializers 478 | 479 | ```typescript 480 | import { Source } from 'cdk-docker-image-deployment' 481 | 482 | new Source() 483 | ``` 484 | 485 | | **Name** | **Type** | **Description** | 486 | | --- | --- | --- | 487 | 488 | --- 489 | 490 | #### Methods 491 | 492 | | **Name** | **Description** | 493 | | --- | --- | 494 | | bind | Bind grants the CodeBuild role permissions to pull from a repository if necessary. | 495 | 496 | --- 497 | 498 | ##### `bind` 499 | 500 | ```typescript 501 | public bind(scope: Construct, context: SourceContext): SourceConfig 502 | ``` 503 | 504 | Bind grants the CodeBuild role permissions to pull from a repository if necessary. 505 | 506 | Bind should be invoked by the caller to get the SourceConfig. 507 | 508 | ###### `scope`Required 509 | 510 | - *Type:* constructs.Construct 511 | 512 | --- 513 | 514 | ###### `context`Required 515 | 516 | - *Type:* SourceContext 517 | 518 | --- 519 | 520 | #### Static Functions 521 | 522 | | **Name** | **Description** | 523 | | --- | --- | 524 | | directory | Uses a local image built from a Dockerfile in a local directory as the source. | 525 | 526 | --- 527 | 528 | ##### `directory` 529 | 530 | ```typescript 531 | import { Source } from 'cdk-docker-image-deployment' 532 | 533 | Source.directory(path: string) 534 | ``` 535 | 536 | Uses a local image built from a Dockerfile in a local directory as the source. 537 | 538 | ###### `path`Required 539 | 540 | - *Type:* string 541 | 542 | path to the directory containing your Dockerfile (not a path to a file). 543 | 544 | --- 545 | 546 | 547 | 548 | 549 | -------------------------------------------------------------------------------- /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 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CDK Docker Image Deployment 2 | 3 | This module allows you to copy docker image assets to a repository you control. 4 | This can be necessary if you want to build a Docker image in one CDK app and consume it in a different app or outside the CDK, 5 | or if you want to apply a lifecycle policy to all images of a part of your application. 6 | 7 | ### Getting Started 8 | 9 | Below is a basic example for how to use the `DockerImageDeployment` API: 10 | 11 | ```ts 12 | import * as ecr from 'aws-cdk-lib/aws-ecr'; 13 | import * as imagedeploy from 'cdk-docker-image-deployment'; 14 | 15 | const repo = ecr.Repository.fromRepositoryName(this, 'MyRepository', 'myrepository'); 16 | 17 | new imagedeploy.DockerImageDeployment(this, 'ExampleImageDeploymentWithTag', { 18 | source: imagedeploy.Source.directory('path/to/directory'), 19 | destination: imagedeploy.Destination.ecr(repo, { 20 | tag: 'myspecialtag', 21 | }), 22 | }); 23 | ``` 24 | 25 | ### Currently Supported Sources 26 | 27 | - `Source.directory()`: Supply a path to a local docker image as source. 28 | 29 | > Don't see a source listed? See if there is an open [issue](https://github.com/cdklabs/cdk-docker-image-deployment/issues) 30 | > or [PR](https://github.com/cdklabs/cdk-docker-image-deployment/pulls) already. If not, please open an issue asking for it 31 | > or better yet, submit a contribution! 32 | 33 | ### Currently Supported Destinations 34 | 35 | - `Destination.ecr(repo, options)`: Send your docker image to an ECR repository in your stack's account. 36 | 37 | > Don't see a destination listed? See if there is an open [issue](https://github.com/cdklabs/cdk-docker-image-deployment/issues) 38 | > or [PR](https://github.com/cdklabs/cdk-docker-image-deployment/pulls) already. If not, please open an issue asking for it 39 | > or better yet, submit a contribution! 40 | 41 | ### Under the Hood 42 | 43 | 1. When this stack is deployed (either via cdk deploy or via CI/CD), the contents of the local Docker image will be archived and uploaded to an intermediary assets ECR Repository using the cdk-assets mechanism. 44 | 45 | 2. The `DockerImageDeployment` construct synthesizes a CodeBuild Project which uses docker to pull the image from the intermediary repository, tag the image if a tag is provided, and push the image to the destination repository. 46 | 47 | 3. The deployment will wait until the CodeBuild Project completes successfully before finishing. 48 | 49 | The architecture of this construct can be seen here: 50 | 51 | ![Construct-Architecture](https://user-images.githubusercontent.com/36202692/187282269-7ab29d3e-192f-470f-9123-5dbb62d9dac3.jpg) 52 | 53 | 54 | ## Security 55 | 56 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 57 | 58 | ## License 59 | 60 | This project is licensed under the Apache-2.0 License. 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-docker-image-deployment", 3 | "description": "This module allows you to copy docker image assets to a repository you control. This can be necessary if you want to build a Docker image in one CDK app and consume it in a different app or outside the CDK.", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/cdklabs/cdk-docker-image-deployment.git" 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 | "package": "npx projen package", 19 | "package-all": "npx projen package-all", 20 | "package:dotnet": "npx projen package:dotnet", 21 | "package:java": "npx projen package:java", 22 | "package:js": "npx projen package:js", 23 | "package:python": "npx projen package:python", 24 | "post-compile": "npx projen post-compile", 25 | "post-upgrade": "npx projen post-upgrade", 26 | "pre-compile": "npx projen pre-compile", 27 | "release": "npx projen release", 28 | "test": "npx projen test", 29 | "test:watch": "npx projen test:watch", 30 | "unbump": "npx projen unbump", 31 | "upgrade": "npx projen upgrade", 32 | "watch": "npx projen watch", 33 | "projen": "npx projen" 34 | }, 35 | "author": { 36 | "name": "Parker Scanlon", 37 | "url": "https://aws.amazon.com/", 38 | "organization": false 39 | }, 40 | "devDependencies": { 41 | "@stylistic/eslint-plugin": "^2", 42 | "@types/jest": "^27", 43 | "@types/node": "^16 <= 16.18.78", 44 | "@typescript-eslint/eslint-plugin": "^8", 45 | "@typescript-eslint/parser": "^8", 46 | "aws-cdk-lib": "2.24.0", 47 | "commit-and-tag-version": "^12", 48 | "constructs": "10.0.5", 49 | "esbuild": "^0.25.11", 50 | "eslint": "^9", 51 | "eslint-import-resolver-typescript": "^3.10.1", 52 | "eslint-plugin-import": "^2.32.0", 53 | "jest": "^27", 54 | "jest-junit": "^16", 55 | "jsii": "~5.8.0", 56 | "jsii-diff": "^1.117.0", 57 | "jsii-docgen": "^10.5.0", 58 | "jsii-pacmak": "^1.117.0", 59 | "jsii-rosetta": "~5.8.0", 60 | "projen": "^0.98.4", 61 | "ts-jest": "^27", 62 | "ts-node": "^10.9.2", 63 | "typescript": "^4.9.5" 64 | }, 65 | "peerDependencies": { 66 | "aws-cdk-lib": "^2.24.0", 67 | "constructs": "^10.0.5" 68 | }, 69 | "dependencies": { 70 | "@types/aws-lambda": "^8.10.156", 71 | "aws-sdk": "^2.1692.0" 72 | }, 73 | "bundledDependencies": [ 74 | "@types/aws-lambda", 75 | "aws-sdk" 76 | ], 77 | "keywords": [ 78 | "cdk" 79 | ], 80 | "main": "lib/index.js", 81 | "license": "Apache-2.0", 82 | "homepage": "https://github.com/cdklabs/cdk-docker-image-deployment#readme", 83 | "publishConfig": { 84 | "access": "public" 85 | }, 86 | "version": "0.0.0", 87 | "jest": { 88 | "coverageProvider": "v8", 89 | "testMatch": [ 90 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 91 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 92 | "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", 93 | "/@(projenrc)/**/__tests__/**/*.ts?(x)" 94 | ], 95 | "clearMocks": true, 96 | "collectCoverage": true, 97 | "coverageReporters": [ 98 | "json", 99 | "lcov", 100 | "clover", 101 | "cobertura", 102 | "text" 103 | ], 104 | "coverageDirectory": "coverage", 105 | "coveragePathIgnorePatterns": [ 106 | "/node_modules/" 107 | ], 108 | "testPathIgnorePatterns": [ 109 | "/node_modules/" 110 | ], 111 | "watchPathIgnorePatterns": [ 112 | "/node_modules/" 113 | ], 114 | "reporters": [ 115 | "default", 116 | [ 117 | "jest-junit", 118 | { 119 | "outputDirectory": "test-reports" 120 | } 121 | ] 122 | ], 123 | "preset": "ts-jest", 124 | "globals": { 125 | "ts-jest": { 126 | "tsconfig": "tsconfig.dev.json" 127 | } 128 | } 129 | }, 130 | "types": "lib/index.d.ts", 131 | "stability": "stable", 132 | "jsii": { 133 | "outdir": "dist", 134 | "targets": { 135 | "java": { 136 | "package": "io.github.cdklabs.cdk.docker.image.deployment", 137 | "maven": { 138 | "groupId": "io.github.cdklabs", 139 | "artifactId": "cdk-docker-image-deployment" 140 | } 141 | }, 142 | "python": { 143 | "distName": "cdk-docker-image-deployment", 144 | "module": "cdk_docker_image_deployment" 145 | }, 146 | "dotnet": { 147 | "namespace": "Cdklabs.CdkDockerImageDeployment", 148 | "packageId": "Cdklabs.CdkDockerImageDeployment" 149 | } 150 | }, 151 | "tsc": { 152 | "outDir": "lib", 153 | "rootDir": "src" 154 | } 155 | }, 156 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 157 | } 158 | -------------------------------------------------------------------------------- /src/codebuild-handler/index.ts: -------------------------------------------------------------------------------- 1 | import * as AWSLambda from 'aws-lambda'; 2 | import * as AWS from 'aws-sdk'; 3 | 4 | const cb = new AWS.CodeBuild(); 5 | 6 | export async function onEventhandler(event: AWSLambda.CloudFormationCustomResourceEvent) { 7 | switch (event.RequestType) { 8 | case 'Create': 9 | case 'Update': 10 | return startBuild(event); 11 | case 'Delete': 12 | return onDelete(); 13 | } 14 | } 15 | 16 | export async function startBuild(event: AWSLambda.CloudFormationCustomResourceEvent) { 17 | const projectName = event.ResourceProperties.projectName; 18 | const buildOutput = await cb.startBuild({ 19 | projectName: projectName, 20 | }).promise(); 21 | 22 | const buildId = buildOutput.build?.id; 23 | 24 | if (buildId) { 25 | return { 26 | BuildId: buildId, // pass to isComplete 27 | }; 28 | } else { 29 | throw new Error('BuildId does not exist after CodeBuild:StartBuild call'); 30 | } 31 | } 32 | 33 | // pass isComplete a value to indicate it does not need to wait on delete events 34 | export async function onDelete() { 35 | return { 36 | BuildId: 'onDelete', 37 | }; 38 | } 39 | 40 | export async function isCompleteHandler(event: AWSLambda.CloudFormationCustomResourceEvent) { 41 | 42 | const buildId = (event as any).BuildId; // BuildId is passed in from onEvent CodeBuild:StartBuild call 43 | 44 | if (!buildId) { 45 | throw new Error('BuildId was not found or undefined'); 46 | } 47 | 48 | // isComplete does not need to wait on delete events 49 | if (buildId === 'onDelete') { 50 | return { IsComplete: true }; 51 | } 52 | 53 | const build = await cb.batchGetBuilds({ 54 | ids: [buildId], 55 | }).promise(); 56 | 57 | // we should always have a build since we have a valid buildId 58 | if (!build.builds || build.builds.length <= 0) { 59 | throw new Error(`Build does not exist for BuildId: ${buildId}`); 60 | } 61 | 62 | const buildResponse = build.builds[0]; 63 | const currentPhase = buildResponse.currentPhase; 64 | const buildStatus = buildResponse.buildStatus; 65 | 66 | if (currentPhase === 'COMPLETED' && buildStatus === 'SUCCEEDED') { 67 | return { 68 | IsComplete: true, 69 | Data: { 70 | Status: 'CodeBuild completed successfully', 71 | LogsUrl: `${JSON.stringify(buildResponse.logs?.deepLink)}`, 72 | }, 73 | }; 74 | } else if (currentPhase === 'COMPLETED' && buildStatus === 'FAILED') { 75 | if (buildResponse.logs?.deepLink) { 76 | throw new Error(`CodeBuild failed, check the logs here: ${buildResponse.logs.deepLink}`); 77 | } else { 78 | throw new Error('CodeBuild failed'); // this case should never be reached 79 | } 80 | } else { 81 | return { IsComplete: false }; // not finished 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/destination.ts: -------------------------------------------------------------------------------- 1 | import * as ecr from 'aws-cdk-lib/aws-ecr'; 2 | import * as iam from 'aws-cdk-lib/aws-iam'; 3 | import { LoginConfig } from './login'; 4 | 5 | /** 6 | * Destination information 7 | */ 8 | export interface DestinationConfig { 9 | /** 10 | * The URI of the destination repository to deploy to. 11 | */ 12 | readonly destinationUri: string; 13 | 14 | /** 15 | * The login command and region. 16 | */ 17 | readonly loginConfig: LoginConfig; 18 | 19 | /** 20 | * The tag of the deployed image. 21 | * 22 | * @default - the tag of the source 23 | */ 24 | readonly destinationTag?: string; 25 | } 26 | 27 | /** 28 | * Properties needed for Source.ecr 29 | */ 30 | export interface EcrSourceOptions { 31 | /** 32 | * Tag of deployed image. 33 | * 34 | * @default - tag of source 35 | */ 36 | readonly tag?: string; 37 | } 38 | 39 | /** 40 | * Specifies docker image deployment destination 41 | * 42 | * Usage: 43 | * 44 | * ```ts 45 | * declare const repo: ecr.IRepository; 46 | * const destinationEcr = dockerDeploy.Destination.ecr(repository, { 47 | * tag: 'tag', 48 | * }); 49 | * ``` 50 | * 51 | */ 52 | export abstract class Destination { 53 | /** 54 | * Uses an ECR repository in the same account as the stack as the destination for the image. 55 | */ 56 | public static ecr(repository: ecr.IRepository, options?: EcrSourceOptions): Destination { 57 | return new EcrDestination(repository, options); 58 | } 59 | 60 | /** 61 | * Bind grants the CodeBuild role permissions to pull and push to a repository if necessary. 62 | * Bind should be invoked by the caller to get the DestinationConfig. 63 | */ 64 | public abstract bind(role: iam.IGrantable): DestinationConfig; 65 | } 66 | 67 | /** 68 | * Class used when the destination of docker image deployment is an ECR repository in the same account as the stack 69 | */ 70 | class EcrDestination extends Destination { 71 | private repository: ecr.IRepository; 72 | private options?: EcrSourceOptions; 73 | 74 | constructor(repository: ecr.IRepository, options?: EcrSourceOptions) { 75 | super(); 76 | 77 | this.repository = repository; 78 | this.options = options; 79 | 80 | } 81 | 82 | public bind(role: iam.IGrantable): DestinationConfig { 83 | const accountId = this.repository.env.account; 84 | const region = this.repository.env.region; 85 | 86 | this.repository.grantPullPush(role); 87 | 88 | return { 89 | destinationUri: this.repository.repositoryUri, 90 | loginConfig: { 91 | loginCommand: `aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${accountId}.dkr.ecr.${region}.amazonaws.com`, 92 | region: region, 93 | }, 94 | destinationTag: this.options?.tag, 95 | }; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/docker-image-deployment.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { CustomResource, Duration, CfnOutput, Token } from 'aws-cdk-lib'; 3 | import * as codebuild from 'aws-cdk-lib/aws-codebuild'; 4 | import * as iam from 'aws-cdk-lib/aws-iam'; 5 | import { Runtime } from 'aws-cdk-lib/aws-lambda'; 6 | import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs'; 7 | import * as cr from 'aws-cdk-lib/custom-resources'; 8 | import { Construct } from 'constructs'; 9 | import { Destination } from './destination'; 10 | import { Source } from './source'; 11 | 12 | export interface DockerImageDeploymentProps { 13 | /** 14 | * Source of the image to deploy. 15 | */ 16 | readonly source: Source; 17 | 18 | /** 19 | * Destination repository to deploy the image to. 20 | */ 21 | readonly destination: Destination; 22 | } 23 | 24 | /** 25 | * `DockerImageDeployment` pushes an image from a local or external source to a specified external destination 26 | */ 27 | export class DockerImageDeployment extends Construct { 28 | private readonly cb: codebuild.Project; 29 | 30 | constructor(scope: Construct, id: string, props: DockerImageDeploymentProps) { 31 | super(scope, id); 32 | 33 | const handlerRole = new iam.Role(this, 'DockerImageDeployRole', { 34 | assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'), 35 | }); 36 | 37 | const sourceConfig = props.source.bind(this, { handlerRole }); 38 | const destinationConfig = props.destination.bind(handlerRole); 39 | 40 | const sourceUri = sourceConfig.imageUri; 41 | 42 | const destTag = destinationConfig.destinationTag ?? sourceConfig.imageTag; 43 | this.validateTag(destTag); 44 | 45 | const destUri = `${destinationConfig.destinationUri}:${destTag}`; 46 | 47 | const commands = [ 48 | sourceConfig.loginConfig.loginCommand, 49 | `docker pull ${sourceUri}`, 50 | `docker tag ${sourceUri} ${destUri}`, 51 | ]; 52 | 53 | if (sourceConfig.loginConfig.region !== destinationConfig.loginConfig.region || !sourceConfig.loginConfig.region) { // different regions or either undefined should logout and login 54 | commands.push('docker logout'); 55 | commands.push(destinationConfig.loginConfig.loginCommand); 56 | } 57 | 58 | commands.push(`docker push ${destUri}`); 59 | commands.push('docker logout'); 60 | 61 | this.cb = new codebuild.Project(this, 'DockerImageDeployProject', { 62 | buildSpec: codebuild.BuildSpec.fromObject({ 63 | version: '0.2', 64 | phases: { 65 | build: { 66 | commands: commands, 67 | }, 68 | }, 69 | }), 70 | environment: { 71 | privileged: true, 72 | buildImage: codebuild.LinuxBuildImage.STANDARD_5_0, 73 | }, 74 | role: handlerRole, 75 | }); 76 | 77 | const onEventHandler = new lambda.NodejsFunction(this, 'onEventHandler', { 78 | entry: path.join(__dirname, 'codebuild-handler/index.js'), 79 | handler: 'onEventhandler', 80 | runtime: Runtime.NODEJS_16_X, 81 | }); 82 | 83 | const isCompleteHandler = new lambda.NodejsFunction(this, 'isCompleteHandler', { 84 | entry: path.join(__dirname, 'codebuild-handler/index.js'), 85 | handler: 'isCompleteHandler', 86 | runtime: Runtime.NODEJS_16_X, 87 | }); 88 | 89 | // https://github.com/aws/aws-cdk/issues/21721 issue to add grant methods to codebuild 90 | const grantOnEvent = iam.Grant.addToPrincipal({ 91 | grantee: onEventHandler, 92 | actions: ['codebuild:StartBuild'], 93 | resourceArns: [this.cb.projectArn], 94 | scope: this, 95 | }); 96 | 97 | const grantIsComplete = iam.Grant.addToPrincipal({ 98 | grantee: isCompleteHandler, 99 | actions: [ 100 | 'codebuild:ListBuildsForProject', 101 | 'codebuild:BatchGetBuilds', 102 | ], 103 | resourceArns: [this.cb.projectArn], 104 | scope: this, 105 | }); 106 | 107 | const crProvider = new cr.Provider(this, 'CRProvider', { 108 | onEventHandler: onEventHandler, 109 | isCompleteHandler: isCompleteHandler, 110 | queryInterval: Duration.seconds(30), 111 | totalTimeout: Duration.minutes(30), 112 | }); 113 | 114 | const customResource = new CustomResource(this, `CustomResource${Date.now().toString()}`, { 115 | serviceToken: crProvider.serviceToken, 116 | properties: { 117 | projectName: this.cb.projectName, 118 | }, 119 | }); 120 | 121 | customResource.node.addDependency(grantOnEvent, grantIsComplete); 122 | 123 | try { 124 | new CfnOutput(this, 'CustomResourceReport', { 125 | value: `${customResource.getAttString('Status')}, see the logs here: ${customResource.getAtt('LogsUrl')}`, 126 | }); 127 | } catch (error) { 128 | throw new Error('Error getting the report from the custom resource'); 129 | } 130 | } 131 | 132 | private validateTag(tag: string): void { 133 | if (Token.isUnresolved(tag)) { 134 | return; // if token tag is likely from source, so assume it is valid 135 | } 136 | if (tag.length > 128) { 137 | throw new Error (`Invalid tag: tags may contain a maximum of 128 characters; your tag ${tag} has ${tag.length} characters`); 138 | } 139 | if (!/^[^-.][a-zA-Z0-9-_.]+$/.test(tag)) { 140 | throw new Error(`Invalid tag: tags must contain alphanumeric characters and \'-\' \'_\' \'.\' only and must not begin with \'.\' or \'-\'; your tag was ${tag}`); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './docker-image-deployment'; 2 | export * from './source'; 3 | export * from './destination'; 4 | export * from './login'; 5 | -------------------------------------------------------------------------------- /src/login.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Login commands for specified registry 3 | */ 4 | export interface LoginConfig { 5 | /** 6 | * Command to run in codebuild to login. 7 | * Formatted `docker login ...`. 8 | */ 9 | readonly loginCommand: string; 10 | 11 | /** 12 | * Region of ECR repository. 13 | * 14 | * @default - undefined if not an ECR repository 15 | */ 16 | readonly region?: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/source.ts: -------------------------------------------------------------------------------- 1 | import { Fn } from 'aws-cdk-lib'; 2 | import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets'; 3 | import * as iam from 'aws-cdk-lib/aws-iam'; 4 | import { Construct } from 'constructs'; 5 | import { LoginConfig } from './login'; 6 | 7 | /** 8 | * Source information 9 | */ 10 | export interface SourceConfig { 11 | /** 12 | * The source image URI. 13 | */ 14 | readonly imageUri: string; 15 | 16 | /** 17 | * The login command and region. 18 | */ 19 | readonly loginConfig: LoginConfig; 20 | 21 | /** 22 | * The source tag. 23 | */ 24 | readonly imageTag: string; 25 | } 26 | 27 | /** 28 | * Bind context for Source 29 | */ 30 | export interface SourceContext { 31 | /** 32 | * The role for the handler. 33 | */ 34 | readonly handlerRole: iam.IRole; 35 | } 36 | 37 | /** 38 | * Specifies docker image deployment source 39 | * 40 | * Usage: 41 | * 42 | * ```ts 43 | * import * as path from 'path'; 44 | * const path = path.join(__dirname, 'path/to/directory'); 45 | * const sourceDirectory = Source.directory(path); 46 | * ``` 47 | * or with additional `assetOptions` 48 | * ```ts 49 | * import * as path from 'path'; 50 | * const path = path.join(__dirname, 'path/to/directory'); 51 | * const sourceDirectory = Source.directory(path, { 52 | * file: 'Dockerfile.api', 53 | * buildArgs: { 54 | * HTTP_PROXY: 'http://10.20.30.2:1234' 55 | * } 56 | * }) 57 | * ``` 58 | */ 59 | export abstract class Source { 60 | /** 61 | * Uses a local image built from a Dockerfile in a local directory as the source. 62 | * 63 | * @param path - path to the directory containing your Dockerfile (not a path to a file) 64 | * @param assetOptions - specify any additional [DockerImageAssetOptions](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecr_assets.DockerImageAssetOptions.html) (except `path`) 65 | */ 66 | public static directory( 67 | path: string, 68 | assetOptions?: ecr_assets.DockerImageAssetOptions, 69 | ): Source { 70 | return new DirectorySource(path, assetOptions); 71 | } 72 | 73 | /** 74 | * Bind grants the CodeBuild role permissions to pull from a repository if necessary. 75 | * Bind should be invoked by the caller to get the SourceConfig. 76 | */ 77 | public abstract bind(scope: Construct, context: SourceContext): SourceConfig; 78 | } 79 | 80 | /** 81 | * Source of docker image deployment is a local image from a directory 82 | */ 83 | class DirectorySource extends Source { 84 | private assetProps: ecr_assets.DockerImageAssetProps; 85 | 86 | constructor(path: string, assetOptions?: ecr_assets.DockerImageAssetOptions) { 87 | super(); 88 | this.assetProps = { 89 | directory: path, 90 | ...assetOptions, 91 | }; 92 | } 93 | 94 | public bind(scope: Construct, context: SourceContext): SourceConfig { 95 | const asset = new ecr_assets.DockerImageAsset(scope, 'asset', this.assetProps); 96 | 97 | const accountId = asset.repository.env.account; 98 | const region = asset.repository.env.region; 99 | 100 | asset.repository.grantPull(context.handlerRole); 101 | 102 | return { 103 | imageUri: asset.imageUri, 104 | loginConfig: { 105 | loginCommand: `aws ecr get-login-password --region ${region} | docker login --username AWS --password-stdin ${accountId}.dkr.ecr.${region}.amazonaws.com`, 106 | region: region, 107 | }, 108 | imageTag: Fn.select(1, Fn.split(':', asset.imageUri)), // uri will be something like 'directory/of/image:tag' so this picks out the tag from the token 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/assets/test1/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM node:12-alpine 3 | WORKDIR /app 4 | COPY . . 5 | -------------------------------------------------------------------------------- /test/assets/test2/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM node:12-alpine 3 | WORKDIR /app 4 | COPY . . 5 | EXPOSE 1000 6 | -------------------------------------------------------------------------------- /test/docker-image-deploy.test.ts: -------------------------------------------------------------------------------- 1 | // imports 2 | import * as path from 'path'; 3 | import { Match, Template } from 'aws-cdk-lib/assertions'; 4 | import * as ecr from 'aws-cdk-lib/aws-ecr'; 5 | import * as cdk from 'aws-cdk-lib/core'; 6 | import * as imagedeploy from '../lib/index'; 7 | 8 | describe('DockerImageDeploy', () => { 9 | // GIVEN 10 | const stack = new cdk.Stack(); 11 | describe('Source: directory', () => { 12 | // GIVEN 13 | const testSource = imagedeploy.Source.directory(path.join(__dirname, 'assets/test1')); 14 | 15 | describe('Destination: ecr', () => { 16 | // GIVEN 17 | const repo = new ecr.Repository(stack, 'TestRepository'); 18 | const testDesination = imagedeploy.Destination.ecr(repo, { tag: 'testtag' }); 19 | const testDesinationNoTag = imagedeploy.Destination.ecr(repo, {}); 20 | const testDesinationNoOptions = imagedeploy.Destination.ecr(repo); 21 | 22 | // WHEN 23 | new imagedeploy.DockerImageDeployment(stack, 'TestDeployment', { 24 | source: testSource, 25 | destination: testDesination, 26 | }); 27 | 28 | new imagedeploy.DockerImageDeployment(stack, 'TestDeploymentNoTag', { 29 | source: testSource, 30 | destination: testDesinationNoTag, 31 | }); 32 | 33 | new imagedeploy.DockerImageDeployment(stack, 'TestDeploymentNoOptions', { 34 | source: testSource, 35 | destination: testDesinationNoOptions, 36 | }); 37 | 38 | test('iam policy is granted correct permissions', () => { 39 | Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 40 | PolicyDocument: { 41 | Statement: Match.arrayWith([ 42 | { 43 | Action: [ 44 | 'ecr:BatchCheckLayerAvailability', 45 | 'ecr:GetDownloadUrlForLayer', 46 | 'ecr:BatchGetImage', 47 | ], 48 | Effect: 'Allow', 49 | Resource: { 50 | 'Fn::Join': [ 51 | '', 52 | [ 53 | 'arn:', 54 | { 55 | Ref: 'AWS::Partition', 56 | }, 57 | ':ecr:', 58 | { 59 | Ref: 'AWS::Region', 60 | }, 61 | ':', 62 | { 63 | Ref: 'AWS::AccountId', 64 | }, 65 | ':repository/', 66 | { 67 | 'Fn::Sub': 'cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}', 68 | }, 69 | ], 70 | ], 71 | }, 72 | }, 73 | { 74 | Action: 'ecr:GetAuthorizationToken', 75 | Effect: 'Allow', 76 | Resource: '*', 77 | }, 78 | { 79 | Action: [ 80 | 'ecr:BatchCheckLayerAvailability', 81 | 'ecr:GetDownloadUrlForLayer', 82 | 'ecr:BatchGetImage', 83 | ], 84 | Effect: 'Allow', 85 | Resource: { 86 | 'Fn::GetAtt': [ 87 | 'TestRepositoryC0DA8195', 88 | 'Arn', 89 | ], 90 | }, 91 | }, 92 | { 93 | Action: [ 94 | 'ecr:PutImage', 95 | 'ecr:InitiateLayerUpload', 96 | 'ecr:UploadLayerPart', 97 | 'ecr:CompleteLayerUpload', 98 | ], 99 | Effect: 'Allow', 100 | Resource: { 101 | 'Fn::GetAtt': [ 102 | 'TestRepositoryC0DA8195', 103 | 'Arn', 104 | ], 105 | }, 106 | }, 107 | { 108 | Action: [ 109 | 'logs:CreateLogGroup', 110 | 'logs:CreateLogStream', 111 | 'logs:PutLogEvents', 112 | ], 113 | Effect: 'Allow', 114 | Resource: [ 115 | { 116 | 'Fn::Join': [ 117 | '', 118 | [ 119 | 'arn:', 120 | { 121 | Ref: 'AWS::Partition', 122 | }, 123 | ':logs:', 124 | { 125 | Ref: 'AWS::Region', 126 | }, 127 | ':', 128 | { 129 | Ref: 'AWS::AccountId', 130 | }, 131 | ':log-group:/aws/codebuild/', 132 | { 133 | Ref: 'TestDeploymentDockerImageDeployProject0884B3B5', 134 | }, 135 | ], 136 | ], 137 | }, 138 | { 139 | 'Fn::Join': [ 140 | '', 141 | [ 142 | 'arn:', 143 | { 144 | Ref: 'AWS::Partition', 145 | }, 146 | ':logs:', 147 | { 148 | Ref: 'AWS::Region', 149 | }, 150 | ':', 151 | { 152 | Ref: 'AWS::AccountId', 153 | }, 154 | ':log-group:/aws/codebuild/', 155 | { 156 | Ref: 'TestDeploymentDockerImageDeployProject0884B3B5', 157 | }, 158 | ':*', 159 | ], 160 | ], 161 | }, 162 | ], 163 | }, 164 | { 165 | Action: [ 166 | 'codebuild:CreateReportGroup', 167 | 'codebuild:CreateReport', 168 | 'codebuild:UpdateReport', 169 | 'codebuild:BatchPutTestCases', 170 | 'codebuild:BatchPutCodeCoverages', 171 | ], 172 | Effect: 'Allow', 173 | Resource: { 174 | 'Fn::Join': [ 175 | '', 176 | [ 177 | 'arn:', 178 | { 179 | Ref: 'AWS::Partition', 180 | }, 181 | ':codebuild:', 182 | { 183 | Ref: 'AWS::Region', 184 | }, 185 | ':', 186 | { 187 | Ref: 'AWS::AccountId', 188 | }, 189 | ':report-group/', 190 | { 191 | Ref: 'TestDeploymentDockerImageDeployProject0884B3B5', 192 | }, 193 | '-*', 194 | ], 195 | ], 196 | }, 197 | }, 198 | ]), 199 | }, 200 | }); 201 | }); 202 | 203 | test('docker tag command is well formatted: tag provided', () => { 204 | Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 205 | Source: { 206 | BuildSpec: { 207 | 'Fn::Join': Match.arrayWith([ 208 | Match.arrayWith([ 209 | '",\n "docker tag ', 210 | { 'Fn::Sub': '${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:70d1a3115d17d2ad7210b272e45b7398a7661e7b0cf24b52e059ae3f1fa8f2c1' }, 211 | ' ', 212 | { 213 | 'Fn::Select': [4, { 214 | 'Fn::Split': [':', { 215 | 'Fn::GetAtt': [ 216 | 'TestRepositoryC0DA8195', 217 | 'Arn', 218 | ], 219 | }], 220 | }], 221 | }, 222 | '.dkr.ecr.', 223 | { 224 | 'Fn::Select': [3, { 225 | 'Fn::Split': [':', { 226 | 'Fn::GetAtt': [ 227 | 'TestRepositoryC0DA8195', 228 | 'Arn', 229 | ], 230 | }], 231 | }], 232 | }, 233 | '.', 234 | { Ref: 'AWS::URLSuffix' }, 235 | '/', 236 | { Ref: 'TestRepositoryC0DA8195' }, 237 | Match.stringLikeRegexp('^:testtag",(.)*'), 238 | ]), 239 | ]), 240 | }, 241 | }, 242 | }); 243 | }); 244 | 245 | test('docker tag command is well formatted: no tag provided', () => { 246 | Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 247 | Source: { 248 | BuildSpec: { 249 | 'Fn::Join': Match.arrayWith([ 250 | Match.arrayWith([ 251 | '",\n "docker tag ', 252 | { 'Fn::Sub': '${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:70d1a3115d17d2ad7210b272e45b7398a7661e7b0cf24b52e059ae3f1fa8f2c1' }, 253 | ' ', 254 | { 255 | 'Fn::Select': [4, { 256 | 'Fn::Split': [':', { 257 | 'Fn::GetAtt': [ 258 | 'TestRepositoryC0DA8195', 259 | 'Arn', 260 | ], 261 | }], 262 | }], 263 | }, 264 | '.dkr.ecr.', 265 | { 266 | 'Fn::Select': [3, { 267 | 'Fn::Split': [':', { 268 | 'Fn::GetAtt': [ 269 | 'TestRepositoryC0DA8195', 270 | 'Arn', 271 | ], 272 | }], 273 | }], 274 | }, 275 | '.', 276 | { Ref: 'AWS::URLSuffix' }, 277 | '/', 278 | { Ref: 'TestRepositoryC0DA8195' }, 279 | // need no-tag validation here, will be better once cdk 2.38.1 is recognized 280 | ]), 281 | ]), 282 | }, 283 | }, 284 | }); 285 | }); 286 | 287 | }); 288 | 289 | test('ECR login and pull commands are well formatted', () => { 290 | Template.fromStack(stack).hasResourceProperties('AWS::CodeBuild::Project', { 291 | Source: { 292 | BuildSpec: { 293 | 'Fn::Join': Match.arrayWith([ 294 | Match.arrayWith([ 295 | /* 296 | '{\n "version": "0.2",\n "phases": {\n "pre_build": {\n "commands": [\n "aws ecr get-login-password --region ', 297 | { Ref: 'AWS::Region' }, 298 | ' | docker login --username AWS --password-stdin ', 299 | { Ref: 'AWS::AccountId' }, 300 | '.dkr.ecr.', 301 | { Ref: 'AWS::Region' }, 302 | '.amazonaws.com"\n ]\n },\n "build": {\n "commands": [\n "docker pull ', 303 | { 'Fn::Sub': '${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:70d1a3115d17d2ad7210b272e45b7398a7661e7b0cf24b52e059ae3f1fa8f2c1' }, 304 | */ 305 | '{\n "version": "0.2",\n "phases": {\n "build": {\n "commands": [\n "aws ecr get-login-password --region ', 306 | { Ref: 'AWS::Region' }, 307 | ' | docker login --username AWS --password-stdin ', 308 | { Ref: 'AWS::AccountId' }, 309 | '.dkr.ecr.', 310 | { Ref: 'AWS::Region' }, 311 | '.amazonaws.com",\n "docker pull ', 312 | { 'Fn::Sub': '${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:70d1a3115d17d2ad7210b272e45b7398a7661e7b0cf24b52e059ae3f1fa8f2c1' }, 313 | '",\n "docker tag ', 314 | { 'Fn::Sub': '${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:70d1a3115d17d2ad7210b272e45b7398a7661e7b0cf24b52e059ae3f1fa8f2c1' }, 315 | ' ', 316 | { 'Fn::Select': [4, { 'Fn::Split': [':', { 'Fn::GetAtt': ['TestRepositoryC0DA8195', 'Arn'] }] }] }, 317 | '.dkr.ecr.', 318 | { 'Fn::Select': [3, { 'Fn::Split': [':', { 'Fn::GetAtt': ['TestRepositoryC0DA8195', 'Arn'] }] }] }, 319 | '.', 320 | { Ref: 'AWS::URLSuffix' }, 321 | '/', 322 | { Ref: 'TestRepositoryC0DA8195' }, 323 | ':testtag",\n "docker push ', 324 | { 'Fn::Select': [4, { 'Fn::Split': [':', { 'Fn::GetAtt': ['TestRepositoryC0DA8195', 'Arn'] }] }] }, 325 | '.dkr.ecr.', 326 | { 'Fn::Select': [3, { 'Fn::Split': [':', { 'Fn::GetAtt': ['TestRepositoryC0DA8195', 'Arn'] }] }] }, 327 | '.', 328 | { Ref: 'AWS::URLSuffix' }, 329 | '/', 330 | { Ref: 'TestRepositoryC0DA8195' }, 331 | ':testtag",\n "docker logout"\n ]\n }\n }\n}', 332 | ]), 333 | ]), 334 | }, 335 | }, 336 | }); 337 | }); 338 | }); 339 | 340 | describe('Tag validation', () => { 341 | // GIVEN 342 | const repo = new ecr.Repository(stack, 'TestTagsRepository'); 343 | const testSource = imagedeploy.Source.directory(path.join(__dirname, 'assets/test1')); 344 | 345 | test('valid tag', () => { 346 | new imagedeploy.DockerImageDeployment(stack, 'TestValidTag', { 347 | source: testSource, 348 | destination: imagedeploy.Destination.ecr(repo, { tag: '_test_TEST-1234.tag-' }), 349 | }); 350 | }); 351 | 352 | test('options not provided does not throw', () => { 353 | new imagedeploy.DockerImageDeployment(stack, 'TestNoOptions', { 354 | source: testSource, 355 | destination: imagedeploy.Destination.ecr(repo), 356 | }); 357 | }); 358 | 359 | test('tag not provided does not throw', () => { 360 | new imagedeploy.DockerImageDeployment(stack, 'TestNoTag', { 361 | source: testSource, 362 | destination: imagedeploy.Destination.ecr(repo, {}), 363 | }); 364 | }); 365 | 366 | test('empyty tag', () => { 367 | expect(() => { 368 | new imagedeploy.DockerImageDeployment(stack, 'TestEmptyTag', { 369 | source: testSource, 370 | destination: imagedeploy.Destination.ecr(repo, { tag: '' }), 371 | }); 372 | }).toThrow('Invalid tag: tags must contain alphanumeric characters and \'-\' \'_\' \'.\' only and must not begin with \'.\' or \'-\''); 373 | }); 374 | 375 | test('tag contains invalid character', () => { 376 | expect(() => { 377 | new imagedeploy.DockerImageDeployment(stack, 'TestInvalidCharacter', { 378 | source: testSource, 379 | destination: imagedeploy.Destination.ecr(repo, { tag: 'testTag123!' }), 380 | }); 381 | }).toThrow('Invalid tag: tags must contain alphanumeric characters and \'-\' \'_\' \'.\' only and must not begin with \'.\' or \'-\''); 382 | }); 383 | 384 | test('tag starts with invalid character \'-\'', () => { 385 | expect(() => { 386 | new imagedeploy.DockerImageDeployment(stack, 'TestStartInvalidCharacterDash', { 387 | source: testSource, 388 | destination: imagedeploy.Destination.ecr(repo, { tag: '-testTag123' }), 389 | }); 390 | }).toThrow('Invalid tag: tags must contain alphanumeric characters and \'-\' \'_\' \'.\' only and must not begin with \'.\' or \'-\''); 391 | }); 392 | 393 | test('tag starts with invalid character \'.\'', () => { 394 | expect(() => { 395 | new imagedeploy.DockerImageDeployment(stack, 'TestStartInvalidCharacterPeriod', { 396 | source: testSource, 397 | destination: imagedeploy.Destination.ecr(repo, { tag: '.testTag123' }), 398 | }); 399 | }).toThrow('Invalid tag: tags must contain alphanumeric characters and \'-\' \'_\' \'.\' only and must not begin with \'.\' or \'-\''); 400 | }); 401 | 402 | test('tag is over max length', () => { 403 | expect(() => { 404 | new imagedeploy.DockerImageDeployment(stack, 'TestMaxTagLength', { 405 | source: testSource, 406 | destination: imagedeploy.Destination.ecr(repo, { tag: 'longtag-10longtag-20longtag-30longtag-40longtag-50longtag-60longtag-70longtag-80longtag-90longtag-101logestTagOneCharacterOver128' }), 407 | }); 408 | }).toThrow('Invalid tag: tags may contain a maximum of 128 characters'); 409 | }); 410 | }); 411 | 412 | describe('Custom Resrouces', () => { 413 | test('onEventHandler has correct permissions', () => { 414 | Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 415 | PolicyDocument: { 416 | Statement: Match.arrayWith([ 417 | { 418 | Action: 'codebuild:StartBuild', 419 | Effect: 'Allow', 420 | Resource: { 421 | 'Fn::GetAtt': [ 422 | 'TestDeploymentDockerImageDeployProject0884B3B5', 423 | 'Arn', 424 | ], 425 | }, 426 | }, 427 | ]), 428 | }, 429 | }); 430 | }); 431 | 432 | test('isCompleteHandler has correct permissions', () => { 433 | Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { 434 | PolicyDocument: { 435 | Statement: Match.arrayWith([ 436 | { 437 | Action: [ 438 | 'codebuild:ListBuildsForProject', 439 | 'codebuild:BatchGetBuilds', 440 | ], 441 | Effect: 'Allow', 442 | Resource: { 443 | 'Fn::GetAtt': [ 444 | 'TestDeploymentDockerImageDeployProject0884B3B5', 445 | 'Arn', 446 | ], 447 | }, 448 | }, 449 | ]), 450 | }, 451 | }); 452 | }); 453 | }); 454 | }); -------------------------------------------------------------------------------- /test/integ/app.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { Stack, App } from 'aws-cdk-lib'; 3 | import * as ecr from 'aws-cdk-lib/aws-ecr'; 4 | import { Construct } from 'constructs'; 5 | import * as imagedeploy from '../../src/index'; 6 | 7 | const app = new App(); 8 | 9 | class DockerImageDeploymentStack extends Stack { 10 | constructor(scope: Construct, id: string) { 11 | super(scope, id); 12 | 13 | const repo = new ecr.Repository(this, 'MyRepository', { 14 | repositoryName: 'myrepository', 15 | }); 16 | 17 | new imagedeploy.DockerImageDeployment(this, 'MyImageDeployment', { 18 | source: imagedeploy.Source.directory(path.join(__dirname, '../assets/test1')), 19 | destination: imagedeploy.Destination.ecr(repo, { 20 | tag: 'myTag', 21 | }), 22 | }); 23 | } 24 | } 25 | 26 | new DockerImageDeploymentStack(app, 'MyStack'); -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2020" 12 | ], 13 | "module": "CommonJS", 14 | "noEmitOnError": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": true, 25 | "stripInternal": true, 26 | "target": "ES2020" 27 | }, 28 | "include": [ 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.ts", 32 | "projenrc/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | --------------------------------------------------------------------------------