@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,297 @@
|
|
|
1
|
+
vcl 4.0;
|
|
2
|
+
|
|
3
|
+
import std;
|
|
4
|
+
import directors;
|
|
5
|
+
|
|
6
|
+
probe ploneBackendProbe {
|
|
7
|
+
.url = "/ok";
|
|
8
|
+
.timeout = 5s;
|
|
9
|
+
.interval = 15s;
|
|
10
|
+
.window = 10;
|
|
11
|
+
.threshold = 8;
|
|
12
|
+
# .initial = 3;
|
|
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
|
+
probe ploneFrontendProbe {
|
|
24
|
+
.url = "/ok";
|
|
25
|
+
.timeout = 5s;
|
|
26
|
+
.interval = 15s;
|
|
27
|
+
.window = 10;
|
|
28
|
+
.threshold = 8;
|
|
29
|
+
# .initial = 3;
|
|
30
|
+
}
|
|
31
|
+
backend ploneFrontend {
|
|
32
|
+
.host = "{{ .Env.FRONTEND_SERVICE_NAME }}";
|
|
33
|
+
.port = "{{ .Env.FRONTEND_SERVICE_PORT }}";
|
|
34
|
+
.probe = ploneFrontendProbe;
|
|
35
|
+
.connect_timeout = 0.5s;
|
|
36
|
+
.first_byte_timeout = 120s;
|
|
37
|
+
.between_bytes_timeout = 60s;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Only allow PURGE from kubernetes network */
|
|
41
|
+
acl purge {
|
|
42
|
+
"10.0.0.0/8";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sub detect_debug{
|
|
46
|
+
# Requests with X-Varnish-Debug will display additional
|
|
47
|
+
# information about requests
|
|
48
|
+
unset req.http.x-vcl-debug;
|
|
49
|
+
# Should be changed after switch to live
|
|
50
|
+
#if (req.http.x-varnish-debug) {
|
|
51
|
+
# set req.http.x-vcl-debug = false;
|
|
52
|
+
#}
|
|
53
|
+
set req.http.x-vcl-debug = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
sub detect_auth{
|
|
57
|
+
unset req.http.x-auth;
|
|
58
|
+
if (
|
|
59
|
+
(req.http.Cookie && (
|
|
60
|
+
req.http.Cookie ~ "__ac(_(name|password|persistent))?=" || req.http.Cookie ~ "_ZopeId" || req.http.Cookie ~ "auth_token")) ||
|
|
61
|
+
(req.http.Authenticate) ||
|
|
62
|
+
(req.http.Authorization)
|
|
63
|
+
) {
|
|
64
|
+
set req.http.x-auth = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
sub detect_requesttype{
|
|
69
|
+
unset req.http.x-varnish-reqtype;
|
|
70
|
+
set req.http.x-varnish-reqtype = "Default";
|
|
71
|
+
if (req.http.x-auth){
|
|
72
|
+
set req.http.x-varnish-reqtype = "auth";
|
|
73
|
+
} elseif (req.url ~ "\/@@(images|download|)\/?(.*)?$"){
|
|
74
|
+
set req.http.x-varnish-reqtype = "blob";
|
|
75
|
+
} elseif (req.url ~ "\/\+\+api\+\+/?(.*)?$") {
|
|
76
|
+
set req.http.x-varnish-reqtype = "api";
|
|
77
|
+
} else {
|
|
78
|
+
set req.http.x-varnish-reqtype = "express";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
sub process_redirects{
|
|
83
|
+
if (req.http.x-redirect-to) {
|
|
84
|
+
return (synth(301, req.http.x-redirect-to));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
sub vcl_init {
|
|
89
|
+
new lbPloneBackend = directors.round_robin();
|
|
90
|
+
lbPloneBackend.add_backend(ploneBackend);
|
|
91
|
+
|
|
92
|
+
new lbPloneFrontend = directors.round_robin();
|
|
93
|
+
lbPloneFrontend.add_backend(ploneFrontend);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
sub vcl_recv {
|
|
97
|
+
# Annotate request with x-vcl-debug
|
|
98
|
+
call detect_debug;
|
|
99
|
+
|
|
100
|
+
# Annotate request with x-auth indicating if request is authenticated or not
|
|
101
|
+
call detect_auth;
|
|
102
|
+
|
|
103
|
+
# Annotate request with x-varnish-reqtype with a classification for the request
|
|
104
|
+
call detect_requesttype;
|
|
105
|
+
|
|
106
|
+
# Process redirects
|
|
107
|
+
call process_redirects;
|
|
108
|
+
|
|
109
|
+
# Routing: set the current Varnish backend to the matching Plone backend
|
|
110
|
+
# Attention:
|
|
111
|
+
# Confusing wording, we have two possible Varnish backends: the Plone frontend and the Plone backend *sigh*
|
|
112
|
+
if ((req.url ~ "^/\+\+api\+\+") || (req.http.x-varnish-reqtype ~ "blob")) {
|
|
113
|
+
set req.http.x-vcl-plone = "Backend";
|
|
114
|
+
set req.backend_hint = lbPloneBackend.backend();
|
|
115
|
+
# here we need a rewrite to add the virtualhost part of the URL for the Plone Backend
|
|
116
|
+
set req.http.x-vcl-proto = "http";
|
|
117
|
+
if (req.http.X-Forwarded-Proto) {
|
|
118
|
+
set req.http.x-vcl-proto = req.http.X-Forwarded-Proto;
|
|
119
|
+
}
|
|
120
|
+
set req.url = "/VirtualHostBase/" + req.http.x-vcl-proto + "/" + req.http.host + "/Plone/VirtualHostRoot" + req.url;
|
|
121
|
+
} else {
|
|
122
|
+
set req.http.x-vcl-plone = "Frontend";
|
|
123
|
+
set req.backend_hint = lbPloneFrontend.backend();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# short cut authenticated requests to pass
|
|
127
|
+
if (req.http.x-auth) {
|
|
128
|
+
return(pass);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Sanitize cookies so they do not needlessly destroy cacheability for anonymous pages
|
|
132
|
+
if (req.http.Cookie) {
|
|
133
|
+
set req.http.Cookie = ";" + req.http.Cookie;
|
|
134
|
+
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
|
|
135
|
+
set req.http.Cookie = regsuball(req.http.Cookie, ";(sticky|I18N_LANGUAGE|statusmessages|__ac|_ZopeId|__cp|beaker\.session|authomatic|serverid|__rf|auth_token)=", "; \1=");
|
|
136
|
+
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
|
|
137
|
+
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
|
|
138
|
+
|
|
139
|
+
if (req.http.Cookie == "") {
|
|
140
|
+
unset req.http.Cookie;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# Handle the different request types
|
|
145
|
+
if (req.method == "PURGE") {
|
|
146
|
+
if (!client.ip ~ purge) {
|
|
147
|
+
return (synth(405, "Not allowed."));
|
|
148
|
+
} else {
|
|
149
|
+
ban("req.url == " + req.url);
|
|
150
|
+
return (synth(200, "Purged."));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
} elseif (req.method == "BAN") {
|
|
154
|
+
# Same ACL check as above:
|
|
155
|
+
if (!client.ip ~ purge) {
|
|
156
|
+
return (synth(405, "Not allowed."));
|
|
157
|
+
}
|
|
158
|
+
ban("req.http.host == " + req.http.host + "&& req.url == " + req.url);
|
|
159
|
+
# Throw a synthetic page so the
|
|
160
|
+
# request won't go to the backend.
|
|
161
|
+
return (synth(200, "Ban added"));
|
|
162
|
+
|
|
163
|
+
} elseif (req.method != "GET" &&
|
|
164
|
+
req.method != "HEAD" &&
|
|
165
|
+
req.method != "PUT" &&
|
|
166
|
+
req.method != "POST" &&
|
|
167
|
+
req.method != "PATCH" &&
|
|
168
|
+
req.method != "TRACE" &&
|
|
169
|
+
req.method != "OPTIONS" &&
|
|
170
|
+
req.method != "DELETE") {
|
|
171
|
+
/* Non-RFC2616 or CONNECT which is weird. */
|
|
172
|
+
return (pipe);
|
|
173
|
+
} elseif (req.method != "GET" &&
|
|
174
|
+
req.method != "HEAD" &&
|
|
175
|
+
req.method != "OPTIONS") {
|
|
176
|
+
/* POST, PUT, PATCH will pass, always */
|
|
177
|
+
return(pass);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return(hash);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
sub vcl_pipe {
|
|
184
|
+
/* This is not necessary if you do not do any request rewriting. */
|
|
185
|
+
set req.http.connection = "close";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
sub vcl_purge {
|
|
189
|
+
return (synth(200, "PURGE: " + req.url + " - " + req.hash));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
sub vcl_synth {
|
|
193
|
+
if (resp.status == 301) {
|
|
194
|
+
set resp.http.location = resp.reason;
|
|
195
|
+
set resp.reason = "Moved";
|
|
196
|
+
return (deliver);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
sub vcl_hit {
|
|
201
|
+
if (obj.ttl >= 0s) {
|
|
202
|
+
// A pure unadulterated hit, deliver it
|
|
203
|
+
return (deliver);
|
|
204
|
+
} elsif (obj.ttl + obj.grace > 0s) {
|
|
205
|
+
// Object is in grace, deliver it
|
|
206
|
+
// Automatically triggers a background fetch
|
|
207
|
+
return (deliver);
|
|
208
|
+
} else {
|
|
209
|
+
return (restart);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
sub vcl_backend_response {
|
|
215
|
+
# Annotate request with info about the backend used
|
|
216
|
+
set beresp.http.x-varnish-plone-part = bereq.http.x-vcl-plone;
|
|
217
|
+
# set beresp.http.x-varnish-plone-proto = bereq.http.x-vcl-proto;
|
|
218
|
+
# set beresp.http.x-varnish-plone-host = bereq.http.x-vcl-host;
|
|
219
|
+
# Don't allow static files to set cookies.
|
|
220
|
+
# (?i) denotes case insensitive in PCRE (perl compatible regular expressions).
|
|
221
|
+
# make sure you edit both and keep them equal.
|
|
222
|
+
if (bereq.url ~ "(?i)\.(pdf|asc|dat|txt|doc|xls|ppt|tgz|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") {
|
|
223
|
+
unset beresp.http.set-cookie;
|
|
224
|
+
}
|
|
225
|
+
if (beresp.http.Set-Cookie) {
|
|
226
|
+
set beresp.http.x-varnish-action = "FETCH (pass - response sets cookie)";
|
|
227
|
+
set beresp.uncacheable = true;
|
|
228
|
+
set beresp.ttl = 120s;
|
|
229
|
+
return(deliver);
|
|
230
|
+
}
|
|
231
|
+
if (beresp.http.Cache-Control ~ "(private|no-cache|no-store)") {
|
|
232
|
+
set beresp.http.x-varnish-action = "FETCH (pass - cache control disallows)";
|
|
233
|
+
set beresp.uncacheable = true;
|
|
234
|
+
set beresp.ttl = 120s;
|
|
235
|
+
return(deliver);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# if (beresp.http.Authorization && !beresp.http.Cache-Control ~ "public") {
|
|
239
|
+
# Do NOT cache if there is an "Authorization" header
|
|
240
|
+
# beresp never has an Authorization header in beresp, right?
|
|
241
|
+
if (beresp.http.Authorization) {
|
|
242
|
+
set beresp.http.x-varnish-action = "FETCH (pass - authorized and no public cache control)";
|
|
243
|
+
set beresp.uncacheable = true;
|
|
244
|
+
set beresp.ttl = 120s;
|
|
245
|
+
return(deliver);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Use this rule IF no cache-control (SSR content)
|
|
249
|
+
if ((bereq.http.x-varnish-reqtype ~ "express") && (!beresp.http.Cache-Control)) {
|
|
250
|
+
set beresp.http.x-varnish-action = "INSERT (30s caching / 60s grace)";
|
|
251
|
+
set beresp.uncacheable = false;
|
|
252
|
+
set beresp.ttl = 30s;
|
|
253
|
+
set beresp.grace = 60s;
|
|
254
|
+
return(deliver);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!beresp.http.Cache-Control) {
|
|
258
|
+
set beresp.http.x-varnish-action = "FETCH (override - backend not setting cache control)";
|
|
259
|
+
set beresp.uncacheable = true;
|
|
260
|
+
set beresp.ttl = 120s;
|
|
261
|
+
return (deliver);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (beresp.http.X-Anonymous && !beresp.http.Cache-Control) {
|
|
265
|
+
set beresp.http.x-varnish-action = "FETCH (override - anonymous backend not setting cache control)";
|
|
266
|
+
set beresp.ttl = 600s;
|
|
267
|
+
return (deliver);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
set beresp.http.x-varnish-action = "FETCH (insert)";
|
|
271
|
+
return (deliver);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
sub vcl_deliver {
|
|
275
|
+
|
|
276
|
+
if (req.http.x-vcl-debug) {
|
|
277
|
+
set resp.http.x-varnish-ttl = obj.ttl;
|
|
278
|
+
set resp.http.x-varnish-grace = obj.grace;
|
|
279
|
+
set resp.http.x-hits = obj.hits;
|
|
280
|
+
set resp.http.x-varnish-reqtype = req.http.x-varnish-reqtype;
|
|
281
|
+
if (req.http.x-auth) {
|
|
282
|
+
set resp.http.x-auth = "Logged-in";
|
|
283
|
+
} else {
|
|
284
|
+
set resp.http.x-auth = "Anon";
|
|
285
|
+
}
|
|
286
|
+
if (obj.hits > 0) {
|
|
287
|
+
set resp.http.x-cache = "HIT";
|
|
288
|
+
} else {
|
|
289
|
+
set resp.http.x-cache = "MISS";
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
unset resp.http.x-varnish-action;
|
|
293
|
+
unset resp.http.x-cache-operation;
|
|
294
|
+
unset resp.http.x-cache-rule;
|
|
295
|
+
unset resp.http.x-powered-by;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
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: konq 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 and should be the internal for testing pruposes 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
|
+
* frontendServiceName is the K8S Service name of the Plone frontend (Express)
|
|
40
|
+
* @default - none
|
|
41
|
+
*/
|
|
42
|
+
readonly frontendServiceName: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* backendServiceName is the K8S Service name of the Plone backend (API + Blobs)
|
|
46
|
+
* @default - none
|
|
47
|
+
*/
|
|
48
|
+
readonly backendServiceName: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* httpcacheServiceName is the K8S Service name of the http-cache (Varnish)
|
|
52
|
+
* @default - none
|
|
53
|
+
*/
|
|
54
|
+
readonly httpcacheServiceName: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
export class IngressChart extends Construct {
|
|
59
|
+
|
|
60
|
+
readonly issuer: string;
|
|
61
|
+
|
|
62
|
+
constructor(scope: Construct, id: string, options: IngressOptions) {
|
|
63
|
+
super(scope, id);
|
|
64
|
+
this.issuer = options.issuer;
|
|
65
|
+
if (options.ingressType === 'traefik') {
|
|
66
|
+
this.traefikIngress(
|
|
67
|
+
'main',
|
|
68
|
+
'cached',
|
|
69
|
+
options.domainCached,
|
|
70
|
+
'/',
|
|
71
|
+
options.httpcacheServiceName,
|
|
72
|
+
80,
|
|
73
|
+
);
|
|
74
|
+
this.traefikIngress(
|
|
75
|
+
'main',
|
|
76
|
+
'uncached',
|
|
77
|
+
options.domainUncached,
|
|
78
|
+
`/`,
|
|
79
|
+
options.frontendServiceName,
|
|
80
|
+
3000,
|
|
81
|
+
);
|
|
82
|
+
this.traefikIngress(
|
|
83
|
+
'main',
|
|
84
|
+
'maintenance',
|
|
85
|
+
options.domainMaintenance,
|
|
86
|
+
`/`,
|
|
87
|
+
options.backendServiceName,
|
|
88
|
+
8080,
|
|
89
|
+
`/VirtualHostBase/https/${options.domainMaintenance}/VirtualHostRoot/`
|
|
90
|
+
);
|
|
91
|
+
} else if (options.ingressType === 'kong') {
|
|
92
|
+
|
|
93
|
+
// Create the ingress for the cached (main) domain
|
|
94
|
+
this.kongIngress(
|
|
95
|
+
'main',
|
|
96
|
+
'cached',
|
|
97
|
+
options.domainCached,
|
|
98
|
+
'/',
|
|
99
|
+
options.httpcacheServiceName,
|
|
100
|
+
80,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Create the two ingresses for the uncached (test) domain
|
|
104
|
+
// - to frontend:
|
|
105
|
+
this.kongIngress(
|
|
106
|
+
'uncached',
|
|
107
|
+
'frontend',
|
|
108
|
+
options.domainUncached,
|
|
109
|
+
'/',
|
|
110
|
+
options.frontendServiceName,
|
|
111
|
+
3000,
|
|
112
|
+
);
|
|
113
|
+
// - to backend:
|
|
114
|
+
this.kongIngress(
|
|
115
|
+
'uncached',
|
|
116
|
+
'backend',
|
|
117
|
+
options.domainUncached,
|
|
118
|
+
'/~/((?:(?:\\+\\+api\\+\\+)(?:.*))|(?:(?:.*)/(?:(?:@@images|@@downloads)/.*)))',
|
|
119
|
+
options.backendServiceName,
|
|
120
|
+
8080,
|
|
121
|
+
`/VirtualHostBase/https/${options.domainUncached}/Plone/VirtualHostRoot/$1`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Create the ingress for the maintenance
|
|
125
|
+
this.kongIngress(
|
|
126
|
+
'maintenance',
|
|
127
|
+
'backend',
|
|
128
|
+
options.domainMaintenance,
|
|
129
|
+
'/~/(.*)',
|
|
130
|
+
options.backendServiceName,
|
|
131
|
+
8080,
|
|
132
|
+
`/VirtualHostBase/https/${options.domainMaintenance}/VirtualHostRoot/$1`
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
throw new Error('Unknown ingress type');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
traefikIngress(prefix: string, postfix: string, domain: string, path: string, backendServiceName: string, backendPort: number, rewrite?: string) {
|
|
140
|
+
var annotations: { [key: string]: string } = {
|
|
141
|
+
'kubernetes.io/ingress.class': 'traefik',
|
|
142
|
+
'cert-manager.io/cluster-issuer': this.issuer,
|
|
143
|
+
};
|
|
144
|
+
if (rewrite !== undefined) {
|
|
145
|
+
const rewritemw = new traefik.Middleware(this, `${prefix}-${postfix}-addprefix`,
|
|
146
|
+
{
|
|
147
|
+
metadata: {},
|
|
148
|
+
spec: {
|
|
149
|
+
addPrefix: {
|
|
150
|
+
prefix: rewrite,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
annotations['traefik.ingress.kubernetes.io/router.middlewares'] = `plone-${rewritemw.name}@kubernetescrd`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
new k8s.KubeIngress(this, `${prefix}-${postfix}`, {
|
|
159
|
+
metadata: {
|
|
160
|
+
annotations: annotations,
|
|
161
|
+
},
|
|
162
|
+
spec: {
|
|
163
|
+
ingressClassName: 'traefik',
|
|
164
|
+
rules: [{
|
|
165
|
+
host: domain,
|
|
166
|
+
http: {
|
|
167
|
+
paths: [{
|
|
168
|
+
backend: {
|
|
169
|
+
service: {
|
|
170
|
+
name: backendServiceName,
|
|
171
|
+
port: { number: backendPort },
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
path: path,
|
|
175
|
+
pathType: 'Prefix',
|
|
176
|
+
}],
|
|
177
|
+
},
|
|
178
|
+
}],
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
kongIngress(prefix: string, postfix: string, domain: string, path: string, backendServiceName: string, backendPort: number, rewrite?: string) {
|
|
185
|
+
/*
|
|
186
|
+
Create a konq general ingress
|
|
187
|
+
|
|
188
|
+
Properties:
|
|
189
|
+
- prefix: prefix for the ingress name used for grouping tls secrets
|
|
190
|
+
- postfix: postfix for the ingress name, used concatenated with the prefix as identifer for the ingress
|
|
191
|
+
- domain: domain for the ingress
|
|
192
|
+
- path: path for the ingress
|
|
193
|
+
- backendServiceName: name of the backend service
|
|
194
|
+
- backendPort: port of the backend service
|
|
195
|
+
- rewrite (optional): rewrite path for the ingress
|
|
196
|
+
*/
|
|
197
|
+
var annotations: { [key: string]: string } = {
|
|
198
|
+
'cert-manager.io/cluster-issuer': 'sectigo-issuer',
|
|
199
|
+
'konghq.com/https-redirect-status-code': '308',
|
|
200
|
+
'konghq.com/protocols': 'https',
|
|
201
|
+
};
|
|
202
|
+
if (rewrite !== undefined) {
|
|
203
|
+
annotations['konghq.com/rewrite'] = rewrite;
|
|
204
|
+
}
|
|
205
|
+
new k8s.KubeIngress(this, `${prefix}-${postfix}`, {
|
|
206
|
+
metadata: {
|
|
207
|
+
annotations: annotations,
|
|
208
|
+
},
|
|
209
|
+
spec: {
|
|
210
|
+
ingressClassName: 'kong',
|
|
211
|
+
rules: [{
|
|
212
|
+
host: domain,
|
|
213
|
+
http: {
|
|
214
|
+
paths: [{
|
|
215
|
+
backend: {
|
|
216
|
+
service: {
|
|
217
|
+
name: backendServiceName,
|
|
218
|
+
port: { number: backendPort },
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
path: path,
|
|
222
|
+
pathType: 'Prefix',
|
|
223
|
+
}],
|
|
224
|
+
},
|
|
225
|
+
}],
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ExampleChart } from './main';
|
|
2
|
+
import { Testing } from 'cdk8s';
|
|
3
|
+
|
|
4
|
+
describe('Production Volto Example', () => {
|
|
5
|
+
test('Synthesizes correctly', () => {
|
|
6
|
+
const app = Testing.app();
|
|
7
|
+
const chart = new ExampleChart(app, 'test-chart');
|
|
8
|
+
const results = Testing.synth(chart);
|
|
9
|
+
expect(results).toMatchSnapshot();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import { App, Chart, ChartProps } from 'cdk8s';
|
|
3
|
+
import { Plone, 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 ExampleChart 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
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// prepare the environment variables for the plone deployment
|
|
43
|
+
const dbMDName = db.dbServiceName
|
|
44
|
+
const env = new kplus.Env(
|
|
45
|
+
[],
|
|
46
|
+
{
|
|
47
|
+
SECRET_POSTGRESQL_USERNAME: postgresql_username,
|
|
48
|
+
SECRET_POSTGRESQL_PASSWORD: postgresql_password,
|
|
49
|
+
INSTANCE_db_storage: { value: `relstorage` },
|
|
50
|
+
INSTANCE_db_blob_mode: { value: `cache` },
|
|
51
|
+
INSTANCE_db_cache_size: { value: `5000` },
|
|
52
|
+
INSTANCE_db_cache_size_bytes: { value: `1500MB` },
|
|
53
|
+
INSTANCE_db_relstorage: { value: `postgresql` },
|
|
54
|
+
INSTANCE_db_relstorage_postgresql_dsn: { value: `host='${dbMDName}' dbname='plone' user='$(SECRET_POSTGRESQL_USERNAME)' password='$(SECRET_POSTGRESQL_PASSWORD)'` },
|
|
55
|
+
INSTANCE_db_relstorage_cache_local_mb: { value: `800` },
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// create the plone deployment and related resources
|
|
60
|
+
const plone = new Plone(this, 'plone', {
|
|
61
|
+
version: 'test.version',
|
|
62
|
+
backend: {
|
|
63
|
+
image: process.env.PLONE_BACKEND_IMAGE ?? 'plone/plone-backend:6.1.3',
|
|
64
|
+
environment: env,
|
|
65
|
+
},
|
|
66
|
+
frontend: {
|
|
67
|
+
image: process.env.PLONE_FRONTEND_IMAGE ?? 'plone/plone-frontend:latest',
|
|
68
|
+
readinessEnabled: false,
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// ================================================================================================================
|
|
73
|
+
// Varnish with kube-httpcache
|
|
74
|
+
const httpcache = new PloneHttpcache(
|
|
75
|
+
this,
|
|
76
|
+
'httpcache',
|
|
77
|
+
{
|
|
78
|
+
plone: plone,
|
|
79
|
+
varnishVclFile: path.join(__dirname, 'config', 'varnish.tpl.vcl'),
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// ================================================================================================================
|
|
84
|
+
// Ingress
|
|
85
|
+
new IngressChart(
|
|
86
|
+
this,
|
|
87
|
+
'ingress',
|
|
88
|
+
{
|
|
89
|
+
ingressType: 'traefik',
|
|
90
|
+
issuer: process.env.CLUSTER_ISSUER ?? 'letsencrypt-prod',
|
|
91
|
+
domainCached: process.env.DOMAIN_CACHED ?? 'mxplone-cached.example.com',
|
|
92
|
+
domainUncached: process.env.DOMAIN_UNCACHED ?? 'mxplone-cached.example.com',
|
|
93
|
+
domainMaintenance: process.env.DOMAIN_MAINTENANCE ?? 'mxplone-maintenance.example.com',
|
|
94
|
+
backendServiceName: plone.backendServiceName,
|
|
95
|
+
frontendServiceName: plone.frontendServiceName ?? '',
|
|
96
|
+
httpcacheServiceName: httpcache.httpcacheServiceName,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
const app = new App();
|
|
103
|
+
new ExampleChart(app, 'plone-example');
|
|
104
|
+
app.synth();
|