diff --git a/.gitignore b/.gitignore index 5e4f590..707c213 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,5 @@ junit.xml !/.eslintrc.json .jsii tsconfig.json -tsconfig.dev.json !/API.md !/.projenrc.ts -yarn.lock \ No newline at end of file diff --git a/.projen/deps.json b/.projen/deps.json index c342c78..0b95665 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -102,6 +102,10 @@ "name": "cdk8s", "type": "runtime" }, + { + "name": "cdk8s-plus-24", + "type": "runtime" + }, { "name": "constructs", "type": "runtime" diff --git a/.projen/tasks.json b/.projen/tasks.json index 99c2fe8..3425057 100644 --- a/.projen/tasks.json +++ b/.projen/tasks.json @@ -275,13 +275,13 @@ }, "steps": [ { - "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --dep=dev,peer,prod,optional --filter=@types/jest,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,jsii-docgen,jsii-pacmak,projen,ts-jest,ts-node,typescript,yaml,cdk8s,constructs" + "exec": "npx npm-check-updates@16 --upgrade --target=minor --peer --dep=dev,peer,prod,optional --filter=@types/jest,eslint-import-resolver-typescript,eslint-plugin-import,jest,jsii-diff,jsii-docgen,jsii-pacmak,projen,ts-jest,ts-node,typescript,yaml,cdk8s,cdk8s-plus-24,constructs" }, { "exec": "yarn install --check-files" }, { - "exec": "yarn upgrade @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser constructs eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii projen standard-version ts-jest ts-node typescript yaml cdk8s" + "exec": "yarn upgrade @types/jest @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser constructs eslint-import-resolver-typescript eslint-plugin-import eslint jest jest-junit jsii-diff jsii-docgen jsii-pacmak jsii-rosetta jsii projen standard-version ts-jest ts-node typescript yaml cdk8s cdk8s-plus-24" }, { "exec": "npx projen" diff --git a/.projenrc.ts b/.projenrc.ts index bddadde..bbcb353 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -10,6 +10,7 @@ const project = new cdk.JsiiProject({ description: 'Provides a CMS Plone Backend and Frontend for Kubernetes with cdk8s', deps: [ 'cdk8s', + 'cdk8s-plus-24', 'constructs', ], peerDeps: [ diff --git a/package.json b/package.json index cdb3366..e792267 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ }, "dependencies": { "cdk8s": "^2.68.79", + "cdk8s-plus-24": "^2.12.1", "constructs": "^10.3.0" }, "main": "lib/index.js", diff --git a/src/backend-deployment.ts b/src/backend-deployment.ts index 440ea08..c67a29c 100644 --- a/src/backend-deployment.ts +++ b/src/backend-deployment.ts @@ -34,7 +34,7 @@ export interface PloneBackendDeploymentOptions { * If given * @default - none */ - readonly pdbOptions?: PlonePDBOptions; + readonly pdb?: PlonePDBOptions; } export class PloneBackendDeployment extends Construct { @@ -73,8 +73,8 @@ export class PloneBackendDeployment extends Construct { new k8s.KubeDeployment(this, 'deployment', deploymentOptions); - if (options.pdbOptions ?? false) { - const pdbOptions = options.pdbOptions ?? {}; + if (options.pdb ?? false) { + const pdbOptions = options.pdb ?? {}; new PlonePDB(this, 'pdb', label, pdbOptions); } } diff --git a/src/backend-frontend.ts b/src/backend-frontend.ts deleted file mode 100644 index 440ea08..0000000 --- a/src/backend-frontend.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Names } from 'cdk8s'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { Construct } from 'constructs'; -import * as k8s from './imports/k8s'; -import { PlonePDB, PlonePDBOptions } from './pdb'; - -export interface PloneBackendDeploymentOptions { - /** - * Specify a custom image for Plone Backend. - * @default "plone/plone-backend:latest" - */ - readonly image?: string; - - /** - * Number of replicas. - * @default 2 - */ - readonly replicas?: number; - - /** - * Port number. - * @default 8080 - */ - readonly port?: number; - - /** - * Extra labels to associate with resources. - * @default - none - */ - readonly labels?: { [name: string]: string }; - - /** - * Create a PodDisruptionBugdet for the deployment? - * If given - * @default - none - */ - readonly pdbOptions?: PlonePDBOptions; -} - -export class PloneBackendDeployment extends Construct { - - constructor(scope: Construct, id: string, options: PloneBackendDeploymentOptions = {}) { - super(scope, id); - const image = options.image ?? 'plone/plone-backend:latest'; - const replicas = options.replicas ?? 2; - const label = { app: Names.toLabelValue(this) }; - const template_labels = { - ...options.labels ?? {}, - ...label, - }; - const deploymentOptions: k8s.KubeDeploymentProps = { - metadata: { - labels: options.labels ?? {}, - }, - spec: { - replicas, - selector: { - matchLabels: label, - }, - template: { - metadata: { labels: template_labels }, - spec: { - containers: [ - { - name: id + '-container', // here the namespaced name shold be used, but how? - image: image, - }, - ], - }, - }, - }, - }; - - new k8s.KubeDeployment(this, 'deployment', deploymentOptions); - - if (options.pdbOptions ?? false) { - const pdbOptions = options.pdbOptions ?? {}; - new PlonePDB(this, 'pdb', label, pdbOptions); - } - } -} diff --git a/src/frontend-deployment.ts b/src/frontend-deployment.ts index 27e7acc..dc21474 100644 --- a/src/frontend-deployment.ts +++ b/src/frontend-deployment.ts @@ -34,7 +34,7 @@ export interface PloneFrontendDeploymentOptions { * If given * @default - none */ - readonly pdbOptions?: PlonePDBOptions; + readonly pdb?: PlonePDBOptions; } export class PloneFrontendDeployment extends Construct { @@ -74,9 +74,9 @@ export class PloneFrontendDeployment extends Construct { new k8s.KubeDeployment(this, 'deployment', deploymentOptions); - if (options.pdbOptions ?? false) { - const pdbOptions = options.pdbOptions ?? {}; - new PlonePDB(this, id + '-pdb', label, pdbOptions); + if (options.pdb ?? false) { + const pdbOptions = options.pdb ?? {}; + new PlonePDB(this, 'pdb', label, pdbOptions); } } } diff --git a/src/pdb.ts b/src/pdb.ts index 220eb4b..d00aef8 100644 --- a/src/pdb.ts +++ b/src/pdb.ts @@ -1,6 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { Construct } from 'constructs'; -import { IntOrString, KubePodDisruptionBudget } from './imports/k8s'; +import { IntOrString, KubePodDisruptionBudget, PodDisruptionBudgetSpec } from './imports/k8s'; export interface PlonePDBOptions { /** @@ -11,6 +11,7 @@ export interface PlonePDBOptions { /** * minAvailable specification. + * default only set if maxUnavailable is not set. * @default 1 */ readonly minAvailable?: number | string; @@ -27,28 +28,43 @@ export class PlonePDB extends Construct { constructor(scope: Construct, id: string, selectorLabel: { [name: string]: string }, options: PlonePDBOptions) { super(scope, id); - var maxUnavailable: IntOrString = IntOrString.fromString(''); // default value - if (typeof maxUnavailable === 'number') { - maxUnavailable = IntOrString.fromNumber(options.maxUnavailable as number); - } else if (typeof maxUnavailable === 'string') { - maxUnavailable = IntOrString.fromString(options.maxUnavailable as string); + var spec: PodDisruptionBudgetSpec = {}; + if (typeof options.maxUnavailable === 'number') { + spec = { + maxUnavailable: IntOrString.fromNumber(options.maxUnavailable as number), + }; + } else if (typeof options.maxUnavailable === 'string') { + spec = { + maxUnavailable: IntOrString.fromString(options.maxUnavailable as string), + }; } - var minAvailable: IntOrString = IntOrString.fromString(''); // default value - if (typeof minAvailable === 'number') { - minAvailable = IntOrString.fromNumber(options.minAvailable as number); - } else if (typeof minAvailable === 'string') { - minAvailable = IntOrString.fromString(options.minAvailable as string); + if (typeof options.minAvailable === 'number') { + spec = { + ...spec, + minAvailable: IntOrString.fromNumber(options.minAvailable as number), + }; + } else if (typeof options.minAvailable === 'string') { + spec = { + ...spec, + minAvailable: IntOrString.fromString(options.minAvailable as string), + }; } + if (options.maxUnavailable === undefined && options.minAvailable === undefined) { + spec = { + minAvailable: IntOrString.fromNumber(1), + }; + } + + spec = { + ...spec, + selector: { matchLabels: selectorLabel }, + }; new KubePodDisruptionBudget(this, 'PDB', { metadata: { labels: options.labels ?? {}, }, - spec: { - selector: { matchLabels: selectorLabel }, - maxUnavailable: maxUnavailable, - minAvailable: minAvailable, - }, + spec: spec, }); } } diff --git a/src/plone.ts b/src/plone.ts new file mode 100644 index 0000000..0c74a16 --- /dev/null +++ b/src/plone.ts @@ -0,0 +1,20 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Construct } from 'constructs'; +import { PloneBackend, PloneBackendOptions } from './backend'; +import { PloneFrontend, PloneFrontendOptions } from './frontend'; + +export interface PloneOptions { + readonly frontend?: PloneFrontendOptions; + readonly backend?: PloneBackendOptions; +} + +export class Plone extends Construct { + + constructor(scope: Construct, id: string, options: PloneOptions = {}) { + super(scope, id); + const frontendOptions = options.frontend ?? {}; + const backendOptions = options.backend ?? {}; + new PloneBackend(this, 'backend', backendOptions); + new PloneFrontend(this, 'frontend', frontendOptions); + } +} \ No newline at end of file diff --git a/test/__snapshots__/backend.test.ts.snap b/test/__snapshots__/backend.test.ts.snap index 0bac230..28acbe1 100644 --- a/test/__snapshots__/backend.test.ts.snap +++ b/test/__snapshots__/backend.test.ts.snap @@ -94,8 +94,7 @@ exports[`defaults-with-pdp 1`] = ` "name": "plone-backend_with_pdb-deployment-pdb-c86717f8", }, "spec": { - "maxUnavailable": "", - "minAvailable": "", + "minAvailable": 1, "selector": { "matchLabels": { "app": "plone-backend_with_pdb-deployment-c8de3ed2", diff --git a/test/__snapshots__/frontend.test.ts.snap b/test/__snapshots__/frontend.test.ts.snap index 1cdb6ed..b26dad9 100644 --- a/test/__snapshots__/frontend.test.ts.snap +++ b/test/__snapshots__/frontend.test.ts.snap @@ -91,11 +91,10 @@ exports[`defaults-with-pdp 1`] = ` "apiVersion": "policy/v1", "kind": "PodDisruptionBudget", "metadata": { - "name": "plone-frontend_with_pdb-deployment-deployment-pdb-pdb-c8d88a97", + "name": "plone-frontend_with_pdb-deployment-pdb-c8993f39", }, "spec": { - "maxUnavailable": "", - "minAvailable": "", + "minAvailable": 1, "selector": { "matchLabels": { "app": "plone-frontend_with_pdb-deployment-c8256934", diff --git a/test/__snapshots__/pdb.test.ts.snap b/test/__snapshots__/pdb.test.ts.snap new file mode 100644 index 0000000..1c3a4ec --- /dev/null +++ b/test/__snapshots__/pdb.test.ts.snap @@ -0,0 +1,82 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`both 1`] = ` +[ + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "plone-pdb-c8ec72df", + }, + "spec": { + "maxUnavailable": 4, + "minAvailable": 3, + "selector": { + "matchLabels": { + "app": "plone", + }, + }, + }, + }, +] +`; + +exports[`defaults 1`] = ` +[ + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "plone-pdb-c8ec72df", + }, + "spec": { + "minAvailable": 1, + "selector": { + "matchLabels": { + "app": "plone", + }, + }, + }, + }, +] +`; + +exports[`maxUnavailable 1`] = ` +[ + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "plone-pdb-c8ec72df", + }, + "spec": { + "maxUnavailable": 2, + "selector": { + "matchLabels": { + "app": "plone", + }, + }, + }, + }, +] +`; + +exports[`minAvailable 1`] = ` +[ + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "plone-pdb-c8ec72df", + }, + "spec": { + "minAvailable": 2, + "selector": { + "matchLabels": { + "app": "plone", + }, + }, + }, + }, +] +`; diff --git a/test/__snapshots__/plone.test.ts.snap b/test/__snapshots__/plone.test.ts.snap new file mode 100644 index 0000000..9358a06 --- /dev/null +++ b/test/__snapshots__/plone.test.ts.snap @@ -0,0 +1,241 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`defaults 1`] = ` +[ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "app-plone-backend-deployment-c8e65ede", + }, + "spec": { + "replicas": 2, + "selector": { + "matchLabels": { + "app": "app-plone-backend-deployment-c85baacf", + }, + }, + "template": { + "metadata": { + "labels": { + "app": "app-plone-backend-deployment-c85baacf", + }, + }, + "spec": { + "containers": [ + { + "image": "plone/plone-backend:latest", + "name": "deployment-container", + }, + ], + }, + }, + }, + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "app-plone-backend-service-c8efef4c", + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "port": 8080, + "targetPort": 8080, + }, + ], + "selector": { + "app": "app-plone-backend-deployment-c85baacf", + }, + "type": "ClusterIP", + }, + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "app-plone-frontend-deployment-c8407ca5", + }, + "spec": { + "replicas": 2, + "selector": { + "matchLabels": { + "app": "app-plone-frontend-deployment-c872e367", + }, + }, + "template": { + "metadata": { + "labels": { + "app": "app-plone-frontend-deployment-c872e367", + }, + }, + "spec": { + "containers": [ + { + "image": "plone/plone-backend:latest", + "name": "deployment-container", + }, + ], + }, + }, + }, + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "app-plone-frontend-service-c8f392cd", + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "port": 3000, + "targetPort": 3000, + }, + ], + "selector": { + "app": "app-plone-frontend-deployment-c872e367", + }, + "type": "ClusterIP", + }, + }, +] +`; + +exports[`defaults-with-pdps 1`] = ` +[ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "app-plone_with_pdbs-backend-deployment-c8a42868", + }, + "spec": { + "replicas": 2, + "selector": { + "matchLabels": { + "app": "app-plone_with_pdbs-backend-deployment-c8d5991a", + }, + }, + "template": { + "metadata": { + "labels": { + "app": "app-plone_with_pdbs-backend-deployment-c8d5991a", + }, + }, + "spec": { + "containers": [ + { + "image": "plone/plone-backend:latest", + "name": "deployment-container", + }, + ], + }, + }, + }, + }, + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "app-plone_with_pdbs-backend-deployment-pdb-c8f35341", + }, + "spec": { + "minAvailable": 1, + "selector": { + "matchLabels": { + "app": "app-plone_with_pdbs-backend-deployment-c8d5991a", + }, + }, + }, + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "app-plone_with_pdbs-backend-service-c819541f", + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "port": 8080, + "targetPort": 8080, + }, + ], + "selector": { + "app": "app-plone_with_pdbs-backend-deployment-c8d5991a", + }, + "type": "ClusterIP", + }, + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "app-plone_with_pdbs-frontend-deployment-c8a5664e", + }, + "spec": { + "replicas": 2, + "selector": { + "matchLabels": { + "app": "app-plone_with_pdbs-frontend-deployment-c812b31a", + }, + }, + "template": { + "metadata": { + "labels": { + "app": "app-plone_with_pdbs-frontend-deployment-c812b31a", + }, + }, + "spec": { + "containers": [ + { + "image": "plone/plone-backend:latest", + "name": "deployment-container", + }, + ], + }, + }, + }, + }, + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "app-plone_with_pdbs-frontend-deployment-pdb-c8fa2465", + }, + "spec": { + "minAvailable": 1, + "selector": { + "matchLabels": { + "app": "app-plone_with_pdbs-frontend-deployment-c812b31a", + }, + }, + }, + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "app-plone_with_pdbs-frontend-service-c8999de8", + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "port": 3000, + "targetPort": 3000, + }, + ], + "selector": { + "app": "app-plone_with_pdbs-frontend-deployment-c812b31a", + }, + "type": "ClusterIP", + }, + }, +] +`; diff --git a/test/backend.test.ts b/test/backend.test.ts index 38403c6..e70053b 100644 --- a/test/backend.test.ts +++ b/test/backend.test.ts @@ -20,7 +20,7 @@ test('defaults-with-pdp', () => { const chart = new Chart(app, 'plone'); const options: PloneBackendOptions = { deployment: { - pdbOptions: { + pdb: { minAvailable: 1, }, }, diff --git a/test/frontend.test.ts b/test/frontend.test.ts index e6bfd73..3bad282 100644 --- a/test/frontend.test.ts +++ b/test/frontend.test.ts @@ -20,7 +20,7 @@ test('defaults-with-pdp', () => { const chart = new Chart(app, 'plone'); const options: PloneFrontendOptions = { deployment: { - pdbOptions: { + pdb: { minAvailable: 1, }, }, diff --git a/test/pdb.test.ts b/test/pdb.test.ts new file mode 100644 index 0000000..3472113 --- /dev/null +++ b/test/pdb.test.ts @@ -0,0 +1,51 @@ +import { Chart, Testing } from 'cdk8s'; +import { PlonePDB } from '../src/pdb'; + + +test('defaults', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'plone'); + + // WHEN + new PlonePDB(chart, 'pdb', { app: 'plone' }, {}); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); + +test('minAvailable', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'plone'); + + // WHEN + new PlonePDB(chart, 'pdb', { app: 'plone' }, { minAvailable: 2 }); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); + +test('maxUnavailable', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'plone'); + + // WHEN + new PlonePDB(chart, 'pdb', { app: 'plone' }, { maxUnavailable: 2 }); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); + +test('both', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'plone'); + + // WHEN + new PlonePDB(chart, 'pdb', { app: 'plone' }, { maxUnavailable: 4, minAvailable: 3 }); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); diff --git a/test/plone.test.ts b/test/plone.test.ts new file mode 100644 index 0000000..f9ff759 --- /dev/null +++ b/test/plone.test.ts @@ -0,0 +1,43 @@ +import { Chart, Testing } from 'cdk8s'; +import { Plone, PloneOptions } from '../src/plone'; + + +test('defaults', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'app'); + + // WHEN + new Plone(chart, 'plone'); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); + +test('defaults-with-pdps', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'app'); + const options: PloneOptions = { + frontend: { + deployment: { + pdb: { + minAvailable: 1, + }, + }, + }, + backend: { + deployment: { + pdb: { + minAvailable: 1, + }, + }, + }, + }; + + // WHEN + new Plone(chart, 'plone_with_pdbs', options); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); diff --git a/yarn.lock b/yarn.lock index 1948cfc..26558ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1278,6 +1278,13 @@ case@^1.6.3: resolved "https://registry.npmjs.org/case/-/case-1.6.3.tgz" integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ== +cdk8s-plus-24@^2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/cdk8s-plus-24/-/cdk8s-plus-24-2.12.1.tgz#a7ebe81e4f52b9e2023feee050d4ff70ba068ed3" + integrity sha512-R7pJnR3riVINwJzgEbPdPlr662Lasz3IB630+O7xL5Ylu31eLCqCNO3vdIgI5l49qbSui8xD7MBxlhTdsu3/MA== + dependencies: + minimatch "^3.1.2" + cdk8s@^2.68.79: version "2.68.79" resolved "https://registry.npmjs.org/cdk8s/-/cdk8s-2.68.79.tgz"