├── projenrc ├── latest-k8s-version.txt └── rotate.ts ├── .github ├── pull_request_template.md ├── workflows │ ├── security.yml │ ├── auto-approve.yml │ ├── triage.yml │ ├── pull-request-lint.yml │ ├── backport.yml │ ├── stale.yml │ ├── upgrade-configuration-k8s-31-main.yml │ ├── upgrade-configuration-k8s-32-main.yml │ ├── upgrade-configuration-k8s-33-main.yml │ ├── upgrade-dev-dependencies-k8s-31-main.yml │ ├── upgrade-dev-dependencies-k8s-32-main.yml │ ├── upgrade-dev-dependencies-k8s-33-main.yml │ ├── upgrade-runtime-dependencies-k8s-31-main.yml │ ├── upgrade-runtime-dependencies-k8s-32-main.yml │ ├── upgrade-runtime-dependencies-k8s-33-main.yml │ ├── upgrade-compiler-dependencies-k8s-31-main.yml │ ├── upgrade-compiler-dependencies-k8s-32-main.yml │ └── upgrade-compiler-dependencies-k8s-33-main.yml └── ISSUE_TEMPLATE │ └── config.yml ├── test ├── fixtures │ ├── flat-directory │ │ ├── file1.txt │ │ └── file2.html │ └── nested-directory │ │ ├── file1.txt │ │ └── nested │ │ └── file2.txt ├── __snapshots__ │ ├── namespace.test.ts.snap │ ├── secret.test.ts.snap │ ├── role.test.ts.snap │ ├── service-account.test.ts.snap │ ├── job.test.ts.snap │ ├── pvc.test.ts.snap │ ├── config-map.test.ts.snap │ ├── service.test.ts.snap │ └── daemon-set.test.ts.snap ├── handler.test.ts ├── base.test.ts ├── namespace.test.ts ├── service-account.test.ts ├── job.test.ts ├── daemon-set.test.ts ├── cron-job.test.ts ├── pvc.test.ts ├── service.test.ts └── probe.test.ts ├── NOTICE ├── CODE_OF_CONDUCT.md ├── OWNERS.md ├── cdk8s.yaml ├── SECURITY.md ├── git-hooks ├── setup.sh ├── README.md └── prepare-commit-msg ├── .backportrc.json ├── .npmignore ├── src ├── index.ts ├── utils.ts ├── _action.ts ├── daemon-set.ts ├── handler.ts ├── job.ts ├── base.ts ├── service-account.ts ├── namespace.ts └── workload.ts ├── docs └── plus │ ├── secret.md │ ├── service-account.md │ ├── service.md │ ├── job.md │ ├── namespace.md │ ├── ingress.md │ ├── pv.md │ ├── pvc.md │ ├── volume.md │ ├── config-map.md │ ├── cronjob.md │ ├── rbac.md │ ├── container.md │ └── deployment.md ├── tsconfig.dev.json ├── .mergify.yml ├── DCO ├── .projen ├── files.json └── deps.json ├── README.md ├── .gitignore ├── .gitattributes ├── .projenrc.ts ├── rotate.md ├── .eslintrc.json └── package.json /projenrc/latest-k8s-version.txt: -------------------------------------------------------------------------------- 1 | 33 -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # -------------------------------------------------------------------------------- /test/fixtures/flat-directory/file1.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /test/fixtures/nested-directory/file1.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /test/fixtures/flat-directory/file2.html: -------------------------------------------------------------------------------- 1 | Hey -------------------------------------------------------------------------------- /test/fixtures/nested-directory/nested/file2.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /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). -------------------------------------------------------------------------------- /OWNERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | * Eli Polonsky [@iliapolo](https://github.com/iliapolo) 4 | * Chris Rybicki [@Chriscbr](https://github.com/Chriscbr) 5 | * Nathan Taber [@tabern](https://github.com/tabern) 6 | -------------------------------------------------------------------------------- /cdk8s.yaml: -------------------------------------------------------------------------------- 1 | output: dist 2 | pluginsDirectory: /home/runner/.cdk8s/plugins 3 | imports: 4 | - k8s@1.27.0 5 | - k8s@1.28.0 6 | - k8s@1.29.0 7 | - k8s@1.30.0 8 | - k8s@1.31.0 9 | - k8s@1.32.0 10 | - k8s@1.33.0 11 | -------------------------------------------------------------------------------- /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.** -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.backportrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "commitConflicts": true, 3 | "targetPRLabels": [ 4 | "backport", 5 | "auto-approve" 6 | ], 7 | "backportBranchName": "backport/{{targetBranch}}-{{refValues}}", 8 | "prTitle": "{{sourcePullRequest.title}} (backport #{{sourcePullRequest.number}})", 9 | "targetBranchChoices": [ 10 | "k8s-32/main", 11 | "k8s-31/main" 12 | ], 13 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 14 | } 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /.mergify.yml 10 | /test/ 11 | /tsconfig.dev.json 12 | /src/ 13 | !/lib/ 14 | !/lib/**/*.js 15 | !/lib/**/*.d.ts 16 | dist 17 | /tsconfig.json 18 | /.github/ 19 | /.vscode/ 20 | /.idea/ 21 | /.projenrc.js 22 | tsconfig.tsbuildinfo 23 | /.eslintrc.json 24 | !.jsii 25 | .backportrc.json 26 | /.gitattributes 27 | /.projenrc.ts 28 | /projenrc 29 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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 | -------------------------------------------------------------------------------- /test/__snapshots__/namespace.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`can select all namespaces 1`] = `Object {}`; 4 | 5 | exports[`can select namespaces 1`] = ` 6 | Object { 7 | "matchExpressions": Array [ 8 | Object { 9 | "key": "web", 10 | "operator": "Exists", 11 | "values": undefined, 12 | }, 13 | ], 14 | "matchLabels": Object { 15 | "foo": "bar", 16 | }, 17 | } 18 | `; 19 | 20 | exports[`defaults 1`] = ` 21 | Array [ 22 | Object { 23 | "apiVersion": "v1", 24 | "kind": "Namespace", 25 | "metadata": Object { 26 | "name": "test-namespace-c83f04e1", 27 | }, 28 | "spec": Object {}, 29 | }, 30 | ] 31 | `; 32 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'cdk8s-automation') 18 | steps: 19 | - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /test/__snapshots__/secret.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`can grant permissions on imported 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "rbac.authorization.k8s.io/v1", 7 | "kind": "Role", 8 | "metadata": Object { 9 | "name": "test-role-c8eba390", 10 | }, 11 | "rules": Array [ 12 | Object { 13 | "apiGroups": Array [ 14 | "", 15 | ], 16 | "resourceNames": Array [ 17 | "secret", 18 | ], 19 | "resources": Array [ 20 | "secrets", 21 | ], 22 | "verbs": Array [ 23 | "get", 24 | "list", 25 | "watch", 26 | ], 27 | }, 28 | ], 29 | }, 30 | ] 31 | `; 32 | -------------------------------------------------------------------------------- /projenrc/rotate.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | export function rotate(latestVersion: string) { 5 | 6 | const latestVersionNumber = Number(latestVersion); 7 | 8 | // replace references in docs/plus/** 9 | // all version references here appear in the form: cdk8s-plus-XX 10 | const docsFileNames = fs.readdirSync('docs/plus/', { encoding: 'utf-8' }); 11 | docsFileNames.forEach(function (filePath) { 12 | const curFilePath = path.join('docs/plus', filePath); 13 | let curFileData = fs.readFileSync(curFilePath, 'utf-8'); 14 | curFileData = curFileData.replace(new RegExp(`cdk8s-plus-${latestVersionNumber - 1}`, 'g'), `cdk8s-plus-${latestVersion}`); 15 | fs.writeFileSync(curFilePath, curFileData); 16 | }); 17 | } 18 | 19 | rotate(process.argv.slice(2)[0]); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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-plus/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/workflows/triage.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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-plus') && (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 | -------------------------------------------------------------------------------- /test/handler.test.ts: -------------------------------------------------------------------------------- 1 | import { Container, Handler, k8s } from '../src'; 2 | 3 | test('fromCommand', () => { 4 | const container = new Container({ image: 'image' }); 5 | const handler = Handler.fromCommand(['hello']); 6 | expect(handler._toKube(container).exec).toEqual({ command: ['hello'] }); 7 | }); 8 | 9 | test('fromHttpGet', () => { 10 | const container = new Container({ image: 'image' }); 11 | const handler = Handler.fromHttpGet('/path'); 12 | expect(handler._toKube(container).httpGet).toEqual({ path: '/path', port: k8s.IntOrString.fromNumber(80), scheme: 'HTTP' }); 13 | }); 14 | 15 | test('fromTcpSocket', () => { 16 | const container = new Container({ image: 'image' }); 17 | const handler = Handler.fromTcpSocket({ port: 8888 }); 18 | expect(handler._toKube(container).tcpSocket).toEqual({ port: k8s.IntOrString.fromNumber(8888) }); 19 | }); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base'; 2 | export * from './config-map'; 3 | export * from './container'; 4 | export * from './cron-job'; 5 | export * from './deployment'; 6 | export * from './job'; 7 | export * from './pod'; 8 | export * from './secret'; 9 | export * from './service-account'; 10 | export * from './service'; 11 | export * from './stateful-set'; 12 | export * from './volume'; 13 | export * from './ingress'; 14 | export * from './probe'; 15 | export * from './pvc'; 16 | export * from './pv'; 17 | export * from './handler'; 18 | export * from './horizontal-pod-autoscaler'; 19 | export * from './workload'; 20 | export * from './daemon-set'; 21 | export * from './role'; 22 | export * from './role-binding'; 23 | export * from './network-policy'; 24 | export * from './namespace'; 25 | 26 | export * from './api-resource'; 27 | export * as k8s from './imports/k8s'; 28 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v6 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | requireScope: false 31 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { IConstruct } from 'constructs'; 2 | 3 | export function undefinedIfEmpty(obj: T): T | undefined { 4 | if (typeof(obj) === 'string' && obj === '') { return undefined; } 5 | if (Array.isArray(obj) && obj.length === 0) { return undefined; } 6 | if (typeof(obj) === 'object' && (Object.keys(obj as unknown as object).length === 0 || Object.values(obj as unknown as object).filter(x => x).length === 0)) { return undefined; } 7 | return obj; 8 | } 9 | 10 | export function filterUndefined(obj: any): any { 11 | const ret: any = {}; 12 | for (const [k, v] of Object.entries(obj)) { 13 | if (v !== undefined) { 14 | ret[k] = v; 15 | } 16 | } 17 | return ret; 18 | } 19 | 20 | export function address(...constructs: IConstruct[]) { 21 | const addresses = constructs 22 | .map(c => c.node.addr) 23 | .sort((a, b) => a.localeCompare(b)); 24 | return addresses.join(''); 25 | } 26 | -------------------------------------------------------------------------------- /docs/plus/secret.md: -------------------------------------------------------------------------------- 1 | # Secret 2 | 3 | Secrets are used to store confidential information. Never store such information on the definition of the pod itself. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#secret) 7 | 8 | ## Use an existing `Secret` 9 | 10 | To reference a secret created outside of your deployment definition, use the following. Note that this does not create a new object, 11 | and will therefore not be included in the resulting manifest. 12 | 13 | ```typescript 14 | import * as kplus from 'cdk8s-plus-33'; 15 | 16 | const secret = kplus.Secret.fromSecretName('aws-creds'); 17 | ``` 18 | 19 | ## Adding data 20 | 21 | To create a new secret with some data, use: 22 | 23 | ```typescript 24 | import * as kplus from 'cdk8s-plus-33'; 25 | import * as k from 'cdk8s'; 26 | 27 | const app = new k.App(); 28 | const chart = new k.Chart(app, 'Chart'); 29 | 30 | const secret = new kplus.Secret(chart, 'Secret'); 31 | secret.addStringData('password', 'some-encrypted-data'); 32 | ``` 33 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2020" 12 | ], 13 | "module": "CommonJS", 14 | "noEmitOnError": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": true, 25 | "stripInternal": true, 26 | "target": "ES2020" 27 | }, 28 | "include": [ 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.ts", 32 | "projenrc/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /test/base.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, ApiObject } from 'cdk8s'; 2 | import { Construct } from 'constructs'; 3 | import { Resource, ResourceProps, k8s } from '../src'; 4 | 5 | test('Can mutate metadata', () => { 6 | 7 | interface CustomProps extends ResourceProps { 8 | 9 | } 10 | 11 | class Custom extends Resource { 12 | 13 | public readonly apiObject: ApiObject; 14 | 15 | public readonly resourceType = 'configmaps'; 16 | 17 | constructor(scope: Construct, id: string, props: CustomProps) { 18 | super(scope, id); 19 | 20 | this.apiObject = new k8s.KubeConfigMap(this, 'ConfigMap', { 21 | metadata: props.metadata, 22 | }); 23 | } 24 | } 25 | 26 | const chart = Testing.chart(); 27 | 28 | const custom = new Custom(chart, 'Custom', {}); 29 | 30 | custom.metadata.addLabel('key', 'value'); 31 | 32 | expect(Testing.synth(chart)).toStrictEqual([{ 33 | apiVersion: 'v1', 34 | kind: 'ConfigMap', 35 | metadata: { 36 | labels: { 37 | key: 'value', 38 | }, 39 | name: 'test-custom-configmap-c8a1da6c', 40 | }, 41 | }]); 42 | 43 | }); -------------------------------------------------------------------------------- /.github/workflows/backport.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: backport 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - unlabeled 9 | - closed 10 | jobs: 11 | backport: 12 | name: Backport PR 13 | runs-on: ubuntu-latest 14 | permissions: {} 15 | if: github.event.pull_request.merged == true && !(contains(github.event.pull_request.labels.*.name, 'backport')) 16 | steps: 17 | - name: Check for backport labels 18 | id: check_labels 19 | run: |- 20 | labels='${{ toJSON(github.event.pull_request.labels.*.name) }}' 21 | matched=$(echo $labels | jq '.|map(select(startswith("backport-to-"))) | length') 22 | echo "matched=$matched" 23 | echo "matched=$matched" >> $GITHUB_OUTPUT 24 | shell: bash 25 | - name: Backport Action 26 | if: fromJSON(steps.check_labels.outputs.matched) > 0 27 | uses: sqren/backport-github-action@v9.5.1 28 | with: 29 | github_token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 30 | auto_backport_label_prefix: backport-to- 31 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | queue_conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | - status-success=package-js 11 | - status-success=package-java 12 | - status-success=package-python 13 | - status-success=package-dotnet 14 | - status-success=package-go 15 | merge_method: squash 16 | commit_message_template: |- 17 | {{ title }} (#{{ number }}) 18 | 19 | {{ body }} 20 | pull_request_rules: 21 | - name: Automatic merge on approval and successful build 22 | actions: 23 | delete_head_branch: {} 24 | queue: 25 | name: default 26 | conditions: 27 | - "#approved-reviews-by>=1" 28 | - -label~=(do-not-merge) 29 | - status-success=build 30 | - status-success=package-js 31 | - status-success=package-java 32 | - status-success=package-python 33 | - status-success=package-dotnet 34 | - status-success=package-go 35 | merge_queue: 36 | max_parallel_checks: 1 37 | -------------------------------------------------------------------------------- /docs/plus/service-account.md: -------------------------------------------------------------------------------- 1 | # ServiceAccount 2 | 3 | Use service accounts to provide an identity for pods. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#serviceaccount) 7 | 8 | ## Use an existing `ServiceAccount` 9 | 10 | To reference a service account created outside of your deployment definition, use the following. Note that this does not create a new object, 11 | and will therefore not be included in the resulting manifest. 12 | 13 | ```typescript 14 | import * as kplus from 'cdk8s-plus-33'; 15 | 16 | const serviceAccount = kplus.ServiceAccount.fromServiceAccountName('aws-service'); 17 | ``` 18 | 19 | ## Allowing access to secrets 20 | 21 | To create a new service account, and give it access to some secrets, use the following: 22 | 23 | ```typescript 24 | import * as kplus from 'cdk8s-plus-33'; 25 | import * as k from 'cdk8s'; 26 | 27 | const app = new k.App(); 28 | const chart = new k.Chart(app, 'Chart'); 29 | 30 | const awsCreds = kplus.Secret.fromSecretName('aws-creds'); 31 | const awsService = new kplus.ServiceAccount(chart, 'AWS'); 32 | 33 | // give access to the aws creds secret. 34 | awsService.addSecret(awsCreds); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/plus/service.md: -------------------------------------------------------------------------------- 1 | # Service 2 | 3 | Use services when you want to expose a set of pods using a stable network 4 | identity. They can also be used for externalizing endpoints to clients outside 5 | of the kubernetes cluster. 6 | 7 | !!! tip "" 8 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#service) 9 | 10 | ## Selectors 11 | 12 | Services must be configured with selectors that tell it which pods should it serve. 13 | The most common selector method is using labels. 14 | 15 | ```typescript 16 | import * as k from 'cdk8s'; 17 | import * as kplus from 'cdk8s-plus-33'; 18 | 19 | const app = new k.App(); 20 | const chart = new k.Chart(app, 'Chart'); 21 | const frontends = new kplus.Service(chart, 'FrontEnds'); 22 | 23 | // this will cause the service to select all pods with the 'run: frontend' label. 24 | frontends.select(kplus.LabelSelector.equals('run', 'frontend')); 25 | ``` 26 | 27 | ## Ports 28 | 29 | Ports that the service will listen and redirect to can be configured like so: 30 | 31 | ```typescript 32 | import * as k from 'cdk8s'; 33 | import * as kplus from 'cdk8s-plus-33'; 34 | 35 | const app = new k.App(); 36 | const chart = new k.Chart(app, 'Chart'); 37 | const frontends = new kplus.Service(chart, 'FrontEnds'); 38 | 39 | // make the service bind to port 9000 and redirect to port 80 on the associated containers. 40 | frontends.bind({port: 9000, targetPort: 80}) 41 | ``` 42 | -------------------------------------------------------------------------------- /test/namespace.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, ApiObject } from 'cdk8s'; 2 | import { Node } from 'constructs'; 3 | import * as kplus from '../src'; 4 | 5 | test('defaultChild', () => { 6 | 7 | const chart = Testing.chart(); 8 | 9 | const defaultChild = Node.of(new kplus.Namespace(chart, 'Namespace')) 10 | .defaultChild as ApiObject; 11 | 12 | expect(defaultChild.kind).toEqual('Namespace'); 13 | 14 | }); 15 | 16 | test('defaults', () => { 17 | 18 | const chart = Testing.chart(); 19 | 20 | new kplus.Namespace(chart, 'Namespace'); 21 | 22 | expect(Testing.synth(chart)).toMatchSnapshot(); 23 | }); 24 | 25 | test('can select namespaces', () => { 26 | const chart = Testing.chart(); 27 | const namespaces = kplus.Namespaces.select(chart, 'Namespaces', { 28 | labels: { foo: 'bar' }, 29 | expressions: [kplus.LabelExpression.exists('web')], 30 | names: ['web'], 31 | }); 32 | expect(namespaces.toNamespaceSelectorConfig().names).toEqual(['web']); 33 | expect(namespaces.toNamespaceSelectorConfig()?.labelSelector?._toKube()).toMatchSnapshot(); 34 | }); 35 | 36 | test('can select all namespaces', () => { 37 | const chart = Testing.chart(); 38 | const namespaces = kplus.Namespaces.all(chart, 'All'); 39 | expect(namespaces.toNamespaceSelectorConfig().names).toBeUndefined(); 40 | expect(namespaces.toNamespaceSelectorConfig()?.labelSelector?._toKube()).toMatchSnapshot(); 41 | }); 42 | -------------------------------------------------------------------------------- /docs/plus/job.md: -------------------------------------------------------------------------------- 1 | # Job 2 | 3 | Jobs are a very useful concept in kubernetes deployments. 4 | They can be used for add-hoc provisioning tasks, as well as long running processing jobs. 5 | 6 | In configuration, they don't differ much from regular pods, but offer some 7 | additional properties. 8 | 9 | !!! tip "" 10 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#job) 11 | 12 | ## Delete a Job after its finished 13 | 14 | You can configure a TTL for the job after it finished its execution successfully. 15 | 16 | ```typescript 17 | import * as kplus from 'cdk8s-plus-33'; 18 | import { Construct } from 'constructs'; 19 | import { App, Chart, ChartProps, Duration } from 'cdk8s'; 20 | 21 | export class MyChart extends Chart { 22 | constructor(scope: Construct, id: string, props: ChartProps = { }) { 23 | super(scope, id, props); 24 | 25 | // let's define a job spec, and set a 1 second TTL. 26 | const job = new kplus.Job(this, 'LoadData', { 27 | ttlAfterFinished: Duration.seconds(1) 28 | }); 29 | 30 | // now add a container to all the pods created by this job 31 | job.addContainer({ 32 | image: 'loader' 33 | }); 34 | } 35 | } 36 | 37 | const app = new App(); 38 | new MyChart(app, 'Job'); 39 | app.synth(); 40 | ``` 41 | 42 | ## Scheduling 43 | 44 | See [Deployment scheduling](./deployment.md#scheduling). 45 | 46 | ## Connections 47 | 48 | See [Pod connections](./pod.md#connections). 49 | -------------------------------------------------------------------------------- /test/__snapshots__/role.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ClusterRole can grant permissions on imported 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "rbac.authorization.k8s.io/v1", 7 | "kind": "Role", 8 | "metadata": Object { 9 | "name": "test-role-c8eba390", 10 | }, 11 | "rules": Array [ 12 | Object { 13 | "apiGroups": Array [ 14 | "rbac.authorization.k8s.io", 15 | ], 16 | "resourceNames": Array [ 17 | "r2", 18 | ], 19 | "resources": Array [ 20 | "clusterroles", 21 | ], 22 | "verbs": Array [ 23 | "get", 24 | "list", 25 | "watch", 26 | ], 27 | }, 28 | ], 29 | }, 30 | ] 31 | `; 32 | 33 | exports[`Role can grant permissions on imported 1`] = ` 34 | Array [ 35 | Object { 36 | "apiVersion": "rbac.authorization.k8s.io/v1", 37 | "kind": "Role", 38 | "metadata": Object { 39 | "name": "test-role-c8eba390", 40 | }, 41 | "rules": Array [ 42 | Object { 43 | "apiGroups": Array [ 44 | "rbac.authorization.k8s.io", 45 | ], 46 | "resourceNames": Array [ 47 | "r2", 48 | ], 49 | "resources": Array [ 50 | "roles", 51 | ], 52 | "verbs": Array [ 53 | "get", 54 | "list", 55 | "watch", 56 | ], 57 | }, 58 | ], 59 | }, 60 | ] 61 | `; 62 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /src/_action.ts: -------------------------------------------------------------------------------- 1 | import { Container } from './container'; 2 | import * as k8s from './imports/k8s'; 3 | import { ConnectionScheme, HttpHeader } from './probe'; 4 | 5 | /** 6 | * Utility class to implement the conversion between our API and the k8s action 7 | * structure. Used both for probes and handlers. 8 | * 9 | * @internal 10 | */ 11 | export class Action { 12 | 13 | public static fromTcpSocket(container: Container, options: { port?: number; host?: string } = {}): k8s.TcpSocketAction { 14 | return { 15 | port: k8s.IntOrString.fromNumber(options.port ?? container.portNumber ?? 80), 16 | host: options.host, 17 | }; 18 | } 19 | 20 | public static fromCommand(command: string[]): k8s.ExecAction { 21 | return { command }; 22 | } 23 | 24 | public static fromHttpGet(container: Container, path: string, options: { 25 | port?: number; 26 | scheme?: ConnectionScheme; 27 | host?: string; 28 | httpHeaders?: HttpHeader[]; 29 | } = {}): k8s.HttpGetAction { 30 | return { 31 | path, 32 | port: k8s.IntOrString.fromNumber(options.port ?? container.portNumber ?? 80), 33 | scheme: options.scheme ?? ConnectionScheme.HTTP, 34 | host: options.host, 35 | httpHeaders: options.httpHeaders, 36 | }; 37 | } 38 | 39 | public static fromGrpc(container: Container, options: { port?: number; service?: string } = {}): k8s.GrpcAction { 40 | return { 41 | port: options.port ?? container.portNumber ?? 80, 42 | service: options.service, 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts 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 | -------------------------------------------------------------------------------- /test/__snapshots__/service-account.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`can grant permissions on imported 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "rbac.authorization.k8s.io/v1", 7 | "kind": "Role", 8 | "metadata": Object { 9 | "name": "test-role-c8eba390", 10 | }, 11 | "rules": Array [ 12 | Object { 13 | "apiGroups": Array [ 14 | "", 15 | ], 16 | "resourceNames": Array [ 17 | "service-account", 18 | ], 19 | "resources": Array [ 20 | "serviceaccounts", 21 | ], 22 | "verbs": Array [ 23 | "get", 24 | "list", 25 | "watch", 26 | ], 27 | }, 28 | ], 29 | }, 30 | ] 31 | `; 32 | 33 | exports[`role can bind to imported 1`] = ` 34 | Array [ 35 | Object { 36 | "apiVersion": "rbac.authorization.k8s.io/v1", 37 | "kind": "Role", 38 | "metadata": Object { 39 | "name": "test-role-c8eba390", 40 | }, 41 | "rules": Array [], 42 | }, 43 | Object { 44 | "apiVersion": "rbac.authorization.k8s.io/v1", 45 | "kind": "RoleBinding", 46 | "metadata": Object { 47 | "name": "rolebindingc8d4456c17d6b366ba98c773dfc8a963b7-c8f143a2", 48 | }, 49 | "roleRef": Object { 50 | "apiGroup": "rbac.authorization.k8s.io", 51 | "kind": "Role", 52 | "name": "test-role-c8eba390", 53 | }, 54 | "subjects": Array [ 55 | Object { 56 | "apiGroup": "", 57 | "kind": "ServiceAccount", 58 | "name": "sa-name", 59 | "namespace": "kube-system", 60 | }, 61 | ], 62 | }, 63 | ] 64 | `; 65 | -------------------------------------------------------------------------------- /docs/plus/namespace.md: -------------------------------------------------------------------------------- 1 | # Namespace 2 | 3 | Namespaces provides a mechanism for isolating groups of resources within a single cluster. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#namespace) 7 | 8 | ## Create a `Namespace` 9 | 10 | To create a new namespace in the cluster: 11 | 12 | ```ts 13 | import * as kplus from 'cdk8s-plus-33'; 14 | import * as k from 'cdk8s'; 15 | 16 | const app = new k.App(); 17 | const chart = new k.Chart(app, 'Chart'); 18 | 19 | const namespace = new kplus.Namespace(chart, 'BackOfficeNamespace'); 20 | ``` 21 | 22 | Like any other resource, if you don't specify a name, cdk8s will auto-generate one, which you 23 | can access by `namespace.name`. 24 | 25 | ## Select namespaces 26 | 27 | Namespaces can also be selected by various mechanisms. These selections are often used in other 28 | cdk8s+ API's, such as [pod selection](./pod.md#pod-selection) during scheduling. 29 | 30 | ### Select namespaces by name 31 | 32 | Select a namespace called `backoffice`. 33 | 34 | ```ts 35 | import * as kplus from 'cdk8s-plus-33'; 36 | 37 | const backoffice = kplus.Namespaces.select(this, 'Backoffice', { names: ['backoffice'] }); 38 | ``` 39 | 40 | ### Select namespace by labels 41 | 42 | Select all namespaces that have the `processing=batch` label. 43 | 44 | ```ts 45 | import * as kplus from 'cdk8s-plus-33'; 46 | 47 | const batch = kplus.Namespaces.select(this, 'Batch', { labels: { processing: 'batch'} }); 48 | ``` 49 | 50 | ### Select all namespaces 51 | 52 | Select all namespaces in the cluster. 53 | 54 | ```ts 55 | import * as kplus from 'cdk8s-plus-33'; 56 | 57 | const all = kplus.Namespaces.all(); 58 | ``` 59 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".backportrc.json", 4 | ".eslintrc.json", 5 | ".gitattributes", 6 | ".github/ISSUE_TEMPLATE/config.yml", 7 | ".github/pull_request_template.md", 8 | ".github/workflows/auto-approve.yml", 9 | ".github/workflows/backport.yml", 10 | ".github/workflows/build.yml", 11 | ".github/workflows/pull-request-lint.yml", 12 | ".github/workflows/release-k8s.33.yml", 13 | ".github/workflows/security.yml", 14 | ".github/workflows/stale.yml", 15 | ".github/workflows/triage.yml", 16 | ".github/workflows/upgrade-compiler-dependencies-k8s-31-main.yml", 17 | ".github/workflows/upgrade-compiler-dependencies-k8s-32-main.yml", 18 | ".github/workflows/upgrade-compiler-dependencies-k8s-33-main.yml", 19 | ".github/workflows/upgrade-configuration-k8s-31-main.yml", 20 | ".github/workflows/upgrade-configuration-k8s-32-main.yml", 21 | ".github/workflows/upgrade-configuration-k8s-33-main.yml", 22 | ".github/workflows/upgrade-dev-dependencies-k8s-31-main.yml", 23 | ".github/workflows/upgrade-dev-dependencies-k8s-32-main.yml", 24 | ".github/workflows/upgrade-dev-dependencies-k8s-33-main.yml", 25 | ".github/workflows/upgrade-runtime-dependencies-k8s-31-main.yml", 26 | ".github/workflows/upgrade-runtime-dependencies-k8s-32-main.yml", 27 | ".github/workflows/upgrade-runtime-dependencies-k8s-33-main.yml", 28 | ".gitignore", 29 | ".mergify.yml", 30 | ".projen/deps.json", 31 | ".projen/files.json", 32 | ".projen/tasks.json", 33 | "CODE_OF_CONDUCT.md", 34 | "DCO", 35 | "git-hooks/prepare-commit-msg", 36 | "git-hooks/README.md", 37 | "git-hooks/setup.sh", 38 | "LICENSE", 39 | "SECURITY.md", 40 | "tsconfig.dev.json" 41 | ], 42 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cdk8s+ (cdk8s-plus) 2 | 3 | ### High level constructs for Kubernetes 4 | 5 | ![Stability:Stable](https://img.shields.io/badge/stability-stable-success) 6 | 7 | | k8s version | npm (JS/TS) | PyPI (Python) | Maven (Java) | Go | 8 | | ----------- | --------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------- | 9 | | 1.31.0 | [Link](https://www.npmjs.com/package/cdk8s-plus-31) | [Link](https://pypi.org/project/cdk8s-plus-31/) | [Link](https://search.maven.org/artifact/org.cdk8s/cdk8s-plus-31) | [Link](https://github.com/cdk8s-team/cdk8s-plus-go/tree/k8s.31) | 10 | | 1.32.0 | [Link](https://www.npmjs.com/package/cdk8s-plus-32) | [Link](https://pypi.org/project/cdk8s-plus-32/) | [Link](https://search.maven.org/artifact/org.cdk8s/cdk8s-plus-32) | [Link](https://github.com/cdk8s-team/cdk8s-plus-go/tree/k8s.32) | 11 | | 1.33.0 | [Link](https://www.npmjs.com/package/cdk8s-plus-33) | [Link](https://pypi.org/project/cdk8s-plus-33/) | [Link](https://search.maven.org/artifact/org.cdk8s/cdk8s-plus-33) | [Link](https://github.com/cdk8s-team/cdk8s-plus-go/tree/k8s.33) | 12 | 13 | **cdk8s+** is a software development framework that provides high level 14 | abstractions for authoring Kubernetes applications. Built on top of the auto 15 | generated building blocks provided by [cdk8s](../cdk8s), this library includes a 16 | hand crafted *construct* for each native kubernetes object, exposing richer 17 | API's with reduced complexity. 18 | 19 | ## :books: Documentation 20 | 21 | See [cdk8s.io](https://cdk8s.io/docs/latest/plus). 22 | 23 | ## :raised_hand: Contributing 24 | 25 | If you'd like to add a new feature or fix a bug, please visit 26 | [CONTRIBUTING.md](CONTRIBUTING.md)! 27 | 28 | ## :balance_scale: License 29 | 30 | This project is distributed under the [Apache License, Version 2.0](./LICENSE). 31 | 32 | This module is part of the [cdk8s project](https://github.com/cdk8s-team). 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | /test-reports/ 35 | junit.xml 36 | /coverage/ 37 | !/.github/workflows/build.yml 38 | /dist/changelog.md 39 | /dist/version.txt 40 | !/.github/workflows/release-k8s.33.yml 41 | !/.mergify.yml 42 | !/.github/workflows/upgrade-runtime-dependencies-k8s-33-main.yml 43 | !/.github/workflows/upgrade-runtime-dependencies-k8s-32-main.yml 44 | !/.github/workflows/upgrade-runtime-dependencies-k8s-31-main.yml 45 | !/.github/pull_request_template.md 46 | !/test/ 47 | !/tsconfig.dev.json 48 | !/src/ 49 | /lib 50 | /dist/ 51 | !/.eslintrc.json 52 | .jsii 53 | tsconfig.json 54 | !/API.md 55 | !/CODE_OF_CONDUCT.md 56 | !/DCO 57 | !/git-hooks/prepare-commit-msg 58 | !/git-hooks/README.md 59 | !/git-hooks/setup.sh 60 | !/.github/ISSUE_TEMPLATE/config.yml 61 | !/SECURITY.md 62 | !/.github/workflows/security.yml 63 | !/.github/workflows/triage.yml 64 | !/.github/workflows/stale.yml 65 | !/.github/workflows/upgrade-configuration-k8s-33-main.yml 66 | !/.github/workflows/upgrade-configuration-k8s-32-main.yml 67 | !/.github/workflows/upgrade-configuration-k8s-31-main.yml 68 | !/.github/workflows/upgrade-dev-dependencies-k8s-33-main.yml 69 | !/.github/workflows/upgrade-dev-dependencies-k8s-32-main.yml 70 | !/.github/workflows/upgrade-dev-dependencies-k8s-31-main.yml 71 | !/.github/workflows/upgrade-compiler-dependencies-k8s-33-main.yml 72 | !/.github/workflows/upgrade-compiler-dependencies-k8s-32-main.yml 73 | !/.github/workflows/upgrade-compiler-dependencies-k8s-31-main.yml 74 | !/.backportrc.json 75 | !/.github/workflows/backport.yml 76 | .vscode/ 77 | .idea/ 78 | docs/typescript.md 79 | docs/python.md 80 | docs/java.md 81 | !/.projenrc.ts 82 | -------------------------------------------------------------------------------- /test/__snapshots__/job.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Can be isolated 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "batch/v1", 7 | "kind": "Job", 8 | "metadata": Object { 9 | "name": "test-job-c8ab3340", 10 | }, 11 | "spec": Object { 12 | "template": Object { 13 | "metadata": Object { 14 | "labels": Object { 15 | "cdk8s.io/metadata.addr": "test-Job-c8ebb678", 16 | }, 17 | }, 18 | "spec": Object { 19 | "automountServiceAccountToken": false, 20 | "containers": Array [ 21 | Object { 22 | "image": "foobar", 23 | "imagePullPolicy": "Always", 24 | "name": "main", 25 | "resources": Object { 26 | "limits": Object { 27 | "cpu": "1500m", 28 | "memory": "2048Mi", 29 | }, 30 | "requests": Object { 31 | "cpu": "1000m", 32 | "memory": "512Mi", 33 | }, 34 | }, 35 | "securityContext": Object { 36 | "allowPrivilegeEscalation": false, 37 | "privileged": false, 38 | "readOnlyRootFilesystem": true, 39 | "runAsNonRoot": true, 40 | }, 41 | }, 42 | ], 43 | "dnsPolicy": "ClusterFirst", 44 | "hostNetwork": false, 45 | "restartPolicy": "Never", 46 | "securityContext": Object { 47 | "fsGroupChangePolicy": "Always", 48 | "runAsNonRoot": true, 49 | }, 50 | "setHostnameAsFQDN": false, 51 | "shareProcessNamespace": false, 52 | "terminationGracePeriodSeconds": 30, 53 | }, 54 | }, 55 | }, 56 | }, 57 | Object { 58 | "apiVersion": "networking.k8s.io/v1", 59 | "kind": "NetworkPolicy", 60 | "metadata": Object { 61 | "name": "test-job-defaultdenyall-c8a9d814", 62 | }, 63 | "spec": Object { 64 | "podSelector": Object { 65 | "matchLabels": Object { 66 | "cdk8s.io/metadata.addr": "test-Job-c8ebb678", 67 | }, 68 | }, 69 | "policyTypes": Array [ 70 | "Egress", 71 | "Ingress", 72 | ], 73 | }, 74 | }, 75 | ] 76 | `; 77 | -------------------------------------------------------------------------------- /src/daemon-set.ts: -------------------------------------------------------------------------------- 1 | import { ApiObject, Lazy } from 'cdk8s'; 2 | import { Construct } from 'constructs'; 3 | import * as k8s from './imports/k8s'; 4 | import * as workload from './workload'; 5 | 6 | /** 7 | * Properties for `DaemonSet`. 8 | */ 9 | export interface DaemonSetProps extends workload.WorkloadProps { 10 | 11 | /** 12 | * Minimum number of seconds for which a newly created pod should 13 | * be ready without any of its container crashing, for it to be considered available. 14 | * 15 | * @default 0 16 | */ 17 | readonly minReadySeconds?: number; 18 | 19 | } 20 | 21 | /** 22 | * A DaemonSet ensures that all (or some) Nodes run a copy of a Pod. 23 | * As nodes are added to the cluster, Pods are added to them. 24 | * As nodes are removed from the cluster, those Pods are garbage collected. 25 | * Deleting a DaemonSet will clean up the Pods it created. 26 | * 27 | * Some typical uses of a DaemonSet are: 28 | * 29 | * - running a cluster storage daemon on every node 30 | * - running a logs collection daemon on every node 31 | * - running a node monitoring daemon on every node 32 | * 33 | * In a simple case, one DaemonSet, covering all nodes, would be used for each type of daemon. 34 | * A more complex setup might use multiple DaemonSets for a single type of daemon, 35 | * but with different flags and/or different memory and cpu requests for different hardware types. 36 | */ 37 | export class DaemonSet extends workload.Workload { 38 | 39 | /** 40 | * @see base.Resource.apiObject 41 | */ 42 | protected readonly apiObject: ApiObject; 43 | 44 | public readonly resourceType = 'daemonsets'; 45 | 46 | public readonly minReadySeconds: number; 47 | 48 | constructor(scope: Construct, id: string, props: DaemonSetProps = {}) { 49 | super(scope, id, props); 50 | 51 | this.apiObject = new k8s.KubeDaemonSet(this, 'Resource', { 52 | metadata: props.metadata, 53 | spec: Lazy.any({ produce: () => this._toKube() }), 54 | }); 55 | 56 | this.minReadySeconds = props.minReadySeconds ?? 0; 57 | 58 | if (this.isolate) { 59 | this.connections.isolate(); 60 | } 61 | } 62 | 63 | /** 64 | * @internal 65 | */ 66 | public _toKube(): k8s.DaemonSetSpec { 67 | return { 68 | minReadySeconds: this.minReadySeconds, 69 | template: { 70 | metadata: this.podMetadata.toJson(), 71 | spec: this._toPodSpec(), 72 | }, 73 | selector: this._toLabelSelector(), 74 | }; 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.backportrc.json linguist-generated 6 | /.eslintrc.json linguist-generated 7 | /.gitattributes linguist-generated 8 | /.github/ISSUE_TEMPLATE/config.yml linguist-generated 9 | /.github/pull_request_template.md linguist-generated 10 | /.github/workflows/auto-approve.yml linguist-generated 11 | /.github/workflows/backport.yml linguist-generated 12 | /.github/workflows/build.yml linguist-generated 13 | /.github/workflows/pull-request-lint.yml linguist-generated 14 | /.github/workflows/release-k8s.33.yml linguist-generated 15 | /.github/workflows/security.yml linguist-generated 16 | /.github/workflows/stale.yml linguist-generated 17 | /.github/workflows/triage.yml linguist-generated 18 | /.github/workflows/upgrade-compiler-dependencies-k8s-31-main.yml linguist-generated 19 | /.github/workflows/upgrade-compiler-dependencies-k8s-32-main.yml linguist-generated 20 | /.github/workflows/upgrade-compiler-dependencies-k8s-33-main.yml linguist-generated 21 | /.github/workflows/upgrade-configuration-k8s-31-main.yml linguist-generated 22 | /.github/workflows/upgrade-configuration-k8s-32-main.yml linguist-generated 23 | /.github/workflows/upgrade-configuration-k8s-33-main.yml linguist-generated 24 | /.github/workflows/upgrade-dev-dependencies-k8s-31-main.yml linguist-generated 25 | /.github/workflows/upgrade-dev-dependencies-k8s-32-main.yml linguist-generated 26 | /.github/workflows/upgrade-dev-dependencies-k8s-33-main.yml linguist-generated 27 | /.github/workflows/upgrade-runtime-dependencies-k8s-31-main.yml linguist-generated 28 | /.github/workflows/upgrade-runtime-dependencies-k8s-32-main.yml linguist-generated 29 | /.github/workflows/upgrade-runtime-dependencies-k8s-33-main.yml linguist-generated 30 | /.gitignore linguist-generated 31 | /.mergify.yml linguist-generated 32 | /.npmignore linguist-generated 33 | /.projen/** linguist-generated 34 | /.projen/deps.json linguist-generated 35 | /.projen/files.json linguist-generated 36 | /.projen/tasks.json linguist-generated 37 | /API.md linguist-generated 38 | /CODE_OF_CONDUCT.md linguist-generated 39 | /DCO linguist-generated 40 | /git-hooks/prepare-commit-msg linguist-generated 41 | /git-hooks/README.md linguist-generated 42 | /git-hooks/setup.sh linguist-generated 43 | /LICENSE linguist-generated 44 | /package.json linguist-generated 45 | /SECURITY.md linguist-generated 46 | /tsconfig.dev.json linguist-generated 47 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /docs/plus/ingress.md: -------------------------------------------------------------------------------- 1 | # Ingress 2 | 3 | [Ingress] manages external access to services in a cluster, typically through 4 | HTTP. Ingress may provide load balancing, SSL termination and name-based virtual 5 | hosting. 6 | 7 | !!! tip "" 8 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#ingressv1beta1) 9 | 10 | You must have an [Ingress controller] to satisfy an Ingress. Only creating an 11 | Ingress resource has no effect. 12 | 13 | [Ingress]: https://kubernetes.io/docs/concepts/services-networking/ingress/ 14 | [Ingress controller]: https://kubernetes.io/docs/concepts/services-networking/ingress-controllers 15 | 16 | The following example will route HTTP requests sent to the `/hello` url prefix 17 | to a service associated with a deployment of the 18 | [hashicorp/http-echo](https://github.com/hashicorp/http-echo) image. 19 | 20 | ```typescript 21 | import * as kplus from 'cdk8s-plus-33'; 22 | import { Construct } from 'constructs'; 23 | import { App, Chart, ChartProps } from 'cdk8s'; 24 | 25 | export class MyChart extends Chart { 26 | constructor(scope: Construct, id: string, props: ChartProps = { }) { 27 | super(scope, id, props); 28 | 29 | const helloDeployment = new kplus.Deployment(this, "Deployment", { 30 | containers: [ 31 | { 32 | image: 'hashicorp/http-echo', 33 | args: [ '-text', 'hello ingress' ], 34 | portNumber: 5678, 35 | } 36 | ] 37 | }); 38 | 39 | const helloService = helloDeployment.exposeViaService({ 40 | ports: [ 41 | { 42 | port: 5678, 43 | } 44 | ] 45 | }); 46 | 47 | const ingress = new kplus.Ingress(this, 'ingress'); 48 | ingress.addRule('/hello', kplus.IngressBackend.fromService(helloService)); 49 | } 50 | } 51 | 52 | const app = new App(); 53 | new MyChart(app, 'ingress'); 54 | app.synth(); 55 | ``` 56 | 57 | You can use `addHostRule(host, path, backend)` to define a route that will only 58 | apply to requests with this `Host` header. This can be used to implement virtual 59 | hosts. 60 | 61 | The `addDefaultBackend(backend)` and `addHostDefaultBackend(host, backend)` 62 | methods can be used to define backends that will accept all requests that do not 63 | match any other rules. 64 | 65 | The TCP port used to route requests to services will be determined based on 66 | which ports are exposed by the service (e.g. through `serve()`). If the service 67 | exposes multiple ports, then a port must be specified via 68 | `IngressBackend.fromService(service, { port })`. 69 | -------------------------------------------------------------------------------- /src/handler.ts: -------------------------------------------------------------------------------- 1 | import * as _action from './_action'; 2 | import * as container from './container'; 3 | import * as k8s from './imports/k8s'; 4 | 5 | /** 6 | * Options for `Handler.fromTcpSocket`. 7 | */ 8 | export interface HandlerFromTcpSocketOptions { 9 | /** 10 | * The TCP port to connect to on the container. 11 | * 12 | * @default - defaults to `container.port`. 13 | */ 14 | readonly port?: number; 15 | 16 | /** 17 | * The host name to connect to on the container. 18 | * 19 | * @default - defaults to the pod IP 20 | */ 21 | readonly host?: string; 22 | 23 | } 24 | 25 | /** 26 | * Options for `Handler.fromHttpGet`. 27 | */ 28 | export interface HandlerFromHttpGetOptions { 29 | 30 | /** 31 | * The TCP port to use when sending the GET request. 32 | * 33 | * @default - defaults to `container.port`. 34 | */ 35 | readonly port?: number; 36 | 37 | } 38 | 39 | /** 40 | * Defines a specific action that should be taken. 41 | */ 42 | export class Handler { 43 | 44 | /** 45 | * Defines a handler based on an HTTP GET request to the IP address of the container. 46 | * 47 | * @param path The URL path to hit 48 | * @param options Options 49 | */ 50 | public static fromHttpGet(path: string, options: HandlerFromHttpGetOptions = {}): Handler { 51 | return new Handler(undefined, undefined, { path, ...options }); 52 | } 53 | 54 | /** 55 | * Defines a handler based on a command which is executed within the container. 56 | * 57 | * @param command The command to execute 58 | */ 59 | public static fromCommand(command: string[]): Handler { 60 | return new Handler(undefined, { command }, undefined); 61 | } 62 | 63 | /** 64 | * Defines a handler based opening a connection to a TCP socket on the container. 65 | * 66 | * @param options Options 67 | */ 68 | public static fromTcpSocket(options: HandlerFromTcpSocketOptions = {}): Handler { 69 | return new Handler(options, undefined, undefined); 70 | } 71 | 72 | private constructor( 73 | private readonly tcpSocketOptions?: HandlerFromTcpSocketOptions, 74 | private readonly commandOptions?: { command: string[] }, 75 | private readonly httpGetOptions?: { path: string } & HandlerFromHttpGetOptions) {} 76 | 77 | /** 78 | * @internal 79 | */ 80 | public _toKube(cont: container.Container): k8s.LifecycleHandler { 81 | 82 | const exec = this.commandOptions ? _action.Action.fromCommand(this.commandOptions.command) : undefined; 83 | const httpGet = this.httpGetOptions ? _action.Action.fromHttpGet(cont, this.httpGetOptions.path, this.httpGetOptions) : undefined; 84 | const tcpSocket = this.tcpSocketOptions ? _action.Action.fromTcpSocket(cont, this.tcpSocketOptions) : undefined; 85 | 86 | return { exec, httpGet, tcpSocket }; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /docs/plus/pv.md: -------------------------------------------------------------------------------- 1 | # PersistentVolume 2 | 3 | A `PersistentVolume` (PV) is a piece of storage in the cluster that has been provisioned by an administrator or dynamically provisioned using Storage Classes. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#persistent-volume) 7 | 8 | PV's are used by pods via the pod's `volumes` spec, just like regular [volumes](./volume.md). 9 | They are not intended to be interchangable with volumes, you can think of a `PersistentVolume` 10 | as a specific type of volume, that is detached from a pod's lifecycle, and exist even if the pod is shutdown. 11 | 12 | The `PersistentVolume` construct represents a pre-existing volume in the cluster. 13 | 14 | ## Types 15 | 16 | Each type is implmented as its own construct, exposing both common properties as well as type 17 | specific ones. Currently the supported types are: 18 | 19 | - `AwsElasticBlockStorePersistentVolume` 20 | - `AzureDiskPersistentVolume` 21 | - `GCEPersistentDiskPersistentVolume` 22 | 23 | For example, to create a PV from an existing AWS EBS volume: 24 | 25 | ```ts 26 | import * as kplus from 'cdk8s-plus-33'; 27 | import * as cdk8s from 'cdk8s'; 28 | 29 | const vol = new kplus.AwsElasticBlockStorePersistentVolume(chart, 'Volume', { 30 | // must exist in aws 31 | volumeId: 'vol1234', 32 | 33 | // assign the volume to small-ebs storage class 34 | storageClassName: 'small-ebs', 35 | 36 | // what is the volume storage 37 | storage: cdk8s.Size.gibibytes(50), 38 | }); 39 | ``` 40 | 41 | Note that this **does not** actually create a new volume, it merely manifests an existing 42 | volume in AWS as a Kubernetes resource. 43 | 44 | ## Reserve 45 | 46 | > See https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reserving-a-persistentvolume 47 | 48 | Once the PV is defined, you can reserve it: 49 | 50 | ```ts 51 | const claim = vol.reserve(); 52 | ``` 53 | 54 | This method creates a new `PersistentVolumeClaim` and performs a 55 | bi-directional binding that reserves the volume for usage. 56 | You can use the claim to mount a volume onto a container like usual: 57 | 58 | ```ts 59 | container.mount('/data', kplus.Volume.fromPersistentVolumeClaim(claim)); 60 | ``` 61 | 62 | You can also directly mount a persistent volume, which will implicitly reserve it 63 | and create a volume from the created claim: 64 | 65 | ```ts 66 | const vol = new kplus.AwsElasticBlockStorePersistentVolume(chart, 'Volume', { volumeId: 'vol1234' }); 67 | container.mount('/data', vol); 68 | ``` 69 | 70 | ## Bind 71 | 72 | Binding is a part of the reservation process, but it only creates a one directional link. 73 | You can use it to bind a PV to an existing PVC. Note however that if the PVC is not bound to the PV, 74 | there's no guarantee this volume will indeed be given that specific claim. 75 | 76 | ```ts 77 | const claim = kplus.PersistentVolumeClaim.fromClaimName('claim'); 78 | 79 | // will modify the vol resource to refer to the claim. 80 | // but no the other way around. 81 | vol.bind(claim); 82 | ``` 83 | -------------------------------------------------------------------------------- /test/__snapshots__/pvc.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`can be bounded to a volume at instantiation 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "v1", 7 | "kind": "PersistentVolumeClaim", 8 | "metadata": Object { 9 | "name": "test-persistentvolumeclaim-c8af0974", 10 | }, 11 | "spec": Object { 12 | "volumeMode": "Filesystem", 13 | "volumeName": "vol", 14 | }, 15 | }, 16 | ] 17 | `; 18 | 19 | exports[`can be bounded to a volume post instantiation 1`] = ` 20 | Array [ 21 | Object { 22 | "apiVersion": "v1", 23 | "kind": "PersistentVolumeClaim", 24 | "metadata": Object { 25 | "name": "test-persistentvolumeclaim-c8af0974", 26 | }, 27 | "spec": Object { 28 | "volumeMode": "Filesystem", 29 | "volumeName": "vol", 30 | }, 31 | }, 32 | ] 33 | `; 34 | 35 | exports[`can grant permissions on imported 1`] = ` 36 | Array [ 37 | Object { 38 | "apiVersion": "rbac.authorization.k8s.io/v1", 39 | "kind": "Role", 40 | "metadata": Object { 41 | "name": "test-role-c8eba390", 42 | }, 43 | "rules": Array [ 44 | Object { 45 | "apiGroups": Array [ 46 | "", 47 | ], 48 | "resourceNames": Array [ 49 | "claim", 50 | ], 51 | "resources": Array [ 52 | "persistentvolumeclaims", 53 | ], 54 | "verbs": Array [ 55 | "get", 56 | "list", 57 | "watch", 58 | ], 59 | }, 60 | ], 61 | }, 62 | ] 63 | `; 64 | 65 | exports[`custom 1`] = ` 66 | Array [ 67 | Object { 68 | "apiVersion": "v1", 69 | "kind": "PersistentVolumeClaim", 70 | "metadata": Object { 71 | "name": "test-persistentvolumeclaim-c8af0974", 72 | }, 73 | "spec": Object { 74 | "accessModes": Array [ 75 | "ReadWriteMany", 76 | ], 77 | "resources": Object { 78 | "requests": Object { 79 | "storage": "50Gi", 80 | }, 81 | }, 82 | "storageClassName": "storage-class", 83 | "volumeMode": "Block", 84 | }, 85 | }, 86 | ] 87 | `; 88 | 89 | exports[`defaults 1`] = ` 90 | Array [ 91 | Object { 92 | "apiVersion": "v1", 93 | "kind": "PersistentVolumeClaim", 94 | "metadata": Object { 95 | "name": "test-persistentvolumeclaim-c8af0974", 96 | }, 97 | "spec": Object { 98 | "volumeMode": "Filesystem", 99 | }, 100 | }, 101 | ] 102 | `; 103 | 104 | exports[`small size 1`] = ` 105 | Array [ 106 | Object { 107 | "apiVersion": "v1", 108 | "kind": "PersistentVolumeClaim", 109 | "metadata": Object { 110 | "name": "test-persistentvolumeclaim-c8af0974", 111 | }, 112 | "spec": Object { 113 | "resources": Object { 114 | "requests": Object { 115 | "storage": "0.5Gi", 116 | }, 117 | }, 118 | "volumeMode": "Filesystem", 119 | }, 120 | }, 121 | ] 122 | `; 123 | -------------------------------------------------------------------------------- /docs/plus/pvc.md: -------------------------------------------------------------------------------- 1 | # PersistentVolumeClaim 2 | 3 | A `PersistentVolumeClaim` (PVC) is a request for storage by a pod. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#persistent-volume-claim) 7 | 8 | A `PersistentVolumeClaim` contains the requirements of the request, and the Kubernetes control plane is responsible providing a physical volume that satisfies the claim's requirements. 9 | 10 | ```ts 11 | import * as kplus from 'cdk8s-plus-33'; 12 | import * as cdk8s from 'cdk8s'; 13 | 14 | const pod = new kplus.Pod(chart, 'Pod'); 15 | const container = pod.addContainer({ image: 'node' }); 16 | 17 | // create the storage request 18 | const claim = new kplus.PersistentVolumeClaim(chart, 'Claim', { 19 | storage: cdk8s.Size.gibibytes(50), 20 | }); 21 | 22 | // mount a volume based on the request to the container 23 | // this will also add the volume itself to the pod spec. 24 | container.mount('/data', kplus.Volume.fromPersistentVolumeClaim(claim)); 25 | ``` 26 | 27 | ## Storage Class 28 | 29 | By default, the `storageClassName` property of a claim is not set. 30 | This means that the backing volume can be provided by one of two methods: 31 | 32 | 1. Dynamically provision a volume with the default storage class. 33 | 2. If a default storage class is not configured in the cluster, the backing 34 | volume must pre-exist and not be assigned to any storage class. 35 | 36 | > See [Provisioning](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#provisioning) for more details. 37 | 38 | You can also provide an explicit storage class name, 39 | 40 | ```ts 41 | const claim = new kplus.PersistentVolumeClaim(chart, 'Claim', { 42 | storage: cdk8s.Size.gibibytes(50), 43 | storageClassName: 'large-ebs', 44 | }); 45 | ``` 46 | 47 | In this case, Kubernetes control plane will either locate an existing volume with the `larg-ebs` storage class, or dynamically provision a new using the appropriate provisioner. 48 | 49 | You can also pass in a special `""` value, this means the volume must not be assigned to any storage class. 50 | Since all dynamically provisioend volumes belong to a storage class, setting this value effectively disables 51 | dynamic provisioning for this claim. 52 | 53 | ```ts 54 | const claim = new kplus.PersistentVolumeClaim(chart, 'Claim', { 55 | storage: cdk8s.Size.gibibytes(50), 56 | // disable dynamic provisioning 57 | storageClassName: "", 58 | }); 59 | ``` 60 | 61 | ## Bind 62 | 63 | Binding is a part of the reservation process, but it only creates a one directional link. 64 | You can use it to bind a PVC to an existing PV. Note however that if the PV is not bound to the PVC, 65 | there's no guarantee this claim will indeed be given to that specific volume. 66 | 67 | ```ts 68 | const claim = new kplus.PersistentVolumeClaim(chart, 'Claim', { 69 | storage: cdk8s.Size.gibibytes(50), 70 | }); 71 | 72 | const vol = kplus.PersistentVolume.fromPersistentVolumeName('vol'); 73 | 74 | // will modify the claim resource to refer to the volume. 75 | // but no the other way around. 76 | claim.bind(vol); 77 | ``` 78 | -------------------------------------------------------------------------------- /test/__snapshots__/config-map.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`addDirectory() adds all files in a directory to the config map "exclude" exclusion via globs 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "v1", 7 | "data": Object { 8 | "file1.txt": "Hello, world!", 9 | }, 10 | "immutable": false, 11 | "kind": "ConfigMap", 12 | "metadata": Object { 13 | "name": "test-my-config-map-c8eaefa4", 14 | }, 15 | }, 16 | ] 17 | `; 18 | 19 | exports[`addDirectory() adds all files in a directory to the config map "prefix" can be used to prefix keys" 1`] = ` 20 | Array [ 21 | Object { 22 | "apiVersion": "v1", 23 | "data": Object { 24 | "prefix.file1.txt": "Hello, world!", 25 | "prefix.file2.html": "Hey", 26 | }, 27 | "immutable": false, 28 | "kind": "ConfigMap", 29 | "metadata": Object { 30 | "name": "test-my-config-map-c8eaefa4", 31 | }, 32 | }, 33 | ] 34 | `; 35 | 36 | exports[`addDirectory() adds all files in a directory to the config map keys are based on file names 1`] = ` 37 | Array [ 38 | Object { 39 | "apiVersion": "v1", 40 | "data": Object { 41 | "file1.txt": "Hello, world!", 42 | "file2.html": "Hey", 43 | }, 44 | "immutable": false, 45 | "kind": "ConfigMap", 46 | "metadata": Object { 47 | "name": "test-my-config-map-c8eaefa4", 48 | }, 49 | }, 50 | ] 51 | `; 52 | 53 | exports[`addDirectory() adds all files in a directory to the config map sub-directories are skipped 1`] = ` 54 | Array [ 55 | Object { 56 | "apiVersion": "v1", 57 | "data": Object { 58 | "file1.txt": "Hello, world!", 59 | }, 60 | "immutable": false, 61 | "kind": "ConfigMap", 62 | "metadata": Object { 63 | "name": "test-my-config-map-c8eaefa4", 64 | }, 65 | }, 66 | ] 67 | `; 68 | 69 | exports[`addFile() adds local files to the config map 1`] = ` 70 | Array [ 71 | Object { 72 | "apiVersion": "v1", 73 | "data": Object { 74 | "file1.txt": "Hello, world!", 75 | "hey-there": "Hey", 76 | }, 77 | "immutable": false, 78 | "kind": "ConfigMap", 79 | "metadata": Object { 80 | "name": "test-my-config-map-c8eaefa4", 81 | }, 82 | }, 83 | ] 84 | `; 85 | 86 | exports[`can grant permissions on imported 1`] = ` 87 | Array [ 88 | Object { 89 | "apiVersion": "rbac.authorization.k8s.io/v1", 90 | "kind": "Role", 91 | "metadata": Object { 92 | "name": "test-role-c8eba390", 93 | }, 94 | "rules": Array [ 95 | Object { 96 | "apiGroups": Array [ 97 | "", 98 | ], 99 | "resourceNames": Array [ 100 | "name", 101 | ], 102 | "resources": Array [ 103 | "configmaps", 104 | ], 105 | "verbs": Array [ 106 | "get", 107 | "list", 108 | "watch", 109 | ], 110 | }, 111 | ], 112 | }, 113 | ] 114 | `; 115 | -------------------------------------------------------------------------------- /.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": "cdk8s", 33 | "type": "build" 34 | }, 35 | { 36 | "name": "cdk8s-cli", 37 | "type": "build" 38 | }, 39 | { 40 | "name": "commit-and-tag-version", 41 | "version": "^12", 42 | "type": "build" 43 | }, 44 | { 45 | "name": "constructs", 46 | "version": "^10.0.0", 47 | "type": "build" 48 | }, 49 | { 50 | "name": "eslint-import-resolver-typescript", 51 | "type": "build" 52 | }, 53 | { 54 | "name": "eslint-plugin-import", 55 | "type": "build" 56 | }, 57 | { 58 | "name": "eslint", 59 | "version": "^9", 60 | "type": "build" 61 | }, 62 | { 63 | "name": "jest", 64 | "type": "build" 65 | }, 66 | { 67 | "name": "jest-junit", 68 | "version": "^16", 69 | "type": "build" 70 | }, 71 | { 72 | "name": "jsii-diff", 73 | "type": "build" 74 | }, 75 | { 76 | "name": "jsii-docgen", 77 | "version": "^10.5.0", 78 | "type": "build" 79 | }, 80 | { 81 | "name": "jsii-pacmak", 82 | "type": "build" 83 | }, 84 | { 85 | "name": "jsii-rosetta", 86 | "version": "~5.8.0", 87 | "type": "build" 88 | }, 89 | { 90 | "name": "jsii", 91 | "version": "~5.8.0", 92 | "type": "build" 93 | }, 94 | { 95 | "name": "projen", 96 | "type": "build" 97 | }, 98 | { 99 | "name": "snake-case", 100 | "type": "build" 101 | }, 102 | { 103 | "name": "ts-jest", 104 | "type": "build" 105 | }, 106 | { 107 | "name": "ts-node", 108 | "type": "build" 109 | }, 110 | { 111 | "name": "typescript", 112 | "type": "build" 113 | }, 114 | { 115 | "name": "minimatch", 116 | "type": "bundled" 117 | }, 118 | { 119 | "name": "**/downlevel-dts/**/typescript", 120 | "version": "~5.2.2", 121 | "type": "override" 122 | }, 123 | { 124 | "name": "cdk8s", 125 | "type": "peer" 126 | }, 127 | { 128 | "name": "constructs", 129 | "type": "peer" 130 | }, 131 | { 132 | "name": "minimatch", 133 | "type": "runtime" 134 | } 135 | ], 136 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 137 | } 138 | -------------------------------------------------------------------------------- /test/service-account.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, ApiObject } from 'cdk8s'; 2 | import { Node } from 'constructs'; 3 | import * as kplus from '../src'; 4 | 5 | test('can grant permissions on imported', () => { 6 | 7 | const chart = Testing.chart(); 8 | const sa = kplus.ServiceAccount.fromServiceAccountName(chart, 'ServiceAccount', 'service-account'); 9 | 10 | const role = new kplus.Role(chart, 'Role'); 11 | role.allowRead(sa); 12 | 13 | expect(Testing.synth(chart)).toMatchSnapshot(); 14 | 15 | }); 16 | 17 | test('role can bind to imported', () => { 18 | 19 | const chart = Testing.chart(); 20 | const role = new kplus.Role(chart, 'Role'); 21 | 22 | const sa = kplus.ServiceAccount.fromServiceAccountName(chart, 'ServiceAccount', 'sa-name', { 23 | namespaceName: 'kube-system', 24 | }); 25 | 26 | role.bind(sa); 27 | 28 | expect(Testing.synth(chart)).toMatchSnapshot(); 29 | 30 | }); 31 | 32 | test('defaultChild', () => { 33 | const chart = Testing.chart(); 34 | 35 | const defaultChild = Node.of( 36 | new kplus.ServiceAccount(chart, 'ServiceAccount'), 37 | ).defaultChild as ApiObject; 38 | 39 | expect(defaultChild.kind).toEqual('ServiceAccount'); 40 | }); 41 | 42 | test('minimal definition', () => { 43 | // GIVEN 44 | const chart = Testing.chart(); 45 | 46 | // WHEN 47 | const sa = new kplus.ServiceAccount(chart, 'my-service-account'); 48 | 49 | // THEN 50 | expect(sa.automountToken).toBeFalsy(); 51 | expect(Testing.synth(chart)).toMatchInlineSnapshot(` 52 | Array [ 53 | Object { 54 | "apiVersion": "v1", 55 | "automountServiceAccountToken": false, 56 | "kind": "ServiceAccount", 57 | "metadata": Object { 58 | "name": "test-my-service-account-c84bb46b", 59 | }, 60 | }, 61 | ] 62 | `); 63 | }); 64 | 65 | test('secrets can be added to the service account', () => { 66 | // GIVEN 67 | const chart = Testing.chart(); 68 | const secret1 = kplus.Secret.fromSecretName(chart, 'Secret1', 'my-secret-1'); 69 | const secret2 = kplus.Secret.fromSecretName(chart, 'Secret2', 'my-secret-2'); 70 | 71 | // WHEN 72 | const sa = new kplus.ServiceAccount(chart, 'my-service-account', { 73 | secrets: [secret1], 74 | }); 75 | 76 | sa.addSecret(secret2); 77 | 78 | // THEN 79 | const manifest = Testing.synth(chart); 80 | expect(manifest[0]?.secrets[0]).toStrictEqual({ name: 'my-secret-1' }); 81 | expect(manifest[0]?.secrets[1]).toStrictEqual({ name: 'my-secret-2' }); 82 | }); 83 | 84 | test('auto mounting token can be disabled', () => { 85 | 86 | const chart = Testing.chart(); 87 | const sa = new kplus.ServiceAccount(chart, 'my-service-account', { 88 | automountToken: false, 89 | }); 90 | 91 | expect(sa.automountToken).toBeFalsy(); 92 | expect(Testing.synth(chart)).toMatchInlineSnapshot(` 93 | Array [ 94 | Object { 95 | "apiVersion": "v1", 96 | "automountServiceAccountToken": false, 97 | "kind": "ServiceAccount", 98 | "metadata": Object { 99 | "name": "test-my-service-account-c84bb46b", 100 | }, 101 | }, 102 | ] 103 | `); 104 | 105 | }); -------------------------------------------------------------------------------- /docs/plus/volume.md: -------------------------------------------------------------------------------- 1 | # Volume 2 | 3 | Volume represents a named volume in a pod that may be accessed by any container in the pod. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#volume) 7 | 8 | ## Create from a ConfigMap 9 | 10 | A very useful operation is to create a volume from a `ConfigMap`. Kubernetes will translate every key in the config map to a file, 11 | who's content is the value of the key. 12 | 13 | ```typescript 14 | import * as kplus from 'cdk8s-plus-33'; 15 | 16 | const configMap = kplus.ConfigMap.fromConfigMapName('redis-config'); 17 | const configVolume = kplus.Volume.fromConfigMap(configMap); 18 | ``` 19 | 20 | ## Create from an EmptyDir 21 | 22 | The easiest way to allocate some persistent storage to your container is to create a volume from an empty directory. 23 | This volume, as the name suggests, is initially empty, and can be written to by containers who mount it. 24 | The data in the volume is preserved throughout the lifecycle of the pod, but is deleted forever as soon as the pod itself is removed. 25 | 26 | ```typescript 27 | import * as kplus from 'cdk8s-plus-33'; 28 | 29 | const data = kplus.Volume.fromEmptyDir(configMap); 30 | 31 | const pod = new kplus.Pod(this, 'Pod'); 32 | const redis = pod.addContainer({ 33 | image: 'redis' 34 | }) 35 | 36 | // mount to the redis container. 37 | redis.mount('/var/lib/redis', data); 38 | ``` 39 | 40 | ## Using PersistentVolumeClaim Templates with StatefulSets 41 | 42 | When working with StatefulSets, you can use PersistentVolumeClaim templates to create stable storage for each pod in the StatefulSet. This allows each pod to have its own storage that persists even if the pod is rescheduled to a different node. 43 | 44 | ```typescript 45 | import * as kplus from 'cdk8s-plus-32'; 46 | import { Size } from 'cdk8s'; 47 | 48 | const dataVolume = Volume.fromName(chart, "pvc-template-data", "data-volume") 49 | 50 | // Create a StatefulSet with a PVC template 51 | const statefulSet = new kplus.StatefulSet(this, 'StatefulSet', { 52 | containers: [{ 53 | image: 'nginx', 54 | volumeMounts: [{ 55 | volume: dataVolume, 56 | path: '/data', 57 | }, { 58 | volume: Volume.fromName(chart, "pvc-template-temp", "temp-volume"), 59 | path: '/data', 60 | }], 61 | }], 62 | // Define PVC templates during initialization 63 | volumeClaimTemplates: [{ 64 | name: dataVolume.name, 65 | storage: Size.gibibytes(10), 66 | accessModes: [kplus.PersistentVolumeAccessMode.READ_WRITE_ONCE], 67 | storageClassName: 'standard', // Optional: Specify the storage class 68 | }, { 69 | name: 'temp-volume', // Must match a volume mount name in a container 70 | storage: Size.gibibytes(10), 71 | accessModes: [kplus.PersistentVolumeAccessMode.READ_WRITE_ONCE], 72 | storageClassName: 'standard', // Optional: Specify the storage class 73 | }], 74 | }); 75 | 76 | // Or add PVC templates after creation 77 | statefulSet.addVolumeClaimTemplate({ 78 | name: 'logs-volume', 79 | storage: Size.gibibytes(5), 80 | accessModes: [kplus.PersistentVolumeAccessMode.READ_WRITE_ONCE], 81 | }); 82 | ``` 83 | 84 | Each pod in the StatefulSet will get its own PVC instance based on these templates, with names like `data-volume-my-statefulset-0`, `data-volume-my-statefulset-1`, etc. 85 | -------------------------------------------------------------------------------- /docs/plus/config-map.md: -------------------------------------------------------------------------------- 1 | # ConfigMap 2 | 3 | ConfigMap are used to store configuration data. They provide a dictionary based 4 | data structure that can be consumed in various shapes and forms. 5 | 6 | !!! tip "" 7 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#configmap) 8 | 9 | ## Use an existing `ConfigMap` 10 | 11 | You can reference to an existing `ConfigMap` like so. Note that this does not create a new object, 12 | and will therefore not be included in the resulting manifest. 13 | 14 | ```typescript 15 | import * as kplus from 'cdk8s-plus-33'; 16 | import { Construct } from 'constructs'; 17 | import { App, Chart, ChartProps } from 'cdk8s'; 18 | 19 | export class MyChart extends Chart { 20 | constructor(scope: Construct, id: string, props?: ChartProps) { 21 | super(scope, id, props); 22 | 23 | const config: kplus.IConfigMap = kplus.ConfigMap.fromConfigMapName(this, 'ConfigMap', 'config'); 24 | 25 | // the 'config' constant can later be used by API's that require an IConfigMap. 26 | // for example when creating a volume. 27 | kplus.Volume.fromConfigMap(this, 'Volume', config); 28 | } 29 | } 30 | 31 | const app = new App(); 32 | new MyChart(app, 'VolumeFromConfigMap'); 33 | app.synth(); 34 | ``` 35 | 36 | ## Adding data 37 | 38 | You can create config maps and add some data to them like so: 39 | 40 | ```typescript 41 | import * as kplus from 'cdk8s-plus-33'; 42 | import { Construct } from 'constructs'; 43 | import { App, Chart, ChartProps } from 'cdk8s'; 44 | 45 | export class MyChart extends Chart { 46 | constructor(scope: Construct, id: string, props?: ChartProps) { 47 | super(scope, id, props); 48 | 49 | const config = new kplus.ConfigMap(this, 'Config'); 50 | config.addData('url', 'https://my-endpoint:8080'); 51 | } 52 | } 53 | 54 | const app = new App(); 55 | new MyChart(app, 'ConfigMap'); 56 | app.synth(); 57 | ``` 58 | 59 | ## Creating a volume from a directory 60 | 61 | Here is a nifty little trick you can use to create a volume that contains a directory on the client machine (machine that runs `cdk8s synth`): 62 | 63 | ```typescript 64 | import * as kplus from 'cdk8s-plus-33'; 65 | import * as path from 'path'; 66 | import { Construct } from 'constructs'; 67 | import { App, Chart, ChartProps } from 'cdk8s'; 68 | 69 | export class MyChart extends Chart { 70 | constructor(scope: Construct, id: string, props?: ChartProps) { 71 | super(scope, id, props); 72 | const appMap = new kplus.ConfigMap(this, 'Config'); 73 | 74 | // add the files in the directory to the config map. 75 | // this will create a key for each file. 76 | // note: this directory needs to exist 77 | // note: that only top level files will be included, sub-directories are not yet supported. 78 | appMap.addDirectory(path.join(__dirname, 'app')); 79 | 80 | const appVolume = kplus.Volume.fromConfigMap(this, 'ConfigMap', appMap); 81 | 82 | const mountPath = '/var/app'; 83 | const pod = new kplus.Pod(this, 'Pod'); 84 | const container = pod.addContainer({ 85 | image: 'node', 86 | command: [ 'node', 'app.js' ], 87 | workingDir: mountPath, 88 | }); 89 | 90 | // from here, just mount the volume to a container, and run your app! 91 | container.mount(mountPath, appVolume); 92 | } 93 | } 94 | 95 | const app = new App(); 96 | new MyChart(app, 'AppWithDir'); 97 | app.synth(); 98 | ``` 99 | -------------------------------------------------------------------------------- /docs/plus/cronjob.md: -------------------------------------------------------------------------------- 1 | # CronJob 2 | 3 | CronJob resource is responsible for creating recurring [Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/job/). The job recurrence is determined by a [Cron](https://github.com/cdk8s-team/cdk8s-core/blob/2.x/src/cron.ts) expression. 4 | 5 | CronJob is similar to a [job](https://cdk8s.io/docs/latest/plus/job/) but it is suitable when there is a need to run a job indefinitely following a schedule. These repetitive jobs can be utilized for recurring tasks such as backing up a database, pinging a server for health checks, creating snapshots of systems and much more. 6 | 7 | !!! tip "" 8 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#cronjob) 9 | 10 | ## Creating a `CronJob` 11 | 12 | ```typescript 13 | import * as kplus from 'cdk8s-plus-33'; 14 | import { Construct } from 'constructs'; 15 | import { App, Chart, ChartProps, Cron } from 'cdk8s'; 16 | 17 | export class MyChart extends Chart { 18 | constructor(scope: Construct, id: string, props: ChartProps = { }) { 19 | super(scope, id, props); 20 | 21 | new kplus.CronJob(this, 'CronJob', { 22 | containers: [{ 23 | image: 'databack/mysql-backup', 24 | }], 25 | // You can pass a custom cron schedule using our Cron class 26 | schedule: Cron.schedule({ 27 | minute: '*', 28 | hour: '*', 29 | day: '*', 30 | month: '*', 31 | weekDay: '*', 32 | }), 33 | }); 34 | } 35 | } 36 | 37 | const app = new App(); 38 | new MyChart(app, 'cronjob-readme'); 39 | app.synth(); 40 | 41 | ``` 42 | 43 | ### Defaults 44 | 45 | The above would create a cronjob resource which would schedule `databack/mysql-backup` to run every minute. With this resource, we also set some meaningful defaults. For instance, this cronjob would not run jobs concurrently by default. It will also retain 3 instance of successful job runs and 1 instance of a failed run for debugging later if needed. To customize the properties, see [API Reference](../../reference/cdk8s-plus-33/typescript.md#cronjob). 46 | 47 | ### Helper Functions 48 | 49 | As we see in the previous example, we can pass custom cron expression for scheduling our jobs. But, we have also have added helper functions that would make it easy to mention some of the commonly used schedules. These include scheduling jobs to run every minute, hour, day, week, month or year. For instance, the same example mentioned before could be written as, 50 | 51 | ```typescript 52 | new kplus.CronJob(this, 'CronJob', { 53 | containers: [{ 54 | image: 'databack/mysql-backup', 55 | }], 56 | // This would schedule jobs to be scheduled to run every minute 57 | schedule: Cron.everyMinute(), 58 | }); 59 | ``` 60 | 61 | ### Validations 62 | 63 | The cronjob construct also validates some of the properties so that the manifest created works as expected. 64 | 65 | * You cannot pass `startingDeadline` property value less that 10 seconds. This is because the Kubernetes CronJobController checks things every 10 seconds and if the value passed is less than that then the jobs would not be scheduled. 66 | 67 | * `ttlAfterFinished` job property limits the lifetime of a job that has finished execution. You cannot pass the `ttlAfterFinished` property with any/both of the `successfulJobsRetained` and `failedJobsRetained` property since this would not let retention of jobs work in an expected manner. 68 | -------------------------------------------------------------------------------- /test/job.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, Duration, ApiObject } from 'cdk8s'; 2 | import { Node } from 'constructs'; 3 | import * as kplus from '../src'; 4 | 5 | test('defaultChild', () => { 6 | 7 | const chart = Testing.chart(); 8 | 9 | const defaultChild = Node.of(new kplus.Job(chart, 'Job')).defaultChild as ApiObject; 10 | 11 | expect(defaultChild.kind).toEqual('Job'); 12 | 13 | }); 14 | 15 | test('Allows setting all options', () => { 16 | 17 | const chart = Testing.chart(); 18 | 19 | const job = new kplus.Job(chart, 'Job', { 20 | containers: [{ image: 'image' }], 21 | activeDeadline: Duration.seconds(20), 22 | backoffLimit: 4, 23 | ttlAfterFinished: Duration.seconds(1), 24 | }); 25 | 26 | // assert the k8s spec has it. 27 | const spec = Testing.synth(chart)[0].spec; 28 | 29 | expect(spec.activeDeadlineSeconds).toEqual(20); 30 | expect(spec.backoffLimit).toEqual(job.backoffLimit); 31 | expect(spec.ttlSecondsAfterFinished).toEqual(1); 32 | 33 | // assert the job object has it. 34 | expect(job.restartPolicy).toEqual(kplus.RestartPolicy.NEVER); 35 | expect(job.activeDeadline!.toSeconds()).toEqual(20); 36 | expect(job.backoffLimit).toEqual(4); 37 | expect(job.ttlAfterFinished!.toSeconds()).toEqual(1); 38 | 39 | }); 40 | 41 | test('Applies default restart policy to pod spec', () => { 42 | 43 | const chart = Testing.chart(); 44 | 45 | const job = new kplus.Job(chart, 'Job', { 46 | containers: [{ image: 'image' }], 47 | ttlAfterFinished: Duration.seconds(1), 48 | }); 49 | 50 | // assert the k8s spec has it. 51 | const spec = Testing.synth(chart)[0].spec; 52 | expect(spec.template.spec?.restartPolicy).toEqual('Never'); 53 | 54 | // assert the job object has it. 55 | expect(job.restartPolicy).toEqual(kplus.RestartPolicy.NEVER); 56 | 57 | }); 58 | 59 | test('Does not modify existing restart policy of pod spec', () => { 60 | 61 | const chart = Testing.chart(); 62 | 63 | const job = new kplus.Job(chart, 'Job', { 64 | containers: [{ image: 'image' }], 65 | restartPolicy: kplus.RestartPolicy.ALWAYS, 66 | ttlAfterFinished: Duration.seconds(1), 67 | }); 68 | 69 | // assert the k8s spec has it. 70 | const spec = Testing.synth(chart)[0].spec; 71 | expect(spec.template.spec?.restartPolicy).toEqual('Always'); 72 | 73 | // assert the job object has it. 74 | expect(job.restartPolicy).toEqual(kplus.RestartPolicy.ALWAYS); 75 | 76 | }); 77 | 78 | test('Synthesizes spec lazily', () => { 79 | 80 | const chart = Testing.chart(); 81 | 82 | const job = new kplus.Job(chart, 'Job'); 83 | 84 | job.addContainer( 85 | { 86 | image: 'image', 87 | }, 88 | ); 89 | 90 | const container = Testing.synth(chart)[0].spec.template.spec.containers[0]; 91 | expect(container.image).toEqual('image'); 92 | 93 | }); 94 | 95 | test('Can be isolated', () => { 96 | 97 | const chart = Testing.chart(); 98 | 99 | new kplus.Job(chart, 'Job', { 100 | containers: [{ image: 'foobar' }], 101 | isolate: true, 102 | }); 103 | 104 | const manifest = Testing.synth(chart); 105 | expect(manifest).toMatchSnapshot(); 106 | 107 | const networkPolicy = manifest[1].spec; 108 | expect(networkPolicy.podSelector.matchLabels).toBeDefined; 109 | expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']); 110 | }); -------------------------------------------------------------------------------- /test/daemon-set.test.ts: -------------------------------------------------------------------------------- 1 | import { ApiObject, Testing } from 'cdk8s'; 2 | import { Node } from 'constructs'; 3 | import * as kplus from '../src'; 4 | 5 | test('default child', () => { 6 | 7 | const chart = Testing.chart(); 8 | const ds = new kplus.DaemonSet(chart, 'DaemonSet'); 9 | const defaultChild = Node.of(ds).defaultChild as ApiObject; 10 | 11 | expect(defaultChild.kind).toEqual('DaemonSet'); 12 | 13 | }); 14 | 15 | test('defaults', () => { 16 | 17 | const chart = Testing.chart(); 18 | new kplus.DaemonSet(chart, 'DaemonSet', { 19 | containers: [{ image: 'image' }], 20 | }); 21 | 22 | expect(Testing.synth(chart)).toMatchSnapshot(); 23 | 24 | }); 25 | 26 | test('custom', () => { 27 | 28 | const chart = Testing.chart(); 29 | new kplus.DaemonSet(chart, 'DaemonSet', { 30 | containers: [{ image: 'image' }], 31 | minReadySeconds: 5, 32 | }); 33 | 34 | expect(Testing.synth(chart)).toMatchSnapshot(); 35 | 36 | }); 37 | 38 | test('a label selector is automatically allocated', () => { 39 | 40 | const chart = Testing.chart(); 41 | 42 | const ds = new kplus.DaemonSet(chart, 'DaemonSet'); 43 | ds.addContainer({ image: 'foobar' }); 44 | 45 | const expectedValue = 'test-DaemonSet-c8f77186'; 46 | const expectedSelector = { 'cdk8s.io/metadata.addr': expectedValue }; 47 | 48 | // assert the k8s spec has it. 49 | const spec = Testing.synth(chart)[0].spec; 50 | expect(spec.selector.matchLabels).toEqual(expectedSelector); 51 | expect(spec.template.metadata?.labels).toEqual(expectedSelector); 52 | 53 | // assert the deployment object has it. 54 | expect(ds.matchLabels).toEqual(expectedSelector); 55 | 56 | }); 57 | 58 | test('no selector is generated if "select" is false', () => { 59 | 60 | const chart = Testing.chart(); 61 | 62 | const ds = new kplus.DaemonSet(chart, 'DaemonSet', { 63 | select: false, 64 | containers: [{ image: 'foobar' }], 65 | }); 66 | 67 | // assert the k8s spec doesnt have it. 68 | const spec = Testing.synth(chart)[0].spec; 69 | expect(spec.selector.matchLabels).toBeUndefined(); 70 | 71 | // assert the deployment object doesnt have it. 72 | expect(ds.matchLabels).toEqual({}); 73 | 74 | }); 75 | 76 | test('can select by label', () => { 77 | 78 | const chart = Testing.chart(); 79 | 80 | const ds = new kplus.DaemonSet(chart, 'DaemonSet', { 81 | containers: [{ image: 'image' }], 82 | select: false, 83 | }); 84 | 85 | const expectedSelector = { foo: 'bar' }; 86 | 87 | ds.select(kplus.LabelSelector.of({ labels: { foo: expectedSelector.foo } })); 88 | 89 | // assert the k8s spec has it. 90 | const spec = Testing.synth(chart)[0].spec; 91 | expect(spec.selector.matchLabels).toEqual(expectedSelector); 92 | 93 | // assert the deployment object has it. 94 | expect(ds.matchLabels).toEqual(expectedSelector); 95 | 96 | }); 97 | 98 | test('Can be isolated', () => { 99 | 100 | const chart = Testing.chart(); 101 | 102 | new kplus.DaemonSet(chart, 'DaemonSet', { 103 | containers: [{ image: 'foobar' }], 104 | isolate: true, 105 | }); 106 | 107 | const manifest = Testing.synth(chart); 108 | expect(manifest).toMatchSnapshot(); 109 | 110 | const networkPolicy = manifest[1].spec; 111 | expect(networkPolicy.podSelector.matchLabels).toBeDefined; 112 | expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']); 113 | }); -------------------------------------------------------------------------------- /test/cron-job.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, ApiObject, Duration, CronOptions, Cron } from 'cdk8s'; 2 | import { Node } from 'constructs'; 3 | import * as kplus from '../src'; 4 | 5 | test('defaultChild', () => { 6 | 7 | const chart = Testing.chart(); 8 | 9 | const cronOptions: CronOptions = { 10 | minute: '*', 11 | hour: '*', 12 | day: '*', 13 | month: '*', 14 | weekDay: '*', 15 | }; 16 | 17 | const schedule = new Cron(cronOptions); 18 | 19 | const defaultChild = Node.of(new kplus.CronJob(chart, 'CronJob', { 20 | schedule: schedule, 21 | })).defaultChild as ApiObject; 22 | 23 | expect(defaultChild.kind).toEqual('CronJob'); 24 | }); 25 | 26 | test('default configuration', () => { 27 | 28 | const chart = Testing.chart(); 29 | 30 | const schedule = Cron.everyMinute(); 31 | 32 | new kplus.CronJob(chart, 'CronJob', { 33 | schedule: schedule, 34 | containers: [{ image: 'image' }], 35 | }); 36 | 37 | // assert the k8s spec has it. 38 | const manifest = Testing.synth(chart); 39 | expect(manifest).toMatchSnapshot(); 40 | const spec = manifest[0].spec; 41 | 42 | expect(spec.schedule).toEqual('* * * * *'); 43 | expect(spec.concurrencyPolicy).toEqual('Forbid'); 44 | expect(spec.timeZone).toEqual(undefined); 45 | expect(spec.startingDeadlineSeconds).toEqual(10); 46 | expect(spec.suspend).toEqual(false); 47 | expect(spec.successfulJobsHistoryLimit).toEqual(3); 48 | expect(spec.failedJobsHistoryLimit).toEqual(1); 49 | }); 50 | 51 | test('custom configuration', () => { 52 | 53 | const chart = Testing.chart(); 54 | 55 | const cronOptions: CronOptions = { 56 | minute: '5', 57 | hour: '*', 58 | day: '*', 59 | month: '*', 60 | weekDay: '*', 61 | }; 62 | 63 | const schedule = Cron.schedule(cronOptions); 64 | 65 | new kplus.CronJob(chart, 'CronJob', { 66 | activeDeadline: Duration.seconds(60), 67 | backoffLimit: 4, 68 | schedule: schedule, 69 | timeZone: 'America/Los_Angeles', 70 | concurrencyPolicy: kplus.ConcurrencyPolicy.ALLOW, 71 | startingDeadline: Duration.seconds(60), 72 | suspend: false, 73 | successfulJobsRetained: 3, 74 | failedJobsRetained: 3, 75 | containers: [{ image: 'image' }], 76 | }); 77 | 78 | // assert the k8s spec has it. 79 | const manifest = Testing.synth(chart); 80 | expect(manifest).toMatchSnapshot(); 81 | const spec = manifest[0].spec; 82 | 83 | expect(spec.schedule).toEqual('5 * * * *'); 84 | expect(spec.concurrencyPolicy).toEqual('Allow'); 85 | expect(spec.timeZone).toEqual('America/Los_Angeles'); 86 | expect(spec.startingDeadlineSeconds).toEqual(60); 87 | expect(spec.suspend).toEqual(false); 88 | expect(spec.successfulJobsHistoryLimit).toEqual(3); 89 | expect(spec.failedJobsHistoryLimit).toEqual(3); 90 | 91 | expect(spec.jobTemplate.spec.activeDeadlineSeconds).toEqual(60); 92 | expect(spec.jobTemplate.spec.backoffLimit).toEqual(4); 93 | expect(spec.jobTemplate.spec.template.spec.containers[0].image).toEqual('image'); 94 | }); 95 | 96 | test('Can be isolated', () => { 97 | 98 | const chart = Testing.chart(); 99 | 100 | new kplus.CronJob(chart, 'CronJob', { 101 | containers: [{ image: 'foobar' }], 102 | schedule: Cron.daily(), 103 | isolate: true, 104 | }); 105 | 106 | const manifest = Testing.synth(chart); 107 | expect(manifest).toMatchSnapshot(); 108 | 109 | const networkPolicy = manifest[1].spec; 110 | expect(networkPolicy.podSelector.matchLabels).toBeDefined; 111 | expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']); 112 | }); -------------------------------------------------------------------------------- /.github/workflows/upgrade-configuration-k8s-31-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-configuration-k8s-31-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@v5 19 | with: 20 | ref: k8s-31/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-31/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade configuration 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-configuration-k8s-31-main" workflow* 81 | branch: github-actions/upgrade-configuration-k8s-31-main 82 | title: "chore(deps): upgrade configuration" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-configuration-k8s-31-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-configuration-k8s-32-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-configuration-k8s-32-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@v5 19 | with: 20 | ref: k8s-32/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-32/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade configuration 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-configuration-k8s-32-main" workflow* 81 | branch: github-actions/upgrade-configuration-k8s-32-main 82 | title: "chore(deps): upgrade configuration" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-configuration-k8s-32-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-configuration-k8s-33-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-configuration-k8s-33-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@v5 19 | with: 20 | ref: k8s-33/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-33/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade configuration 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-configuration-k8s-33-main" workflow* 81 | branch: github-actions/upgrade-configuration-k8s-33-main 82 | title: "chore(deps): upgrade configuration" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-configuration-k8s-33-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dev-dependencies-k8s-31-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-dev-dependencies-k8s-31-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@v5 19 | with: 20 | ref: k8s-31/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-31/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade dev dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-dev-dependencies-k8s-31-main" workflow* 81 | branch: github-actions/upgrade-dev-dependencies-k8s-31-main 82 | title: "chore(deps): upgrade dev dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-dev-dependencies-k8s-31-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dev-dependencies-k8s-32-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-dev-dependencies-k8s-32-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@v5 19 | with: 20 | ref: k8s-32/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-32/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade dev dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-dev-dependencies-k8s-32-main" workflow* 81 | branch: github-actions/upgrade-dev-dependencies-k8s-32-main 82 | title: "chore(deps): upgrade dev dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-dev-dependencies-k8s-32-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-dev-dependencies-k8s-33-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-dev-dependencies-k8s-33-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@v5 19 | with: 20 | ref: k8s-33/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-33/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade dev dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-dev-dependencies-k8s-33-main" workflow* 81 | branch: github-actions/upgrade-dev-dependencies-k8s-33-main 82 | title: "chore(deps): upgrade dev dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-dev-dependencies-k8s-33-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-runtime-dependencies-k8s-31-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-runtime-dependencies-k8s-31-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@v5 19 | with: 20 | ref: k8s-31/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-31/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade runtime dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-runtime-dependencies-k8s-31-main" workflow* 81 | branch: github-actions/upgrade-runtime-dependencies-k8s-31-main 82 | title: "chore(deps): upgrade runtime dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-runtime-dependencies-k8s-31-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-runtime-dependencies-k8s-32-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-runtime-dependencies-k8s-32-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@v5 19 | with: 20 | ref: k8s-32/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-32/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade runtime dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-runtime-dependencies-k8s-32-main" workflow* 81 | branch: github-actions/upgrade-runtime-dependencies-k8s-32-main 82 | title: "chore(deps): upgrade runtime dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-runtime-dependencies-k8s-32-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-runtime-dependencies-k8s-33-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-runtime-dependencies-k8s-33-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@v5 19 | with: 20 | ref: k8s-33/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-33/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade runtime dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-runtime-dependencies-k8s-33-main" workflow* 81 | branch: github-actions/upgrade-runtime-dependencies-k8s-33-main 82 | title: "chore(deps): upgrade runtime dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-runtime-dependencies-k8s-33-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-compiler-dependencies-k8s-31-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-compiler-dependencies-k8s-31-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@v5 19 | with: 20 | ref: k8s-31/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-31/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade compiler dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-compiler-dependencies-k8s-31-main" workflow* 81 | branch: github-actions/upgrade-compiler-dependencies-k8s-31-main 82 | title: "chore(deps): upgrade compiler dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-compiler-dependencies-k8s-31-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-compiler-dependencies-k8s-32-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-compiler-dependencies-k8s-32-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@v5 19 | with: 20 | ref: k8s-32/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-32/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade compiler dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-compiler-dependencies-k8s-32-main" workflow* 81 | branch: github-actions/upgrade-compiler-dependencies-k8s-32-main 82 | title: "chore(deps): upgrade compiler dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-compiler-dependencies-k8s-32-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /.github/workflows/upgrade-compiler-dependencies-k8s-33-main.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: upgrade-compiler-dependencies-k8s-33-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@v5 19 | with: 20 | ref: k8s-33/main 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | - name: Install dependencies 26 | run: yarn install --check-files --frozen-lockfile 27 | - name: Upgrade dependencies 28 | run: npx projen upgrade-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 | shell: bash 35 | working-directory: ./ 36 | - name: Upload patch 37 | if: steps.create_patch.outputs.patch_created 38 | uses: actions/upload-artifact@v4.6.2 39 | with: 40 | name: repo.patch 41 | path: repo.patch 42 | overwrite: true 43 | pr: 44 | name: Create Pull Request 45 | needs: upgrade 46 | runs-on: ubuntu-latest 47 | permissions: 48 | contents: read 49 | if: ${{ needs.upgrade.outputs.patch_created }} 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v5 53 | with: 54 | ref: k8s-33/main 55 | - name: Download patch 56 | uses: actions/download-artifact@v5 57 | with: 58 | name: repo.patch 59 | path: ${{ runner.temp }} 60 | - name: Apply patch 61 | run: '[ -s ${{ runner.temp }}/repo.patch ] && git apply ${{ runner.temp }}/repo.patch || echo "Empty patch. Skipping."' 62 | - name: Set git identity 63 | run: |- 64 | git config user.name "github-actions[bot]" 65 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v7 69 | with: 70 | token: ${{ secrets.PROJEN_GITHUB_TOKEN }} 71 | commit-message: |- 72 | chore(deps): upgrade compiler dependencies 73 | 74 | Upgrades project dependencies. See details in [workflow run]. 75 | 76 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 77 | 78 | ------ 79 | 80 | *Automatically created by projen via the "upgrade-compiler-dependencies-k8s-33-main" workflow* 81 | branch: github-actions/upgrade-compiler-dependencies-k8s-33-main 82 | title: "chore(deps): upgrade compiler dependencies" 83 | labels: auto-approve 84 | body: |- 85 | Upgrades project dependencies. See details in [workflow run]. 86 | 87 | [Workflow Run]: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 88 | 89 | ------ 90 | 91 | *Automatically created by projen via the "upgrade-compiler-dependencies-k8s-33-main" workflow* 92 | author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 93 | committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 94 | signoff: true 95 | -------------------------------------------------------------------------------- /test/__snapshots__/service.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`can be exposed by an ingress 1`] = ` 4 | Object { 5 | "apiVersion": "networking.k8s.io/v1", 6 | "kind": "Ingress", 7 | "metadata": Object { 8 | "name": "test-service-ingress-c8a1c328", 9 | }, 10 | "spec": Object { 11 | "rules": Array [ 12 | Object { 13 | "http": Object { 14 | "paths": Array [ 15 | Object { 16 | "backend": Object { 17 | "service": Object { 18 | "name": "test-service-c85b0531", 19 | "port": Object { 20 | "number": 80, 21 | }, 22 | }, 23 | }, 24 | "path": "/hello", 25 | "pathType": "Prefix", 26 | }, 27 | ], 28 | }, 29 | }, 30 | ], 31 | }, 32 | } 33 | `; 34 | 35 | exports[`can select a deployment 1`] = ` 36 | Array [ 37 | Object { 38 | "apiVersion": "apps/v1", 39 | "kind": "Deployment", 40 | "metadata": Object { 41 | "name": "test-deployment-c898c72d", 42 | }, 43 | "spec": Object { 44 | "minReadySeconds": 0, 45 | "progressDeadlineSeconds": 600, 46 | "replicas": 2, 47 | "revisionHistoryLimit": 10, 48 | "selector": Object { 49 | "matchLabels": Object { 50 | "cdk8s.io/metadata.addr": "test-Deployment-c83f5e59", 51 | }, 52 | }, 53 | "strategy": Object { 54 | "rollingUpdate": Object { 55 | "maxSurge": "25%", 56 | "maxUnavailable": "25%", 57 | }, 58 | "type": "RollingUpdate", 59 | }, 60 | "template": Object { 61 | "metadata": Object { 62 | "labels": Object { 63 | "cdk8s.io/metadata.addr": "test-Deployment-c83f5e59", 64 | }, 65 | }, 66 | "spec": Object { 67 | "automountServiceAccountToken": false, 68 | "containers": Array [ 69 | Object { 70 | "image": "image", 71 | "imagePullPolicy": "Always", 72 | "name": "main", 73 | "resources": Object { 74 | "limits": Object { 75 | "cpu": "1500m", 76 | "memory": "2048Mi", 77 | }, 78 | "requests": Object { 79 | "cpu": "1000m", 80 | "memory": "512Mi", 81 | }, 82 | }, 83 | "securityContext": Object { 84 | "allowPrivilegeEscalation": false, 85 | "privileged": false, 86 | "readOnlyRootFilesystem": true, 87 | "runAsNonRoot": true, 88 | }, 89 | }, 90 | ], 91 | "dnsPolicy": "ClusterFirst", 92 | "hostNetwork": false, 93 | "restartPolicy": "Always", 94 | "securityContext": Object { 95 | "fsGroupChangePolicy": "Always", 96 | "runAsNonRoot": true, 97 | }, 98 | "setHostnameAsFQDN": false, 99 | "shareProcessNamespace": false, 100 | "terminationGracePeriodSeconds": 30, 101 | }, 102 | }, 103 | }, 104 | }, 105 | Object { 106 | "apiVersion": "v1", 107 | "kind": "Service", 108 | "metadata": Object { 109 | "name": "test-service-c8569d2f", 110 | }, 111 | "spec": Object { 112 | "externalIPs": Array [], 113 | "ports": Array [ 114 | Object { 115 | "port": 9000, 116 | }, 117 | ], 118 | "selector": Object { 119 | "cdk8s.io/metadata.addr": "test-Deployment-c83f5e59", 120 | }, 121 | "type": "ClusterIP", 122 | }, 123 | }, 124 | ] 125 | `; 126 | -------------------------------------------------------------------------------- /.projenrc.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { Cdk8sTeamJsiiProject } from '@cdk8s/projen-common'; 4 | 5 | // the version of k8s this branch supports 6 | const SPEC_VERSION = fs.readFileSync('projenrc/latest-k8s-version.txt', 'utf-8'); 7 | const K8S_VERSION = `1.${SPEC_VERSION}.0`; 8 | 9 | // the latest version of k8s we support 10 | const LATEST_SUPPORTED_K8S_VERSION = Number(SPEC_VERSION); 11 | 12 | const project = new Cdk8sTeamJsiiProject({ 13 | name: `cdk8s-plus-${SPEC_VERSION}`, 14 | description: `cdk8s+ is a software development framework that provides high level abstractions for authoring Kubernetes applications. cdk8s-plus-${SPEC_VERSION} synthesizes Kubernetes manifests for Kubernetes ${K8S_VERSION}`, 15 | projenrcTs: true, 16 | repoName: 'cdk8s-plus', 17 | 18 | keywords: [ 19 | 'cdk', 20 | 'kubernetes', 21 | 'k8s', 22 | 'constructs', 23 | 'containers', 24 | 'configuration', 25 | 'microservices', 26 | ], 27 | 28 | peerDeps: [ 29 | 'cdk8s', 30 | 'constructs', 31 | ], 32 | deps: [ 33 | 'minimatch', 34 | ], 35 | bundledDeps: [ 36 | 'minimatch', 37 | ], 38 | devDeps: [ 39 | 'constructs', 40 | 'cdk8s', 41 | 'cdk8s-cli', 42 | 'constructs', 43 | 'snake-case', 44 | '@cdk8s/projen-common', 45 | ], 46 | 47 | majorVersion: 2, 48 | releaseTagPrefix: `cdk8s-plus-${SPEC_VERSION}/`, 49 | releaseWorkflowName: `release-k8s.${SPEC_VERSION}`, 50 | defaultReleaseBranch: `k8s-${SPEC_VERSION}/main`, 51 | githubOptions: { 52 | // use mergify because merge queues don't support branch patterns 53 | mergify: true, 54 | mergeQueue: false, 55 | }, 56 | 57 | golangBranch: `k8s.${SPEC_VERSION}`, 58 | depsUpgradeOptions: { 59 | workflowOptions: { 60 | branches: [ 61 | // Support the 3 latest major versions 62 | `k8s-${LATEST_SUPPORTED_K8S_VERSION}/main`, 63 | `k8s-${LATEST_SUPPORTED_K8S_VERSION - 1}/main`, 64 | `k8s-${LATEST_SUPPORTED_K8S_VERSION - 2}/main`, 65 | ], 66 | }, 67 | }, 68 | backport: true, 69 | backportBranches: [ 70 | `k8s-${LATEST_SUPPORTED_K8S_VERSION - 1}/main`, 71 | `k8s-${LATEST_SUPPORTED_K8S_VERSION - 2}/main`, 72 | ], 73 | }); 74 | 75 | // not using `npmAccess` property because projen omits values that are 76 | // identical to npm defaults. 77 | project.package.addField('publishConfig', { access: 'public' }); 78 | 79 | project.gitignore.exclude('.vscode/', '.idea/'); 80 | 81 | const importdir = path.join('src', 'imports'); 82 | 83 | project.addTask('import', { 84 | exec: `cdk8s -l typescript -o ${importdir} import k8s@${K8S_VERSION}`, 85 | description: 'Updates imports based on latest version of cdk8s-cli.', 86 | }); 87 | 88 | const docgenTask = project.tasks.tryFind('docgen')!; 89 | docgenTask.reset(); 90 | for (const lang of ['typescript', 'python', 'java']) { 91 | const genTask = project.addTask(`docgen:${lang}`); 92 | const output = `docs/${lang}.md`; 93 | genTask.exec(`mkdir -p docs && jsii-docgen -l ${lang} -o ${output}`); 94 | docgenTask.spawn(genTask); 95 | // ignoring since it creates merge conflicts in 96 | // the backport PR's. 97 | project.gitignore.exclude(output); 98 | } 99 | 100 | // Allow skipping tests in build based on an env variable 101 | // This is only used by the package integrity check running outside the repo 102 | // While this check needs to replicate the release, it does not need to run tests 103 | project.testTask.addCondition("node -e \"if (process.env.SKIP_TESTS==='1') process.exit(1)\""); 104 | 105 | // Projen task to update references to old versions of cdk8s-plus 106 | const versionTaskObject = project.addTask('rotate'); 107 | versionTaskObject.exec('ts-node projenrc/rotate.ts ' + SPEC_VERSION); 108 | 109 | project.synth(); 110 | -------------------------------------------------------------------------------- /src/job.ts: -------------------------------------------------------------------------------- 1 | import { ApiObject, Lazy, Duration } from 'cdk8s'; 2 | import { Construct } from 'constructs'; 3 | import * as k8s from './imports/k8s'; 4 | import * as pod from './pod'; 5 | import * as workload from './workload'; 6 | 7 | /** 8 | * Properties for `Job`. 9 | */ 10 | export interface JobProps extends workload.WorkloadProps { 11 | 12 | /** 13 | * Specifies the duration the job may be active before the system tries to terminate it. 14 | * 15 | * @default - If unset, then there is no deadline. 16 | */ 17 | readonly activeDeadline?: Duration; 18 | 19 | /** 20 | * Specifies the number of retries before marking this job failed. 21 | * 22 | * @default - If not set, system defaults to 6. 23 | */ 24 | readonly backoffLimit?: number; 25 | 26 | /** 27 | * Limits the lifetime of a Job that has finished execution (either Complete 28 | * or Failed). If this field is set, after the Job finishes, it is eligible to 29 | * be automatically deleted. When the Job is being deleted, its lifecycle 30 | * guarantees (e.g. finalizers) will be honored. If this field is set to zero, 31 | * the Job becomes eligible to be deleted immediately after it finishes. This 32 | * field is alpha-level and is only honored by servers that enable the 33 | * `TTLAfterFinished` feature. 34 | * 35 | * @default - If this field is unset, the Job won't be automatically deleted. 36 | */ 37 | readonly ttlAfterFinished?: Duration; 38 | 39 | } 40 | 41 | /** 42 | * A Job creates one or more Pods and ensures that a specified number of them successfully terminate. As pods successfully complete, 43 | * the Job tracks the successful completions. When a specified number of successful completions is reached, the task (ie, Job) is complete. 44 | * Deleting a Job will clean up the Pods it created. A simple case is to create one Job object in order to reliably run one Pod to completion. 45 | * The Job object will start a new Pod if the first Pod fails or is deleted (for example due to a node hardware failure or a node reboot). 46 | * You can also use a Job to run multiple Pods in parallel. 47 | */ 48 | export class Job extends workload.Workload { 49 | 50 | /** 51 | * Duration before job is terminated. If undefined, there is no deadline. 52 | */ 53 | public readonly activeDeadline?: Duration; 54 | 55 | /** 56 | * Number of retries before marking failed. 57 | */ 58 | public readonly backoffLimit?: number; 59 | 60 | /** 61 | * TTL before the job is deleted after it is finished. 62 | */ 63 | public readonly ttlAfterFinished?: Duration; 64 | 65 | /** 66 | * @see base.Resource.apiObject 67 | */ 68 | protected readonly apiObject: ApiObject; 69 | 70 | public readonly resourceType = 'jobs'; 71 | 72 | constructor(scope: Construct, id: string, props: JobProps = {}) { 73 | super(scope, id, { 74 | restartPolicy: pod.RestartPolicy.NEVER, 75 | select: false, 76 | ...props, 77 | }); 78 | 79 | this.apiObject = new k8s.KubeJob(this, 'Resource', { 80 | metadata: props.metadata, 81 | spec: Lazy.any({ produce: () => this._toKube() }), 82 | }); 83 | 84 | this.activeDeadline = props.activeDeadline; 85 | this.backoffLimit = props.backoffLimit; 86 | this.ttlAfterFinished = props.ttlAfterFinished; 87 | 88 | if (this.isolate) { 89 | this.connections.isolate(); 90 | } 91 | } 92 | 93 | /** 94 | * @internal 95 | */ 96 | public _toKube(): k8s.JobSpec { 97 | return { 98 | template: { 99 | metadata: this.podMetadata.toJson(), 100 | spec: this._toPodSpec(), 101 | }, 102 | activeDeadlineSeconds: this.activeDeadline?.toSeconds(), 103 | backoffLimit: this.backoffLimit, 104 | ttlSecondsAfterFinished: this.ttlAfterFinished ? this.ttlAfterFinished.toSeconds() : undefined, 105 | }; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /rotate.md: -------------------------------------------------------------------------------- 1 | # Rotate 2 | 3 | This document describes the changes required in order to create a new cdk8s-plus-XX library based 4 | on the latest kubernetes version. These steps should be executed in order. 5 | 6 | ## :one: Prerequisite 7 | 8 | ### Add new k8s spec to cdk8s repo 9 | 10 | 1. ([cdk8s repo](https://github.com/cdk8s-team/cdk8s)): Generate [`kubernetes-schema/vX.XX.X/_definitions.json`](https://github.com/cdk8s-team/cdk8s/tree/master/kubernetes-schemas) 11 | 12 | ```sh 13 | tools/import-spec.sh x.xx.x 14 | # provide the new version number e.g. 1.25.0 15 | ``` 16 | 17 | 2. Create PR, review then merge the updated spec into cdk8s/master branch ([e.g. v25 PR](https://github.com/cdk8s-team/cdk8s/pull/1007)). 18 | 19 | ### Publish new branch to cdk8s-plus-go repo 20 | 21 | 3. ([cdk8s-plus-go repo](https://github.com/cdk8s-team/cdk8s-plus-go)): Create and publish a new branch for the new k8s version 22 | 23 | ```sh 24 | git checkout -b k8s.xx 25 | # e.g. k8s.25 for version 1.25.0 26 | git push --set-upstream origin k8s.xx 27 | ``` 28 | 29 | ### Create a new backport label in cdk8s-plus' GitHub 30 | 31 | 4. Open the [cdk8s-plus GitHub label list](https://github.com/cdk8s-team/cdk8s-plus/issues/labels) 32 | 33 | 5. Add a new label for the **current** Kubernetes version. 34 | - Label name: `backport-to-k8s-XX/main` 35 | - Label color: `#F53E94` 36 | 37 | e.g. If you were upgrading from v24 -> v25 the new label would be `backport-to-k8s-24/main` 38 | 39 | ## :two: Create the new cdk8s-plus branch 40 | 41 | 6. Create a new branch in the [cdk8s-plus](https://github.com/cdk8s-team/cdk8s-plus) off the current default branch. 42 | The new branch should be named `k8s-XX/main` (e.g. `k8s-25/main` for K8s v1.25.0). 43 | 44 | 7. **On a new branch**, based off `k8s-XX/main`, do the following: 45 | 46 | 1. Bump the minor version in [latest-k8s-version.txt](./projenrc/latest-k8s-version.txt) 47 | 2. ([`README.md`](./README.md)): In the table of supported versions, add a new row and remove the oldest one. 48 | 3. `yarn projen` 49 | 4. `yarn rotate` # updates all version references in documenation 50 | 5. `yarn run import` # codegen the k8s imports file based on the new schema. 51 | 6. `yarn build` 52 | 7. Create a PR to the `k8s-XX/main` branch. (See [example](https://github.com/cdk8s-team/cdk8s-plus/pull/4260)). 53 | 54 | 8. Wait for the PR above to be merged and verify that automation builds/tags/releases the new version successfully. 55 | 56 | 9. Update cdk8s-plus default branch to the new branch in the [GitHub repo settings](https://github.com/cdk8s-team/cdk8s-plus/settings). 57 | 58 | 10. Update any existing PRs to use k8s-XX/main as the base. 59 | 60 | 11. Rotate the backport labels on existing PRs. (for exmaple when rotating from k8s.29 to k8s.30, remove the `backport-to-k8s-27/main` label and add the `backport-to-k8s-29/main` label. 61 | 62 | 12. Delete the `backport-to-k8s-(XX - 3)/main` label. (for exmaple when rotating from k8s.29 to k8s.30, delete the `backport-to-k8s-27/main` label 63 | 64 | ## :three: Update Website 65 | 66 | In the ([cdk8s repo](https://github.com/cdk8s-team/cdk8s)): 67 | 68 | 11. Create a new branch off of cdk8s/master and: 69 | 70 | 1. Bump the minor version in [latest-k8s-version.txt](https://github.com/cdk8s-team/cdk8s/blob/master/src/latest-k8s-version.txt) 71 | 2. `yarn projen` 72 | 3. `yarn rotate-cdk8s-plus` 73 | 74 | 12. Create a PR for the new branch, review then merge into cdk8s/master branch. (See [example](https://github.com/cdk8s-team/cdk8s/pull/1988)) 75 | 76 | ## :four: Update Ops 77 | 78 | In the ([cdk-ops](https://github.com/cdklabs/cdk-ops)): 79 | 80 | 13. Create a new branch and: 81 | 82 | 1. Bump the minor version in [latest-cdk8s-plus-version.txt](https://github.com/cdklabs/cdk-ops/blob/master/latest-cdk8s-plus-version.txt) 83 | 2. `yarn projen` 84 | 85 | 14. Create a PR and send for approval. (See [example](https://github.com/cdklabs/cdk-ops/pull/3323)) 86 | -------------------------------------------------------------------------------- /src/base.ts: -------------------------------------------------------------------------------- 1 | import { ApiObjectMetadata, ApiObject, ApiObjectMetadataDefinition } from 'cdk8s'; 2 | import { Construct, IConstruct } from 'constructs'; 3 | import { IApiResource, IApiEndpoint } from './api-resource'; 4 | 5 | /** 6 | * Initialization properties for resources. 7 | */ 8 | export interface ResourceProps { 9 | /** 10 | * Metadata that all persisted resources must have, which includes all objects 11 | * users must create. 12 | */ 13 | readonly metadata?: ApiObjectMetadata; 14 | } 15 | 16 | /** 17 | * Represents a resource. 18 | */ 19 | export interface IResource extends IConstruct, IApiResource { 20 | /** 21 | * The Kubernetes name of this resource. 22 | */ 23 | readonly name: string; 24 | 25 | /** 26 | * The object's API version (e.g. "authorization.k8s.io/v1") 27 | */ 28 | readonly apiVersion: string; 29 | 30 | /** 31 | * The object kind (e.g. "Deployment"). 32 | */ 33 | readonly kind: string; 34 | } 35 | 36 | /** 37 | * Base class for all Kubernetes objects in stdk8s. Represents a single 38 | * resource. 39 | */ 40 | export abstract class Resource extends Construct implements IResource, IApiResource, IApiEndpoint { 41 | 42 | /** 43 | * The underlying cdk8s API object. 44 | */ 45 | protected abstract readonly apiObject: ApiObject; 46 | 47 | public readonly abstract resourceType: string; 48 | 49 | public readonly permissions: ResourcePermissions; 50 | 51 | public constructor(scope: Construct, id: string) { 52 | super(scope, id); 53 | this.permissions = new ResourcePermissions(this); 54 | } 55 | 56 | public get metadata(): ApiObjectMetadataDefinition { 57 | return this.apiObject.metadata; 58 | } 59 | 60 | /** 61 | * The name of this API object. 62 | */ 63 | public get name(): string { 64 | return this.apiObject.name; 65 | } 66 | 67 | /** 68 | * The object's API version (e.g. "authorization.k8s.io/v1") 69 | */ 70 | public get apiVersion(): string { 71 | return this.apiObject.apiVersion; 72 | } 73 | 74 | /** 75 | * The group portion of the API version (e.g. "authorization.k8s.io"). 76 | */ 77 | public get apiGroup(): string { 78 | return this.apiObject.apiGroup; 79 | } 80 | 81 | /** 82 | * The object kind (e.g. "Deployment"). 83 | */ 84 | public get kind(): string { 85 | return this.apiObject.kind; 86 | } 87 | 88 | public get resourceName(): string | undefined { 89 | return this.name; 90 | } 91 | 92 | public asApiResource(): IApiResource | undefined { 93 | return this; 94 | } 95 | 96 | public asNonApiResource(): string | undefined { 97 | return undefined; 98 | } 99 | 100 | } 101 | 102 | /** 103 | * Controls permissions for operations on resources. 104 | */ 105 | export class ResourcePermissions { 106 | 107 | constructor(protected readonly instance: Resource) {} 108 | 109 | /** 110 | * Grants the list of subjects permissions to read this resource. 111 | */ 112 | public grantRead(...subjects: rb.ISubject[]): rb.RoleBinding { 113 | const subjectsAddress = address(...subjects); 114 | const role = new r.Role(this.instance, `Role${subjectsAddress}`, { 115 | metadata: { namespace: this.instance.metadata.namespace }, 116 | }); 117 | role.allowRead(this.instance); 118 | return role.bind(...subjects); 119 | } 120 | 121 | /** 122 | * Grants the list of subjects permissions to read and write this resource. 123 | */ 124 | public grantReadWrite(...subjects: rb.ISubject[]): rb.RoleBinding { 125 | const subjectsAddress = address(...subjects); 126 | const role = new r.Role(this.instance, `Role${subjectsAddress}`, { 127 | metadata: { namespace: this.instance.metadata.namespace }, 128 | }); 129 | role.allowReadWrite(this.instance); 130 | return role.bind(...subjects); 131 | } 132 | 133 | } 134 | 135 | // meh, avoiding errors due to circular imports... 136 | import * as r from './role'; 137 | import * as rb from './role-binding'; 138 | import { address } from './utils'; 139 | -------------------------------------------------------------------------------- /docs/plus/rbac.md: -------------------------------------------------------------------------------- 1 | # Role Based Access Control 2 | 3 | Role Based Access Control(RBAC) helps you restrict actions that can be performed on specific Kubernetes resources. To make this possible, RBAC lets you create roles with rules which define access permissions for your specified resource. 4 | 5 | These roles can then be binded to Kubernetes subjects, which could be User, Group or ServiceAccount. 6 | 7 | !!! note 8 | Rules or permissions are purely additive and there are no deny rules. 9 | 10 | Now, there are two types of roles available, 11 | * Role: These set permissions within a particular namespace i.e. is for namespaced resources, like, pods, deployments. 12 | * ClusterRole: These set permissions for non-namespaced resources, like, nodes, urls. 13 | 14 | and, similarly there are two types of binding available, 15 | * RoleBinding: These grant permissions within a specific namespace. 16 | * ClusterRoleBinding: These grant cluster wide permissions . 17 | 18 | !!! tip "Learn more" 19 | * [Role API Reference](../../reference/cdk8s-plus-33/typescript.md#role) 20 | * [RoleBinding API Reference](../../reference/cdk8s-plus-33/typescript.md#role-binding) 21 | 22 | ## Role 23 | 24 | ### Create role and add rules to it 25 | 26 | ```typescript 27 | import * as kplus from 'cdk8s-plus-33'; 28 | import { Construct } from 'constructs'; 29 | import { App, Chart, ChartProps } from 'cdk8s'; 30 | 31 | export class MyChart extends Chart { 32 | constructor(scope: Construct, id: string, props: ChartProps = { }) { 33 | super(scope, id, props); 34 | 35 | // Creating RBAC Role 36 | const role = new kplus.Role(this, 'SampleRole'); 37 | 38 | // The convenience method here `allowReadWrite` would add 39 | // `get, list, watch, create, update, patch, delete, 40 | // deletecollection` rules to the role for deployment resources. 41 | role.allowReadWrite(kplus.ApiResource.DEPLOYMENTS); 42 | 43 | const user = kplus.User.fromName(this, 'SampleUser', 'Jane'); 44 | const group = kplus.Group.fromName(this, 'SampleGroup', 'sample-group'); 45 | const serviceAccount = new kplus.ServiceAccount(this, 'SampleServiceAccount'); 46 | 47 | // You can bind this role to a specific user, group or service account 48 | role.bind(user, group, serviceAccount); 49 | } 50 | } 51 | 52 | const app = new App(); 53 | new MyChart(app, 'rbac-docs'); 54 | app.synth(); 55 | ``` 56 | 57 | ## ClusterRole 58 | 59 | ### Create ClusterRole and add rules to it 60 | 61 | ```typescript 62 | // Creating RBAC ClusterRole 63 | const clusterRole = new kplus.ClusterRole(this, 'SampleClusterRole'); 64 | 65 | // Adding list of rules to the ClusterRole for 'Nodes' and 'URL' non-namespaced resource 66 | clusterRole.allowReadWrite(kplus.ApiResource.NODES, kplus.NonApiResource.of('/healthz')); 67 | 68 | const user = kplus.User.fromName(this, 'SampleUser', 'Jane'); 69 | const group = kplus.Group.fromName(this, 'SampleGroup', 'sample-group'); 70 | const serviceAccount = new kplus.ServiceAccount(this, 'SampleServiceAccount'); 71 | 72 | // You can bind this cluster role to a specific user, group or service account 73 | clusterRole.bind(user, group, serviceAccount); 74 | ``` 75 | 76 | ## Resource Permission Methods 77 | 78 | You can use convenience methods like `grantRead` and `grantReadWrite` which would make it easier to grant list of subjects set of permissions for the resource. 79 | 80 | ### `grantReadWrite` Method 81 | 82 | ```typescript 83 | // Creating a Pod resource 84 | const pod = new kplus.Pod(this, 'Pod', { 85 | containers: [{ image: 'image' }], 86 | }); 87 | 88 | const user = kplus.User.fromName(this, 'SampleUser', 'Jane'); 89 | const group = kplus.Group.fromName(this, 'SampleGroup', 'sample-group'); 90 | const serviceAccount = new kplus.ServiceAccount(this, 'SampleServiceAccount'); 91 | 92 | // You can grant permissions to specific user, group or service account. 93 | pod.permissions.grantReadWrite(user, group, serviceAccount); 94 | ``` 95 | 96 | ## Add subjects to an already bound role 97 | 98 | ```typescript 99 | const user = kplus.User.fromName(this, 'SampleUser', 'Jane'); 100 | const binding = role.bind(user); 101 | 102 | const anotherUser = kplus.User.fromName(this, 'AnotherSampleUser', 'James'); 103 | binding.addSubjects(anotherUser); 104 | ``` -------------------------------------------------------------------------------- /test/pvc.test.ts: -------------------------------------------------------------------------------- 1 | import * as cdk8s from 'cdk8s'; 2 | import { Testing } from 'cdk8s'; 3 | import * as kplus from '../src'; 4 | 5 | test('can grant permissions on imported', () => { 6 | 7 | const chart = Testing.chart(); 8 | const claim = kplus.PersistentVolumeClaim.fromClaimName(chart, 'Claim', 'claim'); 9 | 10 | const role = new kplus.Role(chart, 'Role'); 11 | role.allowRead(claim); 12 | 13 | expect(Testing.synth(chart)).toMatchSnapshot(); 14 | 15 | }); 16 | 17 | test('defaults', () => { 18 | 19 | const chart = cdk8s.Testing.chart(); 20 | const pvc = new kplus.PersistentVolumeClaim(chart, 'PersistentVolumeClaim'); 21 | 22 | expect(pvc.accessModes).toBeUndefined(); 23 | expect(pvc.storage).toBeUndefined(); 24 | expect(pvc.storageClassName).toBeUndefined(); 25 | expect(pvc.volumeMode).toEqual(kplus.PersistentVolumeMode.FILE_SYSTEM); 26 | 27 | const resources = cdk8s.Testing.synth(chart); 28 | expect(resources).toMatchSnapshot(); 29 | 30 | }); 31 | 32 | test('custom', () => { 33 | 34 | const chart = cdk8s.Testing.chart(); 35 | const pvc = new kplus.PersistentVolumeClaim(chart, 'PersistentVolumeClaim', { 36 | accessModes: [kplus.PersistentVolumeAccessMode.READ_WRITE_MANY], 37 | storage: cdk8s.Size.gibibytes(50), 38 | storageClassName: 'storage-class', 39 | volumeMode: kplus.PersistentVolumeMode.BLOCK, 40 | }); 41 | 42 | expect(pvc.accessModes).toEqual([kplus.PersistentVolumeAccessMode.READ_WRITE_MANY]); 43 | expect(pvc.storage).toEqual(cdk8s.Size.gibibytes(50)); 44 | expect(pvc.storageClassName).toEqual('storage-class'); 45 | expect(pvc.volumeMode).toEqual(kplus.PersistentVolumeMode.BLOCK); 46 | 47 | const resources = cdk8s.Testing.synth(chart); 48 | expect(resources).toMatchSnapshot(); 49 | 50 | }); 51 | 52 | test('small size', () => { 53 | 54 | const chart = cdk8s.Testing.chart(); 55 | const pvc = new kplus.PersistentVolumeClaim(chart, 'PersistentVolumeClaim', { 56 | storage: cdk8s.Size.mebibytes(512), 57 | }); 58 | 59 | expect(pvc.storage).toEqual(cdk8s.Size.mebibytes(512)); 60 | 61 | const resources = cdk8s.Testing.synth(chart); 62 | expect(resources).toMatchSnapshot(); 63 | }); 64 | 65 | test('can be imported', () => { 66 | 67 | const chart = Testing.chart(); 68 | const claim = kplus.PersistentVolumeClaim.fromClaimName(chart, 'Claim', 'claim'); 69 | expect(claim.name).toEqual('claim'); 70 | 71 | }); 72 | 73 | test('can be bounded to a volume at instantiation', () => { 74 | 75 | const chart = cdk8s.Testing.chart(); 76 | const vol = kplus.PersistentVolume.fromPersistentVolumeName(chart, 'Vol', 'vol'); 77 | const pvc = new kplus.PersistentVolumeClaim(chart, 'PersistentVolumeClaim', { 78 | volume: vol, 79 | }); 80 | 81 | expect(pvc.volume!.name).toEqual(vol.name); 82 | 83 | const resources = cdk8s.Testing.synth(chart); 84 | expect(resources).toMatchSnapshot(); 85 | 86 | }); 87 | 88 | test('can be bounded to a volume post instantiation', () => { 89 | 90 | const chart = cdk8s.Testing.chart(); 91 | const vol = kplus.PersistentVolume.fromPersistentVolumeName(chart, 'Vol', 'vol'); 92 | const pvc = new kplus.PersistentVolumeClaim(chart, 'PersistentVolumeClaim'); 93 | 94 | pvc.bind(vol); 95 | 96 | expect(pvc.volume!.name).toEqual(vol.name); 97 | 98 | const resources = cdk8s.Testing.synth(chart); 99 | expect(resources).toMatchSnapshot(); 100 | 101 | }); 102 | 103 | test('no-ops if bounded twice to the same volume', () => { 104 | 105 | const chart = cdk8s.Testing.chart(); 106 | const vol = kplus.PersistentVolume.fromPersistentVolumeName(chart, 'Vole', 'vol'); 107 | const pvc = new kplus.PersistentVolumeClaim(chart, 'PersistentVolumeClaim'); 108 | 109 | pvc.bind(vol); 110 | pvc.bind(vol); 111 | 112 | expect(pvc.volume!.name).toEqual(vol.name); 113 | 114 | }); 115 | 116 | test('throws if bounded twice to different volumes', () => { 117 | 118 | const chart = cdk8s.Testing.chart(); 119 | const vol1 = kplus.PersistentVolume.fromPersistentVolumeName(chart, 'Vol1', 'vol1'); 120 | const vol2 = kplus.PersistentVolume.fromPersistentVolumeName(chart, 'Vol2', 'vol2'); 121 | const pvc = new kplus.PersistentVolumeClaim(chart, 'PersistentVolumeClaim'); 122 | 123 | pvc.bind(vol1); 124 | 125 | expect(() => pvc.bind(vol2)).toThrowError('Cannot bind claim \'test-persistentvolumeclaim-c8af0974\' to volume \'vol2\' since it is already bound to volume \'vol1\''); 126 | 127 | }); -------------------------------------------------------------------------------- /docs/plus/container.md: -------------------------------------------------------------------------------- 1 | # Container 2 | 3 | Define containers that run in a pod using the `Container` class. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#container) 7 | 8 | ## Environment 9 | 10 | A container's environment can be populated by various methods. 11 | 12 | ### Variables 13 | 14 | Environment variables can be added to containers by specifying the 15 | variable name and value. The value can come from different sources, either dynamic or static. 16 | 17 | ```typescript 18 | import * as kplus from 'cdk8s-plus-33'; 19 | import { Construct } from 'constructs'; 20 | import { App, Chart, ChartProps } from 'cdk8s'; 21 | 22 | export class MyChart extends Chart { 23 | constructor(scope: Construct, id: string, props: ChartProps = { }) { 24 | super(scope, id, props); 25 | 26 | const pod = new kplus.Pod(this, 'Pod'); 27 | const container = pod.addContainer({ 28 | image: 'my-app' 29 | }); 30 | 31 | // use a static value. 32 | container.env.addVariable('endpoint', kplus.EnvValue.fromValue('value')); 33 | 34 | // use a specific key from a config map. 35 | const backendsConfig = kplus.ConfigMap.fromConfigMapName(this, 'BackendConfig', 'backends'); 36 | container.env.addVariable('endpoint', kplus.EnvValue.fromConfigMap(backendsConfig, 'endpoint')); 37 | 38 | // use a specific key from a secret. 39 | const credentials = kplus.Secret.fromSecretName(this, 'Credentials', 'credentials'); 40 | container.env.addVariable('password', kplus.EnvValue.fromSecretValue({ secret: credentials, key: 'password' })); 41 | } 42 | } 43 | 44 | const app = new App(); 45 | new MyChart(app, 'container'); 46 | app.synth(); 47 | ``` 48 | 49 | ### Sources 50 | 51 | Environment variables can also be populated by referencing other objects as an environment source. 52 | With this method, all the key-value data of the source is added as environment variables, 53 | where the key is the env name and the value is the env value. 54 | 55 | ```typescript 56 | const pod = new kplus.Pod(this, 'Pod'); 57 | const cm = new kplus.ConfigMap(this, 'ConfigMap', { 58 | data: { 59 | key: 'value', 60 | } 61 | }); 62 | const container = pod.addContainer({ 63 | image: 'my-app' 64 | }); 65 | 66 | // this will add 'key=value' env variable at runtime. 67 | container.env.copyFrom(kplus.Env.fromConfigMap(cm)); 68 | ``` 69 | 70 | ## Volume Mounts 71 | 72 | A very common capability is to mount a volume with some data onto a container. Using pure kubernetes API, this would require writing something like: 73 | 74 | ```yaml 75 | kind: Pod 76 | apiVersion: v1 77 | spec: 78 | containers: 79 | - name: main 80 | volumeMounts: 81 | - mountPath: /path/to/mount 82 | name: 'config-volume' 83 | volumes: 84 | - name: 'config-volume' 85 | configMap: 86 | name: 'config' 87 | ``` 88 | 89 | Notice the apparent redundancy of having to specify the volume name twice. Also, if you happen to need the same mount in other pods, 90 | you would need to duplicate this configuration. This can get complex and cluttered very fast. 91 | 92 | In contrast, here is how to do this with `cdk8s+`: 93 | 94 | ```typescript 95 | const config = kplus.ConfigMap.fromConfigMapName(this, 'Config', 'config'); 96 | const volume = kplus.Volume.fromConfigMap(this, 'Volume', config); 97 | 98 | const pod = new kplus.Pod(this, 'Pod'); 99 | const container = pod.addContainer({ 100 | image: 'my-app' 101 | }) 102 | 103 | // Cool alert: every pod that will later be configured with this container, 104 | // will automatically have access to this volume, so you don't need to explicitly add it to the pod spec!. 105 | container.mount('/path/to/mount', volume); 106 | ``` 107 | 108 | ## Probes 109 | 110 | A [Probe] is a diagnostic performed periodically by the kubelet on a Container. To 111 | perform a diagnostic, the kubelet calls a Handler implemented by the container. 112 | 113 | [Probe]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#probe-v1-core 114 | 115 | A `Probe` instance can be created through one of the `fromXxx` static methods: 116 | 117 | - `Probe.fromHttpGet()` 118 | - `Probe.fromCommand()` 119 | 120 | Readiness, liveness, and startup probes can be configured at the container-level through the `readiness`, `liveness`, and `startup` options: 121 | 122 | ```typescript 123 | new kplus.Pod(this, 'Pod', { 124 | containers: [ 125 | { 126 | image: 'my-app', 127 | readiness: kplus.Probe.fromHttpGet('/ping'), 128 | } 129 | ] 130 | }); 131 | ``` 132 | 133 | See the API reference for details. 134 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "env": { 4 | "jest": true, 5 | "node": true 6 | }, 7 | "root": true, 8 | "plugins": [ 9 | "@typescript-eslint", 10 | "import", 11 | "@stylistic" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module", 17 | "project": "./tsconfig.dev.json" 18 | }, 19 | "extends": [ 20 | "plugin:import/typescript" 21 | ], 22 | "settings": { 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [ 25 | ".ts", 26 | ".tsx" 27 | ] 28 | }, 29 | "import/resolver": { 30 | "node": {}, 31 | "typescript": { 32 | "project": "./tsconfig.dev.json", 33 | "alwaysTryTypes": true 34 | } 35 | } 36 | }, 37 | "ignorePatterns": [ 38 | "*.js", 39 | "*.d.ts", 40 | "node_modules/", 41 | "*.generated.ts", 42 | "coverage", 43 | "!.projenrc.ts", 44 | "!projenrc/**/*.ts" 45 | ], 46 | "rules": { 47 | "@stylistic/indent": [ 48 | "error", 49 | 2 50 | ], 51 | "@stylistic/quotes": [ 52 | "error", 53 | "single", 54 | { 55 | "avoidEscape": true 56 | } 57 | ], 58 | "@stylistic/comma-dangle": [ 59 | "error", 60 | "always-multiline" 61 | ], 62 | "@stylistic/comma-spacing": [ 63 | "error", 64 | { 65 | "before": false, 66 | "after": true 67 | } 68 | ], 69 | "@stylistic/no-multi-spaces": [ 70 | "error", 71 | { 72 | "ignoreEOLComments": false 73 | } 74 | ], 75 | "@stylistic/array-bracket-spacing": [ 76 | "error", 77 | "never" 78 | ], 79 | "@stylistic/array-bracket-newline": [ 80 | "error", 81 | "consistent" 82 | ], 83 | "@stylistic/object-curly-spacing": [ 84 | "error", 85 | "always" 86 | ], 87 | "@stylistic/object-curly-newline": [ 88 | "error", 89 | { 90 | "multiline": true, 91 | "consistent": true 92 | } 93 | ], 94 | "@stylistic/object-property-newline": [ 95 | "error", 96 | { 97 | "allowAllPropertiesOnSameLine": true 98 | } 99 | ], 100 | "@stylistic/keyword-spacing": [ 101 | "error" 102 | ], 103 | "@stylistic/brace-style": [ 104 | "error", 105 | "1tbs", 106 | { 107 | "allowSingleLine": true 108 | } 109 | ], 110 | "@stylistic/space-before-blocks": [ 111 | "error" 112 | ], 113 | "@stylistic/member-delimiter-style": [ 114 | "error" 115 | ], 116 | "@stylistic/semi": [ 117 | "error", 118 | "always" 119 | ], 120 | "@stylistic/max-len": [ 121 | "error", 122 | { 123 | "code": 150, 124 | "ignoreUrls": true, 125 | "ignoreStrings": true, 126 | "ignoreTemplateLiterals": true, 127 | "ignoreComments": true, 128 | "ignoreRegExpLiterals": true 129 | } 130 | ], 131 | "@stylistic/quote-props": [ 132 | "error", 133 | "consistent-as-needed" 134 | ], 135 | "@stylistic/key-spacing": [ 136 | "error" 137 | ], 138 | "@stylistic/no-multiple-empty-lines": [ 139 | "error" 140 | ], 141 | "@stylistic/no-trailing-spaces": [ 142 | "error" 143 | ], 144 | "curly": [ 145 | "error", 146 | "multi-line", 147 | "consistent" 148 | ], 149 | "@typescript-eslint/no-require-imports": "error", 150 | "import/no-extraneous-dependencies": [ 151 | "error", 152 | { 153 | "devDependencies": [ 154 | "**/test/**", 155 | "**/build-tools/**", 156 | ".projenrc.ts", 157 | "projenrc/**/*.ts" 158 | ], 159 | "optionalDependencies": false, 160 | "peerDependencies": true 161 | } 162 | ], 163 | "import/no-unresolved": [ 164 | "error" 165 | ], 166 | "import/order": [ 167 | "warn", 168 | { 169 | "groups": [ 170 | "builtin", 171 | "external" 172 | ], 173 | "alphabetize": { 174 | "order": "asc", 175 | "caseInsensitive": true 176 | } 177 | } 178 | ], 179 | "import/no-duplicates": [ 180 | "error" 181 | ], 182 | "no-shadow": [ 183 | "off" 184 | ], 185 | "@typescript-eslint/no-shadow": "error", 186 | "@typescript-eslint/no-floating-promises": "error", 187 | "no-return-await": [ 188 | "off" 189 | ], 190 | "@typescript-eslint/return-await": "error", 191 | "dot-notation": [ 192 | "error" 193 | ], 194 | "no-bitwise": [ 195 | "error" 196 | ], 197 | "@typescript-eslint/member-ordering": [ 198 | "error", 199 | { 200 | "default": [ 201 | "public-static-field", 202 | "public-static-method", 203 | "protected-static-field", 204 | "protected-static-method", 205 | "private-static-field", 206 | "private-static-method", 207 | "field", 208 | "constructor", 209 | "method" 210 | ] 211 | } 212 | ] 213 | }, 214 | "overrides": [ 215 | { 216 | "files": [ 217 | ".projenrc.ts" 218 | ], 219 | "rules": { 220 | "@typescript-eslint/no-require-imports": "off", 221 | "import/no-extraneous-dependencies": "off" 222 | } 223 | } 224 | ] 225 | } 226 | -------------------------------------------------------------------------------- /test/service.test.ts: -------------------------------------------------------------------------------- 1 | import { Testing, ApiObject } from 'cdk8s'; 2 | import { Node } from 'constructs'; 3 | import * as kplus from '../src'; 4 | 5 | test('defaultChild', () => { 6 | 7 | const chart = Testing.chart(); 8 | 9 | const defaultChild = Node.of(new kplus.Service(chart, 'Service')).defaultChild as ApiObject; 10 | 11 | expect(defaultChild.kind).toEqual('Service'); 12 | 13 | }); 14 | 15 | test('Must be configured with at least one port', () => { 16 | 17 | const chart = Testing.chart(); 18 | 19 | new kplus.Service(chart, 'service'); 20 | 21 | expect(() => Testing.synth(chart)).toThrowError( 22 | 'A service must be configured with a port', 23 | ); 24 | 25 | }); 26 | 27 | test('Can provide cluster IP', () => { 28 | // GIVEN 29 | const chart = Testing.chart(); 30 | 31 | // WHEN 32 | new kplus.Service(chart, 'service', { 33 | ports: [{ port: 9000 }], 34 | clusterIP: '3000', 35 | }); 36 | 37 | // THEN 38 | const spec = Testing.synth(chart)[0].spec; 39 | expect(spec).toEqual({ 40 | clusterIP: '3000', 41 | externalIPs: [], 42 | ports: [ 43 | { 44 | port: 9000, 45 | }, 46 | ], 47 | selector: {}, 48 | type: 'ClusterIP', 49 | }); 50 | }); 51 | 52 | test('can select a deployment', () => { 53 | 54 | const chart = Testing.chart(); 55 | 56 | const deployment = new kplus.Deployment(chart, 'Deployment', { 57 | containers: [{ image: 'image' }], 58 | }); 59 | 60 | new kplus.Service(chart, 'service', { 61 | ports: [{ port: 9000 }], 62 | selector: deployment, 63 | }); 64 | 65 | expect(Testing.synth(chart)).toMatchSnapshot(); 66 | 67 | }); 68 | 69 | test('Can select by label', () => { 70 | 71 | const chart = Testing.chart(); 72 | 73 | const service = new kplus.Service(chart, 'service', { 74 | ports: [{ port: 9000 }], 75 | }); 76 | 77 | service.selectLabel('key', 'value'); 78 | 79 | // assert the k8s spec has it. 80 | const spec = Testing.synth(chart)[0].spec; 81 | expect(spec.selector).toEqual({ key: 'value' }); 82 | 83 | }); 84 | 85 | test('Can serve by port', () => { 86 | 87 | const chart = Testing.chart(); 88 | 89 | const service = new kplus.Service(chart, 'service'); 90 | 91 | service.bind(9000, { targetPort: 80, nodePort: 30080 }); 92 | 93 | // assert the k8s spec has it. 94 | const spec = Testing.synth(chart)[0].spec; 95 | expect(spec.ports).toEqual([{ port: 9000, targetPort: 80, nodePort: 30080 }]); 96 | 97 | // assert the service object has it. 98 | expect(service.ports).toEqual([{ port: 9000, targetPort: 80, nodePort: 30080 }]); 99 | 100 | }); 101 | 102 | test('Synthesizes spec lazily', () => { 103 | 104 | const chart = Testing.chart(); 105 | 106 | const service = new kplus.Service(chart, 'Service'); 107 | 108 | service.select(kplus.Pods.select(chart, 'Pods', { labels: { key: 'value' } })); 109 | service.bind(9000); 110 | 111 | const spec = Testing.synth(chart)[0].spec; 112 | expect(spec.selector).toEqual({ key: 'value' }); 113 | expect(spec.ports).toEqual([{ port: 9000 }]); 114 | 115 | }); 116 | 117 | test('Must set externalIPs if provided', () => { 118 | 119 | const chart = Testing.chart(); 120 | const externalIPs = ['1.1.1.1', '8.8.8.8']; 121 | const service = new kplus.Service(chart, 'service', { externalIPs }); 122 | service.bind(53); 123 | 124 | const spec = Testing.synth(chart)[0].spec; 125 | 126 | expect(spec.externalIPs).toEqual(externalIPs); 127 | 128 | }); 129 | 130 | test('Must be configured with externalName if type is EXTERNAL_NAME', () => { 131 | 132 | const chart = Testing.chart(); 133 | 134 | const service = new kplus.Service(chart, 'service', { 135 | type: kplus.ServiceType.EXTERNAL_NAME, 136 | }); 137 | 138 | service.bind(5432); 139 | 140 | expect(() => Testing.synth(chart)).toThrowError( 141 | 'A service with type EXTERNAL_NAME requires an externalName prop', 142 | ); 143 | 144 | }); 145 | 146 | test('Type defaults to EXTERNAL_NAME if externalName if given', () => { 147 | 148 | const chart = Testing.chart(); 149 | 150 | const service = new kplus.Service(chart, 'service', { 151 | externalName: 'test-external-name', 152 | }); 153 | 154 | service.bind(5432); 155 | 156 | const spec = Testing.synth(chart)[0].spec; 157 | 158 | expect(spec.type).toEqual(kplus.ServiceType.EXTERNAL_NAME); 159 | 160 | }); 161 | 162 | test('Can restrict CIDR IP addresses for a LoadBalancer type', () => { 163 | const sourceRanges = ['143.231.0.0/16']; 164 | const chart = Testing.chart(); 165 | new kplus.Service(chart, 'service', { 166 | ports: [{ port: 80 }], 167 | type: kplus.ServiceType.LOAD_BALANCER, 168 | loadBalancerSourceRanges: sourceRanges, 169 | }); 170 | 171 | // assert the k8s spec has it. 172 | const spec = Testing.synth(chart)[0].spec; 173 | expect(spec.loadBalancerSourceRanges).toEqual(sourceRanges); 174 | 175 | }); 176 | 177 | test('can be exposed by an ingress', () => { 178 | 179 | const chart = Testing.chart(); 180 | 181 | const service = new kplus.Service(chart, 'Service'); 182 | service.bind(80); 183 | 184 | service.exposeViaIngress('/hello'); 185 | const ingress = Testing.synth(chart)[1]; 186 | expect(ingress).toMatchSnapshot(); 187 | }); 188 | 189 | test('can set publishNotReadyAddresses', () => { 190 | const chart = Testing.chart(); 191 | new kplus.Service(chart, 'service', { 192 | ports: [{ port: 80 }], 193 | publishNotReadyAddresses: true, 194 | }); 195 | 196 | const spec = Testing.synth(chart)[0].spec; 197 | expect(spec.publishNotReadyAddresses).toBeTruthy(); 198 | }); 199 | -------------------------------------------------------------------------------- /src/service-account.ts: -------------------------------------------------------------------------------- 1 | import { ApiObject, Lazy } from 'cdk8s'; 2 | import { Construct } from 'constructs'; 3 | import * as base from './base'; 4 | import * as k8s from './imports/k8s'; 5 | import * as rb from './role-binding'; 6 | import * as secret from './secret'; 7 | import { undefinedIfEmpty } from './utils'; 8 | 9 | 10 | export interface IServiceAccount extends base.IResource, rb.ISubject { 11 | 12 | } 13 | 14 | /** 15 | * Properties for initialization of `ServiceAccount`. 16 | */ 17 | export interface ServiceAccountProps extends base.ResourceProps { 18 | /** 19 | * List of secrets allowed to be used by pods running using this 20 | * ServiceAccount. 21 | * 22 | * @see https://kubernetes.io/docs/concepts/configuration/secret 23 | */ 24 | readonly secrets?: secret.ISecret[]; 25 | 26 | /** 27 | * Indicates whether pods running as this service account 28 | * should have an API token automatically mounted. Can be overridden at the pod level. 29 | * 30 | * @default false 31 | * @see https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server 32 | */ 33 | readonly automountToken?: boolean; 34 | } 35 | 36 | export interface FromServiceAccountNameOptions { 37 | 38 | /** 39 | * The name of the namespace the service account belongs to. 40 | * 41 | * @default "default" 42 | */ 43 | readonly namespaceName?: string; 44 | } 45 | 46 | class ImportedServiceAccount extends Construct implements IServiceAccount { 47 | 48 | private readonly _name: string; 49 | private readonly _namespaceName: string; 50 | 51 | public readonly resourceType = 'serviceaccounts'; 52 | 53 | constructor(scope: Construct, id: string, name: string, options: FromServiceAccountNameOptions = {}) { 54 | super(scope, id); 55 | this._name = name; 56 | this._namespaceName = options.namespaceName ?? 'default'; 57 | } 58 | 59 | public toSubjectConfiguration(): rb.SubjectConfiguration { 60 | return { 61 | kind: this.kind, 62 | name: this.name, 63 | apiGroup: this.apiGroup, 64 | namespace: this._namespaceName, 65 | }; 66 | } 67 | 68 | public get name(): string { 69 | return this._name; 70 | } 71 | 72 | public get apiVersion(): string { 73 | return k8s.KubeServiceAccount.GVK.apiVersion; 74 | } 75 | 76 | public get apiGroup(): string { 77 | return ''; 78 | } 79 | 80 | public get kind(): string { 81 | return k8s.KubeServiceAccount.GVK.kind; 82 | } 83 | 84 | public get resourceName(): string { 85 | return this.name; 86 | } 87 | 88 | } 89 | 90 | /** 91 | * A service account provides an identity for processes that run in a Pod. 92 | * 93 | * When you (a human) access the cluster (for example, using kubectl), you are 94 | * authenticated by the apiserver as a particular User Account (currently this 95 | * is usually admin, unless your cluster administrator has customized your 96 | * cluster). Processes in containers inside pods can also contact the apiserver. 97 | * When they do, they are authenticated as a particular Service Account (for 98 | * example, default). 99 | * 100 | * @see https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account 101 | */ 102 | export class ServiceAccount extends base.Resource implements IServiceAccount, rb.ISubject { 103 | 104 | /** 105 | * Imports a service account from the cluster as a reference. 106 | * @param name The name of the service account resource. 107 | * @param options additional options. 108 | */ 109 | public static fromServiceAccountName(scope: Construct, id: string, name: string, options: FromServiceAccountNameOptions = {}): IServiceAccount { 110 | return new ImportedServiceAccount(scope, id, name, options); 111 | } 112 | 113 | /** 114 | * @see base.Resource.apiObject 115 | */ 116 | protected readonly apiObject: ApiObject; 117 | 118 | public readonly resourceType = 'serviceaccounts'; 119 | 120 | private readonly _secrets: secret.ISecret[]; 121 | 122 | /** 123 | * Whether or not a token is automatically mounted for this 124 | * service account. 125 | */ 126 | public readonly automountToken: boolean; 127 | 128 | constructor(scope: Construct, id: string, props: ServiceAccountProps = { }) { 129 | super(scope, id); 130 | 131 | this._secrets = props.secrets ?? []; 132 | this.automountToken = props.automountToken ?? false; 133 | 134 | this.apiObject = new k8s.KubeServiceAccount(this, 'Resource', { 135 | metadata: props.metadata, 136 | secrets: Lazy.any({ produce: () => undefinedIfEmpty(this._secrets.map(s => ({ name: s.name }))) }), 137 | automountServiceAccountToken: this.automountToken, 138 | }); 139 | } 140 | 141 | /** 142 | * Allow a secret to be accessed by pods using this service account. 143 | * @param secr The secret 144 | */ 145 | public addSecret(secr: secret.ISecret) { 146 | this._secrets.push(secr); 147 | } 148 | 149 | /** 150 | * List of secrets allowed to be used by pods running using this service 151 | * account. 152 | * 153 | * Returns a copy. To add a secret, use `addSecret()`. 154 | */ 155 | public get secrets() { 156 | return [...this._secrets]; 157 | } 158 | 159 | /** 160 | * @see ISubect.toSubjectConfiguration() 161 | */ 162 | public toSubjectConfiguration(): rb.SubjectConfiguration { 163 | return { 164 | kind: this.kind, 165 | name: this.name, 166 | apiGroup: this.apiGroup, 167 | namespace: this.metadata.namespace, 168 | }; 169 | } 170 | 171 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk8s-plus-33", 3 | "description": "cdk8s+ is a software development framework that provides high level abstractions for authoring Kubernetes applications. cdk8s-plus-33 synthesizes Kubernetes manifests for Kubernetes 1.33.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/cdk8s-team/cdk8s-plus.git" 7 | }, 8 | "scripts": { 9 | "build": "npx projen build", 10 | "bump": "npx projen bump", 11 | "clobber": "npx projen clobber", 12 | "compat": "npx projen compat", 13 | "compile": "npx projen compile", 14 | "default": "npx projen default", 15 | "docgen": "npx projen docgen", 16 | "docgen:java": "npx projen docgen:java", 17 | "docgen:python": "npx projen docgen:python", 18 | "docgen:typescript": "npx projen docgen:typescript", 19 | "eject": "npx projen eject", 20 | "eslint": "npx projen eslint", 21 | "import": "npx projen import", 22 | "package": "npx projen package", 23 | "package-all": "npx projen package-all", 24 | "package:dotnet": "npx projen package:dotnet", 25 | "package:go": "npx projen package:go", 26 | "package:java": "npx projen package:java", 27 | "package:js": "npx projen package:js", 28 | "package:python": "npx projen package:python", 29 | "post-compile": "npx projen post-compile", 30 | "post-upgrade": "npx projen post-upgrade", 31 | "pre-compile": "npx projen pre-compile", 32 | "release:k8s-33/main": "npx projen release:k8s-33/main", 33 | "rotate": "npx projen rotate", 34 | "test": "npx projen test", 35 | "test:watch": "npx projen test:watch", 36 | "unbump": "npx projen unbump", 37 | "upgrade-compiler-dependencies": "npx projen upgrade-compiler-dependencies", 38 | "upgrade-configuration": "npx projen upgrade-configuration", 39 | "upgrade-dev-dependencies": "npx projen upgrade-dev-dependencies", 40 | "upgrade-runtime-dependencies": "npx projen upgrade-runtime-dependencies", 41 | "watch": "npx projen watch", 42 | "projen": "npx projen" 43 | }, 44 | "author": { 45 | "name": "Amazon Web Services", 46 | "url": "https://aws.amazon.com", 47 | "organization": false 48 | }, 49 | "devDependencies": { 50 | "@cdk8s/projen-common": "^0.0.652", 51 | "@stylistic/eslint-plugin": "^2", 52 | "@types/jest": "^27", 53 | "@types/node": "16.18.78", 54 | "@typescript-eslint/eslint-plugin": "^8", 55 | "@typescript-eslint/parser": "^8", 56 | "cdk8s": "2.70.36", 57 | "cdk8s-cli": "^2.203.14", 58 | "commit-and-tag-version": "^12", 59 | "constructs": "10.3.0", 60 | "eslint": "^9", 61 | "eslint-import-resolver-typescript": "^2.7.1", 62 | "eslint-plugin-import": "^2.32.0", 63 | "jest": "^27", 64 | "jest-junit": "^16", 65 | "jsii": "~5.8.0", 66 | "jsii-diff": "^1.122.0", 67 | "jsii-docgen": "^10.5.0", 68 | "jsii-pacmak": "^1.122.0", 69 | "jsii-rosetta": "~5.8.0", 70 | "projen": "^0.98.30", 71 | "snake-case": "^3.0.4", 72 | "ts-jest": "^27", 73 | "ts-node": "^10", 74 | "typescript": "^4.9.5" 75 | }, 76 | "peerDependencies": { 77 | "cdk8s": "^2.68.11", 78 | "constructs": "^10.3.0" 79 | }, 80 | "dependencies": { 81 | "minimatch": "^9.0.5" 82 | }, 83 | "bundledDependencies": [ 84 | "minimatch" 85 | ], 86 | "resolutions": { 87 | "**/downlevel-dts/**/typescript": "~5.2.2" 88 | }, 89 | "keywords": [ 90 | "cdk", 91 | "configuration", 92 | "constructs", 93 | "containers", 94 | "k8s", 95 | "kubernetes", 96 | "microservices" 97 | ], 98 | "engines": { 99 | "node": ">= 16.20.0" 100 | }, 101 | "main": "lib/index.js", 102 | "license": "Apache-2.0", 103 | "publishConfig": { 104 | "access": "public" 105 | }, 106 | "version": "0.0.0", 107 | "jest": { 108 | "coverageProvider": "v8", 109 | "testMatch": [ 110 | "/@(src|test)/**/*(*.)@(spec|test).ts?(x)", 111 | "/@(src|test)/**/__tests__/**/*.ts?(x)", 112 | "/@(projenrc)/**/*(*.)@(spec|test).ts?(x)", 113 | "/@(projenrc)/**/__tests__/**/*.ts?(x)" 114 | ], 115 | "clearMocks": true, 116 | "collectCoverage": true, 117 | "coverageReporters": [ 118 | "json", 119 | "lcov", 120 | "clover", 121 | "cobertura", 122 | "text" 123 | ], 124 | "coverageDirectory": "coverage", 125 | "coveragePathIgnorePatterns": [ 126 | "/node_modules/" 127 | ], 128 | "testPathIgnorePatterns": [ 129 | "/node_modules/" 130 | ], 131 | "watchPathIgnorePatterns": [ 132 | "/node_modules/" 133 | ], 134 | "reporters": [ 135 | "default", 136 | [ 137 | "jest-junit", 138 | { 139 | "outputDirectory": "test-reports" 140 | } 141 | ] 142 | ], 143 | "preset": "ts-jest", 144 | "globals": { 145 | "ts-jest": { 146 | "tsconfig": "tsconfig.dev.json" 147 | } 148 | } 149 | }, 150 | "types": "lib/index.d.ts", 151 | "stability": "stable", 152 | "jsii": { 153 | "outdir": "dist", 154 | "targets": { 155 | "java": { 156 | "package": "org.cdk8s.plus33", 157 | "maven": { 158 | "groupId": "org.cdk8s", 159 | "artifactId": "cdk8s-plus-33" 160 | } 161 | }, 162 | "python": { 163 | "distName": "cdk8s-plus-33", 164 | "module": "cdk8s_plus_33" 165 | }, 166 | "dotnet": { 167 | "namespace": "Org.Cdk8s.Plus33", 168 | "packageId": "Org.Cdk8s.Plus33" 169 | }, 170 | "go": { 171 | "moduleName": "github.com/cdk8s-team/cdk8s-plus-go" 172 | } 173 | }, 174 | "tsc": { 175 | "outDir": "lib", 176 | "rootDir": "src" 177 | } 178 | }, 179 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 180 | } 181 | -------------------------------------------------------------------------------- /src/namespace.ts: -------------------------------------------------------------------------------- 1 | import { ApiObject, Lazy } from 'cdk8s'; 2 | import { Construct, IConstruct } from 'constructs'; 3 | import * as base from './base'; 4 | import * as k8s from './imports/k8s'; 5 | import * as networkpolicy from './network-policy'; 6 | import * as pod from './pod'; 7 | 8 | /** 9 | * Configuration for selecting namespaces. 10 | */ 11 | export interface NamespaceSelectorConfig { 12 | 13 | /** 14 | * A selector to select namespaces by labels. 15 | */ 16 | readonly labelSelector?: pod.LabelSelector; 17 | 18 | /** 19 | * A list of names to select namespaces by names. 20 | */ 21 | readonly names?: string[]; 22 | } 23 | 24 | /** 25 | * Represents an object that can select namespaces. 26 | */ 27 | export interface INamespaceSelector extends IConstruct { 28 | /** 29 | * Return the configuration of this selector. 30 | */ 31 | toNamespaceSelectorConfig(): NamespaceSelectorConfig; 32 | } 33 | 34 | /** 35 | * Properties for `Namespace`. 36 | */ 37 | export interface NamespaceProps extends base.ResourceProps {} 38 | 39 | /** 40 | * In Kubernetes, namespaces provides a mechanism for isolating groups of resources within a single cluster. 41 | * Names of resources need to be unique within a namespace, but not across namespaces. 42 | * Namespace-based scoping is applicable only for namespaced objects (e.g. Deployments, Services, etc) and 43 | * not for cluster-wide objects (e.g. StorageClass, Nodes, PersistentVolumes, etc). 44 | */ 45 | export class Namespace extends base.Resource implements INamespaceSelector, networkpolicy.INetworkPolicyPeer { 46 | 47 | /** 48 | * @see https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#automatic-labelling 49 | */ 50 | public static readonly NAME_LABEL = 'kubernetes.io/metadata.name'; 51 | 52 | /** 53 | * @see base.Resource.apiObject 54 | */ 55 | protected readonly apiObject: ApiObject; 56 | 57 | public readonly resourceType: string = 'namespaces'; 58 | 59 | private readonly _pods: pod.Pods; 60 | 61 | public constructor(scope: Construct, id: string, props: NamespaceProps = {}) { 62 | super(scope, id); 63 | 64 | this.apiObject = new k8s.KubeNamespace(this, 'Resource', { 65 | metadata: props.metadata, 66 | spec: Lazy.any({ produce: () => this._toKube() }), 67 | }); 68 | 69 | this._pods = pod.Pods.all(this, 'Pods', { 70 | namespaces: Namespaces.select(this, 'Namespaces', { names: [this.name] }), 71 | }); 72 | 73 | } 74 | 75 | /** 76 | * @see INamespaceSelector.toNamespaceSelectorConfig() 77 | */ 78 | public toNamespaceSelectorConfig(): NamespaceSelectorConfig { 79 | return { names: [this.name] }; 80 | } 81 | 82 | /** 83 | * @see INetworkPolicyPeer.toNetworkPolicyPeerConfig() 84 | */ 85 | public toNetworkPolicyPeerConfig(): networkpolicy.NetworkPolicyPeerConfig { 86 | return this._pods.toNetworkPolicyPeerConfig(); 87 | } 88 | 89 | /** 90 | * @see INetworkPolicyPeer.toPodSelector() 91 | */ 92 | public toPodSelector(): pod.IPodSelector | undefined { 93 | return this._pods.toPodSelector(); 94 | } 95 | 96 | /** 97 | * @internal 98 | */ 99 | public _toKube(): k8s.NamespaceSpec { 100 | return {}; 101 | } 102 | 103 | } 104 | 105 | /** 106 | * Options for `Namespaces.select`. 107 | */ 108 | export interface NamespacesSelectOptions { 109 | 110 | /** 111 | * Labels the namespaces must have. 112 | * This is equivalent to using an 'Is' selector. 113 | * 114 | * @default - no strict labels requirements. 115 | */ 116 | readonly labels?: { [key: string]: string }; 117 | 118 | /** 119 | * Namespaces must satisfy these selectors. 120 | * The selectors query labels, just like the `labels` property, but they 121 | * provide a more advanced matching mechanism. 122 | * 123 | * @default - no selector requirements. 124 | */ 125 | readonly expressions?: pod.LabelExpression[]; 126 | 127 | /** 128 | * Namespaces names must be one of these. 129 | * 130 | * @default - no name requirements. 131 | */ 132 | readonly names?: string[]; 133 | 134 | } 135 | 136 | /** 137 | * Represents a group of namespaces. 138 | */ 139 | export class Namespaces extends Construct implements INamespaceSelector, networkpolicy.INetworkPolicyPeer { 140 | 141 | /** 142 | * Select specific namespaces. 143 | */ 144 | public static select(scope: Construct, id: string, options: NamespacesSelectOptions): Namespaces { 145 | return new Namespaces(scope, id, options.expressions, options.names, options.labels); 146 | } 147 | 148 | /** 149 | * Select all namespaces. 150 | */ 151 | public static all(scope: Construct, id: string): Namespaces { 152 | return Namespaces.select(scope, id, { expressions: [], labels: {} }); 153 | } 154 | 155 | private readonly _pods: pod.Pods; 156 | 157 | constructor(scope: Construct, id: string, 158 | private readonly expressions?: pod.LabelExpression[], 159 | private readonly names?: string[], 160 | private readonly labels?: { [key: string]: string }) { 161 | super(scope, id); 162 | 163 | this._pods = pod.Pods.all(this, 'Pods', { namespaces: this }); 164 | } 165 | 166 | /** 167 | * @see INamespaceSelector.toNamespaceSelectorConfig() 168 | */ 169 | public toNamespaceSelectorConfig(): NamespaceSelectorConfig { 170 | return { 171 | labelSelector: pod.LabelSelector.of({ expressions: this.expressions, labels: this.labels } ), 172 | names: this.names, 173 | }; 174 | } 175 | 176 | /** 177 | * @see INetworkPolicyPeer.toNetworkPolicyPeerConfig() 178 | */ 179 | public toNetworkPolicyPeerConfig(): networkpolicy.NetworkPolicyPeerConfig { 180 | return this._pods.toNetworkPolicyPeerConfig(); 181 | } 182 | 183 | /** 184 | * @see INetworkPolicyPeer.toPodSelector() 185 | */ 186 | public toPodSelector(): pod.IPodSelector | undefined { 187 | return this._pods.toPodSelector(); 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /test/__snapshots__/daemon-set.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Can be isolated 1`] = ` 4 | Array [ 5 | Object { 6 | "apiVersion": "apps/v1", 7 | "kind": "DaemonSet", 8 | "metadata": Object { 9 | "name": "test-daemonset-c8482ea2", 10 | }, 11 | "spec": Object { 12 | "minReadySeconds": 0, 13 | "selector": Object { 14 | "matchLabels": Object { 15 | "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186", 16 | }, 17 | }, 18 | "template": Object { 19 | "metadata": Object { 20 | "labels": Object { 21 | "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186", 22 | }, 23 | }, 24 | "spec": Object { 25 | "automountServiceAccountToken": false, 26 | "containers": Array [ 27 | Object { 28 | "image": "foobar", 29 | "imagePullPolicy": "Always", 30 | "name": "main", 31 | "resources": Object { 32 | "limits": Object { 33 | "cpu": "1500m", 34 | "memory": "2048Mi", 35 | }, 36 | "requests": Object { 37 | "cpu": "1000m", 38 | "memory": "512Mi", 39 | }, 40 | }, 41 | "securityContext": Object { 42 | "allowPrivilegeEscalation": false, 43 | "privileged": false, 44 | "readOnlyRootFilesystem": true, 45 | "runAsNonRoot": true, 46 | }, 47 | }, 48 | ], 49 | "dnsPolicy": "ClusterFirst", 50 | "hostNetwork": false, 51 | "restartPolicy": "Always", 52 | "securityContext": Object { 53 | "fsGroupChangePolicy": "Always", 54 | "runAsNonRoot": true, 55 | }, 56 | "setHostnameAsFQDN": false, 57 | "shareProcessNamespace": false, 58 | "terminationGracePeriodSeconds": 30, 59 | }, 60 | }, 61 | }, 62 | }, 63 | Object { 64 | "apiVersion": "networking.k8s.io/v1", 65 | "kind": "NetworkPolicy", 66 | "metadata": Object { 67 | "name": "test-daemonset-defaultdenyall-c826616a", 68 | }, 69 | "spec": Object { 70 | "podSelector": Object { 71 | "matchLabels": Object { 72 | "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186", 73 | }, 74 | }, 75 | "policyTypes": Array [ 76 | "Egress", 77 | "Ingress", 78 | ], 79 | }, 80 | }, 81 | ] 82 | `; 83 | 84 | exports[`custom 1`] = ` 85 | Array [ 86 | Object { 87 | "apiVersion": "apps/v1", 88 | "kind": "DaemonSet", 89 | "metadata": Object { 90 | "name": "test-daemonset-c8482ea2", 91 | }, 92 | "spec": Object { 93 | "minReadySeconds": 5, 94 | "selector": Object { 95 | "matchLabels": Object { 96 | "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186", 97 | }, 98 | }, 99 | "template": Object { 100 | "metadata": Object { 101 | "labels": Object { 102 | "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186", 103 | }, 104 | }, 105 | "spec": Object { 106 | "automountServiceAccountToken": false, 107 | "containers": Array [ 108 | Object { 109 | "image": "image", 110 | "imagePullPolicy": "Always", 111 | "name": "main", 112 | "resources": Object { 113 | "limits": Object { 114 | "cpu": "1500m", 115 | "memory": "2048Mi", 116 | }, 117 | "requests": Object { 118 | "cpu": "1000m", 119 | "memory": "512Mi", 120 | }, 121 | }, 122 | "securityContext": Object { 123 | "allowPrivilegeEscalation": false, 124 | "privileged": false, 125 | "readOnlyRootFilesystem": true, 126 | "runAsNonRoot": true, 127 | }, 128 | }, 129 | ], 130 | "dnsPolicy": "ClusterFirst", 131 | "hostNetwork": false, 132 | "restartPolicy": "Always", 133 | "securityContext": Object { 134 | "fsGroupChangePolicy": "Always", 135 | "runAsNonRoot": true, 136 | }, 137 | "setHostnameAsFQDN": false, 138 | "shareProcessNamespace": false, 139 | "terminationGracePeriodSeconds": 30, 140 | }, 141 | }, 142 | }, 143 | }, 144 | ] 145 | `; 146 | 147 | exports[`defaults 1`] = ` 148 | Array [ 149 | Object { 150 | "apiVersion": "apps/v1", 151 | "kind": "DaemonSet", 152 | "metadata": Object { 153 | "name": "test-daemonset-c8482ea2", 154 | }, 155 | "spec": Object { 156 | "minReadySeconds": 0, 157 | "selector": Object { 158 | "matchLabels": Object { 159 | "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186", 160 | }, 161 | }, 162 | "template": Object { 163 | "metadata": Object { 164 | "labels": Object { 165 | "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186", 166 | }, 167 | }, 168 | "spec": Object { 169 | "automountServiceAccountToken": false, 170 | "containers": Array [ 171 | Object { 172 | "image": "image", 173 | "imagePullPolicy": "Always", 174 | "name": "main", 175 | "resources": Object { 176 | "limits": Object { 177 | "cpu": "1500m", 178 | "memory": "2048Mi", 179 | }, 180 | "requests": Object { 181 | "cpu": "1000m", 182 | "memory": "512Mi", 183 | }, 184 | }, 185 | "securityContext": Object { 186 | "allowPrivilegeEscalation": false, 187 | "privileged": false, 188 | "readOnlyRootFilesystem": true, 189 | "runAsNonRoot": true, 190 | }, 191 | }, 192 | ], 193 | "dnsPolicy": "ClusterFirst", 194 | "hostNetwork": false, 195 | "restartPolicy": "Always", 196 | "securityContext": Object { 197 | "fsGroupChangePolicy": "Always", 198 | "runAsNonRoot": true, 199 | }, 200 | "setHostnameAsFQDN": false, 201 | "shareProcessNamespace": false, 202 | "terminationGracePeriodSeconds": 30, 203 | }, 204 | }, 205 | }, 206 | }, 207 | ] 208 | `; 209 | -------------------------------------------------------------------------------- /src/workload.ts: -------------------------------------------------------------------------------- 1 | import { ApiObjectMetadata, ApiObjectMetadataDefinition, Names } from 'cdk8s'; 2 | import { Construct } from 'constructs'; 3 | import * as k8s from './imports/k8s'; 4 | import * as pod from './pod'; 5 | import { undefinedIfEmpty } from './utils'; 6 | 7 | /** 8 | * Properties for `Workload`. 9 | */ 10 | export interface WorkloadProps extends pod.AbstractPodProps { 11 | 12 | /** 13 | * The pod metadata of this workload. 14 | */ 15 | readonly podMetadata?: ApiObjectMetadata; 16 | 17 | /** 18 | * Automatically allocates a pod label selector for this workload and add 19 | * it to the pod metadata. This ensures this workload manages pods created by 20 | * its pod template. 21 | * 22 | * @default true 23 | */ 24 | readonly select?: boolean; 25 | 26 | /** 27 | * Automatically spread pods across hostname and zones. 28 | * 29 | * @see https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/#internal-default-constraints 30 | * @default false 31 | */ 32 | readonly spread?: boolean; 33 | } 34 | 35 | /** 36 | * A label selector requirement is a selector that contains values, a key, and an operator that 37 | * relates the key and values. 38 | */ 39 | export interface LabelSelectorRequirement { 40 | /** 41 | * The label key that the selector applies to. 42 | */ 43 | readonly key: string; 44 | 45 | /** 46 | * Represents a key's relationship to a set of values. 47 | */ 48 | readonly operator: string; 49 | 50 | /** 51 | * An array of string values. If the operator is In or NotIn, the values array 52 | * must be non-empty. If the operator is Exists or DoesNotExist, 53 | * the values array must be empty. This array is replaced during a strategic merge patch. 54 | */ 55 | readonly values?: string[]; 56 | } 57 | 58 | /** 59 | * A workload is an application running on Kubernetes. Whether your workload is a single 60 | * component or several that work together, on Kubernetes you run it inside a set of pods. 61 | * In Kubernetes, a Pod represents a set of running containers on your cluster. 62 | */ 63 | export abstract class Workload extends pod.AbstractPod { 64 | 65 | public readonly connections: pod.PodConnections; 66 | 67 | public readonly scheduling: WorkloadScheduling; 68 | 69 | private readonly spread: boolean; 70 | 71 | private readonly _matchLabels: Record = {}; 72 | private readonly _matchExpressions: LabelSelectorRequirement[] = []; 73 | 74 | private _podMetadata?: ApiObjectMetadataDefinition; 75 | 76 | private readonly _props: WorkloadProps; 77 | private readonly _matcher: string; 78 | 79 | constructor(scope: Construct, id: string, props: WorkloadProps) { 80 | super(scope, id, props); 81 | 82 | this._props = props; 83 | this.scheduling = new WorkloadScheduling(this); 84 | this.connections = new pod.PodConnections(this); 85 | this.spread = props.spread ?? false; 86 | 87 | this._matcher = Names.toLabelValue(this); 88 | 89 | if (props.select ?? true) { 90 | this.select(pod.LabelSelector.of({ labels: { [pod.Pod.ADDRESS_LABEL]: this._matcher } })); 91 | } 92 | } 93 | 94 | /** 95 | * The metadata of pods in this workload. 96 | */ 97 | public get podMetadata(): ApiObjectMetadataDefinition { 98 | if (!this._podMetadata) { 99 | this._podMetadata = new ApiObjectMetadataDefinition({ 100 | ...this._props.podMetadata, 101 | apiObject: this.apiObject, 102 | }); 103 | this._podMetadata.addLabel(pod.Pod.ADDRESS_LABEL, this._matcher); 104 | } 105 | return this._podMetadata; 106 | } 107 | 108 | /** 109 | * Configure selectors for this workload. 110 | */ 111 | public select(...selectors: pod.LabelSelector[]) { 112 | for (const selector of selectors) { 113 | const kube = selector._toKube(); 114 | this._matchExpressions.push(...kube.matchExpressions ?? []); 115 | for (const [key, value] of Object.entries(kube.matchLabels ?? {})) { 116 | this._matchLabels[key] = value; 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * The label matchers this workload will use in order to select pods. 123 | * 124 | * Returns a a copy. Use `select()` to add label matchers. 125 | */ 126 | public get matchLabels(): Record { 127 | return { ...this._matchLabels }; 128 | } 129 | 130 | /** 131 | * The expression matchers this workload will use in order to select pods. 132 | * 133 | * Returns a a copy. Use `select()` to add expression matchers. 134 | */ 135 | public get matchExpressions(): LabelSelectorRequirement[] { 136 | return [...this._matchExpressions]; 137 | } 138 | 139 | /** 140 | * @internal 141 | */ 142 | public _toLabelSelector(): k8s.LabelSelector { 143 | return { 144 | matchExpressions: undefinedIfEmpty(this._matchExpressions), 145 | matchLabels: undefinedIfEmpty(this._matchLabels), 146 | }; 147 | } 148 | 149 | /** 150 | * @internal 151 | */ 152 | public _toPodSpec(): k8s.PodSpec { 153 | if (this.spread) { 154 | { 155 | this.scheduling.spread({ 156 | topology: pod.Topology.HOSTNAME, 157 | }); 158 | this.scheduling.spread({ 159 | topology: pod.Topology.ZONE, 160 | }); 161 | } 162 | }; 163 | 164 | const scheduling = this.scheduling._toKube(); 165 | 166 | return { 167 | ...super._toPodSpec(), 168 | affinity: scheduling.affinity, 169 | nodeName: scheduling.nodeName, 170 | tolerations: scheduling.tolerations, 171 | }; 172 | } 173 | } 174 | 175 | /** 176 | * Options for `WorkloadScheduling.spread`. 177 | */ 178 | export interface WorkloadSchedulingSpreadOptions { 179 | 180 | /** 181 | * Indicates the spread is optional, with this weight score. 182 | * 183 | * @default - no weight. spread is assumed to be required. 184 | */ 185 | readonly weight?: number; 186 | 187 | /** 188 | * Which topology to spread on. 189 | * 190 | * @default - Topology.HOSTNAME 191 | */ 192 | readonly topology?: pod.Topology; 193 | 194 | } 195 | 196 | /** 197 | * Controls the pod scheduling strategy of this workload. 198 | * It offers some additional API's on top of the core pod scheduling. 199 | */ 200 | export class WorkloadScheduling extends pod.PodScheduling { 201 | 202 | /** 203 | * Spread the pods in this workload by the topology key. 204 | * A spread is a separation of the pod from itself and is used to 205 | * balance out pod replicas across a given topology. 206 | */ 207 | public spread(options: WorkloadSchedulingSpreadOptions = {}) { 208 | this.separate(this.instance, { weight: options.weight, topology: options.topology ?? pod.Topology.HOSTNAME }); 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /docs/plus/deployment.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | Create a deployment to govern the lifecycle and orchestration of a set of identical pods. 4 | 5 | !!! tip "" 6 | [API Reference](../../reference/cdk8s-plus-33/typescript.md#deployment) 7 | 8 | ## Automatic pod selection 9 | 10 | When you specify pods in a deployment, you normally have to configure the appropriate labels and selectors to 11 | make the deployment control the relevant pods. This construct does this automatically. 12 | 13 | ```typescript 14 | import * as kplus from 'cdk8s-plus-33'; 15 | import { Construct } from 'constructs'; 16 | import { App, Chart, ChartProps } from 'cdk8s'; 17 | 18 | export class MyChart extends Chart { 19 | constructor(scope: Construct, id: string, props: ChartProps = { }) { 20 | super(scope, id, props); 21 | 22 | new kplus.Deployment(this, 'FrontEnds', { 23 | containers: [ { image: 'node' } ], 24 | }); 25 | } 26 | } 27 | 28 | const app = new App(); 29 | new MyChart(app, 'deployment'); 30 | app.synth(); 31 | ``` 32 | 33 | Note the resulting manifest contains a special `cdk8s.io/metadata.addr` label that is applied to the pods, and is used as 34 | the selector for the deployment. 35 | 36 | ```yaml 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: deployment-frontends-c8e48310 41 | spec: 42 | minReadySeconds: 0 43 | progressDeadlineSeconds: 600 44 | replicas: 2 45 | selector: 46 | matchLabels: 47 | cdk8s.io/metadata.addr: deployment-FrontEnds-c89e9e97 48 | strategy: 49 | rollingUpdate: 50 | maxSurge: 25% 51 | maxUnavailable: 25% 52 | type: RollingUpdate 53 | template: 54 | metadata: 55 | labels: 56 | cdk8s.io/metadata.addr: deployment-FrontEnds-c89e9e97 57 | spec: 58 | automountServiceAccountToken: false 59 | containers: 60 | - image: node 61 | imagePullPolicy: Always 62 | name: main 63 | resources: 64 | limits: 65 | cpu: 1500m 66 | memory: 2048Mi 67 | requests: 68 | cpu: 1000m 69 | memory: 512Mi 70 | securityContext: 71 | allowPrivilegeEscalation: false 72 | privileged: false 73 | readOnlyRootFilesystem: true 74 | runAsGroup: 26000 75 | runAsNonRoot: true 76 | runAsUser: 25000 77 | dnsPolicy: ClusterFirst 78 | restartPolicy: Always 79 | securityContext: 80 | fsGroupChangePolicy: Always 81 | runAsNonRoot: true 82 | setHostnameAsFQDN: false 83 | ``` 84 | 85 | ## Exposing via a service 86 | 87 | Following up on pod selection, you can also easily create a service that will select the pods relevant to the deployment. 88 | 89 | ```typescript 90 | // store the deployment to created in a constant 91 | const frontends = new kplus.Deployment(this, 'FrontEnds', { 92 | containers: [ { 93 | image: 'node', 94 | portNumber: 9000, 95 | } ], 96 | }); 97 | 98 | // create a ClusterIP service that listens on port 9000 and redirects to port 9000 on the containers. 99 | frontends.exposeViaService({ ports: [{ 100 | port: 9000, 101 | }] 102 | }); 103 | ``` 104 | 105 | Notice the resulting manifest, will have the same `cdk8s.io/metadata.addr` magic label as the selector. 106 | This will cause the service to attach to the pods that were configured as part of the said deployment. 107 | 108 | ```yaml 109 | apiVersion: apps/v1 110 | kind: Deployment 111 | metadata: 112 | name: deployment-frontends-c8e48310 113 | spec: 114 | minReadySeconds: 0 115 | progressDeadlineSeconds: 600 116 | replicas: 2 117 | selector: 118 | matchLabels: 119 | cdk8s.io/metadata.addr: deployment-FrontEnds-c89e9e97 120 | strategy: 121 | rollingUpdate: 122 | maxSurge: 25% 123 | maxUnavailable: 25% 124 | type: RollingUpdate 125 | template: 126 | metadata: 127 | labels: 128 | cdk8s.io/metadata.addr: deployment-FrontEnds-c89e9e97 129 | spec: 130 | automountServiceAccountToken: false 131 | containers: 132 | - image: node 133 | imagePullPolicy: Always 134 | name: main 135 | ports: 136 | - containerPort: 9000 137 | resources: 138 | limits: 139 | cpu: 1500m 140 | memory: 2048Mi 141 | requests: 142 | cpu: 1000m 143 | memory: 512Mi 144 | securityContext: 145 | allowPrivilegeEscalation: false 146 | privileged: false 147 | readOnlyRootFilesystem: true 148 | runAsGroup: 26000 149 | runAsNonRoot: true 150 | runAsUser: 25000 151 | startupProbe: 152 | failureThreshold: 3 153 | tcpSocket: 154 | port: 9000 155 | dnsPolicy: ClusterFirst 156 | restartPolicy: Always 157 | securityContext: 158 | fsGroupChangePolicy: Always 159 | runAsNonRoot: true 160 | setHostnameAsFQDN: false 161 | --- 162 | apiVersion: v1 163 | kind: Service 164 | metadata: 165 | name: deployment-frontends-service-c8206158 166 | spec: 167 | externalIPs: [] 168 | ports: 169 | - port: 9000 170 | selector: 171 | cdk8s.io/metadata.addr: deployment-FrontEnds-c89e9e97 172 | type: ClusterIP 173 | ``` 174 | 175 | ## Scheduling 176 | 177 | In addition to the scheduling capabilities provided by [pod scheduling](./pod.md#scheduling), 178 | a Deployment offers the following: 179 | 180 | ### Spreading 181 | 182 | A spread is a [separation](./pod.md#pod-separation) of pods from themselves. 183 | It can be used to ensure replicas of the same workload are scheduled on different topologies. 184 | 185 | > The same API is also available on all workload resources (i.e `Deployment`, `StatefulSet`, `Job`, `DaemonSet`). 186 | 187 | ```typescript 188 | const redis = new kplus.Deployment(this, 'Redis', { 189 | containers: [{ image: 'redis' }], 190 | replicas: 3, 191 | }); 192 | 193 | redis.scheduling.spread({ 194 | topology: kplus.Topology.HOSTNAME 195 | }); 196 | ``` 197 | 198 | This example ensures that each replica of the `Redis` deployment 199 | will be scheduled on a different node. 200 | 201 | Take, for [example](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#more-practical-use-cases), a three-node cluster running a web application with an in-memory cache like redis. You'd like to co-locate the web servers with the cache as much as possible, while still maintaining node failure resistance. (i.e not all pods are on the same node). 202 | 203 | Here is how you can accomplish that: 204 | 205 | ```typescript 206 | const redis = new kplus.Deployment(this, 'Redis', { 207 | containers: [{ image: 'redis' }], 208 | replicas: 3, 209 | }); 210 | 211 | const web = new kplus.Deployment(this, 'Web', { 212 | containers: [{ image: 'web' }], 213 | replicas: 3, 214 | }); 215 | 216 | // ensure redis is spread across all nodes 217 | redis.scheduling.spread({ 218 | topology: kplus.Topology.HOSTNAME 219 | }); 220 | 221 | // ensure web app is spread across all nodes 222 | web.scheduling.spread({ 223 | topology: kplus.Topology.HOSTNAME 224 | }); 225 | 226 | // ensure a web app pod always runs along side a cache instance 227 | web.scheduling.colocate(redis); 228 | ``` 229 | 230 | ## Connections 231 | 232 | See [Pod connections](./pod.md#connections). 233 | -------------------------------------------------------------------------------- /test/probe.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration } from 'cdk8s'; 2 | import { Container, Probe, k8s } from '../src'; 3 | 4 | describe('fromHttpGet()', () => { 5 | test('defaults to the container port', () => { 6 | // GIVEN 7 | const container = new Container({ image: 'foobar', port: 5555 }); 8 | 9 | // WHEN 10 | const min = Probe.fromHttpGet('/hello'); 11 | 12 | // THEN 13 | expect(min._toKube(container)).toEqual({ 14 | failureThreshold: 3, 15 | httpGet: { 16 | path: '/hello', 17 | port: k8s.IntOrString.fromNumber(5555), 18 | scheme: 'HTTP', 19 | }, 20 | initialDelaySeconds: undefined, 21 | periodSeconds: undefined, 22 | successThreshold: undefined, 23 | timeoutSeconds: undefined, 24 | }); 25 | }); 26 | 27 | test('specific port', () => { 28 | // GIVEN 29 | const container = new Container({ image: 'foobar', port: 5555 }); 30 | 31 | // WHEN 32 | const min = Probe.fromHttpGet('/hello', { port: 1234 }); 33 | 34 | // THEN 35 | expect(min._toKube(container)).toEqual({ 36 | failureThreshold: 3, 37 | httpGet: { 38 | path: '/hello', 39 | port: k8s.IntOrString.fromNumber(1234), 40 | scheme: 'HTTP', 41 | }, 42 | initialDelaySeconds: undefined, 43 | periodSeconds: undefined, 44 | successThreshold: undefined, 45 | timeoutSeconds: undefined, 46 | }); 47 | }); 48 | 49 | test('options', () => { 50 | // GIVEN 51 | const container = new Container({ image: 'foobar', port: 5555 }); 52 | 53 | // WHEN 54 | const min = Probe.fromHttpGet('/hello', { 55 | failureThreshold: 11, 56 | initialDelaySeconds: Duration.minutes(1), 57 | periodSeconds: Duration.seconds(5), 58 | successThreshold: 3, 59 | timeoutSeconds: Duration.minutes(2), 60 | host: '1.1.1.1', 61 | httpHeaders: [{ 62 | name: 'A-Custom-Header', 63 | value: 'some-value', 64 | }], 65 | }); 66 | 67 | // THEN 68 | expect(min._toKube(container)).toEqual({ 69 | httpGet: { 70 | path: '/hello', 71 | port: k8s.IntOrString.fromNumber(5555), 72 | scheme: 'HTTP', 73 | host: '1.1.1.1', 74 | httpHeaders: [ 75 | { 76 | name: 'A-Custom-Header', 77 | value: 'some-value', 78 | }, 79 | ], 80 | }, 81 | failureThreshold: 11, 82 | initialDelaySeconds: 60, 83 | periodSeconds: 5, 84 | successThreshold: 3, 85 | timeoutSeconds: 120, 86 | }); 87 | }); 88 | 89 | }); 90 | 91 | describe('fromCommand()', () => { 92 | 93 | test('minimal usage', () => { 94 | // GIVEN 95 | const container = new Container({ image: 'foobar', port: 5555 }); 96 | 97 | // WHEN 98 | const min = Probe.fromCommand(['foo', 'bar']); 99 | 100 | // THEN 101 | expect(min._toKube(container)).toEqual({ 102 | exec: { command: ['foo', 'bar'] }, 103 | failureThreshold: 3, 104 | initialDelaySeconds: undefined, 105 | periodSeconds: undefined, 106 | successThreshold: undefined, 107 | timeoutSeconds: undefined, 108 | }); 109 | }); 110 | 111 | test('options', () => { 112 | // GIVEN 113 | const container = new Container({ image: 'foobar', port: 5555 }); 114 | 115 | // WHEN 116 | const min = Probe.fromCommand(['foo', 'bar'], { 117 | failureThreshold: 11, 118 | initialDelaySeconds: Duration.minutes(1), 119 | periodSeconds: Duration.seconds(5), 120 | successThreshold: 3, 121 | timeoutSeconds: Duration.minutes(2), 122 | }); 123 | 124 | // THEN 125 | expect(min._toKube(container)).toEqual({ 126 | exec: { command: ['foo', 'bar'] }, 127 | failureThreshold: 11, 128 | initialDelaySeconds: 60, 129 | periodSeconds: 5, 130 | successThreshold: 3, 131 | timeoutSeconds: 120, 132 | }); 133 | }); 134 | 135 | }); 136 | 137 | describe('fromTcpSocket()', () => { 138 | 139 | test('minimal usage', () => { 140 | // GIVEN 141 | const container = new Container({ image: 'foobar', port: 5555 }); 142 | 143 | // WHEN 144 | const min = Probe.fromTcpSocket(); 145 | 146 | // THEN 147 | expect(min._toKube(container)).toEqual({ 148 | tcpSocket: { 149 | port: k8s.IntOrString.fromNumber(5555), 150 | host: undefined, 151 | }, 152 | failureThreshold: 3, 153 | initialDelaySeconds: undefined, 154 | periodSeconds: undefined, 155 | successThreshold: undefined, 156 | timeoutSeconds: undefined, 157 | }); 158 | }); 159 | 160 | test('specific port and hostname', () => { 161 | // GIVEN 162 | const container = new Container({ image: 'foobar', port: 5555 }); 163 | 164 | // WHEN 165 | const min = Probe.fromTcpSocket({ 166 | port: 8080, 167 | host: 'hostname', 168 | }); 169 | 170 | // THEN 171 | expect(min._toKube(container)).toEqual({ 172 | tcpSocket: { 173 | port: k8s.IntOrString.fromNumber(8080), 174 | host: 'hostname', 175 | }, 176 | failureThreshold: 3, 177 | initialDelaySeconds: undefined, 178 | periodSeconds: undefined, 179 | successThreshold: undefined, 180 | timeoutSeconds: undefined, 181 | }); 182 | }); 183 | 184 | test('options', () => { 185 | // GIVEN 186 | const container = new Container({ image: 'foobar', port: 5555 }); 187 | 188 | // WHEN 189 | const min = Probe.fromTcpSocket({ 190 | failureThreshold: 11, 191 | initialDelaySeconds: Duration.minutes(1), 192 | periodSeconds: Duration.seconds(5), 193 | successThreshold: 3, 194 | timeoutSeconds: Duration.minutes(2), 195 | }); 196 | 197 | // THEN 198 | expect(min._toKube(container)).toEqual({ 199 | tcpSocket: { 200 | port: k8s.IntOrString.fromNumber(5555), 201 | host: undefined, 202 | }, 203 | failureThreshold: 11, 204 | initialDelaySeconds: 60, 205 | periodSeconds: 5, 206 | successThreshold: 3, 207 | timeoutSeconds: 120, 208 | }); 209 | }); 210 | 211 | describe('fromGrpc()', () => { 212 | 213 | test('minimal usage', () => { 214 | // GIVEN 215 | const container = new Container({ image: 'foobar', port: 5555 }); 216 | 217 | // WHEN 218 | const min = Probe.fromGrpc( { port: 5555 } ); 219 | 220 | // THEN 221 | expect(min._toKube(container)).toEqual({ 222 | grpc: { 223 | port: 5555, 224 | }, 225 | failureThreshold: 3, 226 | initialDelaySeconds: undefined, 227 | periodSeconds: undefined, 228 | successThreshold: undefined, 229 | timeoutSeconds: undefined, 230 | }); 231 | }); 232 | 233 | test('options', () => { 234 | // GIVEN 235 | const container = new Container({ image: 'foobar', port: 5555 }); 236 | 237 | // WHEN 238 | const min = Probe.fromGrpc({ 239 | port: 5555, 240 | failureThreshold: 11, 241 | initialDelaySeconds: Duration.minutes(1), 242 | periodSeconds: Duration.seconds(5), 243 | successThreshold: 3, 244 | timeoutSeconds: Duration.minutes(2), 245 | }); 246 | 247 | // THEN 248 | expect(min._toKube(container)).toEqual({ 249 | grpc: { 250 | port: 5555, 251 | }, 252 | failureThreshold: 11, 253 | initialDelaySeconds: 60, 254 | periodSeconds: 5, 255 | successThreshold: 3, 256 | timeoutSeconds: 120, 257 | }); 258 | }); 259 | 260 | }); 261 | 262 | }); 263 | --------------------------------------------------------------------------------