From 8ec83d744dad5c3ffa6fb6860909f45ef46c5511 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 26 Jun 2024 14:50:41 +0200 Subject: [PATCH] add frontend and refactor pdb to genric use --- README.md | 4 + src/backend-deployment.ts | 16 ++- src/backend-frontend.ts | 81 +++++++++++++++ src/frontend-deployment.ts | 82 +++++++++++++++ src/frontend-service.ts | 52 ++++++++++ src/frontend.ts | 28 +++++ src/index.ts | 5 +- src/{backend-pdb.ts => pdb.ts} | 13 +-- test/__snapshots__/backend.test.ts.snap | 63 ++++++++++- test/__snapshots__/frontend.test.ts.snap | 127 +++++++++++++++++++++++ test/backend.test.ts | 21 +++- test/frontend.test.ts | 34 ++++++ 12 files changed, 501 insertions(+), 25 deletions(-) create mode 100644 src/backend-frontend.ts create mode 100644 src/frontend-deployment.ts create mode 100644 src/frontend-service.ts create mode 100644 src/frontend.ts rename src/{backend-pdb.ts => pdb.ts} (82%) create mode 100644 test/__snapshots__/frontend.test.ts.snap create mode 100644 test/frontend.test.ts diff --git a/README.md b/README.md index e669f75..0561efc 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Each step need to be implemented with tests! - [ ] Start Backend - [ ] deployment + - [ ] depend on "some" postgres db - which can be provided in different ways - [ ] service - [ ] pdb - [ ] init container running plone-site-create @@ -41,6 +42,7 @@ Each step need to be implemented with tests! - [ ] (optional) direct way to specify logging sidecar (fluentd/loki?) - [ ] Start Frontend - [ ] deployment + - [ ] depend on ready/live backend - [ ] service - [ ] pdb - [ ] lifecycle checks (readiness, liveness) @@ -51,6 +53,7 @@ Each step need to be implemented with tests! - [ ] Start Varnish - [ ] deployment + - [ ] do not depend on backend/front end to be up, but configure to deliver from cache if possible. - [ ] service - [ ] pdb - [ ] lifecycle checks (readiness, liveness) @@ -58,6 +61,7 @@ Each step need to be implemented with tests! - [ ] generic way to inject sidecars - [ ] (optional) direct way to specify metrics sidecar (prometheus exporter) - [ ] (optional) direct way to specify logging sidecar (fluentd/loki?) + - find a way to purge caches. based on kitconcept varnish purger? needs - [ ] Other Languages - [ ] Check Python distribution diff --git a/src/backend-deployment.ts b/src/backend-deployment.ts index 90c605d..440ea08 100644 --- a/src/backend-deployment.ts +++ b/src/backend-deployment.ts @@ -1,8 +1,8 @@ import { Names } from 'cdk8s'; // eslint-disable-next-line import/no-extraneous-dependencies import { Construct } from 'constructs'; -import { PloneBackendPDB, PloneBackendPDBOptions } from './backend-pdb'; import * as k8s from './imports/k8s'; +import { PlonePDB, PlonePDBOptions } from './pdb'; export interface PloneBackendDeploymentOptions { /** @@ -34,12 +34,12 @@ export interface PloneBackendDeploymentOptions { * If given * @default - none */ - readonly pdbOptions?: PloneBackendPDBOptions; + readonly pdbOptions?: PlonePDBOptions; } export class PloneBackendDeployment extends Construct { - constructor(scope: Construct, id: string, options: PloneBackendDeploymentOptions = { }) { + constructor(scope: Construct, id: string, options: PloneBackendDeploymentOptions = {}) { super(scope, id); const image = options.image ?? 'plone/plone-backend:latest'; const replicas = options.replicas ?? 2; @@ -48,7 +48,6 @@ export class PloneBackendDeployment extends Construct { ...options.labels ?? {}, ...label, }; - const pdb = options.pdbOptions ?? true; const deploymentOptions: k8s.KubeDeploymentProps = { metadata: { labels: options.labels ?? {}, @@ -74,12 +73,9 @@ export class PloneBackendDeployment extends Construct { new k8s.KubeDeployment(this, 'deployment', deploymentOptions); - if (pdb ?? false) { - const pdbOptions = { - ...options.pdbOptions ?? {}, - selectorLabel: { app: Names.toLabelValue(this) }, - }; - new PloneBackendPDB(this, 'pdb', pdbOptions); + if (options.pdbOptions ?? false) { + const pdbOptions = options.pdbOptions ?? {}; + new PlonePDB(this, 'pdb', label, pdbOptions); } } } diff --git a/src/backend-frontend.ts b/src/backend-frontend.ts new file mode 100644 index 0000000..440ea08 --- /dev/null +++ b/src/backend-frontend.ts @@ -0,0 +1,81 @@ +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 new file mode 100644 index 0000000..27e7acc --- /dev/null +++ b/src/frontend-deployment.ts @@ -0,0 +1,82 @@ +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 PloneFrontendDeploymentOptions { + /** + * Specify a custom image for Plone Frontend. + * @default "plone/plone-frontend: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 PloneFrontendDeployment extends Construct { + + constructor(scope: Construct, id: string, options: PloneFrontendDeploymentOptions = {}) { + 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, id + '-pdb', label, pdbOptions); + } + } +} diff --git a/src/frontend-service.ts b/src/frontend-service.ts new file mode 100644 index 0000000..9577411 --- /dev/null +++ b/src/frontend-service.ts @@ -0,0 +1,52 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Construct } from 'constructs'; +import { IntOrString, KubeServiceProps, KubeService } from './imports/k8s'; + +export interface PloneFrontendServiceOptions { + /** + * Port number. + * @default 3000 + */ + readonly port?: number; + + /** + * Port number. + * @default 3000; + */ + readonly targetPort?: number; + + /** + * Selector label. + */ + readonly selectorLabel: { [name: string]: string }; + + /** + * Extra labels to associate with resources. + * @default - none + */ + readonly labels?: { [name: string]: string }; +} + +export class PloneFrontendService extends Construct { + + constructor(scope: Construct, id: string, options: PloneFrontendServiceOptions) { + super(scope, id); + + const port = options.port ?? 3000; + const targetPort = IntOrString.fromNumber(options.targetPort ?? 3000); + const selectorLabel = options.selectorLabel; + + const serviceOpts: KubeServiceProps = { + metadata: { + labels: options.labels ?? {}, + }, + spec: { + type: 'ClusterIP', + clusterIp: 'None', + ports: [{ port: port, targetPort: targetPort }], + selector: selectorLabel, + }, + }; + new KubeService(this, 'service', serviceOpts); + } +} diff --git a/src/frontend.ts b/src/frontend.ts new file mode 100644 index 0000000..77a18d7 --- /dev/null +++ b/src/frontend.ts @@ -0,0 +1,28 @@ +import { Names } from 'cdk8s'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { Construct } from 'constructs'; +import { PloneFrontendDeploymentOptions, PloneFrontendDeployment } from './frontend-deployment'; +import { PloneFrontendServiceOptions, PloneFrontendService } from './frontend-service'; + +export interface PloneFrontendOptions { + readonly deployment?: PloneFrontendDeploymentOptions; + readonly service?: PloneFrontendServiceOptions; +} + +export class PloneFrontend extends Construct { + + constructor(scope: Construct, id: string, options: PloneFrontendOptions = {}) { + super(scope, id); + const deploymentOptions = options.deployment ?? {}; + + // Create a deployment + const deployment = new PloneFrontendDeployment(this, 'deployment', deploymentOptions); + + // Create a service + const serviceOptions = { + ...options.service ?? {}, + selectorLabel: { app: Names.toLabelValue(deployment) }, + }; + new PloneFrontendService(this, 'service', serviceOptions); + } +} diff --git a/src/index.ts b/src/index.ts index 2db400b..06647cb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,7 @@ +export { PlonePDBOptions, PlonePDB } from './pdb'; export { PloneBackendDeploymentOptions, PloneBackendDeployment } from './backend-deployment'; -export { PloneBackendPDBOptions, PloneBackendPDB } from './backend-pdb'; export { PloneBackendServiceOptions, PloneBackendService } from './backend-service'; export { PloneBackendOptions, PloneBackend } from './backend'; +export { PloneFrontendDeploymentOptions, PloneFrontendDeployment } from './frontend-deployment'; +export { PloneFrontendServiceOptions, PloneFrontendService } from './frontend-service'; +export { PloneFrontendOptions, PloneFrontend } from './frontend'; diff --git a/src/backend-pdb.ts b/src/pdb.ts similarity index 82% rename from src/backend-pdb.ts rename to src/pdb.ts index 29ebe25..220eb4b 100644 --- a/src/backend-pdb.ts +++ b/src/pdb.ts @@ -2,7 +2,7 @@ import { Construct } from 'constructs'; import { IntOrString, KubePodDisruptionBudget } from './imports/k8s'; -export interface PloneBackendPDBOptions { +export interface PlonePDBOptions { /** * maxUnavailable specification * @default - none @@ -15,11 +15,6 @@ export interface PloneBackendPDBOptions { */ readonly minAvailable?: number | string; - /** - * Selector label. - */ - readonly selectorLabel: { [name: string]: string }; - /** * Extra labels to associate with resources. * @default - none @@ -27,9 +22,9 @@ export interface PloneBackendPDBOptions { readonly labels?: { [name: string]: string }; } -export class PloneBackendPDB extends Construct { +export class PlonePDB extends Construct { - constructor(scope: Construct, id: string, options: PloneBackendPDBOptions) { + constructor(scope: Construct, id: string, selectorLabel: { [name: string]: string }, options: PlonePDBOptions) { super(scope, id); var maxUnavailable: IntOrString = IntOrString.fromString(''); // default value @@ -50,7 +45,7 @@ export class PloneBackendPDB extends Construct { labels: options.labels ?? {}, }, spec: { - selector: { matchLabels: options.selectorLabel }, + selector: { matchLabels: selectorLabel }, maxUnavailable: maxUnavailable, minAvailable: minAvailable, }, diff --git a/test/__snapshots__/backend.test.ts.snap b/test/__snapshots__/backend.test.ts.snap index dc50cfd..0bac230 100644 --- a/test/__snapshots__/backend.test.ts.snap +++ b/test/__snapshots__/backend.test.ts.snap @@ -32,18 +32,73 @@ exports[`defaults 1`] = ` }, }, }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "plone-backend-service-c81df201", + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "port": 8080, + "targetPort": 8080, + }, + ], + "selector": { + "app": "plone-backend-deployment-c83d26f8", + }, + "type": "ClusterIP", + }, + }, +] +`; + +exports[`defaults-with-pdp 1`] = ` +[ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "plone-backend_with_pdb-deployment-c828b7f6", + }, + "spec": { + "replicas": 2, + "selector": { + "matchLabels": { + "app": "plone-backend_with_pdb-deployment-c8de3ed2", + }, + }, + "template": { + "metadata": { + "labels": { + "app": "plone-backend_with_pdb-deployment-c8de3ed2", + }, + }, + "spec": { + "containers": [ + { + "image": "plone/plone-backend:latest", + "name": "deployment-container", + }, + ], + }, + }, + }, + }, { "apiVersion": "policy/v1", "kind": "PodDisruptionBudget", "metadata": { - "name": "plone-backend-deployment-pdb-c84dc10f", + "name": "plone-backend_with_pdb-deployment-pdb-c86717f8", }, "spec": { "maxUnavailable": "", "minAvailable": "", "selector": { "matchLabels": { - "app": "plone-backend-deployment-c83d26f8", + "app": "plone-backend_with_pdb-deployment-c8de3ed2", }, }, }, @@ -52,7 +107,7 @@ exports[`defaults 1`] = ` "apiVersion": "v1", "kind": "Service", "metadata": { - "name": "plone-backend-service-c81df201", + "name": "plone-backend_with_pdb-service-c85257cd", }, "spec": { "clusterIP": "None", @@ -63,7 +118,7 @@ exports[`defaults 1`] = ` }, ], "selector": { - "app": "plone-backend-deployment-c83d26f8", + "app": "plone-backend_with_pdb-deployment-c8de3ed2", }, "type": "ClusterIP", }, diff --git a/test/__snapshots__/frontend.test.ts.snap b/test/__snapshots__/frontend.test.ts.snap new file mode 100644 index 0000000..1cdb6ed --- /dev/null +++ b/test/__snapshots__/frontend.test.ts.snap @@ -0,0 +1,127 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`defaults 1`] = ` +[ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "plone-frontend-deployment-c88bcfaf", + }, + "spec": { + "replicas": 2, + "selector": { + "matchLabels": { + "app": "plone-frontend-deployment-c8d95ffb", + }, + }, + "template": { + "metadata": { + "labels": { + "app": "plone-frontend-deployment-c8d95ffb", + }, + }, + "spec": { + "containers": [ + { + "image": "plone/plone-backend:latest", + "name": "deployment-container", + }, + ], + }, + }, + }, + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "plone-frontend-service-c8d6d7d9", + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "port": 3000, + "targetPort": 3000, + }, + ], + "selector": { + "app": "plone-frontend-deployment-c8d95ffb", + }, + "type": "ClusterIP", + }, + }, +] +`; + +exports[`defaults-with-pdp 1`] = ` +[ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "plone-frontend_with_pdb-deployment-c84d7c2b", + }, + "spec": { + "replicas": 2, + "selector": { + "matchLabels": { + "app": "plone-frontend_with_pdb-deployment-c8256934", + }, + }, + "template": { + "metadata": { + "labels": { + "app": "plone-frontend_with_pdb-deployment-c8256934", + }, + }, + "spec": { + "containers": [ + { + "image": "plone/plone-backend:latest", + "name": "deployment-container", + }, + ], + }, + }, + }, + }, + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "plone-frontend_with_pdb-deployment-deployment-pdb-pdb-c8d88a97", + }, + "spec": { + "maxUnavailable": "", + "minAvailable": "", + "selector": { + "matchLabels": { + "app": "plone-frontend_with_pdb-deployment-c8256934", + }, + }, + }, + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "plone-frontend_with_pdb-service-c88fbf72", + }, + "spec": { + "clusterIP": "None", + "ports": [ + { + "port": 3000, + "targetPort": 3000, + }, + ], + "selector": { + "app": "plone-frontend_with_pdb-deployment-c8256934", + }, + "type": "ClusterIP", + }, + }, +] +`; diff --git a/test/backend.test.ts b/test/backend.test.ts index f122cc2..38403c6 100644 --- a/test/backend.test.ts +++ b/test/backend.test.ts @@ -1,5 +1,5 @@ import { Chart, Testing } from 'cdk8s'; -import { PloneBackend } from '../src/backend'; +import { PloneBackend, PloneBackendOptions } from '../src/backend'; test('defaults', () => { @@ -13,3 +13,22 @@ test('defaults', () => { // THEN expect(Testing.synth(chart)).toMatchSnapshot(); }); + +test('defaults-with-pdp', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'plone'); + const options: PloneBackendOptions = { + deployment: { + pdbOptions: { + minAvailable: 1, + }, + }, + }; + + // WHEN + new PloneBackend(chart, 'backend_with_pdb', options); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); diff --git a/test/frontend.test.ts b/test/frontend.test.ts new file mode 100644 index 0000000..e6bfd73 --- /dev/null +++ b/test/frontend.test.ts @@ -0,0 +1,34 @@ +import { Chart, Testing } from 'cdk8s'; +import { PloneFrontend, PloneFrontendOptions } from '../src/frontend'; + + +test('defaults', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'plone'); + + // WHEN + new PloneFrontend(chart, 'frontend'); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +}); + +test('defaults-with-pdp', () => { + // GIVEN + const app = Testing.app(); + const chart = new Chart(app, 'plone'); + const options: PloneFrontendOptions = { + deployment: { + pdbOptions: { + minAvailable: 1, + }, + }, + }; + + // WHEN + new PloneFrontend(chart, 'frontend_with_pdb', options); + + // THEN + expect(Testing.synth(chart)).toMatchSnapshot(); +});