@bluedynamics/cdk8s-plone 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/claude.md +39 -0
- package/.claude/settings.local.json +31 -0
- package/.jsii +4 -4
- package/README.md +15 -2
- package/documentation/sources/how-to/deploy-classic-ui.md +322 -0
- package/documentation/sources/how-to/deploy-production-volto.md +319 -0
- package/documentation/sources/how-to/index.md +13 -0
- package/documentation/sources/reference/api/index.md +29 -0
- package/examples/classic-ui/.env.example +19 -0
- package/examples/classic-ui/README.md +343 -0
- package/examples/classic-ui/__snapshots__/main.test.ts.snap +1242 -0
- package/examples/classic-ui/cdk8s.yaml +6 -0
- package/examples/classic-ui/config/varnish.tpl.vcl +217 -0
- package/examples/classic-ui/ingress.ts +217 -0
- package/examples/classic-ui/jest.config.js +11 -0
- package/examples/classic-ui/main.test.ts +11 -0
- package/examples/classic-ui/main.ts +100 -0
- package/examples/classic-ui/package-lock.json +5719 -0
- package/examples/classic-ui/package.json +36 -0
- package/examples/classic-ui/postgres.bitnami.ts +49 -0
- package/examples/classic-ui/postgres.cloudnativepg.ts +63 -0
- package/examples/production-volto/.env.example +20 -0
- package/examples/production-volto/README.md +295 -0
- package/examples/production-volto/__snapshots__/main.test.ts.snap +1412 -0
- package/examples/production-volto/cdk8s.yaml +6 -0
- package/examples/production-volto/config/varnish.tpl.vcl +297 -0
- package/examples/production-volto/ingress.ts +229 -0
- package/examples/production-volto/jest.config.js +11 -0
- package/examples/production-volto/main.test.ts +11 -0
- package/examples/production-volto/main.ts +104 -0
- package/examples/production-volto/package-lock.json +5714 -0
- package/examples/production-volto/package.json +36 -0
- package/examples/production-volto/postgres.bitnami.ts +49 -0
- package/examples/production-volto/postgres.cloudnativepg.ts +63 -0
- package/lib/httpcache.js +1 -1
- package/lib/plone.js +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
language: typescript
|
|
2
|
+
app: npx ts-node main.ts
|
|
3
|
+
imports:
|
|
4
|
+
- k8s
|
|
5
|
+
- https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/config/crd/bases/postgresql.cnpg.io_clusters.yaml
|
|
6
|
+
- https://raw.githubusercontent.com/traefik/traefik/v3.2/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
vcl 4.0;
|
|
2
|
+
|
|
3
|
+
import std;
|
|
4
|
+
import directors;
|
|
5
|
+
|
|
6
|
+
probe ploneBackendProbe {
|
|
7
|
+
.url = "/";
|
|
8
|
+
.timeout = 5s;
|
|
9
|
+
.interval = 15s;
|
|
10
|
+
.window = 10;
|
|
11
|
+
.threshold = 8;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
backend ploneBackend {
|
|
15
|
+
.host = "{{ .Env.BACKEND_SERVICE_NAME }}";
|
|
16
|
+
.port = "{{ .Env.BACKEND_SERVICE_PORT }}";
|
|
17
|
+
.probe = ploneBackendProbe;
|
|
18
|
+
.connect_timeout = 0.5s;
|
|
19
|
+
.first_byte_timeout = 120s;
|
|
20
|
+
.between_bytes_timeout = 60s;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Only allow PURGE from kubernetes network */
|
|
24
|
+
acl purge {
|
|
25
|
+
"10.0.0.0/8";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
sub detect_debug{
|
|
29
|
+
# Requests with X-Varnish-Debug will display additional
|
|
30
|
+
# information about requests
|
|
31
|
+
unset req.http.x-vcl-debug;
|
|
32
|
+
set req.http.x-vcl-debug = true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
sub detect_auth{
|
|
36
|
+
unset req.http.x-auth;
|
|
37
|
+
if (
|
|
38
|
+
(req.http.Cookie && (
|
|
39
|
+
req.http.Cookie ~ "__ac(_(name|password|persistent))?=" ||
|
|
40
|
+
req.http.Cookie ~ "_ZopeId" ||
|
|
41
|
+
req.http.Cookie ~ "auth_token")) ||
|
|
42
|
+
(req.http.Authenticate) ||
|
|
43
|
+
(req.http.Authorization)
|
|
44
|
+
) {
|
|
45
|
+
set req.http.x-auth = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
sub vcl_init {
|
|
50
|
+
new lbPloneBackend = directors.round_robin();
|
|
51
|
+
lbPloneBackend.add_backend(ploneBackend);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
sub vcl_recv {
|
|
55
|
+
# Annotate request with x-vcl-debug
|
|
56
|
+
call detect_debug;
|
|
57
|
+
|
|
58
|
+
# Annotate request with x-auth indicating if request is authenticated or not
|
|
59
|
+
call detect_auth;
|
|
60
|
+
|
|
61
|
+
# Routing: All traffic goes to Plone backend for Classic UI
|
|
62
|
+
set req.backend_hint = lbPloneBackend.backend();
|
|
63
|
+
|
|
64
|
+
# short cut authenticated requests to pass
|
|
65
|
+
if (req.http.x-auth) {
|
|
66
|
+
return(pass);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Sanitize cookies so they do not needlessly destroy cacheability for anonymous pages
|
|
70
|
+
if (req.http.Cookie) {
|
|
71
|
+
set req.http.Cookie = ";" + req.http.Cookie;
|
|
72
|
+
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
|
|
73
|
+
set req.http.Cookie = regsuball(req.http.Cookie, ";(sticky|I18N_LANGUAGE|statusmessages|__ac|_ZopeId|__cp|beaker\.session|authomatic|serverid|__rf|auth_token)=", "; \1=");
|
|
74
|
+
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
|
|
75
|
+
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
|
|
76
|
+
|
|
77
|
+
if (req.http.Cookie == "") {
|
|
78
|
+
unset req.http.Cookie;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Handle the different request types
|
|
83
|
+
if (req.method == "PURGE") {
|
|
84
|
+
if (!client.ip ~ purge) {
|
|
85
|
+
return (synth(405, "Not allowed."));
|
|
86
|
+
} else {
|
|
87
|
+
ban("req.url == " + req.url);
|
|
88
|
+
return (synth(200, "Purged."));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
} elseif (req.method == "BAN") {
|
|
92
|
+
# Same ACL check as above:
|
|
93
|
+
if (!client.ip ~ purge) {
|
|
94
|
+
return (synth(405, "Not allowed."));
|
|
95
|
+
}
|
|
96
|
+
ban("req.http.host == " + req.http.host + "&& req.url == " + req.url);
|
|
97
|
+
# Throw a synthetic page so the
|
|
98
|
+
# request won't go to the backend.
|
|
99
|
+
return (synth(200, "Ban added"));
|
|
100
|
+
|
|
101
|
+
} elseif (req.method != "GET" &&
|
|
102
|
+
req.method != "HEAD" &&
|
|
103
|
+
req.method != "PUT" &&
|
|
104
|
+
req.method != "POST" &&
|
|
105
|
+
req.method != "PATCH" &&
|
|
106
|
+
req.method != "TRACE" &&
|
|
107
|
+
req.method != "OPTIONS" &&
|
|
108
|
+
req.method != "DELETE") {
|
|
109
|
+
/* Non-RFC2616 or CONNECT which is weird. */
|
|
110
|
+
return (pipe);
|
|
111
|
+
} elseif (req.method != "GET" &&
|
|
112
|
+
req.method != "HEAD" &&
|
|
113
|
+
req.method != "OPTIONS") {
|
|
114
|
+
/* POST, PUT, PATCH will pass, always */
|
|
115
|
+
return(pass);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return(hash);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
sub vcl_pipe {
|
|
122
|
+
/* This is not necessary if you do not do any request rewriting. */
|
|
123
|
+
set req.http.connection = "close";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
sub vcl_purge {
|
|
127
|
+
return (synth(200, "PURGE: " + req.url + " - " + req.hash));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
sub vcl_synth {
|
|
131
|
+
if (resp.status == 301) {
|
|
132
|
+
set resp.http.location = resp.reason;
|
|
133
|
+
set resp.reason = "Moved";
|
|
134
|
+
return (deliver);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
sub vcl_hit {
|
|
139
|
+
if (obj.ttl >= 0s) {
|
|
140
|
+
// A pure unadulterated hit, deliver it
|
|
141
|
+
return (deliver);
|
|
142
|
+
} elsif (obj.ttl + obj.grace > 0s) {
|
|
143
|
+
// Object is in grace, deliver it
|
|
144
|
+
// Automatically triggers a background fetch
|
|
145
|
+
return (deliver);
|
|
146
|
+
} else {
|
|
147
|
+
return (restart);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
sub vcl_backend_response {
|
|
153
|
+
# Don't allow static files to set cookies.
|
|
154
|
+
# (?i) denotes case insensitive in PCRE (perl compatible regular expressions).
|
|
155
|
+
if (bereq.url ~ "(?i)\.(pdf|asc|dat|txt|doc|xls|ppt|tgz|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
|
|
156
|
+
unset beresp.http.set-cookie;
|
|
157
|
+
}
|
|
158
|
+
if (beresp.http.Set-Cookie) {
|
|
159
|
+
set beresp.http.x-varnish-action = "FETCH (pass - response sets cookie)";
|
|
160
|
+
set beresp.uncacheable = true;
|
|
161
|
+
set beresp.ttl = 120s;
|
|
162
|
+
return(deliver);
|
|
163
|
+
}
|
|
164
|
+
if (beresp.http.Cache-Control ~ "(private|no-cache|no-store)") {
|
|
165
|
+
set beresp.http.x-varnish-action = "FETCH (pass - cache control disallows)";
|
|
166
|
+
set beresp.uncacheable = true;
|
|
167
|
+
set beresp.ttl = 120s;
|
|
168
|
+
return(deliver);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (beresp.http.Authorization) {
|
|
172
|
+
set beresp.http.x-varnish-action = "FETCH (pass - authorized and no public cache control)";
|
|
173
|
+
set beresp.uncacheable = true;
|
|
174
|
+
set beresp.ttl = 120s;
|
|
175
|
+
return(deliver);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!beresp.http.Cache-Control) {
|
|
179
|
+
set beresp.http.x-varnish-action = "FETCH (override - backend not setting cache control)";
|
|
180
|
+
set beresp.uncacheable = true;
|
|
181
|
+
set beresp.ttl = 120s;
|
|
182
|
+
return (deliver);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (beresp.http.X-Anonymous && !beresp.http.Cache-Control) {
|
|
186
|
+
set beresp.http.x-varnish-action = "FETCH (override - anonymous backend not setting cache control)";
|
|
187
|
+
set beresp.ttl = 600s;
|
|
188
|
+
return (deliver);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
set beresp.http.x-varnish-action = "FETCH (insert)";
|
|
192
|
+
return (deliver);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
sub vcl_deliver {
|
|
196
|
+
|
|
197
|
+
if (req.http.x-vcl-debug) {
|
|
198
|
+
set resp.http.x-varnish-ttl = obj.ttl;
|
|
199
|
+
set resp.http.x-varnish-grace = obj.grace;
|
|
200
|
+
set resp.http.x-hits = obj.hits;
|
|
201
|
+
if (req.http.x-auth) {
|
|
202
|
+
set resp.http.x-auth = "Logged-in";
|
|
203
|
+
} else {
|
|
204
|
+
set resp.http.x-auth = "Anon";
|
|
205
|
+
}
|
|
206
|
+
if (obj.hits > 0) {
|
|
207
|
+
set resp.http.x-cache = "HIT";
|
|
208
|
+
} else {
|
|
209
|
+
set resp.http.x-cache = "MISS";
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
unset resp.http.x-varnish-action;
|
|
213
|
+
unset resp.http.x-cache-operation;
|
|
214
|
+
unset resp.http.x-cache-rule;
|
|
215
|
+
unset resp.http.x-powered-by;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as k8s from './imports/k8s';
|
|
3
|
+
import * as traefik from './imports/traefik.io';
|
|
4
|
+
|
|
5
|
+
export interface IngressOptions {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* type: kong or traefik
|
|
9
|
+
* @default - traefik
|
|
10
|
+
*/
|
|
11
|
+
readonly ingressType?: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* issuer: cert-manager/cluster-issuer
|
|
15
|
+
* @default none
|
|
16
|
+
*/
|
|
17
|
+
readonly issuer: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* domainCached is the Domain used for the cached setup and should be the main public domain
|
|
21
|
+
* @default - none
|
|
22
|
+
*/
|
|
23
|
+
readonly domainCached: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* domainUncached is the Domain used for the uncached setup for testing purposes only
|
|
27
|
+
* @default - none
|
|
28
|
+
*/
|
|
29
|
+
readonly domainUncached: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* domainMaintenance is the Domain to access the Plone backend (API) server for maintenance
|
|
33
|
+
* @default - none
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
readonly domainMaintenance: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* backendServiceName is the K8S Service name of the Plone backend
|
|
40
|
+
* @default - none
|
|
41
|
+
*/
|
|
42
|
+
readonly backendServiceName: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* httpcacheServiceName is the K8S Service name of the http-cache (Varnish)
|
|
46
|
+
* @default - none
|
|
47
|
+
*/
|
|
48
|
+
readonly httpcacheServiceName: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
export class IngressChart extends Construct {
|
|
53
|
+
|
|
54
|
+
readonly issuer: string;
|
|
55
|
+
|
|
56
|
+
constructor(scope: Construct, id: string, options: IngressOptions) {
|
|
57
|
+
super(scope, id);
|
|
58
|
+
this.issuer = options.issuer;
|
|
59
|
+
if (options.ingressType === 'traefik') {
|
|
60
|
+
// Cached domain routes through Varnish
|
|
61
|
+
this.traefikIngress(
|
|
62
|
+
'main',
|
|
63
|
+
'cached',
|
|
64
|
+
options.domainCached,
|
|
65
|
+
'/',
|
|
66
|
+
options.httpcacheServiceName,
|
|
67
|
+
80,
|
|
68
|
+
);
|
|
69
|
+
// Uncached domain goes direct to backend
|
|
70
|
+
this.traefikIngress(
|
|
71
|
+
'main',
|
|
72
|
+
'uncached',
|
|
73
|
+
options.domainUncached,
|
|
74
|
+
`/`,
|
|
75
|
+
options.backendServiceName,
|
|
76
|
+
8080,
|
|
77
|
+
`/VirtualHostBase/https/${options.domainUncached}/Plone/VirtualHostRoot/`
|
|
78
|
+
);
|
|
79
|
+
// Maintenance domain for backend access
|
|
80
|
+
this.traefikIngress(
|
|
81
|
+
'main',
|
|
82
|
+
'maintenance',
|
|
83
|
+
options.domainMaintenance,
|
|
84
|
+
`/`,
|
|
85
|
+
options.backendServiceName,
|
|
86
|
+
8080,
|
|
87
|
+
`/VirtualHostBase/https/${options.domainMaintenance}/VirtualHostRoot/`
|
|
88
|
+
);
|
|
89
|
+
} else if (options.ingressType === 'kong') {
|
|
90
|
+
|
|
91
|
+
// Create the ingress for the cached (main) domain
|
|
92
|
+
this.kongIngress(
|
|
93
|
+
'main',
|
|
94
|
+
'cached',
|
|
95
|
+
options.domainCached,
|
|
96
|
+
'/',
|
|
97
|
+
options.httpcacheServiceName,
|
|
98
|
+
80,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Create the ingress for the uncached (test) domain - direct to backend
|
|
102
|
+
this.kongIngress(
|
|
103
|
+
'uncached',
|
|
104
|
+
'backend',
|
|
105
|
+
options.domainUncached,
|
|
106
|
+
'/~/(.*)',
|
|
107
|
+
options.backendServiceName,
|
|
108
|
+
8080,
|
|
109
|
+
`/VirtualHostBase/https/${options.domainUncached}/Plone/VirtualHostRoot/$1`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Create the ingress for the maintenance
|
|
113
|
+
this.kongIngress(
|
|
114
|
+
'maintenance',
|
|
115
|
+
'backend',
|
|
116
|
+
options.domainMaintenance,
|
|
117
|
+
'/~/(.*)',
|
|
118
|
+
options.backendServiceName,
|
|
119
|
+
8080,
|
|
120
|
+
`/VirtualHostBase/https/${options.domainMaintenance}/VirtualHostRoot/$1`
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error('Unknown ingress type');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
traefikIngress(prefix: string, postfix: string, domain: string, path: string, backendServiceName: string, backendPort: number, rewrite?: string) {
|
|
128
|
+
var annotations: { [key: string]: string } = {
|
|
129
|
+
'kubernetes.io/ingress.class': 'traefik',
|
|
130
|
+
'cert-manager.io/cluster-issuer': this.issuer,
|
|
131
|
+
};
|
|
132
|
+
if (rewrite !== undefined) {
|
|
133
|
+
const rewritemw = new traefik.Middleware(this, `${prefix}-${postfix}-addprefix`,
|
|
134
|
+
{
|
|
135
|
+
metadata: {},
|
|
136
|
+
spec: {
|
|
137
|
+
addPrefix: {
|
|
138
|
+
prefix: rewrite,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
annotations['traefik.ingress.kubernetes.io/router.middlewares'] = `plone-${rewritemw.name}@kubernetescrd`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
new k8s.KubeIngress(this, `${prefix}-${postfix}`, {
|
|
147
|
+
metadata: {
|
|
148
|
+
annotations: annotations,
|
|
149
|
+
},
|
|
150
|
+
spec: {
|
|
151
|
+
ingressClassName: 'traefik',
|
|
152
|
+
rules: [{
|
|
153
|
+
host: domain,
|
|
154
|
+
http: {
|
|
155
|
+
paths: [{
|
|
156
|
+
backend: {
|
|
157
|
+
service: {
|
|
158
|
+
name: backendServiceName,
|
|
159
|
+
port: { number: backendPort },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
path: path,
|
|
163
|
+
pathType: 'Prefix',
|
|
164
|
+
}],
|
|
165
|
+
},
|
|
166
|
+
}],
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
kongIngress(prefix: string, postfix: string, domain: string, path: string, backendServiceName: string, backendPort: number, rewrite?: string) {
|
|
173
|
+
/*
|
|
174
|
+
Create a kong general ingress
|
|
175
|
+
|
|
176
|
+
Properties:
|
|
177
|
+
- prefix: prefix for the ingress name used for grouping tls secrets
|
|
178
|
+
- postfix: postfix for the ingress name, used concatenated with the prefix as identifer for the ingress
|
|
179
|
+
- domain: domain for the ingress
|
|
180
|
+
- path: path for the ingress
|
|
181
|
+
- backendServiceName: name of the backend service
|
|
182
|
+
- backendPort: port of the backend service
|
|
183
|
+
- rewrite (optional): rewrite path for the ingress
|
|
184
|
+
*/
|
|
185
|
+
var annotations: { [key: string]: string } = {
|
|
186
|
+
'cert-manager.io/cluster-issuer': 'sectigo-issuer',
|
|
187
|
+
'konghq.com/https-redirect-status-code': '308',
|
|
188
|
+
'konghq.com/protocols': 'https',
|
|
189
|
+
};
|
|
190
|
+
if (rewrite !== undefined) {
|
|
191
|
+
annotations['konghq.com/rewrite'] = rewrite;
|
|
192
|
+
}
|
|
193
|
+
new k8s.KubeIngress(this, `${prefix}-${postfix}`, {
|
|
194
|
+
metadata: {
|
|
195
|
+
annotations: annotations,
|
|
196
|
+
},
|
|
197
|
+
spec: {
|
|
198
|
+
ingressClassName: 'kong',
|
|
199
|
+
rules: [{
|
|
200
|
+
host: domain,
|
|
201
|
+
http: {
|
|
202
|
+
paths: [{
|
|
203
|
+
backend: {
|
|
204
|
+
service: {
|
|
205
|
+
name: backendServiceName,
|
|
206
|
+
port: { number: backendPort },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
path: path,
|
|
210
|
+
pathType: 'Prefix',
|
|
211
|
+
}],
|
|
212
|
+
},
|
|
213
|
+
}],
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ClassicUIChart } from './main';
|
|
2
|
+
import { Testing } from 'cdk8s';
|
|
3
|
+
|
|
4
|
+
describe('Classic UI Example', () => {
|
|
5
|
+
test('Synthesizes correctly', () => {
|
|
6
|
+
const app = Testing.app();
|
|
7
|
+
const chart = new ClassicUIChart(app, 'test-chart');
|
|
8
|
+
const results = Testing.synth(chart);
|
|
9
|
+
expect(results).toMatchSnapshot();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import { App, Chart, ChartProps } from 'cdk8s';
|
|
3
|
+
import { Plone, PloneVariant, PloneHttpcache } from '@bluedynamics/cdk8s-plone';
|
|
4
|
+
import * as kplus from 'cdk8s-plus-30';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { IngressChart } from './ingress';
|
|
7
|
+
import { config } from 'dotenv';
|
|
8
|
+
import { PGBitnamiChart } from './postgres.bitnami';
|
|
9
|
+
import { PGCloudNativePGChart } from './postgres.cloudnativepg';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export class ClassicUIChart extends Chart {
|
|
13
|
+
constructor(scope: Construct, id: string, props: ChartProps = {}) {
|
|
14
|
+
super(scope, id, props);
|
|
15
|
+
|
|
16
|
+
config();
|
|
17
|
+
|
|
18
|
+
// ================================================================================================================
|
|
19
|
+
// Postgresql
|
|
20
|
+
let db: PGBitnamiChart | PGCloudNativePGChart;
|
|
21
|
+
let postgresql_username;
|
|
22
|
+
let postgresql_password;
|
|
23
|
+
if ((process.env.DATABASE ?? 'bitnami') == 'cloudnativepg') {
|
|
24
|
+
const cloudnativepgDb = new PGCloudNativePGChart(this, 'db');
|
|
25
|
+
db = cloudnativepgDb;
|
|
26
|
+
// CloudNativePG creates secrets with format: {cluster-name}-app
|
|
27
|
+
// Use the CDK8S-generated cluster name, never hard-code
|
|
28
|
+
const secretName = `${cloudnativepgDb.clusterName}-app`;
|
|
29
|
+
postgresql_username = { valueFrom: { secretKeyRef: { name: secretName, key: 'username' }}};
|
|
30
|
+
postgresql_password = { valueFrom: { secretKeyRef: { name: secretName, key: 'password' }}};
|
|
31
|
+
} else {
|
|
32
|
+
const bitnamiDb = new PGBitnamiChart(this, 'db');
|
|
33
|
+
db = bitnamiDb;
|
|
34
|
+
postgresql_username = { value: 'plone' };
|
|
35
|
+
postgresql_password = { valueFrom: { secretKeyRef: { name: `${bitnamiDb.dbServiceName}`, key: 'password' }}};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ================================================================================================================
|
|
39
|
+
// Plone Classic UI
|
|
40
|
+
|
|
41
|
+
// prepare the environment variables for the plone deployment
|
|
42
|
+
const dbMDName = db.dbServiceName
|
|
43
|
+
const env = new kplus.Env(
|
|
44
|
+
[],
|
|
45
|
+
{
|
|
46
|
+
SECRET_POSTGRESQL_USERNAME: postgresql_username,
|
|
47
|
+
SECRET_POSTGRESQL_PASSWORD: postgresql_password,
|
|
48
|
+
INSTANCE_db_storage: { value: `relstorage` },
|
|
49
|
+
INSTANCE_db_blob_mode: { value: `cache` },
|
|
50
|
+
INSTANCE_db_cache_size: { value: `5000` },
|
|
51
|
+
INSTANCE_db_cache_size_bytes: { value: `1500MB` },
|
|
52
|
+
INSTANCE_db_relstorage: { value: `postgresql` },
|
|
53
|
+
INSTANCE_db_relstorage_postgresql_dsn: { value: `host='${dbMDName}' dbname='plone' user='$(SECRET_POSTGRESQL_USERNAME)' password='$(SECRET_POSTGRESQL_PASSWORD)'` },
|
|
54
|
+
INSTANCE_db_relstorage_cache_local_mb: { value: `800` },
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// create the plone deployment with Classic UI variant
|
|
59
|
+
const plone = new Plone(this, 'plone', {
|
|
60
|
+
version: 'classic.version',
|
|
61
|
+
variant: PloneVariant.CLASSICUI,
|
|
62
|
+
backend: {
|
|
63
|
+
image: process.env.PLONE_BACKEND_IMAGE ?? 'plone/plone-backend:6.1.3',
|
|
64
|
+
environment: env,
|
|
65
|
+
replicas: 2,
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// ================================================================================================================
|
|
70
|
+
// Varnish with kube-httpcache
|
|
71
|
+
const httpcache = new PloneHttpcache(
|
|
72
|
+
this,
|
|
73
|
+
'httpcache',
|
|
74
|
+
{
|
|
75
|
+
plone: plone,
|
|
76
|
+
varnishVclFile: path.join(__dirname, 'config', 'varnish.tpl.vcl'),
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
// ================================================================================================================
|
|
81
|
+
// Ingress
|
|
82
|
+
new IngressChart(
|
|
83
|
+
this,
|
|
84
|
+
'ingress',
|
|
85
|
+
{
|
|
86
|
+
ingressType: 'traefik',
|
|
87
|
+
issuer: process.env.CLUSTER_ISSUER ?? 'letsencrypt-prod',
|
|
88
|
+
domainCached: process.env.DOMAIN_CACHED ?? 'plone-cached.example.com',
|
|
89
|
+
domainUncached: process.env.DOMAIN_UNCACHED ?? 'plone-uncached.example.com',
|
|
90
|
+
domainMaintenance: process.env.DOMAIN_MAINTENANCE ?? 'plone-maintenance.example.com',
|
|
91
|
+
backendServiceName: plone.backendServiceName,
|
|
92
|
+
httpcacheServiceName: httpcache.httpcacheServiceName,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
const app = new App();
|
|
99
|
+
new ClassicUIChart(app, 'plone-classic');
|
|
100
|
+
app.synth();
|