├── .eslintrc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ └── upgrade-main.yml ├── .gitignore ├── .mergify.yml ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.js ├── .vscode └── settings.json ├── API.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package.json ├── src └── index.ts ├── test └── backup-plan.test.ts ├── tsconfig.dev.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.js 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 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module", 16 | "project": "./tsconfig.dev.json" 17 | }, 18 | "extends": [ 19 | "plugin:import/typescript" 20 | ], 21 | "settings": { 22 | "import/parsers": { 23 | "@typescript-eslint/parser": [ 24 | ".ts", 25 | ".tsx" 26 | ] 27 | }, 28 | "import/resolver": { 29 | "node": {}, 30 | "typescript": { 31 | "project": "./tsconfig.dev.json", 32 | "alwaysTryTypes": true 33 | } 34 | } 35 | }, 36 | "ignorePatterns": [ 37 | "*.js", 38 | "*.d.ts", 39 | "node_modules/", 40 | "*.generated.ts", 41 | "coverage", 42 | "!.projenrc.js" 43 | ], 44 | "rules": { 45 | "indent": [ 46 | "off" 47 | ], 48 | "@typescript-eslint/indent": [ 49 | "error", 50 | 2 51 | ], 52 | "quotes": [ 53 | "error", 54 | "single", 55 | { 56 | "avoidEscape": true 57 | } 58 | ], 59 | "comma-dangle": [ 60 | "error", 61 | "always-multiline" 62 | ], 63 | "comma-spacing": [ 64 | "error", 65 | { 66 | "before": false, 67 | "after": true 68 | } 69 | ], 70 | "no-multi-spaces": [ 71 | "error", 72 | { 73 | "ignoreEOLComments": false 74 | } 75 | ], 76 | "array-bracket-spacing": [ 77 | "error", 78 | "never" 79 | ], 80 | "array-bracket-newline": [ 81 | "error", 82 | "consistent" 83 | ], 84 | "object-curly-spacing": [ 85 | "error", 86 | "always" 87 | ], 88 | "object-curly-newline": [ 89 | "error", 90 | { 91 | "multiline": true, 92 | "consistent": true 93 | } 94 | ], 95 | "object-property-newline": [ 96 | "error", 97 | { 98 | "allowAllPropertiesOnSameLine": true 99 | } 100 | ], 101 | "keyword-spacing": [ 102 | "error" 103 | ], 104 | "brace-style": [ 105 | "error", 106 | "1tbs", 107 | { 108 | "allowSingleLine": true 109 | } 110 | ], 111 | "space-before-blocks": [ 112 | "error" 113 | ], 114 | "curly": [ 115 | "error", 116 | "multi-line", 117 | "consistent" 118 | ], 119 | "@typescript-eslint/member-delimiter-style": [ 120 | "error" 121 | ], 122 | "semi": [ 123 | "error", 124 | "always" 125 | ], 126 | "max-len": [ 127 | "error", 128 | { 129 | "code": 150, 130 | "ignoreUrls": true, 131 | "ignoreStrings": true, 132 | "ignoreTemplateLiterals": true, 133 | "ignoreComments": true, 134 | "ignoreRegExpLiterals": true 135 | } 136 | ], 137 | "quote-props": [ 138 | "error", 139 | "consistent-as-needed" 140 | ], 141 | "@typescript-eslint/no-require-imports": [ 142 | "error" 143 | ], 144 | "import/no-extraneous-dependencies": [ 145 | "error", 146 | { 147 | "devDependencies": [ 148 | "**/test/**", 149 | "**/build-tools/**" 150 | ], 151 | "optionalDependencies": false, 152 | "peerDependencies": true 153 | } 154 | ], 155 | "import/no-unresolved": [ 156 | "error" 157 | ], 158 | "import/order": [ 159 | "warn", 160 | { 161 | "groups": [ 162 | "builtin", 163 | "external" 164 | ], 165 | "alphabetize": { 166 | "order": "asc", 167 | "caseInsensitive": true 168 | } 169 | } 170 | ], 171 | "no-duplicate-imports": [ 172 | "error" 173 | ], 174 | "no-shadow": [ 175 | "off" 176 | ], 177 | "@typescript-eslint/no-shadow": [ 178 | "error" 179 | ], 180 | "key-spacing": [ 181 | "error" 182 | ], 183 | "no-multiple-empty-lines": [ 184 | "error" 185 | ], 186 | "@typescript-eslint/no-floating-promises": [ 187 | "error" 188 | ], 189 | "no-return-await": [ 190 | "off" 191 | ], 192 | "@typescript-eslint/return-await": [ 193 | "error" 194 | ], 195 | "no-trailing-spaces": [ 196 | "error" 197 | ], 198 | "dot-notation": [ 199 | "error" 200 | ], 201 | "no-bitwise": [ 202 | "error" 203 | ], 204 | "@typescript-eslint/member-ordering": [ 205 | "error", 206 | { 207 | "default": [ 208 | "public-static-field", 209 | "public-static-method", 210 | "protected-static-field", 211 | "protected-static-method", 212 | "private-static-field", 213 | "private-static-method", 214 | "field", 215 | "constructor", 216 | "method" 217 | ] 218 | } 219 | ] 220 | }, 221 | "overrides": [ 222 | { 223 | "files": [ 224 | ".projenrc.js" 225 | ], 226 | "rules": { 227 | "@typescript-eslint/no-require-imports": "off", 228 | "import/no-extraneous-dependencies": "off" 229 | } 230 | } 231 | ] 232 | } 233 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | *.snap linguist-generated 4 | /.eslintrc.json linguist-generated 5 | /.gitattributes linguist-generated 6 | /.github/pull_request_template.md linguist-generated 7 | /.github/workflows/build.yml linguist-generated 8 | /.github/workflows/pull-request-lint.yml linguist-generated 9 | /.github/workflows/release.yml linguist-generated 10 | /.github/workflows/upgrade-main.yml linguist-generated 11 | /.gitignore linguist-generated 12 | /.mergify.yml linguist-generated 13 | /.npmignore linguist-generated 14 | /.npmrc linguist-generated 15 | /.projen/** linguist-generated 16 | /.projen/deps.json linguist-generated 17 | /.projen/files.json linguist-generated 18 | /.projen/tasks.json linguist-generated 19 | /API.md linguist-generated 20 | /LICENSE linguist-generated 21 | /package.json linguist-generated 22 | /tsconfig.dev.json linguist-generated 23 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: build 4 | on: 5 | pull_request: {} 6 | workflow_dispatch: {} 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | outputs: 13 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 14 | env: 15 | CI: "true" 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | ref: ${{ github.event.pull_request.head.ref }} 21 | repository: ${{ github.event.pull_request.head.repo.full_name }} 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 16.x 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 | - name: Upload patch 36 | if: steps.self_mutation.outputs.self_mutation_happened 37 | uses: actions/upload-artifact@v3 38 | with: 39 | name: .repo.patch 40 | path: .repo.patch 41 | - name: Fail build on mutation 42 | if: steps.self_mutation.outputs.self_mutation_happened 43 | run: |- 44 | echo "::error::Files were changed during build (see build log). If this was triggered from a fork, you will need to update your branch." 45 | cat .repo.patch 46 | exit 1 47 | - name: Backup artifact permissions 48 | run: cd dist && getfacl -R . > permissions-backup.acl 49 | continue-on-error: true 50 | - name: Upload artifact 51 | uses: actions/upload-artifact@v3 52 | with: 53 | name: build-artifact 54 | path: dist 55 | self-mutation: 56 | needs: build 57 | runs-on: ubuntu-latest 58 | permissions: 59 | contents: write 60 | if: always() && needs.build.outputs.self_mutation_happened && !(github.event.pull_request.head.repo.full_name != github.repository) 61 | steps: 62 | - name: Checkout 63 | uses: actions/checkout@v3 64 | with: 65 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 66 | ref: ${{ github.event.pull_request.head.ref }} 67 | repository: ${{ github.event.pull_request.head.repo.full_name }} 68 | - name: Download patch 69 | uses: actions/download-artifact@v3 70 | with: 71 | name: .repo.patch 72 | path: ${{ runner.temp }} 73 | - name: Apply patch 74 | run: '[ -s ${{ runner.temp }}/.repo.patch ] && git apply ${{ runner.temp }}/.repo.patch || echo "Empty patch. Skipping."' 75 | - name: Set git identity 76 | run: |- 77 | git config user.name "github-actions" 78 | git config user.email "github-actions@github.com" 79 | - name: Push changes 80 | env: 81 | PULL_REQUEST_REF: ${{ github.event.pull_request.head.ref }} 82 | run: |- 83 | git add . 84 | git commit -s -m "chore: self mutation" 85 | git push origin HEAD:$PULL_REQUEST_REF 86 | package-js: 87 | needs: build 88 | runs-on: ubuntu-latest 89 | permissions: {} 90 | if: "! needs.build.outputs.self_mutation_happened" 91 | steps: 92 | - uses: actions/setup-node@v3 93 | with: 94 | node-version: 16.x 95 | - name: Download build artifacts 96 | uses: actions/download-artifact@v3 97 | with: 98 | name: build-artifact 99 | path: dist 100 | - name: Restore build artifact permissions 101 | run: cd dist && setfacl --restore=permissions-backup.acl 102 | continue-on-error: true 103 | - name: Prepare Repository 104 | run: mv dist .repo 105 | - name: Install Dependencies 106 | run: cd .repo && yarn install --check-files --frozen-lockfile 107 | - name: Create js artifact 108 | run: cd .repo && npx projen package:js 109 | - name: Collect js Artifact 110 | run: mv .repo/dist dist 111 | package-python: 112 | needs: build 113 | runs-on: ubuntu-latest 114 | permissions: {} 115 | if: "! needs.build.outputs.self_mutation_happened" 116 | steps: 117 | - uses: actions/setup-node@v3 118 | with: 119 | node-version: 16.x 120 | - uses: actions/setup-python@v4 121 | with: 122 | python-version: 3.x 123 | - name: Download build artifacts 124 | uses: actions/download-artifact@v3 125 | with: 126 | name: build-artifact 127 | path: dist 128 | - name: Restore build artifact permissions 129 | run: cd dist && setfacl --restore=permissions-backup.acl 130 | continue-on-error: true 131 | - name: Prepare Repository 132 | run: mv dist .repo 133 | - name: Install Dependencies 134 | run: cd .repo && yarn install --check-files --frozen-lockfile 135 | - name: Create python artifact 136 | run: cd .repo && npx projen package:python 137 | - name: Collect python Artifact 138 | run: mv .repo/dist dist 139 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | jobs: 14 | validate: 15 | name: Validate PR title 16 | runs-on: ubuntu-latest 17 | permissions: 18 | pull-requests: write 19 | steps: 20 | - uses: amannn/action-semantic-pull-request@v5.0.2 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | with: 24 | types: |- 25 | feat 26 | fix 27 | chore 28 | requireScope: false 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: release 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | outputs: 15 | latest_commit: ${{ steps.git_remote.outputs.latest_commit }} 16 | env: 17 | CI: "true" 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 23 | - name: Set git identity 24 | run: |- 25 | git config user.name "github-actions" 26 | git config user.email "github-actions@github.com" 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: 16.x 31 | - name: Install dependencies 32 | run: yarn install --check-files --frozen-lockfile 33 | - name: release 34 | run: npx projen release 35 | - name: Check for new commits 36 | id: git_remote 37 | run: echo "latest_commit=$(git ls-remote origin -h ${{ github.ref }} | cut -f1)" >> $GITHUB_OUTPUT 38 | - name: Backup artifact permissions 39 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 40 | run: cd dist && getfacl -R . > permissions-backup.acl 41 | continue-on-error: true 42 | - name: Upload artifact 43 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 44 | uses: actions/upload-artifact@v3 45 | with: 46 | name: build-artifact 47 | path: dist 48 | release_github: 49 | name: Publish to GitHub Releases 50 | needs: release 51 | runs-on: ubuntu-latest 52 | permissions: 53 | contents: write 54 | if: needs.release.outputs.latest_commit == github.sha 55 | steps: 56 | - uses: actions/setup-node@v3 57 | with: 58 | node-version: 16.x 59 | - name: Download build artifacts 60 | uses: actions/download-artifact@v3 61 | with: 62 | name: build-artifact 63 | path: dist 64 | - name: Restore build artifact permissions 65 | run: cd dist && setfacl --restore=permissions-backup.acl 66 | continue-on-error: true 67 | - name: Prepare Repository 68 | run: mv dist .repo 69 | - name: Collect GitHub Metadata 70 | run: mv .repo/dist dist 71 | - name: Release 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | GITHUB_REPOSITORY: ${{ github.repository }} 75 | GITHUB_REF: ${{ github.ref }} 76 | run: errout=$(mktemp); gh release create $(cat dist/releasetag.txt) -R $GITHUB_REPOSITORY -F dist/changelog.md -t $(cat dist/releasetag.txt) --target $GITHUB_REF 2> $errout && true; exitcode=$?; if [ $exitcode -ne 0 ] && ! grep -q "Release.tag_name already exists" $errout; then cat $errout; exit $exitcode; fi 77 | release_npm: 78 | name: Publish to npm 79 | needs: release 80 | runs-on: ubuntu-latest 81 | permissions: 82 | contents: read 83 | if: needs.release.outputs.latest_commit == github.sha 84 | steps: 85 | - uses: actions/setup-node@v3 86 | with: 87 | node-version: 16.x 88 | - name: Download build artifacts 89 | uses: actions/download-artifact@v3 90 | with: 91 | name: build-artifact 92 | path: dist 93 | - name: Restore build artifact permissions 94 | run: cd dist && setfacl --restore=permissions-backup.acl 95 | continue-on-error: true 96 | - name: Prepare Repository 97 | run: mv dist .repo 98 | - name: Install Dependencies 99 | run: cd .repo && yarn install --check-files --frozen-lockfile 100 | - name: Create js artifact 101 | run: cd .repo && npx projen package:js 102 | - name: Collect js Artifact 103 | run: mv .repo/dist dist 104 | - name: Release 105 | env: 106 | NPM_DIST_TAG: latest 107 | NPM_REGISTRY: registry.npmjs.org 108 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 109 | run: npx -p publib@latest publib-npm 110 | release_pypi: 111 | name: Publish to PyPI 112 | needs: release 113 | runs-on: ubuntu-latest 114 | permissions: 115 | contents: read 116 | if: needs.release.outputs.latest_commit == github.sha 117 | steps: 118 | - uses: actions/setup-node@v3 119 | with: 120 | node-version: 16.x 121 | - uses: actions/setup-python@v4 122 | with: 123 | python-version: 3.x 124 | - name: Download build artifacts 125 | uses: actions/download-artifact@v3 126 | with: 127 | name: build-artifact 128 | path: dist 129 | - name: Restore build artifact permissions 130 | run: cd dist && setfacl --restore=permissions-backup.acl 131 | continue-on-error: true 132 | - name: Prepare Repository 133 | run: mv dist .repo 134 | - name: Install Dependencies 135 | run: cd .repo && yarn install --check-files --frozen-lockfile 136 | - name: Create python artifact 137 | run: cd .repo && npx projen package:python 138 | - name: Collect python Artifact 139 | run: mv .repo/dist dist 140 | - name: Release 141 | env: 142 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 143 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 144 | run: npx -p publib@latest publib-pypi 145 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 0 * * * 8 | jobs: 9 | upgrade: 10 | name: Upgrade 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | outputs: 15 | patch_created: ${{ steps.create_patch.outputs.patch_created }} 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 16.x 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 | - name: Upload patch 35 | if: steps.create_patch.outputs.patch_created 36 | uses: actions/upload-artifact@v3 37 | with: 38 | name: .repo.patch 39 | path: .repo.patch 40 | pr: 41 | name: Create Pull Request 42 | needs: upgrade 43 | runs-on: ubuntu-latest 44 | permissions: 45 | contents: read 46 | if: ${{ needs.upgrade.outputs.patch_created }} 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v3 50 | with: 51 | ref: main 52 | - name: Download patch 53 | uses: actions/download-artifact@v3 54 | with: 55 | name: .repo.patch 56 | path: ${{ runner.temp }} 57 | - name: Apply patch 58 | run: '[ -s ${{ runner.temp }}/.repo.patch ] && git apply ${{ runner.temp }}/.repo.patch || echo "Empty patch. Skipping."' 59 | - name: Set git identity 60 | run: |- 61 | git config user.name "github-actions" 62 | git config user.email "github-actions@github.com" 63 | - name: Create Pull Request 64 | id: create-pr 65 | uses: peter-evans/create-pull-request@v4 66 | with: 67 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 68 | commit-message: |- 69 | chore(deps): upgrade dependencies 70 | 71 | Upgrades project dependencies. See details in [workflow run]. 72 | 73 | [Workflow Run]: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} 74 | 75 | ------ 76 | 77 | *Automatically created by projen via the "upgrade-main" workflow* 78 | branch: github-actions/upgrade-main 79 | title: "chore(deps): upgrade dependencies" 80 | body: |- 81 | Upgrades project dependencies. See details in [workflow run]. 82 | 83 | [Workflow Run]: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} 84 | 85 | ------ 86 | 87 | *Automatically created by projen via the "upgrade-main" workflow* 88 | author: github-actions 89 | committer: github-actions 90 | signoff: true 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/package.json 8 | !/LICENSE 9 | !/.npmignore 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | lib-cov 22 | coverage 23 | *.lcov 24 | .nyc_output 25 | build/Release 26 | node_modules/ 27 | jspm_packages/ 28 | *.tsbuildinfo 29 | .eslintcache 30 | *.tgz 31 | .yarn-integrity 32 | .cache 33 | !/.projenrc.js 34 | /test-reports/ 35 | junit.xml 36 | /coverage/ 37 | !/.github/workflows/build.yml 38 | /dist/changelog.md 39 | /dist/version.txt 40 | !/.github/workflows/release.yml 41 | !/.mergify.yml 42 | !/.github/workflows/upgrade-main.yml 43 | !/.github/pull_request_template.md 44 | !/.npmrc 45 | !/test/ 46 | !/tsconfig.dev.json 47 | !/src/ 48 | /lib 49 | /dist/ 50 | !/.eslintrc.json 51 | .jsii 52 | tsconfig.json 53 | !/API.md 54 | .dev/ 55 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | - status-success=package-js 11 | - status-success=package-python 12 | pull_request_rules: 13 | - name: Automatic merge on approval and successful build 14 | actions: 15 | delete_head_branch: {} 16 | queue: 17 | method: squash 18 | name: default 19 | commit_message_template: |- 20 | {{ title }} (#{{ number }}) 21 | 22 | {{ body }} 23 | conditions: 24 | - "#approved-reviews-by>=1" 25 | - -label~=(do-not-merge) 26 | - status-success=build 27 | - status-success=package-js 28 | - status-success=package-python 29 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /.mergify.yml 10 | /test/ 11 | /tsconfig.dev.json 12 | /src/ 13 | !/lib/ 14 | !/lib/**/*.js 15 | !/lib/**/*.d.ts 16 | dist 17 | /tsconfig.json 18 | /.github/ 19 | /.vscode/ 20 | /.idea/ 21 | /.projenrc.js 22 | tsconfig.tsbuildinfo 23 | /.eslintrc.json 24 | !.jsii 25 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@types/jest", 5 | "version": "^27", 6 | "type": "build" 7 | }, 8 | { 9 | "name": "@types/node", 10 | "version": "^16", 11 | "type": "build" 12 | }, 13 | { 14 | "name": "@typescript-eslint/eslint-plugin", 15 | "version": "^5", 16 | "type": "build" 17 | }, 18 | { 19 | "name": "@typescript-eslint/parser", 20 | "version": "^5", 21 | "type": "build" 22 | }, 23 | { 24 | "name": "aws-cdk-lib", 25 | "version": "2.49.0", 26 | "type": "build" 27 | }, 28 | { 29 | "name": "constructs", 30 | "version": "10.0.5", 31 | "type": "build" 32 | }, 33 | { 34 | "name": "eslint-import-resolver-node", 35 | "type": "build" 36 | }, 37 | { 38 | "name": "eslint-import-resolver-typescript", 39 | "type": "build" 40 | }, 41 | { 42 | "name": "eslint-plugin-import", 43 | "type": "build" 44 | }, 45 | { 46 | "name": "eslint", 47 | "version": "^8", 48 | "type": "build" 49 | }, 50 | { 51 | "name": "jest-junit", 52 | "version": "^15", 53 | "type": "build" 54 | }, 55 | { 56 | "name": "jest", 57 | "version": "^27", 58 | "type": "build" 59 | }, 60 | { 61 | "name": "jsii-diff", 62 | "type": "build" 63 | }, 64 | { 65 | "name": "jsii-docgen", 66 | "type": "build" 67 | }, 68 | { 69 | "name": "jsii-pacmak", 70 | "type": "build" 71 | }, 72 | { 73 | "name": "jsii", 74 | "version": "1.x", 75 | "type": "build" 76 | }, 77 | { 78 | "name": "npm-check-updates", 79 | "version": "^16", 80 | "type": "build" 81 | }, 82 | { 83 | "name": "projen", 84 | "type": "build" 85 | }, 86 | { 87 | "name": "standard-version", 88 | "version": "^9", 89 | "type": "build" 90 | }, 91 | { 92 | "name": "ts-jest", 93 | "version": "^27", 94 | "type": "build" 95 | }, 96 | { 97 | "name": "typescript", 98 | "type": "build" 99 | }, 100 | { 101 | "name": "@types/babel__traverse", 102 | "version": "7.18.2", 103 | "type": "override" 104 | }, 105 | { 106 | "name": "@types/prettier", 107 | "version": "2.6.0", 108 | "type": "override" 109 | }, 110 | { 111 | "name": "aws-cdk-lib", 112 | "version": "^2.49.0", 113 | "type": "peer" 114 | }, 115 | { 116 | "name": "constructs", 117 | "version": "^10.0.5", 118 | "type": "peer" 119 | } 120 | ], 121 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 122 | } 123 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/build.yml", 7 | ".github/workflows/pull-request-lint.yml", 8 | ".github/workflows/release.yml", 9 | ".github/workflows/upgrade-main.yml", 10 | ".gitignore", 11 | ".mergify.yml", 12 | ".npmrc", 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.js and run \"npx projen\"." 20 | } 21 | -------------------------------------------------------------------------------- /.projen/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build": { 4 | "name": "build", 5 | "description": "Full release build", 6 | "steps": [ 7 | { 8 | "spawn": "default" 9 | }, 10 | { 11 | "spawn": "pre-compile" 12 | }, 13 | { 14 | "spawn": "compile" 15 | }, 16 | { 17 | "spawn": "post-compile" 18 | }, 19 | { 20 | "spawn": "test" 21 | }, 22 | { 23 | "spawn": "package" 24 | } 25 | ] 26 | }, 27 | "bump": { 28 | "name": "bump", 29 | "description": "Bumps version based on latest git tag and generates a changelog entry", 30 | "env": { 31 | "OUTFILE": "package.json", 32 | "CHANGELOG": "dist/changelog.md", 33 | "BUMPFILE": "dist/version.txt", 34 | "RELEASETAG": "dist/releasetag.txt", 35 | "RELEASE_TAG_PREFIX": "" 36 | }, 37 | "steps": [ 38 | { 39 | "builtin": "release/bump-version" 40 | } 41 | ], 42 | "condition": "! git log --oneline -1 | grep -q \"chore(release):\"" 43 | }, 44 | "clobber": { 45 | "name": "clobber", 46 | "description": "hard resets to HEAD of origin and cleans the local repo", 47 | "env": { 48 | "BRANCH": "$(git branch --show-current)" 49 | }, 50 | "steps": [ 51 | { 52 | "exec": "git checkout -b scratch", 53 | "name": "save current HEAD in \"scratch\" branch" 54 | }, 55 | { 56 | "exec": "git checkout $BRANCH" 57 | }, 58 | { 59 | "exec": "git fetch origin", 60 | "name": "fetch latest changes from origin" 61 | }, 62 | { 63 | "exec": "git reset --hard origin/$BRANCH", 64 | "name": "hard reset to origin commit" 65 | }, 66 | { 67 | "exec": "git clean -fdx", 68 | "name": "clean all untracked files" 69 | }, 70 | { 71 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 72 | } 73 | ], 74 | "condition": "git diff --exit-code > /dev/null" 75 | }, 76 | "compat": { 77 | "name": "compat", 78 | "description": "Perform API compatibility check against latest version", 79 | "steps": [ 80 | { 81 | "exec": "jsii-diff npm:$(node -p \"require('./package.json').name\") -k --ignore-file .compatignore || (echo \"\nUNEXPECTED BREAKING CHANGES: add keys such as 'removed:constructs.Node.of' to .compatignore to skip.\n\" && exit 1)" 82 | } 83 | ] 84 | }, 85 | "compile": { 86 | "name": "compile", 87 | "description": "Only compile", 88 | "steps": [ 89 | { 90 | "exec": "jsii --silence-warnings=reserved-word" 91 | } 92 | ] 93 | }, 94 | "default": { 95 | "name": "default", 96 | "description": "Synthesize project files", 97 | "steps": [ 98 | { 99 | "exec": "node .projenrc.js" 100 | } 101 | ] 102 | }, 103 | "docgen": { 104 | "name": "docgen", 105 | "description": "Generate API.md from .jsii manifest", 106 | "steps": [ 107 | { 108 | "exec": "jsii-docgen -o API.md" 109 | } 110 | ] 111 | }, 112 | "eject": { 113 | "name": "eject", 114 | "description": "Remove projen from the project", 115 | "env": { 116 | "PROJEN_EJECTING": "true" 117 | }, 118 | "steps": [ 119 | { 120 | "spawn": "default" 121 | } 122 | ] 123 | }, 124 | "eslint": { 125 | "name": "eslint", 126 | "description": "Runs eslint against the codebase", 127 | "steps": [ 128 | { 129 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test build-tools .projenrc.js" 130 | } 131 | ] 132 | }, 133 | "install": { 134 | "name": "install", 135 | "description": "Install project dependencies and update lockfile (non-frozen)", 136 | "steps": [ 137 | { 138 | "exec": "yarn install --check-files" 139 | } 140 | ] 141 | }, 142 | "install:ci": { 143 | "name": "install:ci", 144 | "description": "Install project dependencies using frozen lockfile", 145 | "steps": [ 146 | { 147 | "exec": "yarn install --check-files --frozen-lockfile" 148 | } 149 | ] 150 | }, 151 | "package": { 152 | "name": "package", 153 | "description": "Creates the distribution package", 154 | "steps": [ 155 | { 156 | "exec": "if [ ! -z ${CI} ]; then rsync -a . .repo --exclude .git --exclude node_modules && rm -rf dist && mv .repo dist; else npx projen package-all; fi" 157 | } 158 | ] 159 | }, 160 | "package-all": { 161 | "name": "package-all", 162 | "description": "Packages artifacts for all target languages", 163 | "steps": [ 164 | { 165 | "spawn": "package:js" 166 | }, 167 | { 168 | "spawn": "package:python" 169 | } 170 | ] 171 | }, 172 | "package:js": { 173 | "name": "package:js", 174 | "description": "Create js language bindings", 175 | "steps": [ 176 | { 177 | "exec": "jsii-pacmak -v --target js" 178 | } 179 | ] 180 | }, 181 | "package:python": { 182 | "name": "package:python", 183 | "description": "Create python language bindings", 184 | "steps": [ 185 | { 186 | "exec": "jsii-pacmak -v --target python" 187 | } 188 | ] 189 | }, 190 | "post-compile": { 191 | "name": "post-compile", 192 | "description": "Runs after successful compilation", 193 | "steps": [ 194 | { 195 | "spawn": "docgen" 196 | } 197 | ] 198 | }, 199 | "post-upgrade": { 200 | "name": "post-upgrade", 201 | "description": "Runs after upgrading dependencies" 202 | }, 203 | "pre-compile": { 204 | "name": "pre-compile", 205 | "description": "Prepare the project for compilation" 206 | }, 207 | "release": { 208 | "name": "release", 209 | "description": "Prepare a release from \"main\" branch", 210 | "env": { 211 | "RELEASE": "true" 212 | }, 213 | "steps": [ 214 | { 215 | "exec": "rm -fr dist" 216 | }, 217 | { 218 | "spawn": "bump" 219 | }, 220 | { 221 | "spawn": "build" 222 | }, 223 | { 224 | "spawn": "unbump" 225 | }, 226 | { 227 | "exec": "git diff --ignore-space-at-eol --exit-code" 228 | } 229 | ] 230 | }, 231 | "test": { 232 | "name": "test", 233 | "description": "Run tests", 234 | "steps": [ 235 | { 236 | "exec": "jest --passWithNoTests --updateSnapshot", 237 | "receiveArgs": true 238 | }, 239 | { 240 | "spawn": "eslint" 241 | } 242 | ] 243 | }, 244 | "test:watch": { 245 | "name": "test:watch", 246 | "description": "Run jest in watch mode", 247 | "steps": [ 248 | { 249 | "exec": "jest --watch" 250 | } 251 | ] 252 | }, 253 | "unbump": { 254 | "name": "unbump", 255 | "description": "Restores version to 0.0.0", 256 | "env": { 257 | "OUTFILE": "package.json", 258 | "CHANGELOG": "dist/changelog.md", 259 | "BUMPFILE": "dist/version.txt", 260 | "RELEASETAG": "dist/releasetag.txt", 261 | "RELEASE_TAG_PREFIX": "" 262 | }, 263 | "steps": [ 264 | { 265 | "builtin": "release/reset-version" 266 | } 267 | ] 268 | }, 269 | "upgrade": { 270 | "name": "upgrade", 271 | "description": "upgrade dependencies", 272 | "env": { 273 | "CI": "0" 274 | }, 275 | "steps": [ 276 | { 277 | "exec": "yarn upgrade npm-check-updates" 278 | }, 279 | { 280 | "exec": "npm-check-updates --dep dev --upgrade --target=minor --reject='aws-cdk-lib,constructs,jsii'" 281 | }, 282 | { 283 | "exec": "npm-check-updates --dep optional --upgrade --target=minor --reject='aws-cdk-lib,constructs,jsii'" 284 | }, 285 | { 286 | "exec": "npm-check-updates --dep peer --upgrade --target=minor --reject='aws-cdk-lib,constructs,jsii'" 287 | }, 288 | { 289 | "exec": "npm-check-updates --dep prod --upgrade --target=minor --reject='aws-cdk-lib,constructs,jsii'" 290 | }, 291 | { 292 | "exec": "npm-check-updates --dep bundle --upgrade --target=minor --reject='aws-cdk-lib,constructs,jsii'" 293 | }, 294 | { 295 | "exec": "yarn install --check-files" 296 | }, 297 | { 298 | "exec": "yarn upgrade" 299 | }, 300 | { 301 | "exec": "npx projen" 302 | }, 303 | { 304 | "spawn": "post-upgrade" 305 | } 306 | ] 307 | }, 308 | "watch": { 309 | "name": "watch", 310 | "description": "Watch & compile in the background", 311 | "steps": [ 312 | { 313 | "exec": "jsii -w --silence-warnings=reserved-word" 314 | } 315 | ] 316 | } 317 | }, 318 | "env": { 319 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 320 | }, 321 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 322 | } 323 | -------------------------------------------------------------------------------- /.projenrc.js: -------------------------------------------------------------------------------- 1 | const { awscdk } = require('projen'); 2 | const project = new awscdk.AwsCdkConstructLibrary({ 3 | author: 'Mauricio Villaescusa', 4 | authorAddress: 'maurovc@amazon.com', 5 | copyrightOwner: 'Amazon.com, Inc. or its affiliates. All Rights Reserved.', 6 | cdkVersion: '2.49.0', 7 | defaultReleaseBranch: 'main', 8 | name: 'cdk-backup-plan', 9 | description: 'CDK construct to create AWS Backup Plans', 10 | repositoryUrl: 'https://github.com/aws-samples/cdk-backup-plan.git', 11 | license: 'MIT-0', 12 | publishToPypi: { 13 | distName: 'cdk-backup-plan', 14 | module: 'cdk_backup_plan', 15 | }, 16 | }); 17 | 18 | project.gitignore.addPatterns('.dev/'); 19 | 20 | project.synth(); 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black" 3 | } -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # CDK Backup Plan 2 | 3 | ![Build](https://github.com/aws-samples/cdk-backup-plan/workflows/build/badge.svg) 4 | ![Release](https://github.com/aws-samples/cdk-backup-plan/workflows/release/badge.svg) 5 | 6 | Provides an easy to use reusable CDK construct to create [Backup Plans](https://docs.aws.amazon.com/aws-backup/latest/devguide/about-backup-plans.html) using [AWS Backups](https://docs.aws.amazon.com/aws-backup/latest/devguide/whatisbackup.html). It allows to indicate how frequently and what resources to backup. 7 | 8 | > **NOTE:** More details on all the available arguments can be found [here](API.md) 9 | 10 | ## Install 11 | 12 | NPM install: 13 | 14 | ```sh 15 | npm install cdk-backup-plan 16 | ``` 17 | 18 | PyPi install: 19 | 20 | ```sh 21 | pip install cdk-backup-plan 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```typescript 27 | // ... 28 | import { Backup } from "cdk-backup-plan"; 29 | 30 | // ... 31 | const vpc = new ec2.Vpc(stack, "TestVPC"); 32 | const engine = rds.DatabaseInstanceEngine.postgres({ 33 | version: rds.PostgresEngineVersion.VER_12_3, 34 | }); 35 | // create rds DB 36 | const db = new rds.DatabaseInstance(stack, "TestInstance", { 37 | engine, 38 | vpc, 39 | credentials: rds.Credentials.fromGeneratedSecret("postgres"), 40 | }); 41 | // create a backup plan for `db` 42 | new Backup(stack, "TestBk", { 43 | backupPlanName: "TestPkPlan", 44 | backupRateHour: 3, // backup every 3 hours 45 | backupCompletionWindow: cdk.Duration.hours(2), // backup should take up to 2 hours 46 | resources: [bk.BackupResource.fromRdsDatabaseInstance(db)], 47 | }); 48 | // ... 49 | ``` 50 | 51 | Python usage: 52 | 53 | ```python 54 | # ... 55 | from cdk_backup_plan import Backup 56 | 57 | # ... 58 | vpc = ec2.Vpc(self, "TestVPC") 59 | engine = rds.DatabaseInstanceEngine.postgres( 60 | version=rds.PostgresEngineVersion.VER_12_3, 61 | ) 62 | db = rds.DatabaseInstance(self, "TestInstance", 63 | engine=engine, 64 | vpc=vpc, 65 | credentials=rds.Credentials.from_generated_secret("postgres"), 66 | ) 67 | Backup(self, "TestBk", 68 | backup_plan_name="TestPkPlan", 69 | backup_rate_hour=3, 70 | backup_completion_window=Duration.hours(2), 71 | resources=[bk.BackupResource.from_rds_database_instance(db)], 72 | ) 73 | # ... 74 | ``` 75 | 76 | > **NOTE:** [Tagging](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_backup.BackupResource.html#static-fromwbrtagkey-value-operation) and/or [ARN](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_backup.BackupResource.html#static-fromwbrarnarn) can be used to reference resources not directly available in the [static methods section](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_backup.BackupResource.html#methods). 77 | 78 | # API Reference 79 | 80 | ## Constructs 81 | 82 | ### Backup 83 | 84 | Construct to create a Backup Plan with specific backing cadence. 85 | 86 | #### Initializers 87 | 88 | ```typescript 89 | import { Backup } from 'cdk-backup-plan' 90 | 91 | new Backup(scope: Construct, id: string, props: BackupProps) 92 | ``` 93 | 94 | | **Name** | **Type** | **Description** | 95 | | --- | --- | --- | 96 | | scope | constructs.Construct | Construct's scope. | 97 | | id | string | Construct's id. | 98 | | props | BackupProps | Construct's props. | 99 | 100 | --- 101 | 102 | ##### `scope`Required 103 | 104 | - *Type:* constructs.Construct 105 | 106 | Construct's scope. 107 | 108 | --- 109 | 110 | ##### `id`Required 111 | 112 | - *Type:* string 113 | 114 | Construct's id. 115 | 116 | --- 117 | 118 | ##### `props`Required 119 | 120 | - *Type:* BackupProps 121 | 122 | Construct's props. 123 | 124 | --- 125 | 126 | #### Methods 127 | 128 | | **Name** | **Description** | 129 | | --- | --- | 130 | | toString | Returns a string representation of this construct. | 131 | 132 | --- 133 | 134 | ##### `toString` 135 | 136 | ```typescript 137 | public toString(): string 138 | ``` 139 | 140 | Returns a string representation of this construct. 141 | 142 | #### Static Functions 143 | 144 | | **Name** | **Description** | 145 | | --- | --- | 146 | | isConstruct | Checks if `x` is a construct. | 147 | 148 | --- 149 | 150 | ##### ~~`isConstruct`~~ 151 | 152 | ```typescript 153 | import { Backup } from 'cdk-backup-plan' 154 | 155 | Backup.isConstruct(x: any) 156 | ``` 157 | 158 | Checks if `x` is a construct. 159 | 160 | ###### `x`Required 161 | 162 | - *Type:* any 163 | 164 | Any object. 165 | 166 | --- 167 | 168 | #### Properties 169 | 170 | | **Name** | **Type** | **Description** | 171 | | --- | --- | --- | 172 | | node | constructs.Node | The tree node. | 173 | | backupPlan | aws-cdk-lib.aws_backup.BackupPlan | Backup plan. | 174 | 175 | --- 176 | 177 | ##### `node`Required 178 | 179 | ```typescript 180 | public readonly node: Node; 181 | ``` 182 | 183 | - *Type:* constructs.Node 184 | 185 | The tree node. 186 | 187 | --- 188 | 189 | ##### `backupPlan`Required 190 | 191 | ```typescript 192 | public readonly backupPlan: BackupPlan; 193 | ``` 194 | 195 | - *Type:* aws-cdk-lib.aws_backup.BackupPlan 196 | 197 | Backup plan. 198 | 199 | --- 200 | 201 | 202 | ## Structs 203 | 204 | ### BackupProps 205 | 206 | #### Initializer 207 | 208 | ```typescript 209 | import { BackupProps } from 'cdk-backup-plan' 210 | 211 | const backupProps: BackupProps = { ... } 212 | ``` 213 | 214 | #### Properties 215 | 216 | | **Name** | **Type** | **Description** | 217 | | --- | --- | --- | 218 | | backupPlanName | string | The display name of the backup plan. | 219 | | resources | aws-cdk-lib.aws_backup.BackupResource[] | Resources to apply backup plan. | 220 | | backupCompletionWindow | aws-cdk-lib.Duration | The duration after a backup job is successfully started before it must be completed or it is canceled by AWS Backup. | 221 | | backupRateHour | number | How frequently backup jobs would be started. | 222 | | backupStartWindow | aws-cdk-lib.Duration | The duration after a backup is scheduled before a job is canceled if it doesn't start successfully. | 223 | | deleteBackupAfter | aws-cdk-lib.Duration | Specifies the duration after creation that a recovery point is deleted. | 224 | | moveBackupToColdStorageAfter | aws-cdk-lib.Duration | Specifies the duration after creation that a recovery point is moved to cold storage. | 225 | 226 | --- 227 | 228 | ##### `backupPlanName`Required 229 | 230 | ```typescript 231 | public readonly backupPlanName: string; 232 | ``` 233 | 234 | - *Type:* string 235 | 236 | The display name of the backup plan. 237 | 238 | --- 239 | 240 | ##### `resources`Required 241 | 242 | ```typescript 243 | public readonly resources: BackupResource[]; 244 | ``` 245 | 246 | - *Type:* aws-cdk-lib.aws_backup.BackupResource[] 247 | 248 | Resources to apply backup plan. 249 | 250 | --- 251 | 252 | ##### `backupCompletionWindow`Optional 253 | 254 | ```typescript 255 | public readonly backupCompletionWindow: Duration; 256 | ``` 257 | 258 | - *Type:* aws-cdk-lib.Duration 259 | - *Default:* 3 hours 260 | 261 | The duration after a backup job is successfully started before it must be completed or it is canceled by AWS Backup. 262 | 263 | Note: `backupCompletionWindow` must be at least 60 minutes greater 264 | than @backupStartWindows 265 | 266 | --- 267 | 268 | ##### `backupRateHour`Optional 269 | 270 | ```typescript 271 | public readonly backupRateHour: number; 272 | ``` 273 | 274 | - *Type:* number 275 | - *Default:* 24 hours 276 | 277 | How frequently backup jobs would be started. 278 | 279 | --- 280 | 281 | ##### `backupStartWindow`Optional 282 | 283 | ```typescript 284 | public readonly backupStartWindow: Duration; 285 | ``` 286 | 287 | - *Type:* aws-cdk-lib.Duration 288 | - *Default:* 1 hour less than 289 | 290 | The duration after a backup is scheduled before a job is canceled if it doesn't start successfully. 291 | 292 | --- 293 | 294 | ##### `deleteBackupAfter`Optional 295 | 296 | ```typescript 297 | public readonly deleteBackupAfter: Duration; 298 | ``` 299 | 300 | - *Type:* aws-cdk-lib.Duration 301 | - *Default:* 30 days 302 | 303 | Specifies the duration after creation that a recovery point is deleted. 304 | 305 | Must be greater than moveToColdStorageAfter. 306 | 307 | --- 308 | 309 | ##### `moveBackupToColdStorageAfter`Optional 310 | 311 | ```typescript 312 | public readonly moveBackupToColdStorageAfter: Duration; 313 | ``` 314 | 315 | - *Type:* aws-cdk-lib.Duration 316 | - *Default:* recovery point is never moved to cold storage 317 | 318 | Specifies the duration after creation that a recovery point is moved to cold storage. 319 | 320 | --- 321 | 322 | 323 | 324 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDK Backup Plan 2 | 3 | ![Build](https://github.com/aws-samples/cdk-backup-plan/workflows/build/badge.svg) 4 | ![Release](https://github.com/aws-samples/cdk-backup-plan/workflows/release/badge.svg) 5 | 6 | Provides an easy to use reusable CDK construct to create [Backup Plans](https://docs.aws.amazon.com/aws-backup/latest/devguide/about-backup-plans.html) using [AWS Backups](https://docs.aws.amazon.com/aws-backup/latest/devguide/whatisbackup.html). It allows to indicate how frequently and what resources to backup. 7 | 8 | > **NOTE:** More details on all the available arguments can be found [here](API.md) 9 | 10 | ## Install 11 | 12 | NPM install: 13 | 14 | ```sh 15 | npm install cdk-backup-plan 16 | ``` 17 | 18 | PyPi install: 19 | 20 | ```sh 21 | pip install cdk-backup-plan 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```typescript 27 | // ... 28 | import { Backup } from "cdk-backup-plan"; 29 | 30 | // ... 31 | const vpc = new ec2.Vpc(stack, "TestVPC"); 32 | const engine = rds.DatabaseInstanceEngine.postgres({ 33 | version: rds.PostgresEngineVersion.VER_12_3, 34 | }); 35 | // create rds DB 36 | const db = new rds.DatabaseInstance(stack, "TestInstance", { 37 | engine, 38 | vpc, 39 | credentials: rds.Credentials.fromGeneratedSecret("postgres"), 40 | }); 41 | // create a backup plan for `db` 42 | new Backup(stack, "TestBk", { 43 | backupPlanName: "TestPkPlan", 44 | backupRateHour: 3, // backup every 3 hours 45 | backupCompletionWindow: cdk.Duration.hours(2), // backup should take up to 2 hours 46 | resources: [bk.BackupResource.fromRdsDatabaseInstance(db)], 47 | }); 48 | // ... 49 | ``` 50 | 51 | Python usage: 52 | 53 | ```python 54 | # ... 55 | from cdk_backup_plan import Backup 56 | 57 | # ... 58 | vpc = ec2.Vpc(self, "TestVPC") 59 | engine = rds.DatabaseInstanceEngine.postgres( 60 | version=rds.PostgresEngineVersion.VER_12_3, 61 | ) 62 | db = rds.DatabaseInstance(self, "TestInstance", 63 | engine=engine, 64 | vpc=vpc, 65 | credentials=rds.Credentials.from_generated_secret("postgres"), 66 | ) 67 | Backup(self, "TestBk", 68 | backup_plan_name="TestPkPlan", 69 | backup_rate_hour=3, 70 | backup_completion_window=Duration.hours(2), 71 | resources=[bk.BackupResource.from_rds_database_instance(db)], 72 | ) 73 | # ... 74 | ``` 75 | 76 | > **NOTE:** [Tagging](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_backup.BackupResource.html#static-fromwbrtagkey-value-operation) and/or [ARN](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_backup.BackupResource.html#static-fromwbrarnarn) can be used to reference resources not directly available in the [static methods section](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_backup.BackupResource.html#methods). 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-backup-plan", 3 | "description": "CDK construct to create AWS Backup Plans", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/aws-samples/cdk-backup-plan.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:js": "npx projen package:js", 21 | "package:python": "npx projen package:python", 22 | "post-compile": "npx projen post-compile", 23 | "post-upgrade": "npx projen post-upgrade", 24 | "pre-compile": "npx projen pre-compile", 25 | "release": "npx projen release", 26 | "test": "npx projen test", 27 | "test:watch": "npx projen test:watch", 28 | "unbump": "npx projen unbump", 29 | "upgrade": "npx projen upgrade", 30 | "watch": "npx projen watch", 31 | "projen": "npx projen" 32 | }, 33 | "author": { 34 | "name": "Mauricio Villaescusa", 35 | "email": "maurovc@amazon.com", 36 | "organization": false 37 | }, 38 | "devDependencies": { 39 | "@types/jest": "^27", 40 | "@types/node": "^16", 41 | "@typescript-eslint/eslint-plugin": "^5", 42 | "@typescript-eslint/parser": "^5", 43 | "aws-cdk-lib": "2.49.0", 44 | "constructs": "10.0.5", 45 | "eslint": "^8", 46 | "eslint-import-resolver-node": "^0.3.7", 47 | "eslint-import-resolver-typescript": "^3.5.5", 48 | "eslint-plugin-import": "^2.27.5", 49 | "jest": "^27", 50 | "jest-junit": "^15", 51 | "jsii": "1.x", 52 | "jsii-diff": "^1.83.0", 53 | "jsii-docgen": "^7.2.9", 54 | "jsii-pacmak": "^1.83.0", 55 | "npm-check-updates": "^16", 56 | "projen": "^0.71.94", 57 | "standard-version": "^9", 58 | "ts-jest": "^27", 59 | "typescript": "^4.9.5" 60 | }, 61 | "peerDependencies": { 62 | "aws-cdk-lib": "^2.49.0", 63 | "constructs": "^10.0.5" 64 | }, 65 | "resolutions": { 66 | "@types/babel__traverse": "7.18.2", 67 | "@types/prettier": "2.6.0" 68 | }, 69 | "keywords": [ 70 | "cdk" 71 | ], 72 | "main": "lib/index.js", 73 | "license": "MIT-0", 74 | "version": "0.0.0", 75 | "jest": { 76 | "testMatch": [ 77 | "/src/**/__tests__/**/*.ts?(x)", 78 | "/(test|src)/**/*(*.)@(spec|test).ts?(x)" 79 | ], 80 | "clearMocks": true, 81 | "collectCoverage": true, 82 | "coverageReporters": [ 83 | "json", 84 | "lcov", 85 | "clover", 86 | "cobertura", 87 | "text" 88 | ], 89 | "coverageDirectory": "coverage", 90 | "coveragePathIgnorePatterns": [ 91 | "/node_modules/" 92 | ], 93 | "testPathIgnorePatterns": [ 94 | "/node_modules/" 95 | ], 96 | "watchPathIgnorePatterns": [ 97 | "/node_modules/" 98 | ], 99 | "reporters": [ 100 | "default", 101 | [ 102 | "jest-junit", 103 | { 104 | "outputDirectory": "test-reports" 105 | } 106 | ] 107 | ], 108 | "preset": "ts-jest", 109 | "globals": { 110 | "ts-jest": { 111 | "tsconfig": "tsconfig.dev.json" 112 | } 113 | } 114 | }, 115 | "types": "lib/index.d.ts", 116 | "stability": "stable", 117 | "jsii": { 118 | "outdir": "dist", 119 | "targets": { 120 | "python": { 121 | "distName": "cdk-backup-plan", 122 | "module": "cdk_backup_plan" 123 | } 124 | }, 125 | "tsc": { 126 | "outDir": "lib", 127 | "rootDir": "src" 128 | } 129 | }, 130 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 131 | } 132 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Duration, 3 | CfnOutput, 4 | aws_backup as bk, 5 | aws_events as events, 6 | } from 'aws-cdk-lib'; 7 | import { Construct } from 'constructs'; 8 | 9 | export interface BackupProps { 10 | /** 11 | * Resources to apply backup plan. 12 | */ 13 | readonly resources: bk.BackupResource[]; 14 | 15 | /** 16 | * The display name of the backup plan. 17 | */ 18 | readonly backupPlanName: string; 19 | 20 | /** 21 | * The duration after a backup job is successfully 22 | * started before it must be completed or it is 23 | * canceled by AWS Backup. 24 | * 25 | * Note: `backupCompletionWindow` must be at least 60 minutes greater 26 | * than @backupStartWindows 27 | * 28 | * @default - 3 hours 29 | */ 30 | readonly backupCompletionWindow?: Duration; 31 | 32 | /** 33 | * The duration after a backup is scheduled before 34 | * a job is canceled if it doesn't start successfully. 35 | * 36 | * @default - 1 hour less than @backupCompletionWindow 37 | */ 38 | readonly backupStartWindow?: Duration; 39 | 40 | /** 41 | * Specifies the duration after creation that a recovery point is deleted. 42 | * Must be greater than moveToColdStorageAfter. 43 | * 44 | * @default - 30 days 45 | */ 46 | readonly deleteBackupAfter?: Duration; 47 | 48 | /** 49 | * Specifies the duration after creation that a recovery point is moved to cold storage. 50 | * 51 | * @default - recovery point is never moved to cold storage 52 | */ 53 | readonly moveBackupToColdStorageAfter?: Duration; 54 | 55 | /** 56 | * How frequently backup jobs would be started. 57 | * 58 | * @default - 24 hours 59 | */ 60 | readonly backupRateHour?: number; 61 | } 62 | 63 | /** 64 | * Construct to create a Backup Plan with specific backing cadence. 65 | * 66 | * @stability stable 67 | */ 68 | export class Backup extends Construct { 69 | /** 70 | * Backup plan 71 | */ 72 | public readonly backupPlan: bk.BackupPlan; 73 | /** 74 | * 75 | * @param scope Construct's scope 76 | * @param id Construct's id 77 | * @param props Construct's props 78 | */ 79 | constructor(scope: Construct, id: string, props: BackupProps) { 80 | super(scope, id); 81 | 82 | const hourlyRate = `0/${props.backupRateHour || 24}`; 83 | 84 | const completionWindow = props.backupCompletionWindow || Duration.hours(3); 85 | const startWindow = 86 | props.backupStartWindow || Duration.hours(completionWindow.toHours() - 1); 87 | 88 | if (completionWindow.toHours() - startWindow.toHours() < 1) { 89 | throw Error( 90 | 'Backup completion window must be at least 60 minutes greater than backup start window', 91 | ); 92 | } 93 | 94 | const scheduledBkRule = new bk.BackupPlanRule({ 95 | completionWindow, 96 | startWindow, 97 | deleteAfter: props.deleteBackupAfter || Duration.days(30), 98 | // Only cron expressions are supported 99 | scheduleExpression: events.Schedule.cron({ 100 | minute: '0', 101 | hour: hourlyRate, 102 | }), 103 | moveToColdStorageAfter: props.moveBackupToColdStorageAfter, 104 | }); 105 | 106 | this.backupPlan = new bk.BackupPlan(this, 'BackupPlan', { 107 | backupPlanName: props.backupPlanName, 108 | backupPlanRules: [scheduledBkRule], 109 | }); 110 | 111 | this.backupPlan.addSelection('BackupSelection', { 112 | resources: props.resources, 113 | }); 114 | 115 | // Outputs 116 | const outputVars = { 117 | BackupPlanId: this.backupPlan.backupPlanId, 118 | BackupPlanArn: this.backupPlan.backupPlanArn, 119 | }; 120 | Object.entries(outputVars).forEach( 121 | ([outName, outValue]) => new CfnOutput(this, outName, { value: outValue }), 122 | ); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /test/backup-plan.test.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from 'aws-cdk-lib'; 2 | import { aws_rds as rds, aws_ec2 as ec2, aws_backup as bk } from 'aws-cdk-lib'; 3 | import { Match, Template } from 'aws-cdk-lib/assertions'; 4 | import { Backup } from '../src/index'; 5 | 6 | let testTemplate: Template; 7 | 8 | const createRequiredResources = () => { 9 | const app = new cdk.App(); 10 | const testStack = new cdk.Stack(app, 'testStack', { 11 | env: { account: '111122223333', region: 'us-east-1' }, 12 | }); 13 | const vpc = new ec2.Vpc(testStack, 'TestVPC'); 14 | const engine = rds.DatabaseInstanceEngine.postgres({ 15 | version: rds.PostgresEngineVersion.VER_12_3, 16 | }); 17 | const db = new rds.DatabaseInstance(testStack, 'TestInstance', { 18 | engine, 19 | vpc, 20 | credentials: rds.Credentials.fromGeneratedSecret('postgres'), // Creates an admin user of postgres with a generated password 21 | }); 22 | return { db, stack: testStack }; 23 | }; 24 | 25 | beforeAll(() => { 26 | const { stack, db } = createRequiredResources(); 27 | new Backup(stack, 'TestBk', { 28 | backupPlanName: 'TestPkPlan', 29 | backupRateHour: 2, 30 | backupCompletionWindow: cdk.Duration.hours(2), 31 | resources: [bk.BackupResource.fromRdsDatabaseInstance(db)], 32 | }); 33 | 34 | testTemplate = Template.fromStack(stack); 35 | }); 36 | 37 | test('backup plan is created', () => { 38 | testTemplate.hasResourceProperties('AWS::Backup::BackupPlan', { 39 | BackupPlan: { 40 | BackupPlanName: 'TestPkPlan', 41 | BackupPlanRule: [ 42 | { 43 | CompletionWindowMinutes: 120, 44 | Lifecycle: { 45 | DeleteAfterDays: 30, 46 | }, 47 | RuleName: 'BackupPlanRule0', 48 | ScheduleExpression: 'cron(0 0/2 * * ? *)', 49 | StartWindowMinutes: 60, 50 | TargetBackupVault: Match.anyValue(), 51 | }, 52 | ], 53 | }, 54 | }); 55 | }); 56 | 57 | test('backup vault is created', () => { 58 | testTemplate.resourceCountIs('AWS::Backup::BackupVault', 1); 59 | }); 60 | 61 | test('validate default args', () => { 62 | const { stack, db } = createRequiredResources(); 63 | new Backup(stack, 'TestBk', { 64 | backupPlanName: 'TestPkPlan2', 65 | resources: [bk.BackupResource.fromRdsDatabaseInstance(db)], 66 | }); 67 | const template = Template.fromStack(stack); 68 | template.hasResourceProperties('AWS::Backup::BackupPlan', { 69 | BackupPlan: { 70 | BackupPlanName: 'TestPkPlan2', 71 | BackupPlanRule: [ 72 | { 73 | CompletionWindowMinutes: 180, 74 | Lifecycle: { 75 | DeleteAfterDays: 30, 76 | }, 77 | RuleName: 'BackupPlanRule0', 78 | ScheduleExpression: 'cron(0 0/24 * * ? *)', 79 | StartWindowMinutes: 120, 80 | TargetBackupVault: Match.anyValue(), 81 | }, 82 | ], 83 | }, 84 | }); 85 | }); 86 | 87 | test('validate start window must be at least 60 min less than completion window', () => { 88 | const { stack, db } = createRequiredResources(); 89 | expect( 90 | () => 91 | new Backup(stack, 'TestBk', { 92 | backupPlanName: 'TestPkPlan2', 93 | resources: [bk.BackupResource.fromRdsDatabaseInstance(db)], 94 | backupCompletionWindow: cdk.Duration.hours(1), 95 | backupStartWindow: cdk.Duration.hours(2), 96 | }), 97 | ).toThrow( 98 | 'Backup completion window must be at least 60 minutes greater than backup start window', 99 | ); 100 | }); 101 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.js 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 | "es2019" 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": "ES2019" 27 | }, 28 | "include": [ 29 | ".projenrc.js", 30 | "src/**/*.ts", 31 | "test/**/*.ts" 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } 37 | --------------------------------------------------------------------------------