├── .dockerignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── config.yml ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── auto-queue.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ ├── security.yml │ ├── stale.yml │ ├── triage.yml │ ├── upgrade-compiler-dependencies-main.yml │ ├── upgrade-configuration-main.yml │ ├── upgrade-dev-dependencies-main.yml │ └── upgrade-runtime-dependencies-main.yml ├── .gitignore ├── .npmignore ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.js ├── API.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DCO ├── LICENSE ├── README.md ├── SECURITY.md ├── cdk8s.yaml ├── git-hooks ├── README.md ├── prepare-commit-msg └── setup.sh ├── package.json ├── src ├── cli │ └── cdk8s-server.ts ├── index.ts ├── operator.ts └── server.ts ├── test ├── __snapshots__ │ └── operator.test.ts.snap ├── fixtures │ ├── echo-app.js │ └── pod-collection.ts ├── operator.test.ts └── server.test.ts ├── tsconfig.dev.json ├── tsconfig.json ├── version.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | test -------------------------------------------------------------------------------- /.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 | "@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.js" 44 | ], 45 | "rules": { 46 | "@stylistic/indent": [ 47 | "error", 48 | 2 49 | ], 50 | "@stylistic/quotes": [ 51 | "error", 52 | "single", 53 | { 54 | "avoidEscape": true 55 | } 56 | ], 57 | "@stylistic/comma-dangle": [ 58 | "error", 59 | "always-multiline" 60 | ], 61 | "@stylistic/comma-spacing": [ 62 | "error", 63 | { 64 | "before": false, 65 | "after": true 66 | } 67 | ], 68 | "@stylistic/no-multi-spaces": [ 69 | "error", 70 | { 71 | "ignoreEOLComments": false 72 | } 73 | ], 74 | "@stylistic/array-bracket-spacing": [ 75 | "error", 76 | "never" 77 | ], 78 | "@stylistic/array-bracket-newline": [ 79 | "error", 80 | "consistent" 81 | ], 82 | "@stylistic/object-curly-spacing": [ 83 | "error", 84 | "always" 85 | ], 86 | "@stylistic/object-curly-newline": [ 87 | "error", 88 | { 89 | "multiline": true, 90 | "consistent": true 91 | } 92 | ], 93 | "@stylistic/object-property-newline": [ 94 | "error", 95 | { 96 | "allowAllPropertiesOnSameLine": true 97 | } 98 | ], 99 | "@stylistic/keyword-spacing": [ 100 | "error" 101 | ], 102 | "@stylistic/brace-style": [ 103 | "error", 104 | "1tbs", 105 | { 106 | "allowSingleLine": true 107 | } 108 | ], 109 | "@stylistic/space-before-blocks": [ 110 | "error" 111 | ], 112 | "@stylistic/member-delimiter-style": [ 113 | "error" 114 | ], 115 | "@stylistic/semi": [ 116 | "error", 117 | "always" 118 | ], 119 | "@stylistic/max-len": [ 120 | "error", 121 | { 122 | "code": 150, 123 | "ignoreUrls": true, 124 | "ignoreStrings": true, 125 | "ignoreTemplateLiterals": true, 126 | "ignoreComments": true, 127 | "ignoreRegExpLiterals": true 128 | } 129 | ], 130 | "@stylistic/quote-props": [ 131 | "error", 132 | "consistent-as-needed" 133 | ], 134 | "@stylistic/key-spacing": [ 135 | "error" 136 | ], 137 | "@stylistic/no-multiple-empty-lines": [ 138 | "error" 139 | ], 140 | "@stylistic/no-trailing-spaces": [ 141 | "error" 142 | ], 143 | "curly": [ 144 | "error", 145 | "multi-line", 146 | "consistent" 147 | ], 148 | "@typescript-eslint/no-require-imports": "error", 149 | "import/no-extraneous-dependencies": [ 150 | "error", 151 | { 152 | "devDependencies": [ 153 | "**/test/**", 154 | "**/build-tools/**" 155 | ], 156 | "optionalDependencies": false, 157 | "peerDependencies": true 158 | } 159 | ], 160 | "import/no-unresolved": [ 161 | "error" 162 | ], 163 | "import/order": [ 164 | "warn", 165 | { 166 | "groups": [ 167 | "builtin", 168 | "external" 169 | ], 170 | "alphabetize": { 171 | "order": "asc", 172 | "caseInsensitive": true 173 | } 174 | } 175 | ], 176 | "import/no-duplicates": [ 177 | "error" 178 | ], 179 | "no-shadow": [ 180 | "off" 181 | ], 182 | "@typescript-eslint/no-shadow": "error", 183 | "@typescript-eslint/no-floating-promises": "error", 184 | "no-return-await": [ 185 | "off" 186 | ], 187 | "@typescript-eslint/return-await": "error", 188 | "dot-notation": [ 189 | "error" 190 | ], 191 | "no-bitwise": [ 192 | "error" 193 | ], 194 | "@typescript-eslint/member-ordering": [ 195 | "error", 196 | { 197 | "default": [ 198 | "public-static-field", 199 | "public-static-method", 200 | "protected-static-field", 201 | "protected-static-method", 202 | "private-static-field", 203 | "private-static-method", 204 | "field", 205 | "constructor", 206 | "method" 207 | ] 208 | } 209 | ] 210 | }, 211 | "overrides": [ 212 | { 213 | "files": [ 214 | ".projenrc.js" 215 | ], 216 | "rules": { 217 | "@typescript-eslint/no-require-imports": "off", 218 | "import/no-extraneous-dependencies": "off" 219 | } 220 | } 221 | ] 222 | } 223 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js 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/ISSUE_TEMPLATE/config.yml linguist-generated 8 | /.github/pull_request_template.md linguist-generated 9 | /.github/workflows/auto-approve.yml linguist-generated 10 | /.github/workflows/auto-queue.yml linguist-generated 11 | /.github/workflows/build.yml linguist-generated 12 | /.github/workflows/pull-request-lint.yml linguist-generated 13 | /.github/workflows/release.yml linguist-generated 14 | /.github/workflows/security.yml linguist-generated 15 | /.github/workflows/stale.yml linguist-generated 16 | /.github/workflows/triage.yml linguist-generated 17 | /.github/workflows/upgrade-compiler-dependencies-main.yml linguist-generated 18 | /.github/workflows/upgrade-configuration-main.yml linguist-generated 19 | /.github/workflows/upgrade-dev-dependencies-main.yml linguist-generated 20 | /.github/workflows/upgrade-runtime-dependencies-main.yml linguist-generated 21 | /.gitignore linguist-generated 22 | /.npmignore linguist-generated 23 | /.projen/** linguist-generated 24 | /.projen/deps.json linguist-generated 25 | /.projen/files.json linguist-generated 26 | /.projen/tasks.json linguist-generated 27 | /API.md linguist-generated 28 | /CODE_OF_CONDUCT.md linguist-generated 29 | /DCO linguist-generated 30 | /git-hooks/prepare-commit-msg linguist-generated 31 | /git-hooks/README.md linguist-generated 32 | /git-hooks/setup.sh linguist-generated 33 | /LICENSE linguist-generated 34 | /package.json linguist-generated 35 | /SECURITY.md linguist-generated 36 | /tsconfig.dev.json linguist-generated 37 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | blank_issues_enabled: false 4 | contact_links: 5 | - name: Slack Community Support @ cdk.dev 6 | url: https://cdk-dev.slack.com/archives/C0184GCBY4X 7 | about: Please ask and answer questions here. 8 | - name: Slack Community Support @ cncf.io 9 | url: https://cloud-native.slack.com/archives/C02KCDACGTT 10 | about: Please ask and answer questions here. 11 | - name: GitHub Community Support 12 | url: https://github.com/cdk8s-team/cdk8s-operator/discussions 13 | about: Please ask and answer questions here. 14 | - name: Open a cdk8s issue 15 | url: https://github.com/cdk8s-team/cdk8s/issues/new/choose 16 | about: All cdk8s issues are tracked in the cdk8s repository. 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'cdk8s-automation') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/auto-queue.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: auto-queue 4 | on: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - reopened 9 | - ready_for_review 10 | jobs: 11 | enableAutoQueue: 12 | name: "Set AutoQueue on PR #${{ github.event.number }}" 13 | runs-on: ubuntu-latest 14 | permissions: 15 | pull-requests: write 16 | contents: write 17 | steps: 18 | - uses: peter-evans/enable-pull-request-automerge@v3 19 | with: 20 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 21 | pull-request-number: ${{ github.event.number }} 22 | merge-method: squash 23 | -------------------------------------------------------------------------------- /.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 | merge_group: {} 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | outputs: 14 | self_mutation_happened: ${{ steps.self_mutation.outputs.self_mutation_happened }} 15 | env: 16 | CI: "true" 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | ref: ${{ github.event.pull_request.head.ref }} 22 | repository: ${{ github.event.pull_request.head.repo.full_name }} 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: lts/* 27 | - name: Install dependencies 28 | run: yarn install --check-files 29 | - name: build 30 | run: npx projen build 31 | - name: Find mutations 32 | id: self_mutation 33 | run: |- 34 | git add . 35 | git diff --staged --patch --exit-code > repo.patch || echo "self_mutation_happened=true" >> $GITHUB_OUTPUT 36 | working-directory: ./ 37 | - name: Upload patch 38 | if: steps.self_mutation.outputs.self_mutation_happened 39 | uses: actions/upload-artifact@v4.4.0 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.4.0 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@v4 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@v4 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" 82 | git config user.email "github-actions@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@v4 98 | with: 99 | node-version: lts/* 100 | - name: Download build artifacts 101 | uses: actions/download-artifact@v4 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@v4 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@v4 132 | with: 133 | distribution: corretto 134 | java-version: "11" 135 | - uses: actions/setup-node@v4 136 | with: 137 | node-version: lts/* 138 | - name: Download build artifacts 139 | uses: actions/download-artifact@v4 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@v4 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@v4 170 | with: 171 | node-version: lts/* 172 | - uses: actions/setup-python@v5 173 | with: 174 | python-version: 3.x 175 | - name: Download build artifacts 176 | uses: actions/download-artifact@v4 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@v4 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@v4 207 | with: 208 | node-version: lts/* 209 | - uses: actions/setup-dotnet@v4 210 | with: 211 | dotnet-version: 6.x 212 | - name: Download build artifacts 213 | uses: actions/download-artifact@v4 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@v4 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 | package-go: 237 | needs: build 238 | runs-on: ubuntu-latest 239 | permissions: 240 | contents: read 241 | if: ${{ !needs.build.outputs.self_mutation_happened }} 242 | steps: 243 | - uses: actions/setup-node@v4 244 | with: 245 | node-version: lts/* 246 | - uses: actions/setup-go@v5 247 | with: 248 | go-version: ^1.18.0 249 | - name: Download build artifacts 250 | uses: actions/download-artifact@v4 251 | with: 252 | name: build-artifact 253 | path: dist 254 | - name: Restore build artifact permissions 255 | run: cd dist && setfacl --restore=permissions-backup.acl 256 | continue-on-error: true 257 | - name: Checkout 258 | uses: actions/checkout@v4 259 | with: 260 | ref: ${{ github.event.pull_request.head.ref }} 261 | repository: ${{ github.event.pull_request.head.repo.full_name }} 262 | path: .repo 263 | - name: Install Dependencies 264 | run: cd .repo && yarn install --check-files --frozen-lockfile 265 | - name: Extract build artifact 266 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 267 | - name: Move build artifact out of the way 268 | run: mv dist dist.old 269 | - name: Create go artifact 270 | run: cd .repo && npx projen package:go 271 | - name: Collect go artifact 272 | run: mv .repo/dist dist 273 | -------------------------------------------------------------------------------- /.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 | 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@v5.4.0 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.js 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@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Set git identity 28 | run: |- 29 | git config user.name "github-actions" 30 | git config user.email "github-actions@github.com" 31 | - name: Setup Node.js 32 | uses: actions/setup-node@v4 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 | - name: Backup artifact permissions 51 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 52 | run: cd dist && getfacl -R . > permissions-backup.acl 53 | continue-on-error: true 54 | - name: Upload artifact 55 | if: ${{ steps.git_remote.outputs.latest_commit == github.sha }} 56 | uses: actions/upload-artifact@v4.4.0 57 | with: 58 | name: build-artifact 59 | path: dist 60 | overwrite: true 61 | release_github: 62 | name: Publish to GitHub Releases 63 | needs: 64 | - release 65 | - release_npm 66 | - release_maven 67 | - release_pypi 68 | - release_nuget 69 | - release_golang 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@v4 76 | with: 77 | node-version: lts/* 78 | - name: Download build artifacts 79 | uses: actions/download-artifact@v4 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@v4 100 | with: 101 | node-version: lts/* 102 | - name: Download build artifacts 103 | uses: actions/download-artifact@v4 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@v4 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@v4 140 | with: 141 | distribution: corretto 142 | java-version: "11" 143 | - uses: actions/setup-node@v4 144 | with: 145 | node-version: lts/* 146 | - name: Download build artifacts 147 | uses: actions/download-artifact@v4 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@v4 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@v4 186 | with: 187 | node-version: lts/* 188 | - uses: actions/setup-python@v5 189 | with: 190 | python-version: 3.x 191 | - name: Download build artifacts 192 | uses: actions/download-artifact@v4 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@v4 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@v4 227 | with: 228 | node-version: lts/* 229 | - uses: actions/setup-dotnet@v4 230 | with: 231 | dotnet-version: 6.x 232 | - name: Download build artifacts 233 | uses: actions/download-artifact@v4 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@v4 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 | release_golang: 259 | name: Publish to GitHub Go Module Repository 260 | needs: release 261 | runs-on: ubuntu-latest 262 | permissions: 263 | contents: read 264 | if: needs.release.outputs.tag_exists != 'true' && needs.release.outputs.latest_commit == github.sha 265 | steps: 266 | - uses: actions/setup-node@v4 267 | with: 268 | node-version: lts/* 269 | - uses: actions/setup-go@v5 270 | with: 271 | go-version: ^1.18.0 272 | - name: Download build artifacts 273 | uses: actions/download-artifact@v4 274 | with: 275 | name: build-artifact 276 | path: dist 277 | - name: Restore build artifact permissions 278 | run: cd dist && setfacl --restore=permissions-backup.acl 279 | continue-on-error: true 280 | - name: Checkout 281 | uses: actions/checkout@v4 282 | with: 283 | path: .repo 284 | - name: Install Dependencies 285 | run: cd .repo && yarn install --check-files --frozen-lockfile 286 | - name: Extract build artifact 287 | run: tar --strip-components=1 -xzvf dist/js/*.tgz -C .repo 288 | - name: Move build artifact out of the way 289 | run: mv dist dist.old 290 | - name: Create go artifact 291 | run: cd .repo && npx projen package:go 292 | - name: Collect go artifact 293 | run: mv .repo/dist dist 294 | - name: Release 295 | env: 296 | GIT_BRANCH: main 297 | GIT_USER_NAME: cdk8s-automation 298 | GIT_USER_EMAIL: cdk8s-team@amazon.com 299 | GITHUB_TOKEN: ${{ secrets.GO_GITHUB_TOKEN }} 300 | run: npx -p publib@latest publib-golang 301 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: security 4 | on: 5 | schedule: 6 | - cron: 0 0 * * * 7 | workflow_dispatch: {} 8 | jobs: 9 | scan: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | security-events: read 13 | issues: write 14 | steps: 15 | - name: scan 16 | uses: cdk8s-team/cdk8s-dependabot-security-alerts@main 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.PROJEN_GITHUB_TOKEN }} 19 | REPO_ROOT: ${{ github.workspace }} 20 | REPO_NAME: ${{ github.repository }} 21 | OWNER_NAME: ${{ github.repository_owner }} 22 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: stale 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 */4 * * * 8 | jobs: 9 | scan: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | issues: write 13 | pull-requests: write 14 | contents: read 15 | steps: 16 | - uses: aws-actions/stale-issue-cleanup@v6 17 | with: 18 | ancient-issue-message: This issue has not received any attention in 1 year and will be closed soon. If you want to keep it open, please leave a comment below @mentioning a maintainer. 19 | stale-issue-message: This issue has not received a response in a while and will be closed soon. If you want to keep it open, please leave a comment below @mentioning a maintainer. 20 | stale-pr-message: This PR has not received a response in a while and will be closed soon. If you want to keep it open, please leave a comment below @mentioning a maintainer. 21 | stale-issue-label: closing-soon 22 | exempt-issue-labels: no-autoclose 23 | stale-pr-label: closing-soon 24 | exempt-pr-labels: no-autoclose 25 | response-requested-label: response-requested 26 | closed-for-staleness-label: closed-for-staleness 27 | days-before-stale: 30 28 | days-before-close: 7 29 | days-before-ancient: 365 30 | minimum-upvotes-to-exempt: 10 31 | repo-token: ${{ secrets.GITHUB_TOKEN }} 32 | loglevel: DEBUG 33 | dry-run: false 34 | -------------------------------------------------------------------------------- /.github/workflows/triage.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: triage 4 | on: 5 | issues: 6 | types: 7 | - opened 8 | pull_request: 9 | types: 10 | - opened 11 | jobs: 12 | assign-to-project: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | issues: write 16 | pull-requests: write 17 | if: (github.repository == 'cdk8s-team/cdk8s-operator') && (github.event.issue || (github.event.pull_request.user.login != 'cdk8s-automation' && github.event.pull_request.head.repo.full_name == github.repository)) 18 | steps: 19 | - uses: actions/add-to-project@v0.4.0 20 | with: 21 | project-url: https://github.com/orgs/cdk8s-team/projects/12 22 | github-token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-compiler-dependencies-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-compiler-dependencies-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 12 * * * 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@v4 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 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-compiler-dependencies 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 | working-directory: ./ 35 | - name: Upload patch 36 | if: steps.create_patch.outputs.patch_created 37 | uses: actions/upload-artifact@v4.4.0 38 | with: 39 | name: repo.patch 40 | path: repo.patch 41 | overwrite: true 42 | pr: 43 | name: Create Pull Request 44 | needs: upgrade 45 | runs-on: ubuntu-latest 46 | permissions: 47 | contents: read 48 | if: ${{ needs.upgrade.outputs.patch_created }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | ref: main 54 | - name: Download patch 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: repo.patch 58 | path: ${{ runner.temp }} 59 | - name: Apply patch 60 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 61 | - name: Set git identity 62 | run: |- 63 | git config user.name "github-actions" 64 | git config user.email "github-actions@github.com" 65 | - name: Create Pull Request 66 | id: create-pr 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | commit-message: |- 71 | chore(deps): upgrade compiler dependencies 72 | 73 | Upgrades project dependencies. See details in [workflow run]. 74 | 75 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 76 | 77 | ------ 78 | 79 | *Automatically created by projen via the "upgrade-compiler-dependencies-main" workflow* 80 | branch: github-actions/upgrade-compiler-dependencies-main 81 | title: "chore(deps): upgrade compiler dependencies" 82 | labels: auto-approve 83 | body: |- 84 | Upgrades project dependencies. See details in [workflow run]. 85 | 86 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 87 | 88 | ------ 89 | 90 | *Automatically created by projen via the "upgrade-compiler-dependencies-main" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-configuration-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-configuration-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 15 * * * 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@v4 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 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-configuration 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 | working-directory: ./ 35 | - name: Upload patch 36 | if: steps.create_patch.outputs.patch_created 37 | uses: actions/upload-artifact@v4.4.0 38 | with: 39 | name: repo.patch 40 | path: repo.patch 41 | overwrite: true 42 | pr: 43 | name: Create Pull Request 44 | needs: upgrade 45 | runs-on: ubuntu-latest 46 | permissions: 47 | contents: read 48 | if: ${{ needs.upgrade.outputs.patch_created }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | ref: main 54 | - name: Download patch 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: repo.patch 58 | path: ${{ runner.temp }} 59 | - name: Apply patch 60 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 61 | - name: Set git identity 62 | run: |- 63 | git config user.name "github-actions" 64 | git config user.email "github-actions@github.com" 65 | - name: Create Pull Request 66 | id: create-pr 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | commit-message: |- 71 | chore(deps): upgrade configuration 72 | 73 | Upgrades project dependencies. See details in [workflow run]. 74 | 75 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 76 | 77 | ------ 78 | 79 | *Automatically created by projen via the "upgrade-configuration-main" workflow* 80 | branch: github-actions/upgrade-configuration-main 81 | title: "chore(deps): upgrade configuration" 82 | labels: auto-approve 83 | body: |- 84 | Upgrades project dependencies. See details in [workflow run]. 85 | 86 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 87 | 88 | ------ 89 | 90 | *Automatically created by projen via the "upgrade-configuration-main" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dev-dependencies-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-dev-dependencies-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 9 * * * 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@v4 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 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-dev-dependencies 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 | working-directory: ./ 35 | - name: Upload patch 36 | if: steps.create_patch.outputs.patch_created 37 | uses: actions/upload-artifact@v4.4.0 38 | with: 39 | name: repo.patch 40 | path: repo.patch 41 | overwrite: true 42 | pr: 43 | name: Create Pull Request 44 | needs: upgrade 45 | runs-on: ubuntu-latest 46 | permissions: 47 | contents: read 48 | if: ${{ needs.upgrade.outputs.patch_created }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | ref: main 54 | - name: Download patch 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: repo.patch 58 | path: ${{ runner.temp }} 59 | - name: Apply patch 60 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 61 | - name: Set git identity 62 | run: |- 63 | git config user.name "github-actions" 64 | git config user.email "github-actions@github.com" 65 | - name: Create Pull Request 66 | id: create-pr 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | commit-message: |- 71 | chore(deps): upgrade dev dependencies 72 | 73 | Upgrades project dependencies. See details in [workflow run]. 74 | 75 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 76 | 77 | ------ 78 | 79 | *Automatically created by projen via the "upgrade-dev-dependencies-main" workflow* 80 | branch: github-actions/upgrade-dev-dependencies-main 81 | title: "chore(deps): upgrade dev dependencies" 82 | labels: auto-approve 83 | body: |- 84 | Upgrades project dependencies. See details in [workflow run]. 85 | 86 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 87 | 88 | ------ 89 | 90 | *Automatically created by projen via the "upgrade-dev-dependencies-main" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-runtime-dependencies-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.js and run "npx projen". 2 | 3 | name: upgrade-runtime-dependencies-main 4 | on: 5 | workflow_dispatch: {} 6 | schedule: 7 | - cron: 0 6 * * * 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@v4 19 | with: 20 | ref: main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 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-runtime-dependencies 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 | working-directory: ./ 35 | - name: Upload patch 36 | if: steps.create_patch.outputs.patch_created 37 | uses: actions/upload-artifact@v4.4.0 38 | with: 39 | name: repo.patch 40 | path: repo.patch 41 | overwrite: true 42 | pr: 43 | name: Create Pull Request 44 | needs: upgrade 45 | runs-on: ubuntu-latest 46 | permissions: 47 | contents: read 48 | if: ${{ needs.upgrade.outputs.patch_created }} 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | with: 53 | ref: main 54 | - name: Download patch 55 | uses: actions/download-artifact@v4 56 | with: 57 | name: repo.patch 58 | path: ${{ runner.temp }} 59 | - name: Apply patch 60 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 61 | - name: Set git identity 62 | run: |- 63 | git config user.name "github-actions" 64 | git config user.email "github-actions@github.com" 65 | - name: Create Pull Request 66 | id: create-pr 67 | uses: peter-evans/create-pull-request@v6 68 | with: 69 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 70 | commit-message: |- 71 | chore(deps): upgrade runtime dependencies 72 | 73 | Upgrades project dependencies. See details in [workflow run]. 74 | 75 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 76 | 77 | ------ 78 | 79 | *Automatically created by projen via the "upgrade-runtime-dependencies-main" workflow* 80 | branch: github-actions/upgrade-runtime-dependencies-main 81 | title: "chore(deps): upgrade runtime dependencies" 82 | labels: auto-approve 83 | body: |- 84 | Upgrades project dependencies. See details in [workflow run]. 85 | 86 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 87 | 88 | ------ 89 | 90 | *Automatically created by projen via the "upgrade-runtime-dependencies-main" workflow* 91 | author: github-actions 92 | committer: github-actions 93 | signoff: true 94 | -------------------------------------------------------------------------------- /.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/auto-queue.yml 7 | !/.github/workflows/pull-request-lint.yml 8 | !/.github/workflows/auto-approve.yml 9 | !/package.json 10 | !/LICENSE 11 | !/.npmignore 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | lerna-debug.log* 18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | lib-cov 24 | coverage 25 | *.lcov 26 | .nyc_output 27 | build/Release 28 | node_modules/ 29 | jspm_packages/ 30 | *.tsbuildinfo 31 | .eslintcache 32 | *.tgz 33 | .yarn-integrity 34 | .cache 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 | !/.github/workflows/upgrade-runtime-dependencies-main.yml 43 | !/.github/pull_request_template.md 44 | !/test/ 45 | !/tsconfig.dev.json 46 | !/src/ 47 | /lib 48 | /dist/ 49 | !/.eslintrc.json 50 | .jsii 51 | tsconfig.json 52 | !/API.md 53 | !/CODE_OF_CONDUCT.md 54 | !/DCO 55 | !/git-hooks/prepare-commit-msg 56 | !/git-hooks/README.md 57 | !/git-hooks/setup.sh 58 | !/.github/ISSUE_TEMPLATE/config.yml 59 | !/SECURITY.md 60 | !/.github/workflows/security.yml 61 | !/.github/workflows/triage.yml 62 | !/.github/workflows/stale.yml 63 | !/.github/workflows/upgrade-configuration-main.yml 64 | !/.github/workflows/upgrade-dev-dependencies-main.yml 65 | !/.github/workflows/upgrade-compiler-dependencies-main.yml 66 | !/.projenrc.js 67 | -------------------------------------------------------------------------------- /.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 | /test/ 10 | /tsconfig.dev.json 11 | /src/ 12 | !/lib/ 13 | !/lib/**/*.js 14 | !/lib/**/*.d.ts 15 | dist 16 | /tsconfig.json 17 | /.github/ 18 | /.vscode/ 19 | /.idea/ 20 | /.projenrc.js 21 | tsconfig.tsbuildinfo 22 | /.eslintrc.json 23 | !.jsii 24 | /.gitattributes 25 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@cdk8s/projen-common", 5 | "type": "build" 6 | }, 7 | { 8 | "name": "@stylistic/eslint-plugin", 9 | "version": "^2", 10 | "type": "build" 11 | }, 12 | { 13 | "name": "@types/jest", 14 | "type": "build" 15 | }, 16 | { 17 | "name": "@types/node", 18 | "version": "16.18.78", 19 | "type": "build" 20 | }, 21 | { 22 | "name": "@typescript-eslint/eslint-plugin", 23 | "version": "^8", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "@typescript-eslint/parser", 28 | "version": "^8", 29 | "type": "build" 30 | }, 31 | { 32 | "name": "commit-and-tag-version", 33 | "version": "^12", 34 | "type": "build" 35 | }, 36 | { 37 | "name": "constructs", 38 | "version": "^10.0.0", 39 | "type": "build" 40 | }, 41 | { 42 | "name": "eslint-import-resolver-typescript", 43 | "type": "build" 44 | }, 45 | { 46 | "name": "eslint-plugin-import", 47 | "type": "build" 48 | }, 49 | { 50 | "name": "eslint", 51 | "version": "^9", 52 | "type": "build" 53 | }, 54 | { 55 | "name": "jest", 56 | "type": "build" 57 | }, 58 | { 59 | "name": "jest-junit", 60 | "version": "^16", 61 | "type": "build" 62 | }, 63 | { 64 | "name": "jsii-diff", 65 | "type": "build" 66 | }, 67 | { 68 | "name": "jsii-docgen", 69 | "version": "^10.5.0", 70 | "type": "build" 71 | }, 72 | { 73 | "name": "jsii-pacmak", 74 | "type": "build" 75 | }, 76 | { 77 | "name": "jsii-rosetta", 78 | "version": "~5.6.0", 79 | "type": "build" 80 | }, 81 | { 82 | "name": "jsii", 83 | "version": "~5.6.0", 84 | "type": "build" 85 | }, 86 | { 87 | "name": "projen", 88 | "type": "build" 89 | }, 90 | { 91 | "name": "ts-jest", 92 | "type": "build" 93 | }, 94 | { 95 | "name": "typescript", 96 | "type": "build" 97 | }, 98 | { 99 | "name": "yaml", 100 | "type": "bundled" 101 | }, 102 | { 103 | "name": "**/downlevel-dts/**/typescript", 104 | "version": "~5.2.2", 105 | "type": "override" 106 | }, 107 | { 108 | "name": "cdk8s", 109 | "type": "peer" 110 | }, 111 | { 112 | "name": "constructs", 113 | "type": "peer" 114 | } 115 | ], 116 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 117 | } 118 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/ISSUE_TEMPLATE/config.yml", 6 | ".github/pull_request_template.md", 7 | ".github/workflows/auto-approve.yml", 8 | ".github/workflows/auto-queue.yml", 9 | ".github/workflows/build.yml", 10 | ".github/workflows/pull-request-lint.yml", 11 | ".github/workflows/release.yml", 12 | ".github/workflows/security.yml", 13 | ".github/workflows/stale.yml", 14 | ".github/workflows/triage.yml", 15 | ".github/workflows/upgrade-compiler-dependencies-main.yml", 16 | ".github/workflows/upgrade-configuration-main.yml", 17 | ".github/workflows/upgrade-dev-dependencies-main.yml", 18 | ".github/workflows/upgrade-runtime-dependencies-main.yml", 19 | ".gitignore", 20 | ".projen/deps.json", 21 | ".projen/files.json", 22 | ".projen/tasks.json", 23 | "CODE_OF_CONDUCT.md", 24 | "DCO", 25 | "git-hooks/prepare-commit-msg", 26 | "git-hooks/README.md", 27 | "git-hooks/setup.sh", 28 | "LICENSE", 29 | "SECURITY.md", 30 | "tsconfig.dev.json" 31 | ], 32 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 33 | } 34 | -------------------------------------------------------------------------------- /.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 | "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\" --grep 'chore\\(deps\\): upgrade runtime dependencies' --grep 'chore\\(deps\\): upgrade compiler dependencies'" 38 | }, 39 | "steps": [ 40 | { 41 | "builtin": "release/bump-version" 42 | } 43 | ], 44 | "condition": "git log --oneline -1 | grep -qv \"chore(release):\"" 45 | }, 46 | "clobber": { 47 | "name": "clobber", 48 | "description": "hard resets to HEAD of origin and cleans the local repo", 49 | "env": { 50 | "BRANCH": "$(git branch --show-current)" 51 | }, 52 | "steps": [ 53 | { 54 | "exec": "git checkout -b scratch", 55 | "name": "save current HEAD in \"scratch\" branch" 56 | }, 57 | { 58 | "exec": "git checkout $BRANCH" 59 | }, 60 | { 61 | "exec": "git fetch origin", 62 | "name": "fetch latest changes from origin" 63 | }, 64 | { 65 | "exec": "git reset --hard origin/$BRANCH", 66 | "name": "hard reset to origin commit" 67 | }, 68 | { 69 | "exec": "git clean -fdx", 70 | "name": "clean all untracked files" 71 | }, 72 | { 73 | "say": "ready to rock! (unpushed commits are under the \"scratch\" branch)" 74 | } 75 | ], 76 | "condition": "git diff --exit-code > /dev/null" 77 | }, 78 | "compat": { 79 | "name": "compat", 80 | "description": "Perform API compatibility check against latest version", 81 | "steps": [ 82 | { 83 | "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)" 84 | } 85 | ] 86 | }, 87 | "compile": { 88 | "name": "compile", 89 | "description": "Only compile", 90 | "steps": [ 91 | { 92 | "exec": "jsii --silence-warnings=reserved-word" 93 | } 94 | ] 95 | }, 96 | "default": { 97 | "name": "default", 98 | "description": "Synthesize project files", 99 | "steps": [ 100 | { 101 | "exec": "node .projenrc.js" 102 | } 103 | ] 104 | }, 105 | "docgen": { 106 | "name": "docgen", 107 | "description": "Generate API.md from .jsii manifest", 108 | "steps": [ 109 | { 110 | "exec": "jsii-docgen -o API.md" 111 | } 112 | ] 113 | }, 114 | "eject": { 115 | "name": "eject", 116 | "description": "Remove projen from the project", 117 | "env": { 118 | "PROJEN_EJECTING": "true" 119 | }, 120 | "steps": [ 121 | { 122 | "spawn": "default" 123 | } 124 | ] 125 | }, 126 | "eslint": { 127 | "name": "eslint", 128 | "description": "Runs eslint against the codebase", 129 | "env": { 130 | "ESLINT_USE_FLAT_CONFIG": "false" 131 | }, 132 | "steps": [ 133 | { 134 | "exec": "eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern $@ src test build-tools .projenrc.js", 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 | "spawn": "package:go" 189 | } 190 | ] 191 | }, 192 | "package:dotnet": { 193 | "name": "package:dotnet", 194 | "description": "Create dotnet language bindings", 195 | "steps": [ 196 | { 197 | "exec": "jsii-pacmak -v --target dotnet" 198 | } 199 | ] 200 | }, 201 | "package:go": { 202 | "name": "package:go", 203 | "description": "Create go language bindings", 204 | "steps": [ 205 | { 206 | "exec": "jsii-pacmak -v --target go" 207 | } 208 | ] 209 | }, 210 | "package:java": { 211 | "name": "package:java", 212 | "description": "Create java language bindings", 213 | "steps": [ 214 | { 215 | "exec": "jsii-pacmak -v --target java" 216 | } 217 | ] 218 | }, 219 | "package:js": { 220 | "name": "package:js", 221 | "description": "Create js language bindings", 222 | "steps": [ 223 | { 224 | "exec": "jsii-pacmak -v --target js" 225 | } 226 | ] 227 | }, 228 | "package:python": { 229 | "name": "package:python", 230 | "description": "Create python language bindings", 231 | "steps": [ 232 | { 233 | "exec": "jsii-pacmak -v --target python" 234 | } 235 | ] 236 | }, 237 | "post-compile": { 238 | "name": "post-compile", 239 | "description": "Runs after successful compilation", 240 | "steps": [ 241 | { 242 | "spawn": "docgen" 243 | } 244 | ] 245 | }, 246 | "post-upgrade": { 247 | "name": "post-upgrade", 248 | "description": "Runs after upgrading dependencies" 249 | }, 250 | "pre-compile": { 251 | "name": "pre-compile", 252 | "description": "Prepare the project for compilation" 253 | }, 254 | "release": { 255 | "name": "release", 256 | "description": "Prepare a release from \"main\" branch", 257 | "env": { 258 | "RELEASE": "true" 259 | }, 260 | "steps": [ 261 | { 262 | "exec": "rm -fr dist" 263 | }, 264 | { 265 | "spawn": "bump" 266 | }, 267 | { 268 | "spawn": "build" 269 | }, 270 | { 271 | "spawn": "unbump" 272 | }, 273 | { 274 | "exec": "git diff --ignore-space-at-eol --exit-code" 275 | } 276 | ] 277 | }, 278 | "test": { 279 | "name": "test", 280 | "description": "Run tests", 281 | "steps": [ 282 | { 283 | "exec": "jest --passWithNoTests --updateSnapshot", 284 | "receiveArgs": true 285 | }, 286 | { 287 | "spawn": "eslint" 288 | } 289 | ] 290 | }, 291 | "test:watch": { 292 | "name": "test:watch", 293 | "description": "Run jest in watch mode", 294 | "steps": [ 295 | { 296 | "exec": "jest --watch" 297 | } 298 | ] 299 | }, 300 | "unbump": { 301 | "name": "unbump", 302 | "description": "Restores version to 0.0.0", 303 | "env": { 304 | "OUTFILE": "package.json", 305 | "CHANGELOG": "dist/changelog.md", 306 | "BUMPFILE": "dist/version.txt", 307 | "RELEASETAG": "dist/releasetag.txt", 308 | "RELEASE_TAG_PREFIX": "", 309 | "BUMP_PACKAGE": "commit-and-tag-version@^12", 310 | "RELEASABLE_COMMITS": "git log --no-merges --oneline $LATEST_TAG..HEAD -E --grep \"^(feat|fix){1}(\\([^()[:space:]]+\\))?(!)?:[[:blank:]]+.+\" --grep 'chore\\(deps\\): upgrade runtime dependencies' --grep 'chore\\(deps\\): upgrade compiler dependencies'" 311 | }, 312 | "steps": [ 313 | { 314 | "builtin": "release/reset-version" 315 | } 316 | ] 317 | }, 318 | "upgrade-compiler-dependencies": { 319 | "name": "upgrade-compiler-dependencies", 320 | "description": "upgrade compiler dependencies", 321 | "env": { 322 | "CI": "0" 323 | }, 324 | "steps": [ 325 | { 326 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=jsii,jsii-docgen,jsii-pacmak,jsii-rosetta,typescript" 327 | }, 328 | { 329 | "exec": "yarn install --check-files" 330 | }, 331 | { 332 | "exec": "yarn upgrade jsii jsii-docgen jsii-pacmak jsii-rosetta typescript" 333 | }, 334 | { 335 | "exec": "npx projen" 336 | }, 337 | { 338 | "spawn": "post-upgrade" 339 | } 340 | ] 341 | }, 342 | "upgrade-configuration": { 343 | "name": "upgrade-configuration", 344 | "description": "upgrade configuration", 345 | "env": { 346 | "CI": "0" 347 | }, 348 | "steps": [ 349 | { 350 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=projen,@cdk8s/projen-common" 351 | }, 352 | { 353 | "exec": "yarn install --check-files" 354 | }, 355 | { 356 | "exec": "yarn upgrade projen @cdk8s/projen-common" 357 | }, 358 | { 359 | "exec": "npx projen" 360 | }, 361 | { 362 | "spawn": "post-upgrade" 363 | } 364 | ] 365 | }, 366 | "upgrade-dev-dependencies": { 367 | "name": "upgrade-dev-dependencies", 368 | "description": "upgrade dev dependencies", 369 | "env": { 370 | "CI": "0" 371 | }, 372 | "steps": [ 373 | { 374 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=dev --filter=@types/jest,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,ts-jest,yaml" 375 | }, 376 | { 377 | "exec": "yarn install --check-files" 378 | }, 379 | { 380 | "exec": "yarn upgrade @stylistic/eslint-plugin @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser commit-and-tag-version constructs eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-diff ts-jest yaml" 381 | }, 382 | { 383 | "exec": "npx projen" 384 | }, 385 | { 386 | "spawn": "post-upgrade" 387 | } 388 | ] 389 | }, 390 | "upgrade-runtime-dependencies": { 391 | "name": "upgrade-runtime-dependencies", 392 | "description": "upgrade runtime dependencies", 393 | "env": { 394 | "CI": "0" 395 | }, 396 | "steps": [ 397 | { 398 | "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --no-deprecated --dep=prod,optional --filter=yaml" 399 | }, 400 | { 401 | "exec": "yarn install --check-files" 402 | }, 403 | { 404 | "exec": "yarn upgrade yaml" 405 | }, 406 | { 407 | "exec": "npx projen" 408 | }, 409 | { 410 | "spawn": "post-upgrade" 411 | } 412 | ] 413 | }, 414 | "watch": { 415 | "name": "watch", 416 | "description": "Watch & compile in the background", 417 | "steps": [ 418 | { 419 | "exec": "jsii -w --silence-warnings=reserved-word" 420 | } 421 | ] 422 | } 423 | }, 424 | "env": { 425 | "PATH": "$(npx -c \"node --print process.env.PATH\")" 426 | }, 427 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 428 | } 429 | -------------------------------------------------------------------------------- /.projenrc.js: -------------------------------------------------------------------------------- 1 | const { Cdk8sTeamJsiiProject } = require('@cdk8s/projen-common'); 2 | 3 | const project = new Cdk8sTeamJsiiProject({ 4 | name: 'cdk8s-operator', 5 | description: 'Create Kubernetes CRD Operators using CDK8s Constructs', 6 | defaultReleaseBranch: 'main', 7 | bundledDeps: [ 8 | 'yaml', 9 | ], 10 | peerDeps: [ 11 | 'cdk8s', 12 | 'constructs', 13 | ], 14 | devDeps: [ 15 | '@cdk8s/projen-common', 16 | ], 17 | keywords: [ 18 | 'cdk8s', 19 | 'kubernetes', 20 | 'crd', 21 | 'operator', 22 | ], 23 | bin: { 24 | 'cdk8s-server': 'lib/cli/cdk8s-server.js', 25 | }, 26 | 27 | projenUpgradeSecret: 'PROJEN_GITHUB_TOKEN', 28 | 29 | }); 30 | 31 | project.synth(); 32 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Constructs 4 | 5 | ### Operator 6 | 7 | A CDK8s app which allows implementing Kubernetes operators using CDK8s constructs. 8 | 9 | #### Initializers 10 | 11 | ```typescript 12 | import { Operator } from 'cdk8s-operator' 13 | 14 | new Operator(props?: OperatorProps) 15 | ``` 16 | 17 | | **Name** | **Type** | **Description** | 18 | | --- | --- | --- | 19 | | props | OperatorProps | *No description.* | 20 | 21 | --- 22 | 23 | ##### `props`Optional 24 | 25 | - *Type:* OperatorProps 26 | 27 | --- 28 | 29 | #### Methods 30 | 31 | | **Name** | **Description** | 32 | | --- | --- | 33 | | toString | Returns a string representation of this construct. | 34 | | synth | Reads a Kubernetes manifest in JSON format from STDIN or the file specified as the first positional command-line argument. | 35 | | synthYaml | Synthesizes the app into a YAML string. | 36 | | addProvider | Adds a custom resource provider to this operator. | 37 | 38 | --- 39 | 40 | ##### `toString` 41 | 42 | ```typescript 43 | public toString(): string 44 | ``` 45 | 46 | Returns a string representation of this construct. 47 | 48 | ##### `synth` 49 | 50 | ```typescript 51 | public synth(): void 52 | ``` 53 | 54 | Reads a Kubernetes manifest in JSON format from STDIN or the file specified as the first positional command-line argument. 55 | 56 | This manifest is expected to 57 | include a single Kubernetes resource. Then, we match `apiVersion` and 58 | `kind` to one of the registered providers and if we do, we invoke 59 | `apply()`, passing it the `spec` of the input manifest and a chart as a 60 | scope. The chart is then synthesized and the output manifest is written to 61 | STDOUT. 62 | 63 | ##### `synthYaml` 64 | 65 | ```typescript 66 | public synthYaml(): string 67 | ``` 68 | 69 | Synthesizes the app into a YAML string. 70 | 71 | ##### `addProvider` 72 | 73 | ```typescript 74 | public addProvider(provider: CustomResourceProvider): void 75 | ``` 76 | 77 | Adds a custom resource provider to this operator. 78 | 79 | ###### `provider`Required 80 | 81 | - *Type:* CustomResourceProvider 82 | 83 | The provider to add. 84 | 85 | --- 86 | 87 | #### Static Functions 88 | 89 | | **Name** | **Description** | 90 | | --- | --- | 91 | | isConstruct | Checks if `x` is a construct. | 92 | | of | *No description.* | 93 | 94 | --- 95 | 96 | ##### `isConstruct` 97 | 98 | ```typescript 99 | import { Operator } from 'cdk8s-operator' 100 | 101 | Operator.isConstruct(x: any) 102 | ``` 103 | 104 | Checks if `x` is a construct. 105 | 106 | Use this method instead of `instanceof` to properly detect `Construct` 107 | instances, even when the construct library is symlinked. 108 | 109 | Explanation: in JavaScript, multiple copies of the `constructs` library on 110 | disk are seen as independent, completely different libraries. As a 111 | consequence, the class `Construct` in each copy of the `constructs` library 112 | is seen as a different class, and an instance of one class will not test as 113 | `instanceof` the other class. `npm install` will not create installations 114 | like this, but users may manually symlink construct libraries together or 115 | use a monorepo tool: in those cases, multiple copies of the `constructs` 116 | library can be accidentally installed, and `instanceof` will behave 117 | unpredictably. It is safest to avoid using `instanceof`, and using 118 | this type-testing method instead. 119 | 120 | ###### `x`Required 121 | 122 | - *Type:* any 123 | 124 | Any object. 125 | 126 | --- 127 | 128 | ##### `of` 129 | 130 | ```typescript 131 | import { Operator } from 'cdk8s-operator' 132 | 133 | Operator.of(c: IConstruct) 134 | ``` 135 | 136 | ###### `c`Required 137 | 138 | - *Type:* constructs.IConstruct 139 | 140 | --- 141 | 142 | #### Properties 143 | 144 | | **Name** | **Type** | **Description** | 145 | | --- | --- | --- | 146 | | node | constructs.Node | The tree node. | 147 | | charts | cdk8s.Chart[] | Returns all the charts in this app, sorted topologically. | 148 | | outdir | string | The output directory into which manifests will be synthesized. | 149 | | outputFileExtension | string | The file extension to use for rendered YAML files. | 150 | | resolvers | cdk8s.IResolver[] | Resolvers used by this app. | 151 | | yamlOutputType | cdk8s.YamlOutputType | How to divide the YAML output into files. | 152 | 153 | --- 154 | 155 | ##### `node`Required 156 | 157 | ```typescript 158 | public readonly node: Node; 159 | ``` 160 | 161 | - *Type:* constructs.Node 162 | 163 | The tree node. 164 | 165 | --- 166 | 167 | ##### `charts`Required 168 | 169 | ```typescript 170 | public readonly charts: Chart[]; 171 | ``` 172 | 173 | - *Type:* cdk8s.Chart[] 174 | 175 | Returns all the charts in this app, sorted topologically. 176 | 177 | --- 178 | 179 | ##### `outdir`Required 180 | 181 | ```typescript 182 | public readonly outdir: string; 183 | ``` 184 | 185 | - *Type:* string 186 | 187 | The output directory into which manifests will be synthesized. 188 | 189 | --- 190 | 191 | ##### `outputFileExtension`Required 192 | 193 | ```typescript 194 | public readonly outputFileExtension: string; 195 | ``` 196 | 197 | - *Type:* string 198 | - *Default:* .k8s.yaml 199 | 200 | The file extension to use for rendered YAML files. 201 | 202 | --- 203 | 204 | ##### `resolvers`Required 205 | 206 | ```typescript 207 | public readonly resolvers: IResolver[]; 208 | ``` 209 | 210 | - *Type:* cdk8s.IResolver[] 211 | 212 | Resolvers used by this app. 213 | 214 | This includes both custom resolvers 215 | passed by the `resolvers` property, as well as built-in resolvers. 216 | 217 | --- 218 | 219 | ##### `yamlOutputType`Required 220 | 221 | ```typescript 222 | public readonly yamlOutputType: YamlOutputType; 223 | ``` 224 | 225 | - *Type:* cdk8s.YamlOutputType 226 | - *Default:* YamlOutputType.FILE_PER_CHART 227 | 228 | How to divide the YAML output into files. 229 | 230 | --- 231 | 232 | 233 | ## Structs 234 | 235 | ### CustomResourceProvider 236 | 237 | #### Initializer 238 | 239 | ```typescript 240 | import { CustomResourceProvider } from 'cdk8s-operator' 241 | 242 | const customResourceProvider: CustomResourceProvider = { ... } 243 | ``` 244 | 245 | #### Properties 246 | 247 | | **Name** | **Type** | **Description** | 248 | | --- | --- | --- | 249 | | apiVersion | string | API version of the custom resource. | 250 | | handler | ICustomResourceProviderHandler | The construct handler. | 251 | | kind | string | Kind of this custom resource. | 252 | 253 | --- 254 | 255 | ##### `apiVersion`Required 256 | 257 | ```typescript 258 | public readonly apiVersion: string; 259 | ``` 260 | 261 | - *Type:* string 262 | - *Default:* "v1" 263 | 264 | API version of the custom resource. 265 | 266 | --- 267 | 268 | ##### `handler`Required 269 | 270 | ```typescript 271 | public readonly handler: ICustomResourceProviderHandler; 272 | ``` 273 | 274 | - *Type:* ICustomResourceProviderHandler 275 | 276 | The construct handler. 277 | 278 | --- 279 | 280 | ##### `kind`Required 281 | 282 | ```typescript 283 | public readonly kind: string; 284 | ``` 285 | 286 | - *Type:* string 287 | 288 | Kind of this custom resource. 289 | 290 | --- 291 | 292 | ### OperatorProps 293 | 294 | #### Initializer 295 | 296 | ```typescript 297 | import { OperatorProps } from 'cdk8s-operator' 298 | 299 | const operatorProps: OperatorProps = { ... } 300 | ``` 301 | 302 | #### Properties 303 | 304 | | **Name** | **Type** | **Description** | 305 | | --- | --- | --- | 306 | | inputFile | string | A Kubernetes JSON manifest with a single resource that is matched against one of the providers within this operator. | 307 | | outputFile | string | Where to write the synthesized output. | 308 | 309 | --- 310 | 311 | ##### `inputFile`Optional 312 | 313 | ```typescript 314 | public readonly inputFile: string; 315 | ``` 316 | 317 | - *Type:* string 318 | - *Default:* first position command-line argument or "/dev/stdin" 319 | 320 | A Kubernetes JSON manifest with a single resource that is matched against one of the providers within this operator. 321 | 322 | --- 323 | 324 | ##### `outputFile`Optional 325 | 326 | ```typescript 327 | public readonly outputFile: string; 328 | ``` 329 | 330 | - *Type:* string 331 | - *Default:* "/dev/stdout" 332 | 333 | Where to write the synthesized output. 334 | 335 | --- 336 | 337 | ### ServerProps 338 | 339 | #### Initializer 340 | 341 | ```typescript 342 | import { ServerProps } from 'cdk8s-operator' 343 | 344 | const serverProps: ServerProps = { ... } 345 | ``` 346 | 347 | #### Properties 348 | 349 | | **Name** | **Type** | **Description** | 350 | | --- | --- | --- | 351 | | appCommand | string | The command to execute in order to synthesize the CDK app. | 352 | 353 | --- 354 | 355 | ##### `appCommand`Required 356 | 357 | ```typescript 358 | public readonly appCommand: string; 359 | ``` 360 | 361 | - *Type:* string 362 | 363 | The command to execute in order to synthesize the CDK app. 364 | 365 | --- 366 | 367 | ## Classes 368 | 369 | ### Server 370 | 371 | #### Initializers 372 | 373 | ```typescript 374 | import { Server } from 'cdk8s-operator' 375 | 376 | new Server(props: ServerProps) 377 | ``` 378 | 379 | | **Name** | **Type** | **Description** | 380 | | --- | --- | --- | 381 | | props | ServerProps | *No description.* | 382 | 383 | --- 384 | 385 | ##### `props`Required 386 | 387 | - *Type:* ServerProps 388 | 389 | --- 390 | 391 | #### Methods 392 | 393 | | **Name** | **Description** | 394 | | --- | --- | 395 | | close | Stop server. | 396 | | listen | Starts HTTP server. | 397 | 398 | --- 399 | 400 | ##### `close` 401 | 402 | ```typescript 403 | public close(): void 404 | ``` 405 | 406 | Stop server. 407 | 408 | ##### `listen` 409 | 410 | ```typescript 411 | public listen(port?: number): number 412 | ``` 413 | 414 | Starts HTTP server. 415 | 416 | ###### `port`Optional 417 | 418 | - *Type:* number 419 | 420 | The port to listen to. 421 | 422 | If not specified, the `PORT` environment 423 | variable will be used. If that's not specified an available port will be 424 | auto-selected. 425 | 426 | --- 427 | 428 | 429 | 430 | 431 | ## Protocols 432 | 433 | ### ICustomResourceProviderHandler 434 | 435 | - *Implemented By:* ICustomResourceProviderHandler 436 | 437 | The handler for this custom resource provider. 438 | 439 | #### Methods 440 | 441 | | **Name** | **Description** | 442 | | --- | --- | 443 | | apply | *No description.* | 444 | 445 | --- 446 | 447 | ##### `apply` 448 | 449 | ```typescript 450 | public apply(scope: Construct, id: string, spec: any): Construct 451 | ``` 452 | 453 | ###### `scope`Required 454 | 455 | - *Type:* constructs.Construct 456 | 457 | --- 458 | 459 | ###### `id`Required 460 | 461 | - *Type:* string 462 | 463 | --- 464 | 465 | ###### `spec`Required 466 | 467 | - *Type:* any 468 | 469 | --- 470 | 471 | 472 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The cdk8s project follows the [CNCF Community Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). -------------------------------------------------------------------------------- /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 | ### Developer Certificate Of Origin (DCO) 43 | 44 | Every commit should be signed-off in compliance with the [Developer Certificate Of Origin](./DCO). 45 | You can sign your commits by using the `git commit -s` command. 46 | 47 | > To configure automatic signoff, see [git-hooks](./git-hooks/README.md). 48 | 49 | ## Finding contributions to work on 50 | 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. 51 | 52 | 53 | ## Code of Conduct 54 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 55 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 56 | opensource-codeofconduct@amazon.com with any additional questions or comments. 57 | 58 | 59 | ## Security issue notifications 60 | 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. 61 | 62 | 63 | ## Licensing 64 | 65 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 66 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 6 | Everyone is permitted to copy and distribute verbatim copies of this 7 | license document, but changing it is not allowed. 8 | 9 | 10 | Developer's Certificate of Origin 1.1 11 | 12 | By making a contribution to this project, I certify that: 13 | 14 | (a) The contribution was created in whole or in part by me and I 15 | have the right to submit it under the open source license 16 | indicated in the file; or 17 | 18 | (b) The contribution is based upon previous work that, to the best 19 | of my knowledge, is covered under an appropriate open source 20 | license and I have the right under that license to submit that 21 | work with modifications, whether created in whole or in part 22 | by me, under the same open source license (unless I am 23 | permitted to submit under a different license), as indicated 24 | in the file; or 25 | 26 | (c) The contribution was provided directly to me by some other 27 | person who certified (a), (b) or (c) and I have not modified 28 | it. 29 | 30 | (d) I understand and agree that this project and the contribution 31 | are public and that a record of the contribution (including all 32 | personal information I submit with it, including my sign-off) is 33 | maintained indefinitely and may be redistributed consistent with 34 | this project or the open source license(s) involved. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cdk8s-operator 2 | 3 | > Create Kubernetes CRD Operators using CDK8s Constructs 4 | 5 | This is a multi-language (jsii) library and a command-line tool that allows you 6 | to create Kubernetes operators for CRDs (Custom Resource Definitions) using 7 | CDK8s. 8 | 9 | ## Getting Started 10 | 11 | Let's create our first CRD served by a CDK8s construct using TypeScript. 12 | 13 | ### Install CDK8s 14 | 15 | Make sure your system has the required CDK8s [prerequisites](https://cdk8s.io/docs/latest/getting-started/#prerequisites). 16 | 17 | Install the CDK8s CLI globally through npm: 18 | 19 | ```shell 20 | $ npm i -g cdk8s-cli 21 | Installing... 22 | 23 | # Verify installation 24 | $ cdk8s --version 25 | 1.0.0-beta.3 26 | ``` 27 | 28 | ### Create a new CDK8s app 29 | 30 | Now, let's create a new CDK8s typescript app: 31 | 32 | ```shell 33 | mkdir hello-operator && cd hello-operator 34 | git init 35 | cdk8s init typescript-app 36 | ``` 37 | 38 | ### Install cdk8s-operator 39 | 40 | Next, let's install this module as a dependency of our TypeScript project: 41 | 42 | ```shell 43 | npm install cdk8s-operator 44 | ``` 45 | 46 | ### Construct 47 | 48 | We will start by creating the construct that implements the abstraction. This is 49 | is just a normal CDK8s custom construct: 50 | 51 | Let's create a construct called `PodCollection` which represents a collection of 52 | pods: 53 | 54 | `pod-collection.ts`: 55 | 56 | ```ts 57 | import { Pod } from 'cdk8s-plus-17'; 58 | import { Construct } from 'constructs'; 59 | 60 | export interface PodCollectionProps { 61 | /** Number of pods */ 62 | readonly count: number; 63 | /** The docker image to deploy */ 64 | readonly image: string; 65 | } 66 | 67 | export class PodCollection extends Construct { 68 | constructor(scope: Construct, id: string, props: PodCollectionProps) { 69 | super(scope, id); 70 | 71 | for (let i = 0; i < props.count; ++i) { 72 | new Pod(this, `pod-${i}`, { 73 | containers: [ { image: props.image } ] 74 | }); 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | ### Operator App 81 | 82 | Now, we will need to replace out `main.ts` file with an "operator app", which is 83 | a special kind of CDK8s app designed to be executed by the `cdk8s-server` CLI 84 | which is included in this module. 85 | 86 | The `Operator` app construct can be used to create "CDK8s Operators" which are 87 | CDK8s apps that accept input from a file (or STDIN) with a Kubernetes manifest, 88 | instantiates a construct with the `spec` as its input and emits the resulting 89 | manifest to STDOUT. 90 | 91 | Replace the contents of `main.ts` with the following. We initialize an 92 | `Operator` app and then register a provider which handles resources of API 93 | version `samples.cdk8s.org/v1alpha1` and kind `PodCollection`. 94 | 95 | `main.ts`: 96 | 97 | ```ts 98 | import { Operator } from 'cdk8s-operator'; 99 | import { PodCollection } from './pod-collection'; 100 | 101 | const app = new Operator(); 102 | 103 | app.addProvider({ 104 | apiVersion: 'samples.cdk8s.org/v1alpha1', 105 | kind: 'PodCollection', 106 | handler: { 107 | apply: (scope, id, props) => new PodCollection(scope, id, props) 108 | } 109 | }) 110 | 111 | app.synth(); 112 | ``` 113 | 114 | > A single operator can handle any number of resource kinds. Simply call 115 | > `addProvider()` for each apiVersion/kind. 116 | 117 | ## Using Operators 118 | 119 | To use this operator, create an `input.json` file, e.g: 120 | 121 | `input.json`: 122 | 123 | ```json 124 | { 125 | "apiVersion": "samples.cdk8s.org/v1alpha1", 126 | "kind": "PodCollection", 127 | "metadata": { 128 | "name": "my-collection" 129 | }, 130 | "spec": { 131 | "image": "paulbouwer/hello-kubernetes", 132 | "count": 5 133 | } 134 | } 135 | ``` 136 | 137 | Compile your code: 138 | 139 | ```shell 140 | # delete `main.test.ts` since it has some code that won't compile 141 | $ rm -f main.test.* 142 | 143 | # compile 144 | $ npm run compile 145 | ``` 146 | 147 | And run: 148 | 149 | ```shell 150 | $ node main.js input.json 151 | ``` 152 | 153 |
154 | STDOUT 155 | 156 | ```yaml 157 | apiVersion: "v1" 158 | kind: "Pod" 159 | metadata: 160 | name: "my-collection-pod-0-c8735c52" 161 | spec: 162 | containers: 163 | - env: [] 164 | image: "paulbouwer/hello-kubernetes" 165 | imagePullPolicy: "Always" 166 | name: "main" 167 | ports: [] 168 | volumeMounts: [] 169 | volumes: [] 170 | --- 171 | apiVersion: "v1" 172 | kind: "Pod" 173 | metadata: 174 | name: "my-collection-pod-1-c89f58d7" 175 | spec: 176 | containers: 177 | - env: [] 178 | image: "paulbouwer/hello-kubernetes" 179 | imagePullPolicy: "Always" 180 | name: "main" 181 | ports: [] 182 | volumeMounts: [] 183 | volumes: [] 184 | --- 185 | apiVersion: "v1" 186 | kind: "Pod" 187 | metadata: 188 | name: "my-collection-pod-2-c88d4268" 189 | spec: 190 | containers: 191 | - env: [] 192 | image: "paulbouwer/hello-kubernetes" 193 | imagePullPolicy: "Always" 194 | name: "main" 195 | ports: [] 196 | volumeMounts: [] 197 | volumes: [] 198 | --- 199 | apiVersion: "v1" 200 | kind: "Pod" 201 | metadata: 202 | name: "my-collection-pod-3-c86866b1" 203 | spec: 204 | containers: 205 | - env: [] 206 | image: "paulbouwer/hello-kubernetes" 207 | imagePullPolicy: "Always" 208 | name: "main" 209 | ports: [] 210 | volumeMounts: [] 211 | volumes: [] 212 | --- 213 | apiVersion: "v1" 214 | kind: "Pod" 215 | metadata: 216 | name: "my-collection-pod-4-c8b74b1d" 217 | spec: 218 | containers: 219 | - env: [] 220 | image: "paulbouwer/hello-kubernetes" 221 | imagePullPolicy: "Always" 222 | name: "main" 223 | ports: [] 224 | volumeMounts: [] 225 | volumes: [] 226 | ``` 227 | 228 |
229 | 230 | ## `cdk8s-server` 231 | 232 | This library is shipped with a program called `cdk8s-server` which can be used 233 | to host your operator inside an HTTP server. This server can be used as a 234 | sidecar container with a generic CRD operator (TBD). 235 | 236 | ```shell 237 | $ PORT=8080 npx cdk8s-server 238 | Listening on 8080 239 | - App command: node main.js 240 | - Request body should include a single k8s resource in JSON format 241 | - Request will be piped through STDIN to "node main.js" 242 | - Response is the STDOUT and expected to be a multi-resource yaml manifest 243 | ``` 244 | 245 | Now, you can send `input.json` over HTTP: 246 | 247 | ```shell 248 | $ curl -d @input.json http://localhost:8080 249 | MANIFEST... 250 | ``` 251 | 252 | ## License 253 | 254 | Apache 2.0 255 | 256 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a Vulnerability 2 | 3 | If you discover a potential security issue in this project we ask that you notify the cdk8s team directly via email to cncf-cdk8s-security@lists.cncf.io. 4 | 5 | **Please do not create a public GitHub issue.** -------------------------------------------------------------------------------- /cdk8s.yaml: -------------------------------------------------------------------------------- 1 | operator: node test/fixtures/echo-app.js 2 | 3 | -------------------------------------------------------------------------------- /git-hooks/README.md: -------------------------------------------------------------------------------- 1 | # Git Hooks 2 | 3 | This directory contains git hooks that the core team uses for various tasks. 4 | 5 | - Commit signoff for automatic compliance of the [DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). 6 | 7 | ## Setup 8 | 9 | To setup these git hooks, run `./git-hooks/setup.sh` from the root directory of the project. 10 | -------------------------------------------------------------------------------- /git-hooks/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NAME=$(git config user.name) 4 | EMAIL=$(git config user.email) 5 | 6 | if [ -z "$NAME" ]; then 7 | echo "empty git config user.name" 8 | exit 1 9 | fi 10 | 11 | if [ -z "$EMAIL" ]; then 12 | echo "empty git config user.email" 13 | exit 1 14 | fi 15 | 16 | git interpret-trailers --if-exists doNothing --trailer \ 17 | "Signed-off-by: $NAME <$EMAIL>" \ 18 | --in-place "$1" 19 | -------------------------------------------------------------------------------- /git-hooks/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################## 4 | # Setup shared .git hooks for this project. 5 | # 6 | 7 | hooksdir="$(cd $(dirname $0) && pwd)" 8 | 9 | git config core.hooksPath ${hooksdir} 10 | echo "Configured core.hooksPath to ${hooksdir}" 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk8s-operator", 3 | "description": "Create Kubernetes CRD Operators using CDK8s Constructs", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/cdk8s-team/cdk8s-operator.git" 7 | }, 8 | "bin": { 9 | "cdk8s-server": "lib/cli/cdk8s-server.js" 10 | }, 11 | "scripts": { 12 | "build": "npx projen build", 13 | "bump": "npx projen bump", 14 | "clobber": "npx projen clobber", 15 | "compat": "npx projen compat", 16 | "compile": "npx projen compile", 17 | "default": "npx projen default", 18 | "docgen": "npx projen docgen", 19 | "eject": "npx projen eject", 20 | "eslint": "npx projen eslint", 21 | "package": "npx projen package", 22 | "package-all": "npx projen package-all", 23 | "package:dotnet": "npx projen package:dotnet", 24 | "package:go": "npx projen package:go", 25 | "package:java": "npx projen package:java", 26 | "package:js": "npx projen package:js", 27 | "package:python": "npx projen package:python", 28 | "post-compile": "npx projen post-compile", 29 | "post-upgrade": "npx projen post-upgrade", 30 | "pre-compile": "npx projen pre-compile", 31 | "release": "npx projen release", 32 | "test": "npx projen test", 33 | "test:watch": "npx projen test:watch", 34 | "unbump": "npx projen unbump", 35 | "upgrade-compiler-dependencies": "npx projen upgrade-compiler-dependencies", 36 | "upgrade-configuration": "npx projen upgrade-configuration", 37 | "upgrade-dev-dependencies": "npx projen upgrade-dev-dependencies", 38 | "upgrade-runtime-dependencies": "npx projen upgrade-runtime-dependencies", 39 | "watch": "npx projen watch", 40 | "projen": "npx projen" 41 | }, 42 | "author": { 43 | "name": "Amazon Web Services", 44 | "url": "https://aws.amazon.com", 45 | "organization": false 46 | }, 47 | "devDependencies": { 48 | "@cdk8s/projen-common": "^0.0.608", 49 | "@stylistic/eslint-plugin": "^2", 50 | "@types/jest": "^27", 51 | "@types/node": "16.18.78", 52 | "@typescript-eslint/eslint-plugin": "^8", 53 | "@typescript-eslint/parser": "^8", 54 | "cdk8s": "2.68.91", 55 | "commit-and-tag-version": "^12", 56 | "constructs": "10.3.0", 57 | "eslint": "^9", 58 | "eslint-import-resolver-typescript": "^2.7.1", 59 | "eslint-plugin-import": "^2.31.0", 60 | "jest": "^27", 61 | "jest-junit": "^16", 62 | "jsii": "~5.6.0", 63 | "jsii-diff": "^1.112.0", 64 | "jsii-docgen": "^10.5.0", 65 | "jsii-pacmak": "^1.112.0", 66 | "jsii-rosetta": "~5.6.0", 67 | "projen": "^0.92.10", 68 | "ts-jest": "^27", 69 | "typescript": "^3.9.10" 70 | }, 71 | "peerDependencies": { 72 | "cdk8s": "^2.68.91", 73 | "constructs": "^10" 74 | }, 75 | "dependencies": { 76 | "yaml": "^1.10.2" 77 | }, 78 | "bundledDependencies": [ 79 | "yaml" 80 | ], 81 | "resolutions": { 82 | "**/downlevel-dts/**/typescript": "~5.2.2" 83 | }, 84 | "keywords": [ 85 | "cdk8s", 86 | "crd", 87 | "kubernetes", 88 | "operator" 89 | ], 90 | "engines": { 91 | "node": ">= 16.20.0" 92 | }, 93 | "main": "lib/index.js", 94 | "license": "Apache-2.0", 95 | "publishConfig": { 96 | "access": "public" 97 | }, 98 | "version": "0.0.0", 99 | "jest": { 100 | "coverageProvider": "v8", 101 | "testMatch": [ 102 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 103 | "/@(src|test)/**/__tests__/**/*.ts?(x)" 104 | ], 105 | "clearMocks": true, 106 | "collectCoverage": true, 107 | "coverageReporters": [ 108 | "json", 109 | "lcov", 110 | "clover", 111 | "cobertura", 112 | "text" 113 | ], 114 | "coverageDirectory": "coverage", 115 | "coveragePathIgnorePatterns": [ 116 | "/node_modules/" 117 | ], 118 | "testPathIgnorePatterns": [ 119 | "/node_modules/" 120 | ], 121 | "watchPathIgnorePatterns": [ 122 | "/node_modules/" 123 | ], 124 | "reporters": [ 125 | "default", 126 | [ 127 | "jest-junit", 128 | { 129 | "outputDirectory": "test-reports" 130 | } 131 | ] 132 | ], 133 | "preset": "ts-jest", 134 | "globals": { 135 | "ts-jest": { 136 | "tsconfig": "tsconfig.dev.json" 137 | } 138 | } 139 | }, 140 | "types": "lib/index.d.ts", 141 | "stability": "stable", 142 | "jsii": { 143 | "outdir": "dist", 144 | "targets": { 145 | "java": { 146 | "package": "org.cdk8s.operator", 147 | "maven": { 148 | "groupId": "org.cdk8s", 149 | "artifactId": "cdk8s-operator" 150 | } 151 | }, 152 | "python": { 153 | "distName": "cdk8s-operator", 154 | "module": "cdk8s_operator" 155 | }, 156 | "dotnet": { 157 | "namespace": "Org.Cdk8s.Operator", 158 | "packageId": "Org.Cdk8s.Operator" 159 | }, 160 | "go": { 161 | "moduleName": "github.com/cdk8s-team/cdk8s-operator-go" 162 | } 163 | }, 164 | "tsc": { 165 | "outDir": "lib", 166 | "rootDir": "src" 167 | } 168 | }, 169 | "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\"." 170 | } 171 | -------------------------------------------------------------------------------- /src/cli/cdk8s-server.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // cdk8s-server: http server which synthesizes cdk operators 4 | // ------------------------------------------------------------------------------------ 5 | // request body should include an input manifest for a single resource 6 | // response will include the synthesized output manifest 7 | 8 | import * as fs from 'fs'; 9 | import * as yaml from 'yaml'; 10 | import { Server } from '../server'; 11 | 12 | async function main() { 13 | const command = resolveAppFromCommandLine() ?? resolveAppFromConfig(); 14 | if (!command) { 15 | throw new Error('unable to determine how to execute your CDK8s app. You can either pass a command line as arguments (cdk8s-server COMMAND) or specify the "app" attribute in cdk8s.yaml'); 16 | } 17 | 18 | // needed in docker 19 | process.on('SIGINT', () => process.exit());; 20 | 21 | const server = new Server({ appCommand: command }); 22 | const port = await server.listen(); 23 | 24 | console.error(`Listening on ${port}`); 25 | console.error(`- App command: ${command}`); 26 | console.error('- Request body should include a single k8s resource in JSON format'); 27 | console.error(`- Request will be piped through STDIN to "${command}"`); 28 | console.error('- Response is the STDOUT and expected to be a multi-resource yaml manifest'); 29 | } 30 | 31 | main().catch((e: Error) => { 32 | console.error(e.stack); 33 | process.exit(1); 34 | }); 35 | 36 | function resolveAppFromConfig() { 37 | if (!fs.existsSync('cdk8s.yaml')) { 38 | return undefined; 39 | } 40 | 41 | const config = yaml.parse(fs.readFileSync('cdk8s.yaml', 'utf-8')); 42 | return config.app; 43 | } 44 | 45 | function resolveAppFromCommandLine() { 46 | if (process.argv.length < 3) { 47 | return undefined; 48 | } 49 | 50 | return process.argv.slice(2).join(' '); 51 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './operator'; 2 | export * from './server'; -------------------------------------------------------------------------------- /src/operator.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as os from 'os'; 3 | import * as path from 'path'; 4 | 5 | import { App, Chart } from 'cdk8s'; 6 | import { Construct } from 'constructs'; 7 | 8 | /** 9 | * The handler for this custom resource provider. 10 | */ 11 | export interface ICustomResourceProviderHandler { 12 | // readonly schema: any; 13 | 14 | apply(scope: Construct, id: string, spec: any): Construct; 15 | } 16 | 17 | export interface CustomResourceProvider { 18 | /** 19 | * Kind of this custom resource. 20 | */ 21 | readonly kind: string; 22 | 23 | /** 24 | * API version of the custom resource. 25 | * 26 | * @default "v1" 27 | */ 28 | readonly apiVersion: string; 29 | 30 | /** 31 | * The construct handler. 32 | */ 33 | readonly handler: ICustomResourceProviderHandler; 34 | } 35 | 36 | export interface OperatorProps { 37 | /** 38 | * A Kubernetes JSON manifest with a single resource that is matched against 39 | * one of the providers within this operator. 40 | * 41 | * @default - first position command-line argument or "/dev/stdin" 42 | */ 43 | readonly inputFile?: string; 44 | 45 | /** 46 | * Where to write the synthesized output. 47 | * 48 | * @default "/dev/stdout" 49 | */ 50 | readonly outputFile?: string; 51 | } 52 | 53 | /** 54 | * A CDK8s app which allows implementing Kubernetes operators using CDK8s constructs. 55 | */ 56 | export class Operator extends App { 57 | 58 | private readonly inputFile: string; 59 | private readonly outputFile?: string; 60 | 61 | private readonly providers: CustomResourceProvider[]; 62 | 63 | constructor(props: OperatorProps = {}) { 64 | const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk8s')); 65 | super({ outdir }); 66 | 67 | this.providers = []; 68 | 69 | this.inputFile = props.inputFile ?? process.argv[2] ?? '/dev/stdin'; 70 | this.outputFile = props.outputFile; 71 | } 72 | 73 | /** 74 | * Adds a custom resource provider to this operator. 75 | * @param provider The provider to add 76 | */ 77 | public addProvider(provider: CustomResourceProvider) { 78 | this.providers.push(provider); 79 | } 80 | 81 | /** 82 | * Reads a Kubernetes manifest in JSON format from STDIN or the file specified 83 | * as the first positional command-line argument. This manifest is expected to 84 | * include a single Kubernetes resource. Then, we match `apiVersion` and 85 | * `kind` to one of the registered providers and if we do, we invoke 86 | * `apply()`, passing it the `spec` of the input manifest and a chart as a 87 | * scope. The chart is then synthesized and the output manifest is written to 88 | * STDOUT. 89 | */ 90 | public synth() { 91 | const input = JSON.parse(fs.readFileSync(this.inputFile, 'utf-8')); 92 | 93 | let write; 94 | if (this.outputFile) { 95 | const outfile = this.outputFile; 96 | write = (data: Buffer) => fs.writeFileSync(outfile, data); 97 | } else { 98 | write = (data: Buffer) => process.stdout.write(data); 99 | } 100 | 101 | if (typeof(input) !== 'object') { 102 | throw new Error('input must be a single kubernetes resource'); 103 | } 104 | 105 | const provider = this.findProvider(input); 106 | 107 | const name = input.metadata?.name; 108 | if (!name) { 109 | throw new Error('"metadata.name" must be defined'); 110 | } 111 | 112 | const namespace = input.metadata?.namespace; 113 | 114 | // TODO: namespace 115 | const spec = input.spec ?? {}; 116 | 117 | const chart = new Chart(this, name, { namespace }); 118 | 119 | console.error(`Synthesizing ${input.kind}.${input.apiVersion}`); 120 | provider.handler.apply(chart, name, spec); 121 | 122 | super.synth(); 123 | 124 | for (const file of fs.readdirSync(this.outdir)) { 125 | const filepath = path.join(this.outdir, file); 126 | const manifest = fs.readFileSync(filepath); 127 | write(manifest); 128 | } 129 | } 130 | 131 | private findProvider(input: { kind: string; apiVersion: string }) { 132 | const { apiVersion, kind } = input; 133 | 134 | if (!apiVersion) { 135 | throw new Error('"apiVersion" is required'); 136 | } 137 | 138 | if (!kind) { 139 | throw new Error('"kind" is required'); 140 | } 141 | 142 | for (const p of this.providers) { 143 | if (p.apiVersion === apiVersion && p.kind === kind) { 144 | return p; 145 | } 146 | } 147 | 148 | throw new Error(`No custom resource provider found for ${kind}.${apiVersion}`); 149 | } 150 | } 151 | 152 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | import { createWriteStream, mkdtempSync, readFileSync } from 'fs'; 3 | import { createServer, IncomingMessage, Server as HttpServer, ServerResponse } from 'http'; 4 | import { tmpdir } from 'os'; 5 | import { join } from 'path'; 6 | 7 | export interface ServerProps { 8 | /** 9 | * The command to execute in order to synthesize the CDK app. 10 | */ 11 | readonly appCommand: string; 12 | } 13 | 14 | export class Server { 15 | private readonly server: HttpServer; 16 | private readonly appCommand: string; 17 | private readonly tmpdir: string; 18 | 19 | constructor(props: ServerProps) { 20 | this.appCommand = props.appCommand; 21 | 22 | this.server = createServer((req, res) => this.handleRequest(req, res).catch(e => { 23 | console.error('server error: ', e); 24 | res.statusCode = 500; 25 | res.write(e.message); 26 | res.end(); 27 | })); 28 | 29 | this.tmpdir = mkdtempSync(join(tmpdir(), 'cdk8s-operator-')); 30 | } 31 | 32 | /** 33 | * Starts HTTP server. 34 | * @param port The port to listen to. If not specified, the `PORT` environment 35 | * variable will be used. If that's not specified an available port will be 36 | * auto-selected. 37 | */ 38 | public async listen(port?: number): Promise { 39 | const lport = port ?? process.env.PORT ?? 0; 40 | return new Promise((ok, ko) => { 41 | this.server.listen(lport, () => { 42 | const addr = this.server.address(); 43 | if (typeof(addr) === 'string') { 44 | throw new Error(`cannot determine port from server address ${addr}`); 45 | } 46 | 47 | return ok(addr?.port ?? Number(lport)); 48 | }); 49 | 50 | this.server.on('error', err => ko(err)); 51 | }); 52 | } 53 | 54 | /** 55 | * Stop server. 56 | */ 57 | public close() { 58 | this.server.close(); 59 | } 60 | 61 | private async handleRequest(req: IncomingMessage, res: ServerResponse) { 62 | const inputfile = await this.writeInputFile(req); 63 | 64 | const child = spawn(this.appCommand, [inputfile], { 65 | stdio: ['ignore', 'pipe', 'pipe'], 66 | shell: true, 67 | }); 68 | 69 | const stderr = new Array(); 70 | 71 | res.setHeader('Content-Type', 'application/json'); 72 | 73 | // stdout should go directly to the response 74 | child.stdout.on('data', chunk => { 75 | process.stderr.write('output: ' + chunk); 76 | res.write(chunk); 77 | }); 78 | 79 | // for stderr: write to server terminal and only send back if we exited with a non-zero 80 | child.stderr.on('data', chunk => { 81 | process.stderr.write(chunk); 82 | stderr.push(chunk); 83 | }); 84 | 85 | // will be caused by the async handler and 500 will be returned. 86 | child.on('error', err => { 87 | throw err; 88 | }); 89 | 90 | child.on('exit', code => { 91 | if (code !== 0) { 92 | res.statusCode = 500; 93 | for (const c of stderr) { 94 | res.write(c); 95 | } 96 | res.end(); 97 | } 98 | 99 | // success 100 | return res.end(); 101 | }); 102 | } 103 | 104 | private async writeInputFile(req: IncomingMessage): Promise { 105 | return new Promise((ok, ko) => { 106 | const inputfile = join(this.tmpdir, `input-${Math.round(Math.random() * 999999)}.json`); 107 | const input = createWriteStream(inputfile); 108 | req.pipe(input); 109 | 110 | input.on('close', () => { 111 | try { 112 | const inputJson = JSON.parse(readFileSync(inputfile, 'utf-8')); 113 | console.error(`input: ${JSON.stringify(inputJson)}`); 114 | return ok(inputfile); 115 | } catch (e) { 116 | return ko(new Error(`unable to parse request body as JSON: ${e}`)); 117 | } 118 | }); 119 | 120 | req.on('error', err => ko(err.message)); 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /test/__snapshots__/operator.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`operator with a single provider 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "v1", 7 | "kind": "Pod", 8 | "metadata": Object { 9 | "name": "my-pods-pod-0-c82b84ba", 10 | }, 11 | "spec": Object { 12 | "containers": Array [ 13 | Object { 14 | "image": "paulbouwer/hello-kubernetes", 15 | }, 16 | ], 17 | }, 18 | }, 19 | Object { 20 | "apiVersion": "v1", 21 | "kind": "Pod", 22 | "metadata": Object { 23 | "name": "my-pods-pod-1-c8741f46", 24 | }, 25 | "spec": Object { 26 | "containers": Array [ 27 | Object { 28 | "image": "paulbouwer/hello-kubernetes", 29 | }, 30 | ], 31 | }, 32 | }, 33 | Object { 34 | "apiVersion": "v1", 35 | "kind": "Pod", 36 | "metadata": Object { 37 | "name": "my-pods-pod-2-c820d644", 38 | }, 39 | "spec": Object { 40 | "containers": Array [ 41 | Object { 42 | "image": "paulbouwer/hello-kubernetes", 43 | }, 44 | ], 45 | }, 46 | }, 47 | Object { 48 | "apiVersion": "v1", 49 | "kind": "Pod", 50 | "metadata": Object { 51 | "name": "my-pods-pod-3-c8c7ccdb", 52 | }, 53 | "spec": Object { 54 | "containers": Array [ 55 | Object { 56 | "image": "paulbouwer/hello-kubernetes", 57 | }, 58 | ], 59 | }, 60 | }, 61 | Object { 62 | "apiVersion": "v1", 63 | "kind": "Pod", 64 | "metadata": Object { 65 | "name": "my-pods-pod-4-c81f2857", 66 | }, 67 | "spec": Object { 68 | "containers": Array [ 69 | Object { 70 | "image": "paulbouwer/hello-kubernetes", 71 | }, 72 | ], 73 | }, 74 | }, 75 | ] 76 | `; 77 | -------------------------------------------------------------------------------- /test/fixtures/echo-app.js: -------------------------------------------------------------------------------- 1 | const { assert } = require("console"); 2 | const { readFileSync } = require("fs"); 3 | 4 | assert(process.argv[2]); 5 | 6 | const input = JSON.parse(readFileSync(process.argv[2], 'utf-8')); 7 | 8 | console.log(JSON.stringify({ 9 | apiVersion: 'v1', 10 | kind: 'Echo', 11 | input: input 12 | })); -------------------------------------------------------------------------------- /test/fixtures/pod-collection.ts: -------------------------------------------------------------------------------- 1 | import { ApiObject } from 'cdk8s'; 2 | import { Construct } from 'constructs'; 3 | 4 | /** 5 | * @xrd org.foo/v1.PodCollection 6 | */ 7 | export interface PodCollectionProps { 8 | /** 9 | * Number of pods. 10 | * @default 10 11 | */ 12 | readonly count?: number; 13 | 14 | /** 15 | * The image to use for the containers. 16 | */ 17 | readonly image: string; 18 | } 19 | 20 | export class PodCollection extends Construct { 21 | constructor(scope: Construct, id: string, props: PodCollectionProps) { 22 | super(scope, id); 23 | 24 | for (let i = 0; i < (props.count ?? 10); ++i) { 25 | new ApiObject(this, `pod-${i}`, { 26 | apiVersion: 'v1', 27 | kind: 'Pod', 28 | spec: { 29 | containers: [ 30 | { image: props.image }, 31 | ], 32 | }, 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/operator.test.ts: -------------------------------------------------------------------------------- 1 | import { mkdtempSync, writeFileSync } from 'fs'; 2 | import { tmpdir } from 'os'; 3 | import { join } from 'path'; 4 | import { Yaml } from 'cdk8s'; 5 | import { Operator } from '../src'; 6 | import { PodCollection } from './fixtures/pod-collection'; 7 | 8 | // disable logs 9 | jest.spyOn(console, 'error').mockReturnValue(); 10 | 11 | test('operator with a single provider', () => { 12 | const workdir = mktmpdir(); 13 | const inputFile = join(workdir, 'input.json'); 14 | const outputFile = join(workdir, 'output.json'); 15 | 16 | const operator = new Operator({ 17 | inputFile, 18 | outputFile, 19 | }); 20 | 21 | operator.addProvider({ 22 | apiVersion: 'foo.bar/v1beta1', 23 | kind: 'PodCollection', 24 | handler: { 25 | apply: (scope, id, spec) => new PodCollection(scope, id, spec), 26 | }, 27 | }); 28 | 29 | // ---------------------------------------------------------------- 30 | 31 | writeFileSync(inputFile, JSON.stringify({ 32 | apiVersion: 'foo.bar/v1beta1', 33 | kind: 'PodCollection', 34 | metadata: { 35 | name: 'my-pods', 36 | }, 37 | spec: { 38 | image: 'paulbouwer/hello-kubernetes', 39 | count: 5, 40 | }, 41 | })); 42 | 43 | operator.synth(); 44 | 45 | expect(Yaml.load(outputFile)).toMatchSnapshot(); 46 | }); 47 | 48 | function mktmpdir() { 49 | return mkdtempSync(join(tmpdir(), 'operator.test.')); 50 | } -------------------------------------------------------------------------------- /test/server.test.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import { join } from 'path'; 3 | import { Server } from '../src'; 4 | 5 | const ECHO_APP = `${process.execPath} ${join(__dirname, 'fixtures', 'echo-app.js') }`; 6 | 7 | jest.spyOn(console, 'error').mockReturnValue(); 8 | 9 | let server: Server | undefined; 10 | 11 | afterEach(() => server?.close()); 12 | 13 | test('happy flow', async () => { 14 | server = new Server({ appCommand: ECHO_APP }); 15 | const port = await server.listen(); 16 | const response = await httpPost(port, JSON.stringify({ hello: 'world' })); 17 | expect(response).toStrictEqual({ apiVersion: 'v1', input: { hello: 'world' }, kind: 'Echo' }); 18 | }); 19 | 20 | test('invalid app command', async () => { 21 | server = new Server({ 22 | appCommand: 'boom boom boom', 23 | }); 24 | 25 | const port = await server.listen(); 26 | await expectError(httpPost(port, JSON.stringify({ hello: 'world' })), 'boom'); 27 | }); 28 | 29 | test('invalid input', async () => { 30 | server = new Server({ appCommand: ECHO_APP }); 31 | const port = await server.listen(); 32 | await expectError(httpPost(port, 'INVALID JSON'), 'Internal Server Error: unable to parse request body as JSON: SyntaxError: Unexpected token \'I\', \"INVALID JSON\" is not valid JSON'); 33 | }); 34 | 35 | async function expectError(promise: Promise, expected: string) { 36 | let error; 37 | try { 38 | await promise; 39 | } catch (e) { 40 | error = e; 41 | } 42 | expect(error).toBeTruthy(); 43 | expect(error?.message.trim()).toContain(expected.trim()); 44 | } 45 | 46 | async function httpPost(port: number, body: string) { 47 | return new Promise((ok, ko) => { 48 | const data = new Array(); 49 | const req = http.request({ port, method: 'POST' }, res => { 50 | res.on('error', err => ko(err)); 51 | res.on('data', chunk => data.push(chunk)); 52 | res.on('close', () => { 53 | const response = Buffer.concat(data).toString('utf-8'); 54 | if (res.statusCode !== 200) { 55 | return ko(new Error(`${res.statusMessage}: ${response}`)); 56 | } 57 | 58 | return ok(JSON.parse(response)); 59 | }); 60 | }); 61 | 62 | req.write(body); 63 | req.end(); 64 | }); 65 | } -------------------------------------------------------------------------------- /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 | "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.js" 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "lib", 4 | "rootDir": "src", 5 | "declarationMap": false, 6 | "inlineSourceMap": true, 7 | "inlineSources": true, 8 | "alwaysStrict": true, 9 | "declaration": true, 10 | "experimentalDecorators": true, 11 | "incremental": true, 12 | "lib": [ 13 | "es2020" 14 | ], 15 | "module": "commonjs", 16 | "noEmitOnError": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitAny": true, 19 | "noImplicitReturns": true, 20 | "noImplicitThis": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true, 26 | "strictNullChecks": true, 27 | "strictPropertyInitialization": true, 28 | "stripInternal": false, 29 | "target": "es2020", 30 | "composite": false, 31 | "tsBuildInfoFile": "lib/tsconfig.tsbuildinfo" 32 | }, 33 | "include": [ 34 | "src/**/*.ts" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ], 39 | "_generated_by_jsii_": "Generated by jsii - safe to delete, and ideally should be in .gitignore" 40 | } -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.120" 3 | } 4 | --------------------------------------------------------------------------------