@underpostnet/underpost 2.99.0 → 2.99.4
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/.env.development +1 -0
- package/.env.production +1 -0
- package/.env.test +1 -0
- package/LICENSE +1 -1
- package/README.md +30 -30
- package/bin/deploy.js +49 -3
- package/cli.md +46 -31
- package/jsconfig.json +4 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/manifests/deployment/playwright/deployment.yaml +52 -0
- package/package.json +1 -1
- package/scripts/rocky-pwa.sh +2 -2
- package/scripts/ssl.sh +12 -6
- package/src/api/user/user.model.js +1 -0
- package/src/cli/baremetal.js +14 -12
- package/src/cli/deploy.js +7 -25
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +4 -4
- package/src/cli/index.js +24 -1
- package/src/cli/monitor.js +17 -0
- package/src/cli/run.js +229 -123
- package/src/cli/ssh.js +49 -0
- package/src/cli/test.js +13 -1
- package/src/client/components/core/Polyhedron.js +896 -7
- package/src/client/components/core/Translate.js +4 -0
- package/src/client/services/default/default.management.js +12 -2
- package/src/index.js +13 -1
- package/src/runtime/express/Express.js +3 -3
- package/src/server/conf.js +6 -4
- package/src/server/logger.js +11 -4
- package/src/server/process.js +27 -2
- package/src/server/proxy.js +4 -6
- package/src/server/tls.js +31 -26
- package/scripts/ssh-cluster-info.sh +0 -15
package/src/cli/image.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from 'fs-extra';
|
|
|
8
8
|
import dotenv from 'dotenv';
|
|
9
9
|
import { loggerFactory } from '../server/logger.js';
|
|
10
10
|
import Underpost from '../index.js';
|
|
11
|
-
import { getUnderpostRootPath } from '../server/conf.js';
|
|
11
|
+
import { getNpmRootPath, getUnderpostRootPath } from '../server/conf.js';
|
|
12
12
|
import { shellExec } from '../server/process.js';
|
|
13
13
|
|
|
14
14
|
dotenv.config();
|
|
@@ -32,7 +32,7 @@ class UnderpostImage {
|
|
|
32
32
|
* @param {boolean} [options.kind=false] - If true, load image into Kind cluster.
|
|
33
33
|
* @param {boolean} [options.kubeadm=false] - If true, load image into Kubeadm cluster.
|
|
34
34
|
* @param {boolean} [options.k3s=false] - If true, load image into K3s cluster.
|
|
35
|
-
* @param {string} [options.path=
|
|
35
|
+
* @param {string} [options.path=''] - Path to the Dockerfile context.
|
|
36
36
|
* @param {boolean} [options.dev=false] - If true, use development mode.
|
|
37
37
|
* @param {string} [options.version=''] - Version tag for the image.
|
|
38
38
|
* @param {string} [options.imageName=''] - Custom name for the image.
|
|
@@ -43,7 +43,7 @@ class UnderpostImage {
|
|
|
43
43
|
kind: false,
|
|
44
44
|
kubeadm: false,
|
|
45
45
|
k3s: false,
|
|
46
|
-
path:
|
|
46
|
+
path: '',
|
|
47
47
|
dev: false,
|
|
48
48
|
version: '',
|
|
49
49
|
imageName: '',
|
|
@@ -137,7 +137,7 @@ class UnderpostImage {
|
|
|
137
137
|
),
|
|
138
138
|
);
|
|
139
139
|
for (const key of Object.keys(envObj)) {
|
|
140
|
-
secretsInput += ` && export ${key}="${envObj[key]}" `;
|
|
140
|
+
secretsInput += ` && export ${key}="${envObj[key]}" `;
|
|
141
141
|
secretDockerInput += ` --secret id=${key},env=${key} \ `;
|
|
142
142
|
}
|
|
143
143
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -456,6 +456,9 @@ program
|
|
|
456
456
|
.option('--retry-count <count>', 'Sets HTTPProxy per-route retry count (e.g., 3).')
|
|
457
457
|
.option('--retry-per-try-timeout <duration>', 'Sets HTTPProxy retry per-try timeout (e.g., "150ms").')
|
|
458
458
|
.option('--disable-private-conf-update', 'Disables updates to private configuration during execution.')
|
|
459
|
+
.option('--versions <deployment-versions>', 'Specifies the deployment versions to monitor. eg. "blue,green", "green"')
|
|
460
|
+
.option('--ready-deployment', 'Run in ready deployment monitor mode.')
|
|
461
|
+
.option('--promote', 'Promotes the deployment after monitoring.')
|
|
459
462
|
.description('Manages health server monitoring for specified deployments.')
|
|
460
463
|
.action(Underpost.monitor.callback);
|
|
461
464
|
|
|
@@ -554,6 +557,20 @@ program
|
|
|
554
557
|
.option('--retry-count <count>', 'Sets HTTPProxy per-route retry count (e.g., 3).')
|
|
555
558
|
.option('--retry-per-try-timeout <duration>', 'Sets HTTPProxy retry per-try timeout (e.g., "150ms").')
|
|
556
559
|
.option('--disable-private-conf-update', 'Disables updates to private configuration during execution.')
|
|
560
|
+
.option('--logs', 'Streams logs during the runner execution.')
|
|
561
|
+
.option('--monitor-status <status>', 'Sets the status to monitor for pod/resource (default: "Running").')
|
|
562
|
+
.option(
|
|
563
|
+
'--monitor-status-kind-type <kind-type>',
|
|
564
|
+
'Sets the Kubernetes resource kind type to monitor (default: "pods").',
|
|
565
|
+
)
|
|
566
|
+
.option(
|
|
567
|
+
'--monitor-status-delta-ms <milliseconds>',
|
|
568
|
+
'Sets the polling interval in milliseconds for status monitoring (default: 1000).',
|
|
569
|
+
)
|
|
570
|
+
.option(
|
|
571
|
+
'--monitor-status-max-attempts <attempts>',
|
|
572
|
+
'Sets the maximum number of status check attempts (default: 600).',
|
|
573
|
+
)
|
|
557
574
|
.description('Runs specified scripts using various runners.')
|
|
558
575
|
.action(Underpost.run.callback);
|
|
559
576
|
|
|
@@ -593,7 +610,13 @@ program
|
|
|
593
610
|
.action(Underpost.lxd.callback);
|
|
594
611
|
|
|
595
612
|
program
|
|
596
|
-
.command('baremetal [workflow-id]
|
|
613
|
+
.command('baremetal [workflow-id]')
|
|
614
|
+
.option('--ip-address <ip-address>', 'The IP address of the control server or the local machine.')
|
|
615
|
+
.option('--hostname <hostname>', 'The hostname of the target baremetal machine.')
|
|
616
|
+
.option('--ip-file-server <ip-file-server>', 'The IP address of the file server (NFS/TFTP).')
|
|
617
|
+
.option('--ip-config <ip-config>', 'IP configuration string for the baremetal machine.')
|
|
618
|
+
.option('--netmask <netmask>', 'Netmask of network.')
|
|
619
|
+
.option('--dns-server <dns-server>', 'DNS server IP address.')
|
|
597
620
|
.option('--control-server-install', 'Installs the baremetal control server.')
|
|
598
621
|
.option('--control-server-uninstall', 'Uninstalls the baremetal control server.')
|
|
599
622
|
.option('--control-server-restart', 'Restarts the baremetal control server.')
|
package/src/cli/monitor.js
CHANGED
|
@@ -42,6 +42,9 @@ class UnderpostMonitor {
|
|
|
42
42
|
* @param {string} [options.timeoutIdle=''] - Timeout for idle connections.
|
|
43
43
|
* @param {string} [options.retryCount=''] - Number of retry attempts for health checks.
|
|
44
44
|
* @param {string} [options.retryPerTryTimeout=''] - Timeout per retry attempt.
|
|
45
|
+
* @param {boolean} [options.promote=false] - Promote the deployment after monitoring.
|
|
46
|
+
* @param {boolean} [options.readyDeployment=false] - Monitor until the deployment is ready.
|
|
47
|
+
* @param {string} [options.versions=''] - Specific version of the deployment to monitor.
|
|
45
48
|
* @param {object} [commanderOptions] - Options passed from the command line interface.
|
|
46
49
|
* @param {object} [auxRouter] - Optional router configuration for the deployment.
|
|
47
50
|
* @memberof UnderpostMonitor
|
|
@@ -61,6 +64,9 @@ class UnderpostMonitor {
|
|
|
61
64
|
timeoutIdle: '',
|
|
62
65
|
retryCount: '',
|
|
63
66
|
retryPerTryTimeout: '',
|
|
67
|
+
promote: false,
|
|
68
|
+
readyDeployment: false,
|
|
69
|
+
versions: '',
|
|
64
70
|
},
|
|
65
71
|
commanderOptions,
|
|
66
72
|
auxRouter,
|
|
@@ -79,6 +85,17 @@ class UnderpostMonitor {
|
|
|
79
85
|
return;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
if (options.readyDeployment) {
|
|
89
|
+
for (const version of options.versions.split(',')) {
|
|
90
|
+
(async () => {
|
|
91
|
+
await Underpost.deploy.monitorReadyRunner(deployId, env, version, [], options.namespace, 'underpost');
|
|
92
|
+
if (options.promote)
|
|
93
|
+
Underpost.deploy.switchTraffic(deployId, env, version, options.replicas, options.namespace, options);
|
|
94
|
+
})();
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
const router = auxRouter ?? (await Underpost.deploy.routerFactory(deployId, env));
|
|
83
100
|
|
|
84
101
|
const confServer = loadReplicas(
|
package/src/cli/run.js
CHANGED
|
@@ -80,6 +80,11 @@ const logger = loggerFactory(import.meta);
|
|
|
80
80
|
* @property {string} user - The user to run as.
|
|
81
81
|
* @property {string} pid - The process ID.
|
|
82
82
|
* @property {boolean} disablePrivateConfUpdate - Whether to disable private configuration updates.
|
|
83
|
+
* @property {string} monitorStatus - The monitor status option.
|
|
84
|
+
* @property {string} monitorStatusKindType - The monitor status kind type option.
|
|
85
|
+
* @property {string} monitorStatusDeltaMs - The monitor status delta in milliseconds.
|
|
86
|
+
* @property {string} monitorStatusMaxAttempts - The maximum number of attempts for monitor status.
|
|
87
|
+
* @property {boolean} logs - Whether to enable logs.
|
|
83
88
|
* @memberof UnderpostRun
|
|
84
89
|
*/
|
|
85
90
|
const DEFAULT_OPTION = {
|
|
@@ -134,6 +139,11 @@ const DEFAULT_OPTION = {
|
|
|
134
139
|
user: '',
|
|
135
140
|
pid: '',
|
|
136
141
|
disablePrivateConfUpdate: false,
|
|
142
|
+
monitorStatus: '',
|
|
143
|
+
monitorStatusKindType: '',
|
|
144
|
+
monitorStatusDeltaMs: '',
|
|
145
|
+
monitorStatusMaxAttempts: '',
|
|
146
|
+
logs: false,
|
|
137
147
|
};
|
|
138
148
|
|
|
139
149
|
/**
|
|
@@ -255,17 +265,25 @@ class UnderpostRun {
|
|
|
255
265
|
},
|
|
256
266
|
|
|
257
267
|
/**
|
|
258
|
-
* @method ssh-
|
|
259
|
-
* @description
|
|
268
|
+
* @method ssh-deploy-info
|
|
269
|
+
* @description Retrieves deployment status and pod information from a remote server via SSH.
|
|
260
270
|
* @param {string} path - The input value, identifier, or path for the operation.
|
|
261
271
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
262
272
|
* @memberof UnderpostRun
|
|
263
273
|
*/
|
|
264
|
-
'ssh-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
274
|
+
'ssh-deploy-info': async (path = '', options = DEFAULT_OPTION) => {
|
|
275
|
+
const env = options.dev ? 'development' : 'production';
|
|
276
|
+
await Underpost.ssh.sshRemoteRunner(
|
|
277
|
+
`node bin deploy ${path ? path : 'dd'} ${env} --status && kubectl get pods -A`,
|
|
278
|
+
{
|
|
279
|
+
deployId: options.deployId,
|
|
280
|
+
user: options.user,
|
|
281
|
+
dev: options.dev,
|
|
282
|
+
remote: true,
|
|
283
|
+
useSudo: true,
|
|
284
|
+
cd: '/home/dd/engine',
|
|
285
|
+
},
|
|
286
|
+
);
|
|
269
287
|
},
|
|
270
288
|
|
|
271
289
|
/**
|
|
@@ -494,19 +512,19 @@ class UnderpostRun {
|
|
|
494
512
|
if (targetTraffic) versions = versions ? versions : targetTraffic;
|
|
495
513
|
|
|
496
514
|
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
515
|
+
const cmdString = options.cmd
|
|
516
|
+
? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
|
|
517
|
+
: '';
|
|
497
518
|
|
|
498
519
|
shellExec(
|
|
499
520
|
`${baseCommand} deploy --kubeadm --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
500
521
|
image ? ` --image ${image}` : ''
|
|
501
522
|
}${versions ? ` --versions ${versions}` : ''}${
|
|
502
523
|
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
503
|
-
}${timeoutFlags} dd ${env}`,
|
|
524
|
+
}${timeoutFlags}${cmdString} dd ${env}`,
|
|
504
525
|
);
|
|
505
526
|
|
|
506
527
|
if (isDeployRunnerContext(path, options)) {
|
|
507
|
-
const cmdString = options.cmd
|
|
508
|
-
? ` --cmd ${options.cmd.find((c) => c.match('"')) ? `"${options.cmd}"` : `'${options.cmd}'`}`
|
|
509
|
-
: '';
|
|
510
528
|
shellExec(
|
|
511
529
|
`${baseCommand} deploy --kubeadm${cmdString} --replicas ${replicas} --disable-update-proxy ${deployId} ${env} --versions ${versions}${
|
|
512
530
|
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
@@ -552,29 +570,121 @@ class UnderpostRun {
|
|
|
552
570
|
* @memberof UnderpostRun
|
|
553
571
|
*/
|
|
554
572
|
'ssh-deploy-stop': async (path, options = DEFAULT_OPTION) => {
|
|
555
|
-
const env = options.dev ? 'development' : 'production';
|
|
556
573
|
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
557
574
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
558
|
-
await Underpost.ssh.setDefautlSshCredentials(options);
|
|
559
|
-
shellExec(`#!/usr/bin/env bash
|
|
560
|
-
set -euo pipefail
|
|
561
575
|
|
|
562
|
-
|
|
563
|
-
REMOTE_HOST=$(node bin config get --plain DEFAULT_SSH_HOST)
|
|
564
|
-
REMOTE_PORT=$(node bin config get --plain DEFAULT_SSH_PORT)
|
|
565
|
-
SSH_KEY=$(node bin config get --plain DEFAULT_SSH_KEY_PATH)
|
|
566
|
-
|
|
567
|
-
chmod 600 "$SSH_KEY"
|
|
568
|
-
|
|
569
|
-
ssh -i "$SSH_KEY" -o BatchMode=yes "$REMOTE_USER@$REMOTE_HOST" -p $REMOTE_PORT sh <<EOF
|
|
570
|
-
cd /home/dd/engine
|
|
571
|
-
sudo -n -- /bin/bash -lc "${[
|
|
576
|
+
const remoteCommand = [
|
|
572
577
|
`${baseCommand} run${baseClusterCommand} stop${path ? ` ${path}` : ''}`,
|
|
573
578
|
` --deploy-id ${options.deployId}${options.instanceId ? ` --instance-id ${options.instanceId}` : ''}`,
|
|
574
579
|
` --namespace ${options.namespace}${options.hosts ? ` --hosts ${options.hosts}` : ''}`,
|
|
575
|
-
].join('')
|
|
576
|
-
|
|
577
|
-
|
|
580
|
+
].join('');
|
|
581
|
+
|
|
582
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
583
|
+
deployId: options.deployId,
|
|
584
|
+
user: options.user,
|
|
585
|
+
dev: options.dev,
|
|
586
|
+
remote: true,
|
|
587
|
+
useSudo: true,
|
|
588
|
+
cd: '/home/dd/engine',
|
|
589
|
+
});
|
|
590
|
+
},
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* @method ssh-deploy-db-rollback
|
|
594
|
+
* @description Performs a database rollback on remote deployment via SSH.
|
|
595
|
+
* @param {string} path - Comma-separated deployId and optional number of commits to reset (format: "deployId,nCommits")
|
|
596
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
597
|
+
* @param {string} options.deployId - The deployment identifier
|
|
598
|
+
* @param {string} options.user - The SSH user for credential lookup
|
|
599
|
+
* @param {boolean} options.dev - Development mode flag
|
|
600
|
+
* @memberof UnderpostRun
|
|
601
|
+
*/
|
|
602
|
+
'ssh-deploy-db-rollback': async (path = '', options = DEFAULT_OPTION) => {
|
|
603
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
604
|
+
let [deployId, nCommitsReset] = path.split(',');
|
|
605
|
+
if (!nCommitsReset) nCommitsReset = 1;
|
|
606
|
+
|
|
607
|
+
const remoteCommand = `${baseCommand} db ${deployId} --git --kubeadm --primary-pod --force-clone --macro-rollback-export ${nCommitsReset}${options.namespace ? ` --ns ${options.namespace}` : ''}`;
|
|
608
|
+
|
|
609
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
610
|
+
deployId: options.deployId,
|
|
611
|
+
user: options.user,
|
|
612
|
+
dev: options.dev,
|
|
613
|
+
remote: true,
|
|
614
|
+
useSudo: true,
|
|
615
|
+
cd: '/home/dd/engine',
|
|
616
|
+
});
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* @method ssh-deploy-db
|
|
621
|
+
* @description Imports/restores a database on remote deployment via SSH.
|
|
622
|
+
* @param {string} path - The deployment ID for database import
|
|
623
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
624
|
+
* @param {string} options.deployId - The deployment identifier
|
|
625
|
+
* @param {string} options.user - The SSH user for credential lookup
|
|
626
|
+
* @param {boolean} options.dev - Development mode flag
|
|
627
|
+
* @memberof UnderpostRun
|
|
628
|
+
*/
|
|
629
|
+
'ssh-deploy-db': async (path, options = DEFAULT_OPTION) => {
|
|
630
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
631
|
+
|
|
632
|
+
const remoteCommand = `${baseCommand} db ${path} --import --drop --preserveUUID --git --kubeadm --primary-pod --force-clone${options.namespace ? ` --ns ${options.namespace}` : ''}`;
|
|
633
|
+
|
|
634
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
635
|
+
deployId: options.deployId,
|
|
636
|
+
user: options.user,
|
|
637
|
+
dev: options.dev,
|
|
638
|
+
remote: true,
|
|
639
|
+
useSudo: true,
|
|
640
|
+
cd: '/home/dd/engine',
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* @method ssh-deploy-db-status
|
|
646
|
+
* @description Retrieves database status/stats for a deployment (or all deployments from dd.router) via SSH.
|
|
647
|
+
* @param {string} path - Comma-separated deployId(s) or 'dd' to use the dd.router list.
|
|
648
|
+
* @param {Object} options - Runner options (uses options.deployId for SSH host lookup).
|
|
649
|
+
* @param {string} options.deployId - Deployment identifier used for SSH config lookup.
|
|
650
|
+
* @param {string} options.user - SSH user for credential lookup.
|
|
651
|
+
* @param {boolean} options.dev - Development mode flag.
|
|
652
|
+
* @param {string} [options.namespace] - Kubernetes namespace to pass to the db check.
|
|
653
|
+
* @memberof UnderpostRun
|
|
654
|
+
*/
|
|
655
|
+
'ssh-deploy-db-status': async (path = '', options = DEFAULT_OPTION) => {
|
|
656
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
657
|
+
|
|
658
|
+
let deployList = [];
|
|
659
|
+
if (!path || path === 'dd') {
|
|
660
|
+
if (!fs.existsSync('./engine-private/deploy/dd.router')) {
|
|
661
|
+
logger.warn('dd.router not found; nothing to run');
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
deployList = fs
|
|
665
|
+
.readFileSync('./engine-private/deploy/dd.router', 'utf8')
|
|
666
|
+
.split(',')
|
|
667
|
+
.map((d) => d.trim())
|
|
668
|
+
.filter(Boolean);
|
|
669
|
+
} else {
|
|
670
|
+
deployList = path
|
|
671
|
+
.split(',')
|
|
672
|
+
.map((d) => d.trim())
|
|
673
|
+
.filter(Boolean);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
for (const deployId of deployList) {
|
|
677
|
+
const remoteCommand = `${baseCommand} db ${deployId} --stats --kubeadm --primary-pod${options.namespace ? ` --ns ${options.namespace}` : ''}`;
|
|
678
|
+
|
|
679
|
+
await Underpost.ssh.sshRemoteRunner(remoteCommand, {
|
|
680
|
+
deployId: options.deployId,
|
|
681
|
+
user: options.user,
|
|
682
|
+
dev: options.dev,
|
|
683
|
+
remote: true,
|
|
684
|
+
useSudo: true,
|
|
685
|
+
cd: '/home/dd/engine',
|
|
686
|
+
});
|
|
687
|
+
}
|
|
578
688
|
},
|
|
579
689
|
|
|
580
690
|
/**
|
|
@@ -897,86 +1007,6 @@ EOF
|
|
|
897
1007
|
shellExec(`${underpostRoot}/scripts/ip-info.sh ${path}`);
|
|
898
1008
|
},
|
|
899
1009
|
|
|
900
|
-
/**
|
|
901
|
-
* @method monitor
|
|
902
|
-
* @description Monitors a specific pod (identified by `path`) for the existence of a file (`/await`), and performs conditional actions (like file copying and opening Firefox) when the file is removed.
|
|
903
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the name of the pod to monitor).
|
|
904
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
905
|
-
* @memberof UnderpostRun
|
|
906
|
-
*/
|
|
907
|
-
monitor: (path, options = DEFAULT_OPTION) => {
|
|
908
|
-
const pid = getTerminalPid();
|
|
909
|
-
logger.info('monitor pid', pid);
|
|
910
|
-
const checkPath = '/await';
|
|
911
|
-
const _monitor = async () => {
|
|
912
|
-
const result = Underpost.deploy.existsContainerFile({ podName: path, path: checkPath });
|
|
913
|
-
logger.info('monitor', result);
|
|
914
|
-
if (result === true) {
|
|
915
|
-
switch (path) {
|
|
916
|
-
case 'tf-vae-test':
|
|
917
|
-
{
|
|
918
|
-
const nameSpace = options.namespace;
|
|
919
|
-
const podName = path;
|
|
920
|
-
const basePath = '/home/dd';
|
|
921
|
-
const scriptPath = '/site/en/tutorials/generative/cvae.py';
|
|
922
|
-
// shellExec(
|
|
923
|
-
// `sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${scriptPath} ${basePath}/lab/src/${scriptPath
|
|
924
|
-
// .split('/')
|
|
925
|
-
// .pop()}`,
|
|
926
|
-
// );
|
|
927
|
-
// const file = fs.readFileSync(`${basePath}/lab/src/${scriptPath.split('/').pop()}`, 'utf8');
|
|
928
|
-
// fs.writeFileSync(
|
|
929
|
-
// `${basePath}/lab/src/${scriptPath.split('/').pop()}`,
|
|
930
|
-
// file.replace(
|
|
931
|
-
// `import time`,
|
|
932
|
-
// `import time
|
|
933
|
-
// print('=== SCRIPT UPDATE TEST ===')`,
|
|
934
|
-
// ),
|
|
935
|
-
// 'utf8',
|
|
936
|
-
// );
|
|
937
|
-
shellExec(
|
|
938
|
-
`sudo kubectl cp ${basePath}/lab/src/${scriptPath
|
|
939
|
-
.split('/')
|
|
940
|
-
.pop()} ${nameSpace}/${podName}:${basePath}/docs${scriptPath}`,
|
|
941
|
-
);
|
|
942
|
-
// shellExec(`sudo kubectl exec -i ${podName} -- sh -c "ipython ${basePath}/docs${scriptPath}"`);
|
|
943
|
-
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "rm -rf ${checkPath}"`);
|
|
944
|
-
|
|
945
|
-
{
|
|
946
|
-
const checkPath = `/latent_space_plot.png`;
|
|
947
|
-
const outsPaths = [];
|
|
948
|
-
logger.info('monitor', checkPath);
|
|
949
|
-
while (!Underpost.deploy.existsContainerFile({ podName, path: `/home/dd/docs${checkPath}` }))
|
|
950
|
-
await timer(1000);
|
|
951
|
-
|
|
952
|
-
{
|
|
953
|
-
const toPath = `${basePath}/lab${checkPath}`;
|
|
954
|
-
outsPaths.push(toPath);
|
|
955
|
-
shellExec(`sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${checkPath} ${toPath}`);
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
for (let i of range(1, 10)) {
|
|
959
|
-
i = `/image_at_epoch_${setPad(i, '0', 4)}.png`;
|
|
960
|
-
const toPath = `${basePath}/lab/${i}`;
|
|
961
|
-
outsPaths.push(toPath);
|
|
962
|
-
shellExec(`sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${i} ${toPath}`);
|
|
963
|
-
}
|
|
964
|
-
openTerminal(`firefox ${outsPaths.join(' ')}`, { single: true });
|
|
965
|
-
}
|
|
966
|
-
shellExec(`sudo kill -9 ${pid}`);
|
|
967
|
-
}
|
|
968
|
-
break;
|
|
969
|
-
|
|
970
|
-
default:
|
|
971
|
-
break;
|
|
972
|
-
}
|
|
973
|
-
return;
|
|
974
|
-
}
|
|
975
|
-
await timer(1000);
|
|
976
|
-
_monitor();
|
|
977
|
-
};
|
|
978
|
-
_monitor();
|
|
979
|
-
},
|
|
980
1010
|
/**
|
|
981
1011
|
* @method db-client
|
|
982
1012
|
* @description Deploys and exposes the Adminer database client application (using `adminer:4.7.6-standalone` image) on the cluster.
|
|
@@ -1034,15 +1064,20 @@ EOF
|
|
|
1034
1064
|
`git config user.email '${email}' && ` +
|
|
1035
1065
|
`git config credential.interactive always &&` +
|
|
1036
1066
|
`git config pull.rebase false`,
|
|
1067
|
+
{
|
|
1068
|
+
disableLog: true,
|
|
1069
|
+
silent: true,
|
|
1070
|
+
},
|
|
1037
1071
|
);
|
|
1038
1072
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1073
|
+
if (options.logs)
|
|
1074
|
+
console.log(
|
|
1075
|
+
shellExec(`git config list`, { silent: true, stdout: true })
|
|
1076
|
+
.replaceAll('user.email', 'user.email'.yellow)
|
|
1077
|
+
.replaceAll(username, username.green)
|
|
1078
|
+
.replaceAll('user.name', 'user.name'.yellow)
|
|
1079
|
+
.replaceAll(email, email.green),
|
|
1080
|
+
);
|
|
1046
1081
|
},
|
|
1047
1082
|
|
|
1048
1083
|
/**
|
|
@@ -1134,7 +1169,9 @@ EOF
|
|
|
1134
1169
|
}
|
|
1135
1170
|
await timer(5000);
|
|
1136
1171
|
for (const deployId of deployList) {
|
|
1137
|
-
shellExec(
|
|
1172
|
+
shellExec(
|
|
1173
|
+
`${baseCommand} db ${deployId} --import --git --drop --preserveUUID --primary-pod${options.namespace ? ` --ns ${options.namespace}` : ''}`,
|
|
1174
|
+
);
|
|
1138
1175
|
}
|
|
1139
1176
|
await timer(5000);
|
|
1140
1177
|
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType} --pull-image --valkey`);
|
|
@@ -1148,7 +1185,7 @@ EOF
|
|
|
1148
1185
|
shellExec(
|
|
1149
1186
|
`${baseCommand} deploy ${deployId} ${env} --${clusterType}${env === 'production' ? ' --cert' : ''}${
|
|
1150
1187
|
env === 'development' ? ' --etc-hosts' : ''
|
|
1151
|
-
}`,
|
|
1188
|
+
}${options.namespace ? ` --namespace ${options.namespace}` : ''}`,
|
|
1152
1189
|
);
|
|
1153
1190
|
}
|
|
1154
1191
|
},
|
|
@@ -1165,7 +1202,7 @@ EOF
|
|
|
1165
1202
|
if (!validVersion) throw new Error('Version mismatch');
|
|
1166
1203
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
1167
1204
|
const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
|
|
1168
|
-
const env = 'production';
|
|
1205
|
+
const env = options.dev ? 'development' : 'production';
|
|
1169
1206
|
const ignorePods = Underpost.deploy
|
|
1170
1207
|
.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace)
|
|
1171
1208
|
.map((p) => p.NAME);
|
|
@@ -1268,7 +1305,11 @@ EOF
|
|
|
1268
1305
|
});
|
|
1269
1306
|
}
|
|
1270
1307
|
await awaitDeployMonitor(true);
|
|
1271
|
-
shellExec(
|
|
1308
|
+
shellExec(
|
|
1309
|
+
`./node_modules/.bin/env-cmd -f .env.development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${
|
|
1310
|
+
options.tls ? ' tls' : ''
|
|
1311
|
+
}`,
|
|
1312
|
+
);
|
|
1272
1313
|
},
|
|
1273
1314
|
|
|
1274
1315
|
/**
|
|
@@ -1538,15 +1579,74 @@ EOF
|
|
|
1538
1579
|
* @memberof UnderpostRun
|
|
1539
1580
|
*/
|
|
1540
1581
|
'tf-vae-test': async (path, options = DEFAULT_OPTION) => {
|
|
1541
|
-
const { underpostRoot } = options;
|
|
1542
1582
|
const podName = 'tf-vae-test';
|
|
1543
1583
|
await Underpost.run.CALL('deploy-job', '', {
|
|
1584
|
+
logs: options.logs,
|
|
1544
1585
|
podName,
|
|
1545
1586
|
// volumeMountPath: '/custom_images',
|
|
1546
1587
|
// volumeHostPath: '/home/dd/engine/src/client/public/cyberia/assets/skin',
|
|
1547
1588
|
on: {
|
|
1548
1589
|
init: async () => {
|
|
1549
|
-
|
|
1590
|
+
// const pid = getTerminalPid();
|
|
1591
|
+
// shellExec(`sudo kill -9 ${pid}`);
|
|
1592
|
+
(async () => {
|
|
1593
|
+
const nameSpace = options.namespace;
|
|
1594
|
+
const basePath = '/home/dd';
|
|
1595
|
+
const scriptPath = '/site/en/tutorials/generative/cvae.py';
|
|
1596
|
+
|
|
1597
|
+
const { close } = await (async () => {
|
|
1598
|
+
const checkAwaitPath = '/await';
|
|
1599
|
+
while (!Underpost.deploy.existsContainerFile({ podName, path: checkAwaitPath })) {
|
|
1600
|
+
logger.info('monitor', checkAwaitPath);
|
|
1601
|
+
await timer(1000);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
return {
|
|
1605
|
+
close: () => shellExec(`sudo kubectl exec -i ${podName} -- sh -c "rm -rf ${checkAwaitPath}"`),
|
|
1606
|
+
};
|
|
1607
|
+
})();
|
|
1608
|
+
|
|
1609
|
+
const localScriptPath = `${basePath}/lab/src/${scriptPath.split('/').pop()}`;
|
|
1610
|
+
if (!fs.existsSync(localScriptPath)) {
|
|
1611
|
+
throw new Error(`Local override script not found: ${localScriptPath}`);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
shellExec(`sudo kubectl cp ${localScriptPath} ${nameSpace}/${podName}:${basePath}/docs${scriptPath}`);
|
|
1615
|
+
|
|
1616
|
+
close();
|
|
1617
|
+
|
|
1618
|
+
{
|
|
1619
|
+
const checkPath = `/latent_space_plot.png`;
|
|
1620
|
+
const outsPaths = [];
|
|
1621
|
+
const labDir = `${basePath}/lab`;
|
|
1622
|
+
|
|
1623
|
+
logger.info('monitor', checkPath);
|
|
1624
|
+
{
|
|
1625
|
+
const checkAwaitPath = `/home/dd/docs${checkPath}`;
|
|
1626
|
+
while (!Underpost.deploy.existsContainerFile({ podName, path: checkAwaitPath })) {
|
|
1627
|
+
logger.info('waiting for', checkAwaitPath);
|
|
1628
|
+
await timer(1000);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
{
|
|
1633
|
+
const toPath = `${labDir}${checkPath}`;
|
|
1634
|
+
outsPaths.push(toPath);
|
|
1635
|
+
shellExec(`sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${checkPath} ${toPath}`);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
for (let i of range(1, 10)) {
|
|
1639
|
+
const fileName = `image_at_epoch_${setPad(i, '0', 4)}.png`;
|
|
1640
|
+
const fromPath = `/${fileName}`;
|
|
1641
|
+
const toPath = `${labDir}/${fileName}`;
|
|
1642
|
+
outsPaths.push(toPath);
|
|
1643
|
+
shellExec(`sudo kubectl cp ${nameSpace}/${podName}:${basePath}/docs${fromPath} ${toPath}`);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
openTerminal(`firefox ${outsPaths.join(' ')}`, { single: true });
|
|
1647
|
+
process.exit(0);
|
|
1648
|
+
}
|
|
1649
|
+
})();
|
|
1550
1650
|
},
|
|
1551
1651
|
},
|
|
1552
1652
|
args: [
|
|
@@ -1771,10 +1871,16 @@ EOF`;
|
|
|
1771
1871
|
shellExec(`kubectl delete pod ${podName} -n ${namespace} --ignore-not-found`);
|
|
1772
1872
|
console.log(cmd);
|
|
1773
1873
|
shellExec(cmd, { disableLog: true });
|
|
1774
|
-
const successInstance = await Underpost.test.statusMonitor(
|
|
1874
|
+
const successInstance = await Underpost.test.statusMonitor(
|
|
1875
|
+
podName,
|
|
1876
|
+
options.monitorStatus || 'Running',
|
|
1877
|
+
options.monitorStatusKindType || 'pods',
|
|
1878
|
+
options.monitorStatusDeltaMs || 1000,
|
|
1879
|
+
options.monitorStatusMaxAttempts || 600,
|
|
1880
|
+
);
|
|
1775
1881
|
if (successInstance) {
|
|
1776
1882
|
options.on?.init ? await options.on.init() : null;
|
|
1777
|
-
shellExec(`kubectl logs -f ${podName} -n ${namespace}
|
|
1883
|
+
if (options.logs) shellExec(`kubectl logs -f ${podName} -n ${namespace}`, { async: true });
|
|
1778
1884
|
}
|
|
1779
1885
|
},
|
|
1780
1886
|
};
|
package/src/cli/ssh.js
CHANGED
|
@@ -496,6 +496,55 @@ EOF`);
|
|
|
496
496
|
if (options.status) shellExec('service sshd status');
|
|
497
497
|
},
|
|
498
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Generic SSH remote command runner that centralizes SSH execution logic.
|
|
501
|
+
* Executes arbitrary shell commands on a remote server via SSH with proper credential handling.
|
|
502
|
+
* @async
|
|
503
|
+
* @function sshRemoteRunner
|
|
504
|
+
* @param {string} remoteCommand - The command to execute on the remote server
|
|
505
|
+
* @param {Object} options - Configuration options for SSH execution
|
|
506
|
+
* @param {string} [options.deployId] - Deployment ID for credential lookup
|
|
507
|
+
* @param {string} [options.user] - SSH user for credential lookup
|
|
508
|
+
* @param {boolean} [options.dev=false] - Development mode flag
|
|
509
|
+
* @param {string} [options.cd='/home/dd/engine'] - Working directory on remote server
|
|
510
|
+
* @param {boolean} [options.useSudo=true] - Whether to use sudo for command execution
|
|
511
|
+
* @param {boolean} [options.remote=true] - Whether to execute as remote command (if false, runs locally)
|
|
512
|
+
* @returns {Promise<string>} Output from the shell execution
|
|
513
|
+
* @memberof UnderpostSSH
|
|
514
|
+
*/
|
|
515
|
+
sshRemoteRunner: async (remoteCommand, options = {}) => {
|
|
516
|
+
const { deployId = '', user = '', dev = false, cd = '/home/dd/engine', useSudo = true, remote = true } = options;
|
|
517
|
+
|
|
518
|
+
// If not executing remotely, just run locally
|
|
519
|
+
if (!remote) {
|
|
520
|
+
return shellExec(remoteCommand);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Set up SSH credentials from config
|
|
524
|
+
if (deployId && user) {
|
|
525
|
+
await Underpost.ssh.setDefautlSshCredentials({ deployId, user });
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Build the complete SSH command
|
|
529
|
+
const sshScript = `#!/usr/bin/env bash
|
|
530
|
+
set -euo pipefail
|
|
531
|
+
|
|
532
|
+
REMOTE_USER=$(node bin config get --plain DEFAULT_SSH_USER)
|
|
533
|
+
REMOTE_HOST=$(node bin config get --plain DEFAULT_SSH_HOST)
|
|
534
|
+
REMOTE_PORT=$(node bin config get --plain DEFAULT_SSH_PORT)
|
|
535
|
+
SSH_KEY=$(node bin config get --plain DEFAULT_SSH_KEY_PATH)
|
|
536
|
+
|
|
537
|
+
chmod 600 "$SSH_KEY"
|
|
538
|
+
|
|
539
|
+
ssh -i "$SSH_KEY" -o BatchMode=yes "$REMOTE_USER@$REMOTE_HOST" -p $REMOTE_PORT sh <<EOF
|
|
540
|
+
${cd ? `cd ${cd}` : ''}
|
|
541
|
+
${useSudo ? `sudo -n -- /bin/bash -lc "${remoteCommand}"` : remoteCommand}
|
|
542
|
+
EOF
|
|
543
|
+
`;
|
|
544
|
+
|
|
545
|
+
return shellExec(sshScript, { stdout: true });
|
|
546
|
+
},
|
|
547
|
+
|
|
499
548
|
/**
|
|
500
549
|
* Loads saved SSH credentials from config and sets them in the UnderpostRootEnv API.
|
|
501
550
|
* @async
|
package/src/cli/test.js
CHANGED
|
@@ -59,7 +59,19 @@ class UnderpostTest {
|
|
|
59
59
|
* @param {number} options.maxAttempts - The maximum number of attempts.
|
|
60
60
|
* @memberof UnderpostTest
|
|
61
61
|
*/
|
|
62
|
-
async callback(
|
|
62
|
+
async callback(
|
|
63
|
+
deployList = '',
|
|
64
|
+
options = {
|
|
65
|
+
itc: false,
|
|
66
|
+
sh: false,
|
|
67
|
+
logs: false,
|
|
68
|
+
podName: '',
|
|
69
|
+
podStatus: '',
|
|
70
|
+
kindType: '',
|
|
71
|
+
deltaMs: 1000,
|
|
72
|
+
maxAttempts: 60 * 5,
|
|
73
|
+
},
|
|
74
|
+
) {
|
|
63
75
|
if (
|
|
64
76
|
options.podName &&
|
|
65
77
|
typeof options.podName === 'string' &&
|