underpost 3.2.30 → 3.2.80
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/ghpkg.ci.yml +87 -0
- package/.github/workflows/npmpkg.ci.yml +9 -6
- package/.github/workflows/publish.ci.yml +3 -3
- package/.github/workflows/pwa-microservices-template-page.cd.yml +0 -7
- package/.github/workflows/release.cd.yml +1 -1
- package/CHANGELOG.md +1230 -971
- package/CLI-HELP.md +14 -6
- package/README.md +3 -3
- package/bin/build.js +40 -4
- package/bin/deploy.js +2 -2
- package/bump.config.js +1 -0
- package/docker-compose.yml +26 -26
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +15 -15
- package/scripts/disk-clean.sh +85 -60
- package/scripts/test-monitor.sh +1 -1
- package/src/api/core/core.controller.js +4 -65
- package/src/api/core/core.router.js +8 -14
- package/src/api/default/default.controller.js +2 -70
- package/src/api/default/default.router.js +7 -17
- package/src/api/document/document.controller.js +5 -77
- package/src/api/document/document.router.js +9 -13
- package/src/api/file/file.controller.js +9 -53
- package/src/api/file/file.router.js +14 -6
- package/src/api/test/test.controller.js +8 -53
- package/src/api/test/test.router.js +1 -4
- package/src/cli/cluster.js +104 -11
- package/src/cli/db.js +4 -2
- package/src/cli/deploy.js +60 -11
- package/src/cli/docker-compose.js +212 -1
- package/src/cli/fs.js +45 -25
- package/src/cli/image.js +147 -51
- package/src/cli/index.js +26 -6
- package/src/cli/release.js +50 -4
- package/src/cli/repository.js +98 -24
- package/src/cli/run.js +380 -178
- package/src/cli/secrets.js +75 -46
- package/src/cli/ssh.js +30 -11
- package/src/client/components/core/Modal.js +38 -4
- package/src/index.js +1 -1
- package/src/server/catalog.js +2 -0
- package/src/server/conf.js +163 -3
- package/src/server/downloader.js +3 -3
- package/src/server/middlewares.js +152 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import express from 'express';
|
|
1
2
|
import { adminGuard } from '../../server/auth.js';
|
|
2
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
3
3
|
import { FileController } from './file.controller.js';
|
|
4
|
-
import express from 'express';
|
|
5
|
-
|
|
6
|
-
const logger = loggerFactory(import.meta);
|
|
7
4
|
|
|
8
5
|
class FileRouter {
|
|
9
6
|
/**
|
|
7
|
+
* authenticated writes, admin-only deletes.
|
|
10
8
|
* @param {import('../types.js').RouterOptions} options
|
|
11
9
|
* @returns {import('express').Router}
|
|
12
10
|
*/
|
|
@@ -17,8 +15,18 @@ class FileRouter {
|
|
|
17
15
|
router.get(`/blob/:id`, async (req, res) => await FileController.get(req, res, options));
|
|
18
16
|
router.get(`/:id`, async (req, res) => await FileController.get(req, res, options));
|
|
19
17
|
router.get(`/`, async (req, res) => await FileController.get(req, res, options));
|
|
20
|
-
router.delete(
|
|
21
|
-
|
|
18
|
+
router.delete(
|
|
19
|
+
`/:id`,
|
|
20
|
+
options.authMiddleware,
|
|
21
|
+
adminGuard,
|
|
22
|
+
async (req, res) => await FileController.delete(req, res, options),
|
|
23
|
+
);
|
|
24
|
+
router.delete(
|
|
25
|
+
`/`,
|
|
26
|
+
options.authMiddleware,
|
|
27
|
+
adminGuard,
|
|
28
|
+
async (req, res) => await FileController.delete(req, res, options),
|
|
29
|
+
);
|
|
22
30
|
return router;
|
|
23
31
|
}
|
|
24
32
|
}
|
|
@@ -1,59 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { controllerHandler, sendSuccess, serviceHandler } from '../../server/middlewares.js';
|
|
2
2
|
import { TestService } from './test.service.js';
|
|
3
3
|
|
|
4
|
-
const logger = loggerFactory(import.meta);
|
|
5
|
-
|
|
6
4
|
class TestController {
|
|
7
|
-
static post =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logger.error(error, error.stack);
|
|
15
|
-
return res.status(400).json({
|
|
16
|
-
status: 'error',
|
|
17
|
-
message: error.message,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
static get = async (req, res, options) => {
|
|
22
|
-
try {
|
|
23
|
-
const result = await TestService.get(req, res, options);
|
|
24
|
-
if (result)
|
|
25
|
-
return res.status(200).json({
|
|
26
|
-
status: 'success',
|
|
27
|
-
data: result,
|
|
28
|
-
});
|
|
29
|
-
else
|
|
30
|
-
return res.status(400).json({
|
|
31
|
-
status: 'error',
|
|
32
|
-
});
|
|
33
|
-
} catch (error) {
|
|
34
|
-
logger.error(error, error.stack);
|
|
35
|
-
return res.status(400).json({
|
|
36
|
-
status: 'error',
|
|
37
|
-
message: error.message,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
static delete = async (req, res, options) => {
|
|
42
|
-
try {
|
|
43
|
-
const result = await TestService.delete(req, res, options);
|
|
44
|
-
|
|
45
|
-
return res.status(200).json({
|
|
46
|
-
status: 'success',
|
|
47
|
-
data: result,
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error(error, error.stack);
|
|
51
|
-
return res.status(400).json({
|
|
52
|
-
status: 'error',
|
|
53
|
-
message: error.message,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
};
|
|
5
|
+
static post = serviceHandler(TestService.post);
|
|
6
|
+
static get = controllerHandler(async (req, res, options) => {
|
|
7
|
+
const result = await TestService.get(req, res, options);
|
|
8
|
+
if (result) return sendSuccess(res, result);
|
|
9
|
+
return res.status(400).json({ status: 'error' });
|
|
10
|
+
});
|
|
11
|
+
static delete = serviceHandler(TestService.delete);
|
|
57
12
|
}
|
|
58
13
|
|
|
59
14
|
export { TestController };
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { loggerFactory } from '../../server/logger.js';
|
|
2
|
-
import { TestController } from './test.controller.js';
|
|
3
1
|
import express from 'express';
|
|
4
|
-
|
|
5
|
-
const logger = loggerFactory(import.meta);
|
|
2
|
+
import { TestController } from './test.controller.js';
|
|
6
3
|
|
|
7
4
|
class TestRouter {
|
|
8
5
|
/**
|
package/src/cli/cluster.js
CHANGED
|
@@ -876,6 +876,73 @@ net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-k3s.conf > /dev/null`,
|
|
|
876
876
|
}
|
|
877
877
|
},
|
|
878
878
|
|
|
879
|
+
/**
|
|
880
|
+
* @method _pruneContainerCaches
|
|
881
|
+
* @description Reclaims container-runtime disk left behind after a cluster
|
|
882
|
+
* teardown: stopped containers, unused images, build cache and anonymous
|
|
883
|
+
* volumes. Best-effort across every runtime present on the host (docker,
|
|
884
|
+
* podman, and optionally the CRI runtime via crictl). Each block is guarded
|
|
885
|
+
* by a command-existence check, so it is safe to call when a runtime is
|
|
886
|
+
* absent. This is what turns a "cluster deleted" into "disk actually freed":
|
|
887
|
+
* `kind delete` / `kubeadm reset` remove the cluster but leave gigabytes of
|
|
888
|
+
* images and overlay layers under /var/lib/{docker,containers}.
|
|
889
|
+
* @param {object} [options]
|
|
890
|
+
* @param {boolean} [options.all=true] - Remove all unused images, not just dangling ones.
|
|
891
|
+
* @param {boolean} [options.crictl=false] - Also prune the CRI runtime via crictl.
|
|
892
|
+
* @param {string} [options.criSocket] - Optional crictl --runtime-endpoint override.
|
|
893
|
+
* @private
|
|
894
|
+
*/
|
|
895
|
+
_pruneContainerCaches(options = {}) {
|
|
896
|
+
const all = options.all !== false;
|
|
897
|
+
const a = all ? '-a ' : '';
|
|
898
|
+
logger.info(` -> Pruning container-runtime caches (all=${all})...`);
|
|
899
|
+
// Docker (also matches the podman-docker shim when docker is symlinked to podman).
|
|
900
|
+
shellExec(
|
|
901
|
+
`if command -v docker >/dev/null 2>&1; then sudo docker system prune ${a}--volumes -f; sudo docker builder prune ${a}-f; fi`,
|
|
902
|
+
{ silentOnError: true },
|
|
903
|
+
);
|
|
904
|
+
// Podman native — on this host images/overlays live under /var/lib/containers/storage.
|
|
905
|
+
shellExec(`if command -v podman >/dev/null 2>&1; then sudo podman system prune ${a}--volumes -f; fi`, {
|
|
906
|
+
silentOnError: true,
|
|
907
|
+
});
|
|
908
|
+
if (options.crictl) {
|
|
909
|
+
const ep = options.criSocket ? `--runtime-endpoint ${options.criSocket} ` : '';
|
|
910
|
+
shellExec(
|
|
911
|
+
`if command -v crictl >/dev/null 2>&1; then sudo env PATH="$PATH:/usr/local/bin:/usr/bin" crictl ${ep}rmi --prune; fi`,
|
|
912
|
+
{ silentOnError: true },
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
Underpost.cluster._unmountOrphanContainerOverlays();
|
|
916
|
+
},
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* @method _unmountOrphanContainerOverlays
|
|
920
|
+
* @description Unmounts leaked container overlay 'merged' mounts under
|
|
921
|
+
* /var/lib/{containers/storage,docker}/overlay. Pruning images/containers
|
|
922
|
+
* frees the backing data but leaves these mountpoints attached, so they
|
|
923
|
+
* keep showing up as identical `overlay ... /merged` rows in `df -h` long
|
|
924
|
+
* after the containers are gone. Only overlays NOT backing a still-existing
|
|
925
|
+
* container are unmounted, so running workloads are never disturbed.
|
|
926
|
+
* Best-effort; safe to call when no runtime is present.
|
|
927
|
+
* @private
|
|
928
|
+
*/
|
|
929
|
+
_unmountOrphanContainerOverlays() {
|
|
930
|
+
logger.info(' -> Unmounting orphaned container overlay mounts...');
|
|
931
|
+
shellExec(`if command -v podman >/dev/null 2>&1; then sudo podman umount --all >/dev/null 2>&1 || true; fi`, {
|
|
932
|
+
silentOnError: true,
|
|
933
|
+
});
|
|
934
|
+
shellExec(
|
|
935
|
+
`active="$(sudo podman ps -aq 2>/dev/null | xargs -r -I{} sudo podman inspect --format '{{.GraphDriver.Data.MergedDir}}' {} 2>/dev/null || true)"
|
|
936
|
+
findmnt -rn -o TARGET 2>/dev/null | grep -E '/var/lib/(containers/storage|docker)/overlay.*/merged' | sort -r | while IFS= read -r m; do
|
|
937
|
+
if ! printf '%s\\n' "$active" | grep -qxF "$m"; then
|
|
938
|
+
echo "Unmounting orphaned overlay: $m"
|
|
939
|
+
sudo umount -l "$m" 2>/dev/null || true
|
|
940
|
+
fi
|
|
941
|
+
done`,
|
|
942
|
+
{ silentOnError: true },
|
|
943
|
+
);
|
|
944
|
+
},
|
|
945
|
+
|
|
879
946
|
/**
|
|
880
947
|
* @method _lazyUmountKubeletMounts
|
|
881
948
|
* @description Lazy-unmounts every mount under /var/lib/kubelet so a
|
|
@@ -910,7 +977,7 @@ net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-k3s.conf > /dev/null`,
|
|
|
910
977
|
if (options.removeVolumeHostPaths) Underpost.cluster._cleanHostPathPvs();
|
|
911
978
|
else logger.info(' -> Skipping (pass --remove-volume-host-paths to enable).');
|
|
912
979
|
|
|
913
|
-
logger.info('Phase 3/
|
|
980
|
+
logger.info('Phase 3/6: Deleting all Kind clusters...');
|
|
914
981
|
shellExec(`clusters=$(kind get clusters)
|
|
915
982
|
if [ -n "$clusters" ]; then
|
|
916
983
|
for c in $clusters; do
|
|
@@ -919,11 +986,14 @@ if [ -n "$clusters" ]; then
|
|
|
919
986
|
done
|
|
920
987
|
fi`);
|
|
921
988
|
|
|
922
|
-
logger.info('Phase 4/
|
|
989
|
+
logger.info('Phase 4/6: Cleaning kubeconfig and Kind Docker networks...');
|
|
923
990
|
shellExec(`rm -rf "$HOME/.kube"`);
|
|
924
991
|
Underpost.cluster.recoverKindDockerNetworks();
|
|
925
992
|
|
|
926
|
-
logger.info('Phase 5/
|
|
993
|
+
logger.info('Phase 5/6: Pruning container-runtime caches (kindest/node images, build cache, volumes)...');
|
|
994
|
+
Underpost.cluster._pruneContainerCaches({ all: true });
|
|
995
|
+
|
|
996
|
+
logger.info('Phase 6/6: Re-applying host configuration (Docker, containerd, sysctl).');
|
|
927
997
|
Underpost.cluster.config();
|
|
928
998
|
|
|
929
999
|
logger.info('=== KIND SAFE RESET COMPLETE ===');
|
|
@@ -995,7 +1065,7 @@ fi`);
|
|
|
995
1065
|
shellExec(`if ip link show tunl0 >/dev/null 2>&1; then sudo ip link del tunl0; fi`);
|
|
996
1066
|
shellExec(`sudo iptables -F`);
|
|
997
1067
|
shellExec(`sudo iptables -t nat -F`);
|
|
998
|
-
|
|
1068
|
+
Underpost.cluster._pruneContainerCaches({ all: true, crictl: true });
|
|
999
1069
|
|
|
1000
1070
|
logger.info('Phase 7/7: Re-applying host configuration (Docker, containerd, sysctl).');
|
|
1001
1071
|
Underpost.cluster.config();
|
|
@@ -1042,6 +1112,8 @@ fi`);
|
|
|
1042
1112
|
shellExec(`if [ -d /etc/rancher/k3s ]; then sudo rm -rf /etc/rancher/k3s; fi`);
|
|
1043
1113
|
shellExec(`if ip link show flannel.1 >/dev/null 2>&1; then sudo ip link del flannel.1; fi`);
|
|
1044
1114
|
shellExec(`if ip link show cni0 >/dev/null 2>&1; then sudo ip link del cni0; fi`);
|
|
1115
|
+
// k3s-uninstall.sh removes /var/lib/rancher/k3s; still prune any host docker/podman leftovers.
|
|
1116
|
+
Underpost.cluster._pruneContainerCaches({ all: true });
|
|
1045
1117
|
|
|
1046
1118
|
logger.info('Phase 5/5: Re-applying minimal K3s host config.');
|
|
1047
1119
|
Underpost.cluster.configMinimalK3s();
|
|
@@ -1125,13 +1197,34 @@ exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
|
|
|
1125
1197
|
EOF`);
|
|
1126
1198
|
shellExec(`sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes`);
|
|
1127
1199
|
|
|
1128
|
-
// Install Helm
|
|
1129
|
-
shellExec(`
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
shellExec(`
|
|
1134
|
-
|
|
1200
|
+
// Install Helm — check if already installed and linked to /bin/helm first
|
|
1201
|
+
const helmBin = shellExec(`test -x /bin/helm && echo exists || echo missing`, {
|
|
1202
|
+
stdout: true,
|
|
1203
|
+
silent: true,
|
|
1204
|
+
}).trim();
|
|
1205
|
+
const helmLocalBin = shellExec(`test -x /usr/local/bin/helm && echo exists || echo missing`, {
|
|
1206
|
+
stdout: true,
|
|
1207
|
+
silent: true,
|
|
1208
|
+
}).trim();
|
|
1209
|
+
|
|
1210
|
+
if (helmBin === 'exists') {
|
|
1211
|
+
logger.info('Helm is already installed and linked to /bin/helm; skipping.');
|
|
1212
|
+
} else if (helmLocalBin === 'exists') {
|
|
1213
|
+
// Helm binary exists at /usr/local/bin but not linked to /bin/helm
|
|
1214
|
+
logger.info('Helm found at /usr/local/bin; linking to /bin/helm...');
|
|
1215
|
+
shellExec(`sudo ln -sf /usr/local/bin/helm /bin/helm`);
|
|
1216
|
+
} else {
|
|
1217
|
+
// Helm not installed — download and install
|
|
1218
|
+
shellExec(`curl -fsSL -o get_helm.sh https://cdn.jsdelivr.net/gh/helm/helm@main/scripts/get-helm-3`);
|
|
1219
|
+
shellExec(`chmod 700 get_helm.sh`);
|
|
1220
|
+
// Run get_helm.sh but ignore its exit code — it may fail on PATH check
|
|
1221
|
+
// even when installation succeeds (binary placed at /usr/local/bin).
|
|
1222
|
+
shellExec(`bash ./get_helm.sh || true`);
|
|
1223
|
+
// Ensure the binary is executable and linked to /bin/helm
|
|
1224
|
+
shellExec(`test -x /usr/local/bin/helm && sudo chmod +x /usr/local/bin/helm || true`);
|
|
1225
|
+
shellExec(`test -x /usr/local/bin/helm && sudo ln -sf /usr/local/bin/helm /bin/helm || true`);
|
|
1226
|
+
shellExec(`sudo rm -rf get_helm.sh`);
|
|
1227
|
+
}
|
|
1135
1228
|
|
|
1136
1229
|
// Install snap
|
|
1137
1230
|
shellExec(`sudo yum install -y snapd`);
|
package/src/cli/db.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Supports MariaDB and MongoDB with import/export capabilities, Git integration, and multi-pod operations.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { mergeFile, splitFileFactory, loadConfServerJson, resolveConfSecrets } from '../server/conf.js';
|
|
9
|
+
import { mergeFile, splitFileFactory, loadConfServerJson, resolveConfSecrets, loadConfInstances } from '../server/conf.js';
|
|
10
10
|
import { loggerFactory } from '../server/logger.js';
|
|
11
11
|
import { shellExec } from '../server/process.js';
|
|
12
12
|
import fs from 'fs-extra';
|
|
@@ -1119,7 +1119,9 @@ class UnderpostDB {
|
|
|
1119
1119
|
// Process additional instances
|
|
1120
1120
|
const confInstancesPath = `./engine-private/conf/${deployId}/conf.instances.json`;
|
|
1121
1121
|
if (fs.existsSync(confInstancesPath)) {
|
|
1122
|
-
|
|
1122
|
+
// Expands multiInstance variants so each deployed instance is
|
|
1123
|
+
// registered (handles both the bare-array and { instances } shapes).
|
|
1124
|
+
const confInstances = loadConfInstances(deployId);
|
|
1123
1125
|
for (const instance of confInstances) {
|
|
1124
1126
|
const { id, host, path, fromPort, metadata } = instance;
|
|
1125
1127
|
const { runtime } = metadata;
|
package/src/cli/deploy.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
cronDeployIdResolve,
|
|
13
13
|
deployRangePortFactory,
|
|
14
14
|
getDataDeploy,
|
|
15
|
+
loadConfInstances,
|
|
15
16
|
loadConfServerJson,
|
|
16
17
|
loadReplicas,
|
|
17
18
|
pathPortAssignmentFactory,
|
|
@@ -22,8 +23,25 @@ import { INTERNAL_READY_PATH, INTERNAL_HEALTH_PATH } from '../server/runtime-sta
|
|
|
22
23
|
import fs from 'fs-extra';
|
|
23
24
|
import dotenv from 'dotenv';
|
|
24
25
|
import os from 'node:os';
|
|
26
|
+
import crypto from 'node:crypto';
|
|
25
27
|
import Underpost from '../index.js';
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Clamps an identifier to the Kubernetes DNS-1123 label limit (63 chars),
|
|
31
|
+
* used for pod-local `volumes[].name` / `volumeMounts[].name`. Names within the
|
|
32
|
+
* limit are returned verbatim so existing short names are stable; longer ones
|
|
33
|
+
* are truncated and suffixed with an 8-char content hash to stay unique and
|
|
34
|
+
* deterministic (e.g. the per-variant instance volume names, which append the
|
|
35
|
+
* full `<deployId>-<env>-<traffic>` and can exceed 63).
|
|
36
|
+
* @param {string} name - Candidate name.
|
|
37
|
+
* @returns {string} A name no longer than 63 characters.
|
|
38
|
+
*/
|
|
39
|
+
const k8sVolumeName = (name) => {
|
|
40
|
+
if (typeof name !== 'string' || name.length <= 63) return name;
|
|
41
|
+
const hash = crypto.createHash('sha1').update(name).digest('hex').slice(0, 8);
|
|
42
|
+
return `${name.slice(0, 54)}-${hash}`;
|
|
43
|
+
};
|
|
44
|
+
|
|
27
45
|
const logger = loggerFactory(import.meta);
|
|
28
46
|
|
|
29
47
|
/**
|
|
@@ -562,7 +580,7 @@ ${Underpost.deploy
|
|
|
562
580
|
const yamlPath = `./engine-private/conf/${deployId}/build/${env}/secret.yaml`;
|
|
563
581
|
fs.writeFileSync(yamlPath, secretYaml, 'utf8');
|
|
564
582
|
} else {
|
|
565
|
-
const deploymentsFiles = ['Dockerfile', 'proxy.yaml', 'deployment.yaml', 'pv-pvc.yaml'];
|
|
583
|
+
const deploymentsFiles = ['Dockerfile', 'proxy.yaml', 'deployment.yaml', 'pv-pvc.yaml', 'grpc-service.yaml'];
|
|
566
584
|
for (const file of deploymentsFiles) {
|
|
567
585
|
if (fs.existsSync(`./engine-private/conf/${deployId}/build/${env}/${file}`)) {
|
|
568
586
|
fs.copyFileSync(
|
|
@@ -668,7 +686,7 @@ spec:
|
|
|
668
686
|
* @returns {string|null} - Current traffic status ('blue' or 'green') or null if not found.
|
|
669
687
|
* @memberof UnderpostDeploy
|
|
670
688
|
*/
|
|
671
|
-
getCurrentTraffic(deployId, options = { hostTest: '', namespace: '' }) {
|
|
689
|
+
getCurrentTraffic(deployId, options = { hostTest: '', namespace: '', env: '' }) {
|
|
672
690
|
if (!options.namespace) options.namespace = 'default';
|
|
673
691
|
// kubectl get deploy,sts,svc,configmap,secret -n default -o yaml --export > default.yaml
|
|
674
692
|
const hostTest = options?.hostTest
|
|
@@ -683,6 +701,18 @@ spec:
|
|
|
683
701
|
silentOnError: true,
|
|
684
702
|
});
|
|
685
703
|
if (!info) return null;
|
|
704
|
+
// Env-scoped resolution: read THIS deploy's colour from its own service
|
|
705
|
+
// name (`<deployId>-<env>-<colour>-service`). Essential for shared
|
|
706
|
+
// multi-instance hosts, where one HTTPProxy holds several variants' routes
|
|
707
|
+
// on possibly different colours — a whole-document `.match('blue')` would
|
|
708
|
+
// return whichever colour appears first, i.e. a sibling's, not this one's.
|
|
709
|
+
// The regex is anchored on the full `<deployId>-<env>-` prefix so
|
|
710
|
+
// `dd-cyberia-mmo-server` never matches `dd-cyberia-mmo-server-forest`.
|
|
711
|
+
if (options.env) {
|
|
712
|
+
const escaped = deployId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
713
|
+
const match = info.match(new RegExp(`${escaped}-${options.env}-(blue|green)-service`));
|
|
714
|
+
return match ? match[1] : null;
|
|
715
|
+
}
|
|
686
716
|
return info.match('blue') ? 'blue' : info.match('green') ? 'green' : null;
|
|
687
717
|
},
|
|
688
718
|
|
|
@@ -825,9 +855,9 @@ EOF`);
|
|
|
825
855
|
const deployId = _deployId.trim();
|
|
826
856
|
const instances = [];
|
|
827
857
|
if (fs.existsSync(`./engine-private/conf/${deployId}/conf.instances.json`)) {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
);
|
|
858
|
+
// Expands multiInstance variants so status lists every deployed
|
|
859
|
+
// instance (mmo-server, mmo-server-forest, …), not just the templates.
|
|
860
|
+
const confInstances = loadConfInstances(deployId);
|
|
831
861
|
for (const instance of confInstances) {
|
|
832
862
|
const _deployId = `${deployId}-${instance.id}`;
|
|
833
863
|
instances.push({
|
|
@@ -838,7 +868,7 @@ EOF`);
|
|
|
838
868
|
toPort: instance.toPort,
|
|
839
869
|
fromDebugPort: instance.fromDebugPort,
|
|
840
870
|
toDebugPort: instance.toDebugPort,
|
|
841
|
-
traffic: Underpost.deploy.getCurrentTraffic(_deployId, { namespace, hostTest: instance.host }),
|
|
871
|
+
traffic: Underpost.deploy.getCurrentTraffic(_deployId, { namespace, hostTest: instance.host, env }),
|
|
842
872
|
});
|
|
843
873
|
}
|
|
844
874
|
}
|
|
@@ -1009,10 +1039,17 @@ EOF`);
|
|
|
1009
1039
|
*/
|
|
1010
1040
|
configMap(env, namespace = 'default') {
|
|
1011
1041
|
const cronDeployId = cronDeployIdResolve() || 'dd-cron';
|
|
1042
|
+
const envFilePath = `/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${env}`;
|
|
1043
|
+
// `--from-env-file` turns every KEY=VALUE into a secret key that the Deployment injects via
|
|
1044
|
+
// `envFrom`. Strip shell/runtime-critical keys (notably PATH) first — an injected PATH
|
|
1045
|
+
// overrides the image's own and breaks coreutils/sudo resolution inside the pod.
|
|
1046
|
+
const sanitizedEnvPath = `${envFilePath}.secret`;
|
|
1047
|
+
fs.writeFileSync(sanitizedEnvPath, Underpost.secret.sanitizeSecretEnvFile(fs.readFileSync(envFilePath, 'utf8')));
|
|
1012
1048
|
shellExec(`kubectl delete secret underpost-config -n ${namespace} --ignore-not-found`);
|
|
1013
1049
|
shellExec(
|
|
1014
|
-
`kubectl create secret generic underpost-config --from-env-file
|
|
1050
|
+
`kubectl create secret generic underpost-config --from-env-file=${sanitizedEnvPath} --dry-run=client -o yaml | kubectl apply -f - -n ${namespace}`,
|
|
1015
1051
|
);
|
|
1052
|
+
fs.removeSync(sanitizedEnvPath);
|
|
1016
1053
|
},
|
|
1017
1054
|
/**
|
|
1018
1055
|
* Switches the traffic for a deployment.
|
|
@@ -1068,7 +1105,13 @@ EOF`);
|
|
|
1068
1105
|
* runners derive it from the comma-path field or `--node-name`
|
|
1069
1106
|
* (`run sync`: `path.split(',')[4]` > `--node-name` > default) and from
|
|
1070
1107
|
* `--node-name` directly (`run instance`).
|
|
1071
|
-
* 2.
|
|
1108
|
+
* 2. **`UNDERPOST_DEPLOY_NODE` env** — for kubeadm / k3s, the configured
|
|
1109
|
+
* target node name. This makes hostPath PV `nodeAffinity` deterministic
|
|
1110
|
+
* regardless of where the manifest is *built*: building inside a
|
|
1111
|
+
* container or CI runner would otherwise leak that box's `os.hostname()`
|
|
1112
|
+
* (e.g. a random container id) into `nodeSelector`, pinning the PV to a
|
|
1113
|
+
* node that does not exist in the cluster.
|
|
1114
|
+
* 3. **Cluster-type default** — when nothing above is set: `kind-worker`
|
|
1072
1115
|
* for a kind cluster (the node that hosts kind hostPath volumes),
|
|
1073
1116
|
* otherwise the control-plane / current host (`os.hostname()`) for
|
|
1074
1117
|
* kubeadm / k3s. With no explicit cluster flag, `development` is treated
|
|
@@ -1086,7 +1129,8 @@ EOF`);
|
|
|
1086
1129
|
resolveDeployNode({ node = '', kind = false, kubeadm = false, k3s = false, env = '' } = {}) {
|
|
1087
1130
|
if (node) return node;
|
|
1088
1131
|
const isKind = kind || (!kubeadm && !k3s && env !== 'production');
|
|
1089
|
-
|
|
1132
|
+
if (isKind) return 'kind-worker';
|
|
1133
|
+
return process.env.UNDERPOST_DEPLOY_NODE || os.hostname();
|
|
1090
1134
|
},
|
|
1091
1135
|
|
|
1092
1136
|
/**
|
|
@@ -1234,13 +1278,18 @@ EOF
|
|
|
1234
1278
|
volumeName = `${volumeName}-${version}`;
|
|
1235
1279
|
claimName = claimName ? `${claimName}-${version}` : null;
|
|
1236
1280
|
}
|
|
1281
|
+
// The pod-local volume name is a DNS-1123 label (max 63 chars); the PVC
|
|
1282
|
+
// `claimName` it references is a subdomain (max 253) and stays verbatim.
|
|
1283
|
+
// Per-variant instance names append <deployId>-<env>-<traffic> and can
|
|
1284
|
+
// exceed 63, so clamp only the pod-local name (mount name must match it).
|
|
1285
|
+
const podVolumeName = k8sVolumeName(volumeName);
|
|
1237
1286
|
_volumeMounts += `
|
|
1238
|
-
- name: ${
|
|
1287
|
+
- name: ${podVolumeName}
|
|
1239
1288
|
mountPath: ${volumeMountPath}
|
|
1240
1289
|
${secret ? ` readOnly: true\n` : ''}`;
|
|
1241
1290
|
|
|
1242
1291
|
_volumes += `
|
|
1243
|
-
- name: ${
|
|
1292
|
+
- name: ${podVolumeName}
|
|
1244
1293
|
${
|
|
1245
1294
|
emptyDir
|
|
1246
1295
|
? ` emptyDir: {}`
|