cyberia 3.2.9 → 3.2.22
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/.github/workflows/engine-cyberia.cd.yml +7 -0
- package/.github/workflows/engine-cyberia.ci.yml +14 -2
- package/.github/workflows/ghpkg.ci.yml +1 -0
- package/.github/workflows/npmpkg.ci.yml +10 -5
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/extensions.json +9 -9
- package/.vscode/settings.json +20 -4
- package/CHANGELOG.md +363 -1
- package/CLI-HELP.md +975 -1061
- package/README.md +190 -348
- package/bin/build.js +102 -125
- package/bin/build.template.js +33 -0
- package/bin/cyberia.js +238 -56
- package/bin/deploy.js +16 -3
- package/bin/index.js +238 -56
- package/bump.config.js +26 -0
- package/conf.js +131 -24
- package/deployment.yaml +76 -2
- package/hardhat/package-lock.json +113 -144
- package/hardhat/package.json +4 -3
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +76 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/lxd/lxd-admin-profile.yaml +12 -3
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/manifests/mongodb-4.4/headless-service.yaml +10 -0
- package/manifests/mongodb-4.4/kustomization.yaml +3 -1
- package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
- package/manifests/mongodb-4.4/statefulset.yaml +79 -0
- package/manifests/mongodb-4.4/storage-class.yaml +9 -0
- package/manifests/valkey/statefulset.yaml +1 -1
- package/manifests/valkey/valkey-nodeport.yaml +17 -0
- package/package.json +31 -19
- package/scripts/ipxe-setup.sh +52 -49
- package/scripts/k3s-node-setup.sh +81 -46
- package/scripts/link-local-underpost-cli.sh +6 -0
- package/scripts/lxd-vm-setup.sh +193 -8
- package/scripts/maas-nat-firewalld.sh +145 -0
- package/scripts/test-monitor.sh +250 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/crypto/crypto.router.js +18 -12
- package/src/api/crypto/crypto.service.js +3 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +22 -18
- package/src/api/cyberia-action/cyberia-action.service.js +5 -5
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
- package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
- package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
- package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
- package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
- package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
- package/src/api/cyberia-map/cyberia-map.router.js +35 -30
- package/src/api/cyberia-map/cyberia-map.service.js +7 -7
- package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +458 -0
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/instance/instance.router.js +29 -24
- package/src/api/instance/instance.service.js +6 -6
- package/src/api/ipfs/ipfs.router.js +21 -16
- package/src/api/ipfs/ipfs.service.js +8 -8
- package/src/api/object-layer/object-layer.router.js +512 -507
- package/src/api/object-layer/object-layer.service.js +17 -14
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +132 -101
- package/src/cli/cluster.js +700 -232
- package/src/cli/db.js +59 -60
- package/src/cli/deploy.js +291 -294
- package/src/cli/env.js +1 -4
- package/src/cli/fs.js +13 -3
- package/src/cli/image.js +58 -4
- package/src/cli/index.js +127 -15
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +1099 -223
- package/src/cli/monitor.js +396 -9
- package/src/cli/release.js +355 -146
- package/src/cli/repository.js +169 -30
- package/src/cli/run.js +347 -117
- package/src/cli/secrets.js +11 -2
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +5 -0
- package/src/client/components/core/ClientEvents.js +76 -0
- package/src/client/components/core/EventBus.js +4 -0
- package/src/client/components/core/Modal.js +82 -41
- package/src/client/components/core/PanelForm.js +14 -10
- package/src/client/components/core/Worker.js +162 -363
- package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +55 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +223 -361
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +212 -185
- package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +23 -1
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
- package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +115 -15
- package/src/db/mariadb/MariaDB.js +2 -1
- package/src/db/mongo/MongoBootstrap.js +657 -0
- package/src/db/mongo/MongooseDB.js +130 -21
- package/src/grpc/cyberia/grpc-server.js +25 -57
- package/src/index.js +1 -1
- package/src/runtime/cyberia-client/Dockerfile +10 -7
- package/src/runtime/cyberia-client/Dockerfile.dev +67 -0
- package/src/runtime/cyberia-server/Dockerfile +11 -6
- package/src/runtime/cyberia-server/Dockerfile.dev +47 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Dockerfile +3 -3
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/catalog-underpost.js +61 -0
- package/src/server/catalog.js +77 -0
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +496 -135
- package/src/server/ipfs-client.js +5 -3
- package/src/server/process.js +180 -19
- package/src/server/proxy.js +9 -2
- package/src/server/runtime-status.js +235 -0
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +44 -11
- package/src/server/valkey.js +2 -0
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/test/deploy-monitor.test.js +251 -0
- package/bin/file.js +0 -202
- package/bin/vs.js +0 -74
- package/bin/zed.js +0 -84
- package/manifests/deployment/dd-test-development/deployment.yaml +0 -254
- package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /package/src/client/ssr/{pages → views}/Test.js +0 -0
package/src/cli/deploy.js
CHANGED
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
} from '../server/conf.js';
|
|
19
19
|
import { loggerFactory } from '../server/logger.js';
|
|
20
20
|
import { shellExec } from '../server/process.js';
|
|
21
|
+
import { INTERNAL_READY_PATH, INTERNAL_HEALTH_PATH } from '../server/runtime-status.js';
|
|
21
22
|
import fs from 'fs-extra';
|
|
22
23
|
import dotenv from 'dotenv';
|
|
23
|
-
import { timer } from '../client/components/core/CommonJs.js';
|
|
24
24
|
import os from 'node:os';
|
|
25
25
|
import Underpost from '../index.js';
|
|
26
26
|
|
|
@@ -114,6 +114,64 @@ class UnderpostDeploy {
|
|
|
114
114
|
)
|
|
115
115
|
.join('')}`;
|
|
116
116
|
},
|
|
117
|
+
/**
|
|
118
|
+
* Builds Kubernetes probes that gate on the in-pod internal status endpoint.
|
|
119
|
+
*
|
|
120
|
+
* HTTP mode (default) aligns Kubernetes pod readiness with actual Underpost
|
|
121
|
+
* runtime readiness:
|
|
122
|
+
* - readinessProbe → GET /_internal/ready (200 only when running-deployment)
|
|
123
|
+
* - livenessProbe → GET /_internal/health (deadlock / hung-process detection)
|
|
124
|
+
* - startupProbe → GET /_internal/ready (long window for hot-built/slow boots)
|
|
125
|
+
*
|
|
126
|
+
* Migration: pass `useHttp: false` to emit the legacy TCP socket probes
|
|
127
|
+
* (port-bound only) for deployments not yet serving the internal endpoint.
|
|
128
|
+
*
|
|
129
|
+
* @param {object} opts
|
|
130
|
+
* @param {number} opts.port - In-pod internal status port (deployment base PORT).
|
|
131
|
+
* @param {boolean} [opts.useHttp=true] - Emit HTTP probes; false → legacy TCP.
|
|
132
|
+
* @param {boolean} [opts.liveness=true] - Include a livenessProbe.
|
|
133
|
+
* @param {boolean} [opts.startup=true] - Include a startupProbe.
|
|
134
|
+
* @returns {{readinessProbe: object, livenessProbe?: object, startupProbe?: object}}
|
|
135
|
+
* @memberof UnderpostDeploy
|
|
136
|
+
*/
|
|
137
|
+
runtimeProbesFactory({ port, useHttp = true, liveness = true, startup = true } = {}) {
|
|
138
|
+
if (!port) return {};
|
|
139
|
+
if (!useHttp) {
|
|
140
|
+
const tcp = { tcpSocket: { port }, initialDelaySeconds: 5, periodSeconds: 10, failureThreshold: 6 };
|
|
141
|
+
const probes = { readinessProbe: tcp };
|
|
142
|
+
if (liveness) probes.livenessProbe = { ...tcp, initialDelaySeconds: 30 };
|
|
143
|
+
return probes;
|
|
144
|
+
}
|
|
145
|
+
const probes = {
|
|
146
|
+
readinessProbe: {
|
|
147
|
+
httpGet: { path: INTERNAL_READY_PATH, port },
|
|
148
|
+
initialDelaySeconds: 5,
|
|
149
|
+
periodSeconds: 5,
|
|
150
|
+
timeoutSeconds: 3,
|
|
151
|
+
failureThreshold: 3,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
if (liveness)
|
|
155
|
+
probes.livenessProbe = {
|
|
156
|
+
httpGet: { path: INTERNAL_HEALTH_PATH, port },
|
|
157
|
+
initialDelaySeconds: 30,
|
|
158
|
+
periodSeconds: 15,
|
|
159
|
+
timeoutSeconds: 3,
|
|
160
|
+
failureThreshold: 3,
|
|
161
|
+
};
|
|
162
|
+
if (startup)
|
|
163
|
+
// A startupProbe suspends readiness/liveness until it first succeeds, so
|
|
164
|
+
// its window bounds in-container hot builds and slow boots. 180 × 10s =
|
|
165
|
+
// 30 min before the pod is considered failed to start.
|
|
166
|
+
probes.startupProbe = {
|
|
167
|
+
httpGet: { path: INTERNAL_READY_PATH, port },
|
|
168
|
+
initialDelaySeconds: 10,
|
|
169
|
+
periodSeconds: 10,
|
|
170
|
+
timeoutSeconds: 3,
|
|
171
|
+
failureThreshold: 180,
|
|
172
|
+
};
|
|
173
|
+
return probes;
|
|
174
|
+
},
|
|
117
175
|
/**
|
|
118
176
|
* Creates a YAML deployment configuration for a deployment.
|
|
119
177
|
* @param {string} deployId - Deployment ID for which the deployment is being created.
|
|
@@ -127,6 +185,12 @@ class UnderpostDeploy {
|
|
|
127
185
|
* @param {Array<string>} cmd - Command to run in the deployment container.
|
|
128
186
|
* @param {boolean} skipFullBuild - Whether to skip the full client bundle build during deployment.
|
|
129
187
|
* @param {boolean} pullBundle - Whether to pull the pre-built client bundle from Cloudinary before starting. Use together with skipFullBuild to skip the local build entirely.
|
|
188
|
+
* @param {string} [imagePullPolicy] - Container imagePullPolicy override (`Always`, `IfNotPresent`, `Never`). When omitted, defaults to `Never` for `localhost/` images and `IfNotPresent` otherwise.
|
|
189
|
+
* @param {object} lifecycle - Kubernetes lifecycle hooks configuration for the deployment container.
|
|
190
|
+
* @param {object} readinessProbe - Kubernetes readiness probe configuration for the deployment container.
|
|
191
|
+
* @param {object} livenessProbe - Kubernetes liveness probe configuration for the deployment container.
|
|
192
|
+
* @param {object} startupProbe - Kubernetes startup probe configuration for the deployment container.
|
|
193
|
+
* @param {number} containerPort - Container port to expose for the deployment.
|
|
130
194
|
* @returns {string} - YAML deployment configuration for the specified deployment.
|
|
131
195
|
* @memberof UnderpostDeploy
|
|
132
196
|
*/
|
|
@@ -142,6 +206,22 @@ class UnderpostDeploy {
|
|
|
142
206
|
cmd,
|
|
143
207
|
skipFullBuild,
|
|
144
208
|
pullBundle,
|
|
209
|
+
imagePullPolicy,
|
|
210
|
+
// K8S lifecycle + probe wiring. Pass-through structures shaped like the
|
|
211
|
+
// upstream Kubernetes API, spliced verbatim into the container spec.
|
|
212
|
+
// lifecycle: { postStart: { exec: { command: [...] } }, preStop: { exec: { command: [...] } } }
|
|
213
|
+
// readinessProbe: { tcpSocket: { port: 8081 }, ... }
|
|
214
|
+
// livenessProbe: { tcpSocket: { port: 8081 }, ... }
|
|
215
|
+
// containerPort: integer; rendered as ports[0].containerPort. Optional.
|
|
216
|
+
lifecycle,
|
|
217
|
+
readinessProbe,
|
|
218
|
+
livenessProbe,
|
|
219
|
+
startupProbe,
|
|
220
|
+
containerPort,
|
|
221
|
+
// Explicit, secret-free internal status port injected as an env var so the
|
|
222
|
+
// in-pod endpoint binds exactly what the probes and the monitor target,
|
|
223
|
+
// independent of the ambient `PORT` baked into the image/secret.
|
|
224
|
+
internalStatusPort,
|
|
145
225
|
}) {
|
|
146
226
|
if (!cmd)
|
|
147
227
|
cmd =
|
|
@@ -188,26 +268,76 @@ spec:
|
|
|
188
268
|
containers:
|
|
189
269
|
- name: ${deployId}-${env}-${suffix}
|
|
190
270
|
image: ${containerImage}
|
|
191
|
-
imagePullPolicy: ${containerImage.startsWith('localhost/') ? 'Never' : 'IfNotPresent'}
|
|
271
|
+
imagePullPolicy: ${imagePullPolicy ? imagePullPolicy : containerImage.startsWith('localhost/') ? 'Never' : 'IfNotPresent'}
|
|
192
272
|
envFrom:
|
|
193
273
|
- secretRef:
|
|
194
274
|
name: underpost-config
|
|
195
275
|
${
|
|
196
|
-
|
|
197
|
-
? `
|
|
276
|
+
internalStatusPort
|
|
277
|
+
? ` env:
|
|
278
|
+
- name: UNDERPOST_INTERNAL_PORT
|
|
279
|
+
value: "${internalStatusPort}"
|
|
280
|
+
`
|
|
281
|
+
: ''
|
|
282
|
+
}${
|
|
283
|
+
containerPort
|
|
284
|
+
? ` ports:
|
|
285
|
+
- containerPort: ${containerPort}
|
|
286
|
+
`
|
|
287
|
+
: ''
|
|
288
|
+
}${
|
|
289
|
+
resources
|
|
290
|
+
? ` resources:
|
|
198
291
|
requests:
|
|
199
292
|
memory: "${resources.requests.memory}"
|
|
200
293
|
cpu: "${resources.requests.cpu}"
|
|
201
294
|
limits:
|
|
202
295
|
memory: "${resources.limits.memory}"
|
|
203
296
|
cpu: "${resources.limits.cpu}"`
|
|
204
|
-
|
|
205
|
-
}
|
|
297
|
+
: ''
|
|
298
|
+
}
|
|
206
299
|
command:
|
|
207
300
|
- /bin/sh
|
|
208
301
|
- -c
|
|
209
302
|
- >
|
|
210
303
|
${cmd.join(' &&\n ')}
|
|
304
|
+
${
|
|
305
|
+
readinessProbe
|
|
306
|
+
? ` readinessProbe:
|
|
307
|
+
${JSON.stringify(readinessProbe, null, 2)
|
|
308
|
+
.split('\n')
|
|
309
|
+
.map((l) => ' ' + l)
|
|
310
|
+
.join('\n')}
|
|
311
|
+
`
|
|
312
|
+
: ''
|
|
313
|
+
}${
|
|
314
|
+
livenessProbe
|
|
315
|
+
? ` livenessProbe:
|
|
316
|
+
${JSON.stringify(livenessProbe, null, 2)
|
|
317
|
+
.split('\n')
|
|
318
|
+
.map((l) => ' ' + l)
|
|
319
|
+
.join('\n')}
|
|
320
|
+
`
|
|
321
|
+
: ''
|
|
322
|
+
}${
|
|
323
|
+
startupProbe
|
|
324
|
+
? ` startupProbe:
|
|
325
|
+
${JSON.stringify(startupProbe, null, 2)
|
|
326
|
+
.split('\n')
|
|
327
|
+
.map((l) => ' ' + l)
|
|
328
|
+
.join('\n')}
|
|
329
|
+
`
|
|
330
|
+
: ''
|
|
331
|
+
}${
|
|
332
|
+
lifecycle
|
|
333
|
+
? ` lifecycle:
|
|
334
|
+
${JSON.stringify(lifecycle, null, 2)
|
|
335
|
+
.split('\n')
|
|
336
|
+
.map((l) => ' ' + l)
|
|
337
|
+
.join('\n')}
|
|
338
|
+
`
|
|
339
|
+
: ''
|
|
340
|
+
}
|
|
211
341
|
|
|
212
342
|
${
|
|
213
343
|
volumes.length > 0
|
|
@@ -237,17 +367,22 @@ spec:
|
|
|
237
367
|
* @param {object} options - Options for the manifest build process.
|
|
238
368
|
* @param {string} options.replicas - Number of replicas for each deployment.
|
|
239
369
|
* @param {string} options.image - Docker image for the deployment.
|
|
240
|
-
* @param {string} options.namespace - Kubernetes namespace for the deployment.
|
|
370
|
+
* @param {string} options.namespace - Kubernetes namespace for the deployment (defaults to "default").
|
|
241
371
|
* @param {string} [options.versions] - Comma-separated list of versions to deploy.
|
|
242
372
|
* @param {string} [options.cmd] - Custom initialization command for deploymentYamlPartsFactory (comma-separated commands).
|
|
243
|
-
* @param {string} [options.timeoutResponse] -
|
|
244
|
-
* @param {string} [options.timeoutIdle] -
|
|
245
|
-
* @param {string} [options.retryCount] -
|
|
246
|
-
* @param {string} [options.retryPerTryTimeout] -
|
|
247
|
-
* @param {boolean} [options.disableDeploymentProxy] - Whether to disable deployment proxy.
|
|
248
|
-
* @param {string} [options.traffic] -
|
|
249
|
-
* @param {boolean} [options.
|
|
373
|
+
* @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "300000ms", "infinity").
|
|
374
|
+
* @param {string} [options.timeoutIdle] - HTTPProxy per-route idle timeout (e.g. "10s", "infinity").
|
|
375
|
+
* @param {string} [options.retryCount] - HTTPProxy per-route retry count (e.g. 3).
|
|
376
|
+
* @param {string} [options.retryPerTryTimeout] - HTTPProxy per-route per-try timeout (e.g. "150ms").
|
|
377
|
+
* @param {boolean} [options.disableDeploymentProxy] - Whether to disable deployment proxy route generation.
|
|
378
|
+
* @param {string} [options.traffic] - Comma-separated active traffic colour(s) used to select which versions receive traffic (e.g. "blue", "green").
|
|
379
|
+
* @param {boolean} [options.cert] - Whether to include cert-manager Certificate resources in secret.yaml (production only).
|
|
380
|
+
* @param {boolean} [options.selfSigned] - Whether to include TLS block in HTTPProxy using a pre-created self-signed secret. Enables HTTPS for development without cert-manager.
|
|
381
|
+
* @param {boolean} [options.skipFullBuild] - Whether to skip the full client bundle build; forwarded to deploymentYamlPartsFactory.
|
|
250
382
|
* @param {boolean} [options.pullBundle] - Whether to pull the pre-built client bundle from Cloudinary; forwarded to deploymentYamlPartsFactory. Use together with skipFullBuild.
|
|
383
|
+
* @param {string} [options.imagePullPolicy] - Container imagePullPolicy override (`Always`, `IfNotPresent`, `Never`); forwarded to deploymentYamlPartsFactory. Defaults to `Never` for `localhost/` images and `IfNotPresent` otherwise.
|
|
384
|
+
* @param {boolean} [options.disableRuntimeProbes] - Omit internal-status HTTP probes from generated manifests. When true no readiness/liveness/startup probes are emitted.
|
|
385
|
+
* @param {boolean} [options.tcpProbes] - Emit legacy TCP socket probes instead of HTTP internal-status probes (migration path).
|
|
251
386
|
* @returns {Promise<void>} - Promise that resolves when the manifest is built.
|
|
252
387
|
* @memberof UnderpostDeploy
|
|
253
388
|
*/
|
|
@@ -272,6 +407,17 @@ spec:
|
|
|
272
407
|
|
|
273
408
|
logger.info('port range', { deployId, fromPort, toPort });
|
|
274
409
|
|
|
410
|
+
// The internal status endpoint binds `fromPort - 1`: app instances bind
|
|
411
|
+
// the router range starting at `fromPort`, so this slot is always free
|
|
412
|
+
// inside the pod. It is injected into the pod env (UNDERPOST_INTERNAL_PORT)
|
|
413
|
+
// and used for both the probes and the monitor's port-forward target so
|
|
414
|
+
// all three agree regardless of the image's ambient PORT.
|
|
415
|
+
// Opt out with `--disable-runtime-probes` to keep legacy probe-less pods.
|
|
416
|
+
const internalPort = fromPort - 1;
|
|
417
|
+
const probes = options.disableRuntimeProbes
|
|
418
|
+
? {}
|
|
419
|
+
: Underpost.deploy.runtimeProbesFactory({ port: internalPort, useHttp: !options.tcpProbes });
|
|
420
|
+
|
|
275
421
|
let deploymentYamlParts = '';
|
|
276
422
|
for (const deploymentVersion of deploymentVersions) {
|
|
277
423
|
deploymentYamlParts += `---
|
|
@@ -286,6 +432,11 @@ ${Underpost.deploy
|
|
|
286
432
|
cmd: options.cmd ? options.cmd.split(',').map((c) => c.trim()) : undefined,
|
|
287
433
|
skipFullBuild: options.skipFullBuild,
|
|
288
434
|
pullBundle: options.pullBundle,
|
|
435
|
+
imagePullPolicy: options.imagePullPolicy,
|
|
436
|
+
internalStatusPort: options.disableRuntimeProbes ? undefined : internalPort,
|
|
437
|
+
readinessProbe: probes.readinessProbe,
|
|
438
|
+
livenessProbe: probes.livenessProbe,
|
|
439
|
+
startupProbe: probes.startupProbe,
|
|
289
440
|
})
|
|
290
441
|
.replace('{{ports}}', buildKindPorts(fromPort, toPort))}
|
|
291
442
|
`;
|
|
@@ -328,7 +479,7 @@ ${Underpost.deploy
|
|
|
328
479
|
: [];
|
|
329
480
|
|
|
330
481
|
for (const host of Object.keys(confServer)) {
|
|
331
|
-
if (env === 'production')
|
|
482
|
+
if (env === 'production' && options.cert === true)
|
|
332
483
|
secretYaml += Underpost.deploy.buildCertManagerCertificate({ host, namespace: options.namespace });
|
|
333
484
|
|
|
334
485
|
const pathPortAssignment = pathPortAssignmentData[host];
|
|
@@ -509,10 +660,15 @@ spec:
|
|
|
509
660
|
const hostTest = options?.hostTest
|
|
510
661
|
? options.hostTest
|
|
511
662
|
: Object.keys(loadConfServerJson(`./engine-private/conf/${deployId}/conf.server.json`))[0];
|
|
663
|
+
// Missing HTTPProxy is the canonical "no traffic colour set yet" state
|
|
664
|
+
// for blue/green rollouts. silentOnError swallows kubectl's NotFound
|
|
665
|
+
// exit so the function can return null cleanly.
|
|
512
666
|
const info = shellExec(`sudo kubectl get HTTPProxy/${hostTest} -n ${options.namespace} -o yaml`, {
|
|
513
667
|
silent: true,
|
|
514
668
|
stdout: true,
|
|
669
|
+
silentOnError: true,
|
|
515
670
|
});
|
|
671
|
+
if (!info) return null;
|
|
516
672
|
return info.match('blue') ? 'blue' : info.match('green') ? 'green' : null;
|
|
517
673
|
},
|
|
518
674
|
|
|
@@ -526,6 +682,7 @@ spec:
|
|
|
526
682
|
* @memberof UnderpostDeploy
|
|
527
683
|
*/
|
|
528
684
|
baseProxyYamlFactory({ host, env, options }) {
|
|
685
|
+
const includeTls = env !== 'development' || options.selfSigned === true;
|
|
529
686
|
return `
|
|
530
687
|
---
|
|
531
688
|
apiVersion: projectcontour.io/v1
|
|
@@ -536,11 +693,11 @@ metadata:
|
|
|
536
693
|
spec:
|
|
537
694
|
virtualhost:
|
|
538
695
|
fqdn: ${host}${
|
|
539
|
-
|
|
540
|
-
?
|
|
541
|
-
: `
|
|
696
|
+
includeTls
|
|
697
|
+
? `
|
|
542
698
|
tls:
|
|
543
699
|
secretName: ${host}`
|
|
700
|
+
: ''
|
|
544
701
|
}
|
|
545
702
|
routes:`;
|
|
546
703
|
},
|
|
@@ -556,36 +713,41 @@ spec:
|
|
|
556
713
|
* @param {boolean} options.buildManifest - Whether to build the deployment manifest.
|
|
557
714
|
* @param {boolean} options.infoUtil - Whether to display utility information.
|
|
558
715
|
* @param {boolean} options.expose - Whether to expose the deployment.
|
|
559
|
-
* @param {boolean} options.cert - Whether to create
|
|
560
|
-
* @param {string} options.certHosts - Comma-separated list of hosts for which to create certificates.
|
|
716
|
+
* @param {boolean} options.cert - Whether to create cert-manager Certificate resources for the deployment.
|
|
717
|
+
* @param {string} options.certHosts - Comma-separated list of hosts for which to create cert-manager certificates.
|
|
718
|
+
* @param {boolean} options.selfSigned - Use a pre-created self-signed TLS secret instead of cert-manager. The secret must already exist in the namespace with the same name as the host. Enables TLS in the Contour HTTPProxy virtualhost without requiring a production ClusterIssuer.
|
|
561
719
|
* @param {string} options.versions - Comma-separated list of versions to deploy.
|
|
562
720
|
* @param {string} options.image - Docker image for the deployment.
|
|
563
721
|
* @param {string} options.traffic - Traffic status for the deployment.
|
|
564
722
|
* @param {string} options.replicas - Number of replicas for the deployment.
|
|
565
723
|
* @param {string} options.node - Node name for resource allocation.
|
|
566
|
-
* @param {boolean} options.restoreHosts - Whether to restore the hosts file.
|
|
567
724
|
* @param {boolean} options.disableUpdateDeployment - Whether to disable deployment updates.
|
|
568
725
|
* @param {boolean} options.disableUpdateProxy - Whether to disable proxy updates.
|
|
569
726
|
* @param {boolean} options.disableDeploymentProxy - Whether to disable deployment proxy.
|
|
570
727
|
* @param {boolean} options.disableUpdateVolume - Whether to disable volume updates.
|
|
571
728
|
* @param {boolean} options.status - Whether to display deployment status.
|
|
572
|
-
* @param {boolean} options.etcHosts - Whether to display the /etc/hosts file.
|
|
573
729
|
* @param {boolean} options.disableUpdateUnderpostConfig - Whether to disable Underpost config updates.
|
|
574
|
-
* @param {string} [options.namespace] - Kubernetes namespace for the deployment.
|
|
575
|
-
* @param {string} [options.timeoutResponse] -
|
|
576
|
-
* @param {string} [options.timeoutIdle] -
|
|
577
|
-
* @param {string} [options.retryCount] -
|
|
578
|
-
* @param {string} [options.retryPerTryTimeout] -
|
|
579
|
-
* @param {string} [options.kindType] -
|
|
580
|
-
* @param {number} [options.port] - Port number for exposing the deployment.
|
|
581
|
-
* @param {string} [options.cmd] - Custom initialization command
|
|
582
|
-
* @param {number} [options.exposePort] -
|
|
730
|
+
* @param {string} [options.namespace] - Kubernetes namespace for the deployment (defaults to "default").
|
|
731
|
+
* @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "300000ms", "infinity").
|
|
732
|
+
* @param {string} [options.timeoutIdle] - HTTPProxy per-route idle timeout (e.g. "10s", "infinity").
|
|
733
|
+
* @param {string} [options.retryCount] - HTTPProxy per-route retry count (e.g. 3).
|
|
734
|
+
* @param {string} [options.retryPerTryTimeout] - HTTPProxy per-route per-try timeout (e.g. "150ms").
|
|
735
|
+
* @param {string} [options.kindType] - Kubernetes resource kind to target when using --expose (defaults to "svc").
|
|
736
|
+
* @param {number} [options.port] - Port number override for exposing the deployment.
|
|
737
|
+
* @param {string} [options.cmd] - Custom initialization command (comma-separated) for deploymentYamlPartsFactory.
|
|
738
|
+
* @param {number} [options.exposePort] - Remote port override when --expose is active (overrides auto-detected service port). Used as both local and remote port unless exposeLocalPort is also set.
|
|
739
|
+
* @param {number} [options.exposeLocalPort] - Local port override for --expose (e.g. 80); remote port is still auto-detected. Enables /etc/hosts access without a port in the browser URL.
|
|
740
|
+
* @param {boolean} [options.localProxy] - When true (with --expose), forward all service TCP ports locally and start the Node.js path-routing proxy for full path-based routing (e.g. /wp alongside /).
|
|
741
|
+
* @param {boolean} [options.tls] - When true (with --expose --local-proxy), start the proxy on port 443 with TLS using self-signed certificates resolved from the local SSL store.
|
|
583
742
|
* @param {boolean} [options.k3s] - Whether to use k3s cluster context.
|
|
584
743
|
* @param {boolean} [options.kubeadm] - Whether to use kubeadm cluster context.
|
|
585
744
|
* @param {boolean} [options.kind] - Whether to use kind cluster context.
|
|
586
745
|
* @param {boolean} [options.gitClean] - Whether to run git clean on volume mount paths before copying.
|
|
587
746
|
* @param {boolean} [options.skipFullBuild] - Whether to skip the full client bundle build; passed through to buildManifest/deploymentYamlPartsFactory.
|
|
588
747
|
* @param {boolean} [options.pullBundle] - Whether to pull the pre-built client bundle from Cloudinary; passed through to buildManifest/deploymentYamlPartsFactory. Use together with skipFullBuild.
|
|
748
|
+
* @param {string} [options.imagePullPolicy] - Container imagePullPolicy override (`Always`, `IfNotPresent`, `Never`); passed through to buildManifest/deploymentYamlPartsFactory. Defaults to `Never` for `localhost/` images and `IfNotPresent` otherwise.
|
|
749
|
+
* @param {boolean} [options.disableRuntimeProbes] - Omit internal-status HTTP probes from generated manifests. When true no readiness/liveness/startup probes are emitted.
|
|
750
|
+
* @param {boolean} [options.tcpProbes] - Emit legacy TCP socket probes instead of HTTP internal-status probes.
|
|
589
751
|
* @returns {Promise<void>} - Promise that resolves when the deployment process is complete.
|
|
590
752
|
* @memberof UnderpostDeploy
|
|
591
753
|
*/
|
|
@@ -606,13 +768,11 @@ spec:
|
|
|
606
768
|
traffic: '',
|
|
607
769
|
replicas: '',
|
|
608
770
|
node: '',
|
|
609
|
-
restoreHosts: false,
|
|
610
771
|
disableUpdateDeployment: false,
|
|
611
772
|
disableUpdateProxy: false,
|
|
612
773
|
disableDeploymentProxy: false,
|
|
613
774
|
disableUpdateVolume: false,
|
|
614
775
|
status: false,
|
|
615
|
-
etcHosts: false,
|
|
616
776
|
disableUpdateUnderpostConfig: false,
|
|
617
777
|
namespace: '',
|
|
618
778
|
timeoutResponse: '',
|
|
@@ -622,11 +782,16 @@ spec:
|
|
|
622
782
|
kindType: '',
|
|
623
783
|
port: 0,
|
|
624
784
|
exposePort: 0,
|
|
785
|
+
exposeLocalPort: 0,
|
|
786
|
+
localProxy: false,
|
|
787
|
+
tls: false,
|
|
788
|
+
selfSigned: false,
|
|
625
789
|
cmd: '',
|
|
626
790
|
k3s: false,
|
|
627
791
|
kubeadm: false,
|
|
628
792
|
kind: false,
|
|
629
793
|
gitClean: false,
|
|
794
|
+
imagePullPolicy: '',
|
|
630
795
|
},
|
|
631
796
|
) {
|
|
632
797
|
const namespace = options.namespace ? options.namespace : 'default';
|
|
@@ -695,14 +860,6 @@ EOF`);
|
|
|
695
860
|
return;
|
|
696
861
|
}
|
|
697
862
|
if (!options.disableUpdateUnderpostConfig) Underpost.deploy.configMap(env);
|
|
698
|
-
let renderHosts = '';
|
|
699
|
-
let etcHosts = [];
|
|
700
|
-
if (options.restoreHosts === true) {
|
|
701
|
-
const factoryResult = Underpost.deploy.etcHostFactory(etcHosts);
|
|
702
|
-
renderHosts = factoryResult.renderHosts;
|
|
703
|
-
logger.info(renderHosts);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
863
|
|
|
707
864
|
for (const _deployId of deployList.split(',')) {
|
|
708
865
|
const deployId = _deployId.trim();
|
|
@@ -710,20 +867,54 @@ EOF`);
|
|
|
710
867
|
if (options.expose === true) {
|
|
711
868
|
const kindType = options.kindType ? options.kindType : 'svc';
|
|
712
869
|
const svc = Underpost.kubectl.get(deployId, kindType)[0];
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
870
|
+
if (!svc) {
|
|
871
|
+
logger.error(`No ${kindType} found matching '${deployId}', skipping expose`);
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (options.localProxy) {
|
|
875
|
+
const svcPorts = [
|
|
876
|
+
...new Set(
|
|
877
|
+
svc['PORT(S)']
|
|
878
|
+
.split(',')
|
|
879
|
+
.filter((p) => p.includes('/TCP'))
|
|
880
|
+
.map((p) => parseInt(p.split(':')[0])),
|
|
881
|
+
),
|
|
882
|
+
];
|
|
883
|
+
for (const svcPort of svcPorts) {
|
|
884
|
+
shellExec(`sudo kubectl port-forward -n ${namespace} ${kindType}/${svc.NAME} ${svcPort}:${svcPort}`, {
|
|
885
|
+
async: true,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
const envFile = `./engine-private/conf/${deployId}/.env.${env}`;
|
|
889
|
+
let basePort = svcPorts[0] - 1;
|
|
890
|
+
if (fs.existsSync(envFile)) {
|
|
891
|
+
const portMatch = fs.readFileSync(envFile, 'utf8').match(/^PORT=(\d+)/m);
|
|
892
|
+
if (portMatch) basePort = parseInt(portMatch[1]);
|
|
893
|
+
}
|
|
894
|
+
logger.info(deployId, { svc, svcPorts, basePort });
|
|
895
|
+
const tlsFlag = options.tls ? ' tls' : '';
|
|
896
|
+
shellExec(
|
|
897
|
+
`NODE_ENV=${env} PORT=${basePort} DEV_PROXY_PORT_OFFSET=0 node src/proxy proxy ${deployId} ${env}${tlsFlag}`,
|
|
898
|
+
{ async: true },
|
|
899
|
+
);
|
|
900
|
+
} else {
|
|
901
|
+
const remotePort = options.exposePort
|
|
902
|
+
? parseInt(options.exposePort)
|
|
903
|
+
: options.port
|
|
904
|
+
? parseInt(options.port)
|
|
905
|
+
: kindType !== 'svc'
|
|
906
|
+
? 80
|
|
907
|
+
: parseInt(svc[`PORT(S)`].split('/TCP')[0]);
|
|
908
|
+
const localPort = options.exposeLocalPort ? parseInt(options.exposeLocalPort) : remotePort;
|
|
909
|
+
logger.info(deployId, {
|
|
910
|
+
svc,
|
|
911
|
+
localPort,
|
|
912
|
+
remotePort,
|
|
913
|
+
});
|
|
914
|
+
shellExec(`sudo kubectl port-forward -n ${namespace} ${kindType}/${svc.NAME} ${localPort}:${remotePort}`, {
|
|
915
|
+
async: true,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
727
918
|
continue;
|
|
728
919
|
}
|
|
729
920
|
|
|
@@ -759,7 +950,6 @@ EOF`);
|
|
|
759
950
|
if (Underpost.deploy.isValidTLSContext({ host, env, options }))
|
|
760
951
|
shellExec(`sudo kubectl delete Certificate ${host} -n ${namespace} --ignore-not-found`);
|
|
761
952
|
}
|
|
762
|
-
if (!options.remove) etcHosts.push(host);
|
|
763
953
|
}
|
|
764
954
|
|
|
765
955
|
const manifestsPath =
|
|
@@ -776,56 +966,13 @@ EOF`);
|
|
|
776
966
|
if (!options.disableUpdateProxy)
|
|
777
967
|
shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml -n ${namespace}`);
|
|
778
968
|
|
|
779
|
-
if (
|
|
969
|
+
if (
|
|
970
|
+
Underpost.deploy.isValidTLSContext({ host: Object.keys(confServer)[0], env, options }) &&
|
|
971
|
+
!options.selfSigned
|
|
972
|
+
)
|
|
780
973
|
shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml -n ${namespace}`);
|
|
781
974
|
}
|
|
782
975
|
}
|
|
783
|
-
if (options.etcHosts === true) {
|
|
784
|
-
const factoryResult = Underpost.deploy.etcHostFactory(etcHosts);
|
|
785
|
-
renderHosts = factoryResult.renderHosts;
|
|
786
|
-
}
|
|
787
|
-
if (renderHosts)
|
|
788
|
-
logger.info(
|
|
789
|
-
`
|
|
790
|
-
` + renderHosts,
|
|
791
|
-
);
|
|
792
|
-
},
|
|
793
|
-
/**
|
|
794
|
-
* Checks the status of a deployment.
|
|
795
|
-
* @param {string} deployId - Deployment ID for which the status is being checked.
|
|
796
|
-
* @param {string} env - Environment for which the status is being checked.
|
|
797
|
-
* @param {string} traffic - Current traffic status for the deployment.
|
|
798
|
-
* @param {Array<string>} ignoresNames - List of pod names to ignore.
|
|
799
|
-
* @param {string} [namespace='default'] - Kubernetes namespace for the deployment.
|
|
800
|
-
* @returns {object} - Object containing the status of the deployment.
|
|
801
|
-
* @memberof UnderpostDeploy
|
|
802
|
-
*/
|
|
803
|
-
async checkDeploymentReadyStatus(deployId, env, traffic, ignoresNames = [], namespace = 'default') {
|
|
804
|
-
const cmd = `underpost config get container-status`;
|
|
805
|
-
const pods = Underpost.kubectl.get(`${deployId}-${env}-${traffic}`, 'pods', namespace);
|
|
806
|
-
const readyPods = [];
|
|
807
|
-
const notReadyPods = [];
|
|
808
|
-
for (const pod of pods) {
|
|
809
|
-
const { NAME } = pod;
|
|
810
|
-
if (ignoresNames && ignoresNames.find((t) => NAME.trim().toLowerCase().match(t.trim().toLowerCase()))) continue;
|
|
811
|
-
const out = await new Promise((resolve) => {
|
|
812
|
-
shellExec(`sudo kubectl exec -i ${NAME} -n ${namespace} -- sh -c "${cmd}"`, {
|
|
813
|
-
silent: true,
|
|
814
|
-
disableLog: true,
|
|
815
|
-
callback: function (code, stdout, stderr) {
|
|
816
|
-
return resolve(JSON.stringify({ code, stdout, stderr }));
|
|
817
|
-
},
|
|
818
|
-
});
|
|
819
|
-
});
|
|
820
|
-
pod.out = out;
|
|
821
|
-
const ready = out.match(`${deployId}-${env}-running-deployment`);
|
|
822
|
-
ready ? readyPods.push(pod) : notReadyPods.push(pod);
|
|
823
|
-
}
|
|
824
|
-
return {
|
|
825
|
-
ready: pods.length > 0 && notReadyPods.length === 0,
|
|
826
|
-
notReadyPods,
|
|
827
|
-
readyPods,
|
|
828
|
-
};
|
|
829
976
|
},
|
|
830
977
|
/**
|
|
831
978
|
* Creates a Kubernetes Secret for a deployment (replaces configMap for secret data).
|
|
@@ -853,6 +1000,7 @@ EOF`);
|
|
|
853
1000
|
* @param {string} options.timeoutIdle - Timeout idle setting for the deployment.
|
|
854
1001
|
* @param {string} options.retryCount - Retry count setting for the deployment.
|
|
855
1002
|
* @param {string} options.retryPerTryTimeout - Retry per-try timeout setting for the deployment.
|
|
1003
|
+
* @param {string} [options.imagePullPolicy] - Container imagePullPolicy override; forwarded to the manifest rebuild triggered here.
|
|
856
1004
|
* @memberof UnderpostDeploy
|
|
857
1005
|
*/
|
|
858
1006
|
switchTraffic(
|
|
@@ -866,12 +1014,14 @@ EOF`);
|
|
|
866
1014
|
timeoutIdle: '',
|
|
867
1015
|
retryCount: '',
|
|
868
1016
|
retryPerTryTimeout: '',
|
|
1017
|
+
imagePullPolicy: '',
|
|
869
1018
|
},
|
|
870
1019
|
) {
|
|
871
1020
|
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
1021
|
+
const imagePullPolicyFlag = options.imagePullPolicy ? ` --image-pull-policy ${options.imagePullPolicy}` : '';
|
|
872
1022
|
|
|
873
1023
|
shellExec(
|
|
874
|
-
`node bin deploy --info-router --build-manifest --traffic ${targetTraffic} --replicas ${replicas} --namespace ${namespace}${timeoutFlags} ${deployId} ${env}`,
|
|
1024
|
+
`node bin deploy --info-router --build-manifest --traffic ${targetTraffic} --replicas ${replicas} --namespace ${namespace}${timeoutFlags}${imagePullPolicyFlag} ${deployId} ${env}`,
|
|
875
1025
|
);
|
|
876
1026
|
|
|
877
1027
|
shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml -n ${namespace}`);
|
|
@@ -922,15 +1072,12 @@ EOF`);
|
|
|
922
1072
|
if (options.gitClean && volume.volumeMountPath) {
|
|
923
1073
|
Underpost.repo.clean({ paths: [volume.volumeMountPath] });
|
|
924
1074
|
}
|
|
925
|
-
if (
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
shellExec(`docker exec -i kind-worker bash -c "mkdir -p ${rootVolumeHostPath}"`);
|
|
930
|
-
// shellExec(`docker cp ${volume.volumeMountPath} kind-worker:${rootVolumeHostPath}`);
|
|
931
|
-
shellExec(`tar -C ${volume.volumeMountPath} -c . | docker cp - kind-worker:${rootVolumeHostPath}`);
|
|
1075
|
+
if (clusterContext === 'kind') {
|
|
1076
|
+
const kindNode = options.nodeName || 'kind-worker';
|
|
1077
|
+
shellExec(`docker exec -i ${kindNode} bash -c "mkdir -p ${rootVolumeHostPath}"`);
|
|
1078
|
+
shellExec(`tar -C ${volume.volumeMountPath} -c . | docker cp - ${kindNode}:${rootVolumeHostPath}`);
|
|
932
1079
|
shellExec(
|
|
933
|
-
`docker exec -i
|
|
1080
|
+
`docker exec -i ${kindNode} bash -c "chown -R 1000:1000 ${rootVolumeHostPath}; chmod -R 755 ${rootVolumeHostPath}"`,
|
|
934
1081
|
);
|
|
935
1082
|
} else {
|
|
936
1083
|
if (!fs.existsSync(rootVolumeHostPath)) fs.mkdirSync(rootVolumeHostPath, { recursive: true });
|
|
@@ -1070,42 +1217,6 @@ spec:
|
|
|
1070
1217
|
storage: 5Gi`;
|
|
1071
1218
|
},
|
|
1072
1219
|
|
|
1073
|
-
/**
|
|
1074
|
-
* Creates a hosts file for a deployment.
|
|
1075
|
-
* @param {Array<string>} hosts - List of hosts to be added to the hosts file.
|
|
1076
|
-
* @param {object} options - Options for the hosts file creation.
|
|
1077
|
-
* @param {boolean} options.append - Whether to append to the existing hosts file.
|
|
1078
|
-
* @returns {object} - Object containing the rendered hosts file.
|
|
1079
|
-
* @memberof UnderpostDeploy
|
|
1080
|
-
*/
|
|
1081
|
-
etcHostFactory(hosts = [], options = { append: false }) {
|
|
1082
|
-
hosts = hosts.map((host) => {
|
|
1083
|
-
try {
|
|
1084
|
-
if (!host.startsWith('http')) host = `http://${host}`;
|
|
1085
|
-
const hostname = new URL(host).hostname;
|
|
1086
|
-
logger.info('Hostname extract valid', { host, hostname });
|
|
1087
|
-
return hostname;
|
|
1088
|
-
} catch (e) {
|
|
1089
|
-
logger.warn('No hostname extract valid', host);
|
|
1090
|
-
return host;
|
|
1091
|
-
}
|
|
1092
|
-
});
|
|
1093
|
-
const renderHosts = `127.0.0.1 ${hosts.join(
|
|
1094
|
-
' ',
|
|
1095
|
-
)} localhost localhost.localdomain localhost4 localhost4.localdomain4
|
|
1096
|
-
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6`;
|
|
1097
|
-
|
|
1098
|
-
if (options && options.append && fs.existsSync(`/etc/hosts`)) {
|
|
1099
|
-
fs.writeFileSync(
|
|
1100
|
-
`/etc/hosts`,
|
|
1101
|
-
fs.readFileSync(`/etc/hosts`, 'utf8') +
|
|
1102
|
-
`
|
|
1103
|
-
${renderHosts}`,
|
|
1104
|
-
'utf8',
|
|
1105
|
-
);
|
|
1106
|
-
} else fs.writeFileSync(`/etc/hosts`, renderHosts, 'utf8');
|
|
1107
|
-
return { renderHosts };
|
|
1108
|
-
},
|
|
1109
1220
|
/**
|
|
1110
1221
|
* Checks if a TLS context is valid.
|
|
1111
1222
|
* @param {object} options - Options for the check.
|
|
@@ -1116,152 +1227,10 @@ ${renderHosts}`,
|
|
|
1116
1227
|
* @memberof UnderpostDeploy
|
|
1117
1228
|
*/
|
|
1118
1229
|
isValidTLSContext: ({ host, env, options }) =>
|
|
1119
|
-
env === 'production' &&
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
/**
|
|
1124
|
-
* Monitors the ready status of a deployment.
|
|
1125
|
-
* @param {string} deployId - Deployment ID for which the ready status is being monitored.
|
|
1126
|
-
* @param {string} env - Environment for which the ready status is being monitored.
|
|
1127
|
-
* @param {string} targetTraffic - Target traffic status for the deployment.
|
|
1128
|
-
* @param {Array<string>} ignorePods - List of pod names to ignore.
|
|
1129
|
-
* @param {string} [namespace='default'] - Kubernetes namespace for the deployment.
|
|
1130
|
-
* @returns {object} - Object containing the ready status of the deployment.
|
|
1131
|
-
* @memberof UnderpostDeploy
|
|
1132
|
-
*/
|
|
1133
|
-
async monitorReadyRunner(deployId, env, targetTraffic, ignorePods = [], namespace = 'default') {
|
|
1134
|
-
let checkStatusIteration = 0;
|
|
1135
|
-
const checkStatusIterationMsDelay = 1000;
|
|
1136
|
-
const maxIterations = 3000;
|
|
1137
|
-
const deploymentId = `${deployId}-${env}-${targetTraffic}`;
|
|
1138
|
-
const iteratorTag = `[${deploymentId}]`;
|
|
1139
|
-
logger.info('Deployment init', { deployId, env, targetTraffic, checkStatusIterationMsDelay, namespace });
|
|
1140
|
-
const minReadyOk = 3;
|
|
1141
|
-
let readyOk = 0;
|
|
1142
|
-
let result = {
|
|
1143
|
-
ready: false,
|
|
1144
|
-
notReadyPods: [],
|
|
1145
|
-
readyPods: [],
|
|
1146
|
-
};
|
|
1147
|
-
let lastMsg = {};
|
|
1148
|
-
while (readyOk < minReadyOk) {
|
|
1149
|
-
if (checkStatusIteration >= maxIterations) {
|
|
1150
|
-
logger.error(
|
|
1151
|
-
`${iteratorTag} | Deployment check ready status timeout. Max iterations reached: ${maxIterations}`,
|
|
1152
|
-
);
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
result = await Underpost.deploy.checkDeploymentReadyStatus(deployId, env, targetTraffic, ignorePods, namespace);
|
|
1156
|
-
if (result.ready === true) {
|
|
1157
|
-
readyOk++;
|
|
1158
|
-
logger.info(`${iteratorTag} | Deployment ready. Verification number: ${readyOk}`);
|
|
1159
|
-
for (const pod of result.readyPods) {
|
|
1160
|
-
const { NAME } = pod;
|
|
1161
|
-
lastMsg[NAME] = 'Deployment ready';
|
|
1162
|
-
console.log(
|
|
1163
|
-
'Target pod:',
|
|
1164
|
-
NAME[NAME.match('green') ? 'bgGreen' : 'bgBlue'].bold.black,
|
|
1165
|
-
'| Status:',
|
|
1166
|
-
lastMsg[NAME].bold.magenta,
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
{
|
|
1172
|
-
let indexOf = -1;
|
|
1173
|
-
for (const pod of result.notReadyPods) {
|
|
1174
|
-
indexOf++;
|
|
1175
|
-
const { NAME, out } = pod;
|
|
1176
|
-
|
|
1177
|
-
if (out.match('not') && out.match('found') && checkStatusIteration <= 20 && out.match(deploymentId))
|
|
1178
|
-
lastMsg[NAME] = 'Starting deployment';
|
|
1179
|
-
// else if (out.match('not') && out.match('found') && checkStatusIteration <= 20 && out.match('underpost'))
|
|
1180
|
-
// lastMsg[NAME] = 'Installing underpost cli';
|
|
1181
|
-
else if (out.match('not') && out.match('found') && checkStatusIteration <= 20 && out.match('task'))
|
|
1182
|
-
lastMsg[NAME] = 'Initializing setup task';
|
|
1183
|
-
else if (out.match('Empty environment variables')) lastMsg[NAME] = 'Setup environment';
|
|
1184
|
-
else if (out.match(`${deployId}-${env}-build-deployment`)) lastMsg[NAME] = 'Building apps/services';
|
|
1185
|
-
else if (out.match(`${deployId}-${env}-initializing-deployment`))
|
|
1186
|
-
lastMsg[NAME] = 'Initializing apps/services';
|
|
1187
|
-
else if (!lastMsg[NAME]) lastMsg[NAME] = `Waiting for status`;
|
|
1188
|
-
|
|
1189
|
-
console.log(
|
|
1190
|
-
'Target pod:',
|
|
1191
|
-
NAME[NAME.match('green') ? 'bgGreen' : 'bgBlue'].bold.black,
|
|
1192
|
-
'| Status:',
|
|
1193
|
-
lastMsg[NAME].bold.magenta,
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
await timer(checkStatusIterationMsDelay);
|
|
1198
|
-
checkStatusIteration++;
|
|
1199
|
-
logger.info(
|
|
1200
|
-
`${iteratorTag} | Deployment in progress... | Delay number monitor iterations: ${checkStatusIteration}`,
|
|
1201
|
-
);
|
|
1202
|
-
}
|
|
1203
|
-
logger.info(
|
|
1204
|
-
`${iteratorTag} | Deployment ready. | Total delay number monitor iterations: ${checkStatusIteration}`,
|
|
1205
|
-
);
|
|
1206
|
-
return result;
|
|
1207
|
-
},
|
|
1208
|
-
|
|
1209
|
-
/**
|
|
1210
|
-
* Retrieves the currently loaded images in the Kubernetes cluster.
|
|
1211
|
-
* @param {string} [node='kind-worker'] - Node name to check for loaded images.
|
|
1212
|
-
* @param {object} options - Options for the image retrieval.
|
|
1213
|
-
* @param {boolean} options.spec - Whether to retrieve images from the pod specifications.
|
|
1214
|
-
* @param {string} options.namespace - Kubernetes namespace to filter pods.
|
|
1215
|
-
* @returns {Array<object>} - Array of objects containing pod names and their corresponding images.
|
|
1216
|
-
* @memberof UnderpostDeploy
|
|
1217
|
-
*/
|
|
1218
|
-
getCurrentLoadedImages(node = 'kind-worker', options = { spec: false, namespace: '' }) {
|
|
1219
|
-
if (options.spec) {
|
|
1220
|
-
const raw = shellExec(
|
|
1221
|
-
`kubectl get pods ${options.namespace ? `--namespace ${options.namespace}` : `--all-namespaces`} -o=jsonpath='{range .items[*]}{"\\n"}{.metadata.namespace}{"/"}{.metadata.name}{":\\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}'`,
|
|
1222
|
-
{
|
|
1223
|
-
stdout: true,
|
|
1224
|
-
silent: true,
|
|
1225
|
-
},
|
|
1226
|
-
);
|
|
1227
|
-
return raw
|
|
1228
|
-
.split(`\n`)
|
|
1229
|
-
.map((lines) => ({
|
|
1230
|
-
pod: lines.split('\t')[0].replaceAll(':', '').trim(),
|
|
1231
|
-
image: lines.split('\t')[1] ? lines.split('\t')[1].replaceAll(',', '').trim() : null,
|
|
1232
|
-
}))
|
|
1233
|
-
.filter((o) => o.image);
|
|
1234
|
-
}
|
|
1235
|
-
const raw = shellExec(node === 'kind-worker' ? `docker exec -i ${node} crictl images` : `crictl images`, {
|
|
1236
|
-
stdout: true,
|
|
1237
|
-
silent: true,
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
const heads = raw
|
|
1241
|
-
.split(`\n`)[0]
|
|
1242
|
-
.split(' ')
|
|
1243
|
-
.filter((_r) => _r.trim());
|
|
1244
|
-
|
|
1245
|
-
const pods = raw
|
|
1246
|
-
.split(`\n`)
|
|
1247
|
-
.filter((r) => !r.match('IMAGE'))
|
|
1248
|
-
.map((r) => r.split(' ').filter((_r) => _r.trim()));
|
|
1249
|
-
|
|
1250
|
-
const result = [];
|
|
1251
|
-
|
|
1252
|
-
for (const row of pods) {
|
|
1253
|
-
if (row.length === 0) continue;
|
|
1254
|
-
const pod = {};
|
|
1255
|
-
let index = -1;
|
|
1256
|
-
for (const head of heads) {
|
|
1257
|
-
if (head in pod) continue;
|
|
1258
|
-
index++;
|
|
1259
|
-
pod[head] = row[index];
|
|
1260
|
-
}
|
|
1261
|
-
result.push(pod);
|
|
1262
|
-
}
|
|
1263
|
-
return result;
|
|
1264
|
-
},
|
|
1230
|
+
(env === 'production' &&
|
|
1231
|
+
options.cert === true &&
|
|
1232
|
+
(!options.certHosts || options.certHosts.split(',').includes(host))) ||
|
|
1233
|
+
options.selfSigned === true,
|
|
1265
1234
|
|
|
1266
1235
|
/**
|
|
1267
1236
|
* Predefined resource templates for Kubernetes deployments.
|
|
@@ -1363,6 +1332,34 @@ ${renderHosts}`,
|
|
|
1363
1332
|
return undefined;
|
|
1364
1333
|
},
|
|
1365
1334
|
|
|
1335
|
+
/**
|
|
1336
|
+
* Extracts a non-standard `imagePullPolicy` key from an env-resolved
|
|
1337
|
+
* instance lifecycle block (the convention used in `conf.instances.json`,
|
|
1338
|
+
* where `imagePullPolicy` sits alongside `postStart`/`preStop` for
|
|
1339
|
+
* per-instance ergonomics) and returns a clean lifecycle hash that is
|
|
1340
|
+
* safe to splice into the K8S container spec.
|
|
1341
|
+
*
|
|
1342
|
+
* Returns `{ lifecycle, imagePullPolicy }`:
|
|
1343
|
+
* - `lifecycle` — the input minus `imagePullPolicy`, or `undefined` when
|
|
1344
|
+
* the resulting block is empty.
|
|
1345
|
+
* - `imagePullPolicy` — the extracted value, or `undefined` if absent.
|
|
1346
|
+
*
|
|
1347
|
+
* @param {object|undefined} lifecycle - Env-resolved lifecycle block
|
|
1348
|
+
* (already passed through pickEnv). May be `undefined`.
|
|
1349
|
+
* @returns {{ lifecycle: (object|undefined), imagePullPolicy: (string|undefined) }}
|
|
1350
|
+
* @memberof UnderpostDeploy
|
|
1351
|
+
*/
|
|
1352
|
+
extractInstanceImagePullPolicy(lifecycle) {
|
|
1353
|
+
if (!lifecycle || typeof lifecycle !== 'object' || !('imagePullPolicy' in lifecycle)) {
|
|
1354
|
+
return { lifecycle, imagePullPolicy: undefined };
|
|
1355
|
+
}
|
|
1356
|
+
const { imagePullPolicy, ...rest } = lifecycle;
|
|
1357
|
+
return {
|
|
1358
|
+
lifecycle: Object.keys(rest).length > 0 ? rest : undefined,
|
|
1359
|
+
imagePullPolicy,
|
|
1360
|
+
};
|
|
1361
|
+
},
|
|
1362
|
+
|
|
1366
1363
|
/**
|
|
1367
1364
|
* Generates timeout flags string for deployment commands.
|
|
1368
1365
|
* @param {object} options - Options containing timeout settings.
|