underpost 2.92.0 → 2.95.3
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/pwa-microservices-template-page.cd.yml +5 -4
- package/README.md +4 -5
- package/bin/build.js +6 -1
- package/bin/deploy.js +2 -69
- package/cli.md +100 -92
- package/manifests/deployment/dd-default-development/deployment.yaml +4 -4
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/scripts/disk-clean.sh +216 -0
- package/scripts/ssh-cluster-info.sh +4 -3
- package/src/cli/cluster.js +1 -1
- package/src/cli/db.js +80 -89
- package/src/cli/deploy.js +77 -13
- package/src/cli/image.js +198 -133
- package/src/cli/index.js +60 -81
- package/src/cli/lxd.js +73 -74
- package/src/cli/monitor.js +20 -9
- package/src/cli/repository.js +86 -3
- package/src/cli/run.js +167 -63
- package/src/cli/ssh.js +351 -134
- package/src/index.js +1 -1
- package/src/monitor.js +11 -1
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1 -1
- package/src/server/dns.js +88 -1
- package/src/server/process.js +6 -1
- package/scripts/snap-clean.sh +0 -26
- package/src/client/public/default/plantuml/client-conf.svg +0 -1
- package/src/client/public/default/plantuml/client-schema.svg +0 -1
- package/src/client/public/default/plantuml/cron-conf.svg +0 -1
- package/src/client/public/default/plantuml/cron-schema.svg +0 -1
- package/src/client/public/default/plantuml/server-conf.svg +0 -1
- package/src/client/public/default/plantuml/server-schema.svg +0 -1
- package/src/client/public/default/plantuml/ssr-conf.svg +0 -1
- package/src/client/public/default/plantuml/ssr-schema.svg +0 -1
package/src/cli/db.js
CHANGED
|
@@ -25,13 +25,6 @@ const logger = loggerFactory(import.meta);
|
|
|
25
25
|
*/
|
|
26
26
|
const MAX_BACKUP_RETENTION = 5;
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Timeout for kubectl operations in milliseconds
|
|
30
|
-
* @constant {number} KUBECTL_TIMEOUT
|
|
31
|
-
* @memberof UnderpostDB
|
|
32
|
-
*/
|
|
33
|
-
const KUBECTL_TIMEOUT = 300000; // 5 minutes
|
|
34
|
-
|
|
35
28
|
/**
|
|
36
29
|
* @typedef {Object} DatabaseOptions
|
|
37
30
|
* @memberof UnderpostDB
|
|
@@ -49,9 +42,9 @@ const KUBECTL_TIMEOUT = 300000; // 5 minutes
|
|
|
49
42
|
* @property {string} [paths=''] - Comma-separated list of paths to include
|
|
50
43
|
* @property {string} [labelSelector=''] - Kubernetes label selector for pods
|
|
51
44
|
* @property {boolean} [allPods=false] - Flag to target all matching pods
|
|
52
|
-
* @property {boolean} [dryRun=false] - Flag to simulate operations without executing
|
|
53
45
|
* @property {boolean} [primaryPod=false] - Flag to automatically detect and use MongoDB primary pod
|
|
54
46
|
* @property {boolean} [stats=false] - Flag to display collection/table statistics
|
|
47
|
+
* @property {boolean} [forceClone=false] - Flag to force remove and re-clone cron backup repository
|
|
55
48
|
*/
|
|
56
49
|
|
|
57
50
|
/**
|
|
@@ -171,17 +164,11 @@ class UnderpostDB {
|
|
|
171
164
|
* @private
|
|
172
165
|
* @param {string} command - kubectl command to execute
|
|
173
166
|
* @param {Object} options - Execution options
|
|
174
|
-
* @param {boolean} [options.dryRun=false] - Dry run mode
|
|
175
167
|
* @param {string} [options.context=''] - Command context for logging
|
|
176
168
|
* @returns {string|null} Command output or null on error
|
|
177
169
|
*/
|
|
178
170
|
_executeKubectl(command, options = {}) {
|
|
179
|
-
const {
|
|
180
|
-
|
|
181
|
-
if (dryRun) {
|
|
182
|
-
logger.info(`[DRY RUN] Would execute: ${command}`, { context });
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
171
|
+
const { context = '' } = options;
|
|
185
172
|
|
|
186
173
|
try {
|
|
187
174
|
logger.info(`Executing kubectl command`, { command, context });
|
|
@@ -200,13 +187,12 @@ class UnderpostDB {
|
|
|
200
187
|
* @param {string} params.podName - Target pod name
|
|
201
188
|
* @param {string} params.namespace - Pod namespace
|
|
202
189
|
* @param {string} params.destPath - Destination path in pod
|
|
203
|
-
* @param {boolean} [params.dryRun=false] - Dry run mode
|
|
204
190
|
* @returns {boolean} Success status
|
|
205
191
|
*/
|
|
206
|
-
_copyToPod({ sourcePath, podName, namespace, destPath
|
|
192
|
+
_copyToPod({ sourcePath, podName, namespace, destPath }) {
|
|
207
193
|
try {
|
|
208
194
|
const command = `sudo kubectl cp ${sourcePath} ${namespace}/${podName}:${destPath}`;
|
|
209
|
-
UnderpostDB.API._executeKubectl(command, {
|
|
195
|
+
UnderpostDB.API._executeKubectl(command, { context: `copy to pod ${podName}` });
|
|
210
196
|
return true;
|
|
211
197
|
} catch (error) {
|
|
212
198
|
logger.error('Failed to copy file to pod', { sourcePath, podName, destPath, error: error.message });
|
|
@@ -222,13 +208,12 @@ class UnderpostDB {
|
|
|
222
208
|
* @param {string} params.namespace - Pod namespace
|
|
223
209
|
* @param {string} params.sourcePath - Source path in pod
|
|
224
210
|
* @param {string} params.destPath - Destination file path
|
|
225
|
-
* @param {boolean} [params.dryRun=false] - Dry run mode
|
|
226
211
|
* @returns {boolean} Success status
|
|
227
212
|
*/
|
|
228
|
-
_copyFromPod({ podName, namespace, sourcePath, destPath
|
|
213
|
+
_copyFromPod({ podName, namespace, sourcePath, destPath }) {
|
|
229
214
|
try {
|
|
230
215
|
const command = `sudo kubectl cp ${namespace}/${podName}:${sourcePath} ${destPath}`;
|
|
231
|
-
UnderpostDB.API._executeKubectl(command, {
|
|
216
|
+
UnderpostDB.API._executeKubectl(command, { context: `copy from pod ${podName}` });
|
|
232
217
|
return true;
|
|
233
218
|
} catch (error) {
|
|
234
219
|
logger.error('Failed to copy file from pod', { podName, sourcePath, destPath, error: error.message });
|
|
@@ -243,13 +228,12 @@ class UnderpostDB {
|
|
|
243
228
|
* @param {string} params.podName - Pod name
|
|
244
229
|
* @param {string} params.namespace - Pod namespace
|
|
245
230
|
* @param {string} params.command - Command to execute
|
|
246
|
-
* @param {boolean} [params.dryRun=false] - Dry run mode
|
|
247
231
|
* @returns {string|null} Command output or null
|
|
248
232
|
*/
|
|
249
|
-
_execInPod({ podName, namespace, command
|
|
233
|
+
_execInPod({ podName, namespace, command }) {
|
|
250
234
|
try {
|
|
251
235
|
const kubectlCmd = `sudo kubectl exec -n ${namespace} -i ${podName} -- sh -c "${command}"`;
|
|
252
|
-
return UnderpostDB.API._executeKubectl(kubectlCmd, {
|
|
236
|
+
return UnderpostDB.API._executeKubectl(kubectlCmd, { context: `exec in pod ${podName}` });
|
|
253
237
|
} catch (error) {
|
|
254
238
|
logger.error('Failed to execute command in pod', { podName, command, error: error.message });
|
|
255
239
|
throw error;
|
|
@@ -263,9 +247,10 @@ class UnderpostDB {
|
|
|
263
247
|
* @param {string} params.repoName - Repository name
|
|
264
248
|
* @param {string} params.operation - Operation (clone, pull, commit, push)
|
|
265
249
|
* @param {string} [params.message=''] - Commit message
|
|
250
|
+
* @param {boolean} [params.forceClone=false] - Force remove and re-clone repository
|
|
266
251
|
* @returns {boolean} Success status
|
|
267
252
|
*/
|
|
268
|
-
_manageGitRepo({ repoName, operation, message = '' }) {
|
|
253
|
+
_manageGitRepo({ repoName, operation, message = '', forceClone = false }) {
|
|
269
254
|
try {
|
|
270
255
|
const username = process.env.GITHUB_USERNAME;
|
|
271
256
|
if (!username) {
|
|
@@ -277,6 +262,10 @@ class UnderpostDB {
|
|
|
277
262
|
|
|
278
263
|
switch (operation) {
|
|
279
264
|
case 'clone':
|
|
265
|
+
if (forceClone && fs.existsSync(repoPath)) {
|
|
266
|
+
logger.info(`Force clone enabled, removing existing repository: ${repoName}`);
|
|
267
|
+
fs.removeSync(repoPath);
|
|
268
|
+
}
|
|
280
269
|
if (!fs.existsSync(repoPath)) {
|
|
281
270
|
shellExec(`cd .. && underpost clone ${username}/${repoName}`);
|
|
282
271
|
logger.info(`Cloned repository: ${repoName}`);
|
|
@@ -286,7 +275,9 @@ class UnderpostDB {
|
|
|
286
275
|
case 'pull':
|
|
287
276
|
if (fs.existsSync(repoPath)) {
|
|
288
277
|
shellExec(`cd ${repoPath} && git checkout . && git clean -f -d`);
|
|
289
|
-
shellExec(`cd ${repoPath} && underpost pull . ${username}/${repoName}
|
|
278
|
+
shellExec(`cd ${repoPath} && underpost pull . ${username}/${repoName}`, {
|
|
279
|
+
silent: true,
|
|
280
|
+
});
|
|
290
281
|
logger.info(`Pulled repository: ${repoName}`);
|
|
291
282
|
}
|
|
292
283
|
break;
|
|
@@ -301,7 +292,7 @@ class UnderpostDB {
|
|
|
301
292
|
|
|
302
293
|
case 'push':
|
|
303
294
|
if (fs.existsSync(repoPath)) {
|
|
304
|
-
shellExec(`cd ${repoPath} && underpost push . ${username}/${repoName}`, {
|
|
295
|
+
shellExec(`cd ${repoPath} && underpost push . ${username}/${repoName}`, { silent: true });
|
|
305
296
|
logger.info(`Pushed repository: ${repoName}`);
|
|
306
297
|
}
|
|
307
298
|
break;
|
|
@@ -376,10 +367,9 @@ class UnderpostDB {
|
|
|
376
367
|
* @param {string} params.user - Database user
|
|
377
368
|
* @param {string} params.password - Database password
|
|
378
369
|
* @param {string} params.sqlPath - SQL file path
|
|
379
|
-
* @param {boolean} [params.dryRun=false] - Dry run mode
|
|
380
370
|
* @returns {boolean} Success status
|
|
381
371
|
*/
|
|
382
|
-
_importMariaDB({ pod, namespace, dbName, user, password, sqlPath
|
|
372
|
+
_importMariaDB({ pod, namespace, dbName, user, password, sqlPath }) {
|
|
383
373
|
try {
|
|
384
374
|
const podName = pod.NAME;
|
|
385
375
|
const containerSqlPath = `/${dbName}.sql`;
|
|
@@ -391,7 +381,6 @@ class UnderpostDB {
|
|
|
391
381
|
podName,
|
|
392
382
|
namespace,
|
|
393
383
|
command: `rm -rf ${containerSqlPath}`,
|
|
394
|
-
dryRun,
|
|
395
384
|
});
|
|
396
385
|
|
|
397
386
|
// Copy SQL file to pod
|
|
@@ -401,7 +390,6 @@ class UnderpostDB {
|
|
|
401
390
|
podName,
|
|
402
391
|
namespace,
|
|
403
392
|
destPath: containerSqlPath,
|
|
404
|
-
dryRun,
|
|
405
393
|
})
|
|
406
394
|
) {
|
|
407
395
|
return false;
|
|
@@ -410,12 +398,12 @@ class UnderpostDB {
|
|
|
410
398
|
// Create database if it doesn't exist
|
|
411
399
|
UnderpostDB.API._executeKubectl(
|
|
412
400
|
`kubectl exec -n ${namespace} -i ${podName} -- mariadb -p${password} -e 'CREATE DATABASE IF NOT EXISTS ${dbName};'`,
|
|
413
|
-
{
|
|
401
|
+
{ context: `create database ${dbName}` },
|
|
414
402
|
);
|
|
415
403
|
|
|
416
404
|
// Import SQL file
|
|
417
405
|
const importCmd = `mariadb -u ${user} -p${password} ${dbName} < ${containerSqlPath}`;
|
|
418
|
-
UnderpostDB.API._execInPod({ podName, namespace, command: importCmd
|
|
406
|
+
UnderpostDB.API._execInPod({ podName, namespace, command: importCmd });
|
|
419
407
|
|
|
420
408
|
logger.info('Successfully imported MariaDB database', { podName, dbName });
|
|
421
409
|
return true;
|
|
@@ -435,10 +423,9 @@ class UnderpostDB {
|
|
|
435
423
|
* @param {string} params.user - Database user
|
|
436
424
|
* @param {string} params.password - Database password
|
|
437
425
|
* @param {string} params.outputPath - Output file path
|
|
438
|
-
* @param {boolean} [params.dryRun=false] - Dry run mode
|
|
439
426
|
* @returns {boolean} Success status
|
|
440
427
|
*/
|
|
441
|
-
async _exportMariaDB({ pod, namespace, dbName, user, password, outputPath
|
|
428
|
+
async _exportMariaDB({ pod, namespace, dbName, user, password, outputPath }) {
|
|
442
429
|
try {
|
|
443
430
|
const podName = pod.NAME;
|
|
444
431
|
const containerSqlPath = `/home/${dbName}.sql`;
|
|
@@ -450,12 +437,11 @@ class UnderpostDB {
|
|
|
450
437
|
podName,
|
|
451
438
|
namespace,
|
|
452
439
|
command: `rm -rf ${containerSqlPath}`,
|
|
453
|
-
dryRun,
|
|
454
440
|
});
|
|
455
441
|
|
|
456
442
|
// Dump database
|
|
457
443
|
const dumpCmd = `mariadb-dump --user=${user} --password=${password} --lock-tables ${dbName} > ${containerSqlPath}`;
|
|
458
|
-
UnderpostDB.API._execInPod({ podName, namespace, command: dumpCmd
|
|
444
|
+
UnderpostDB.API._execInPod({ podName, namespace, command: dumpCmd });
|
|
459
445
|
|
|
460
446
|
// Copy SQL file from pod
|
|
461
447
|
if (
|
|
@@ -464,14 +450,13 @@ class UnderpostDB {
|
|
|
464
450
|
namespace,
|
|
465
451
|
sourcePath: containerSqlPath,
|
|
466
452
|
destPath: outputPath,
|
|
467
|
-
dryRun,
|
|
468
453
|
})
|
|
469
454
|
) {
|
|
470
455
|
return false;
|
|
471
456
|
}
|
|
472
457
|
|
|
473
458
|
// Split file if it exists
|
|
474
|
-
if (
|
|
459
|
+
if (fs.existsSync(outputPath)) {
|
|
475
460
|
await splitFileFactory(dbName, outputPath);
|
|
476
461
|
}
|
|
477
462
|
|
|
@@ -493,10 +478,9 @@ class UnderpostDB {
|
|
|
493
478
|
* @param {string} params.bsonPath - BSON directory path
|
|
494
479
|
* @param {boolean} params.drop - Whether to drop existing database
|
|
495
480
|
* @param {boolean} params.preserveUUID - Whether to preserve UUIDs
|
|
496
|
-
* @param {boolean} [params.dryRun=false] - Dry run mode
|
|
497
481
|
* @returns {boolean} Success status
|
|
498
482
|
*/
|
|
499
|
-
_importMongoDB({ pod, namespace, dbName, bsonPath, drop, preserveUUID
|
|
483
|
+
_importMongoDB({ pod, namespace, dbName, bsonPath, drop, preserveUUID }) {
|
|
500
484
|
try {
|
|
501
485
|
const podName = pod.NAME;
|
|
502
486
|
const containerBsonPath = `/${dbName}`;
|
|
@@ -508,7 +492,6 @@ class UnderpostDB {
|
|
|
508
492
|
podName,
|
|
509
493
|
namespace,
|
|
510
494
|
command: `rm -rf ${containerBsonPath}`,
|
|
511
|
-
dryRun,
|
|
512
495
|
});
|
|
513
496
|
|
|
514
497
|
// Copy BSON directory to pod
|
|
@@ -518,7 +501,6 @@ class UnderpostDB {
|
|
|
518
501
|
podName,
|
|
519
502
|
namespace,
|
|
520
503
|
destPath: containerBsonPath,
|
|
521
|
-
dryRun,
|
|
522
504
|
})
|
|
523
505
|
) {
|
|
524
506
|
return false;
|
|
@@ -528,7 +510,7 @@ class UnderpostDB {
|
|
|
528
510
|
const restoreCmd = `mongorestore -d ${dbName} ${containerBsonPath}${drop ? ' --drop' : ''}${
|
|
529
511
|
preserveUUID ? ' --preserveUUID' : ''
|
|
530
512
|
}`;
|
|
531
|
-
UnderpostDB.API._execInPod({ podName, namespace, command: restoreCmd
|
|
513
|
+
UnderpostDB.API._execInPod({ podName, namespace, command: restoreCmd });
|
|
532
514
|
|
|
533
515
|
logger.info('Successfully imported MongoDB database', { podName, dbName });
|
|
534
516
|
return true;
|
|
@@ -547,10 +529,9 @@ class UnderpostDB {
|
|
|
547
529
|
* @param {string} params.dbName - Database name
|
|
548
530
|
* @param {string} params.outputPath - Output directory path
|
|
549
531
|
* @param {string} [params.collections=''] - Comma-separated collection list
|
|
550
|
-
* @param {boolean} [params.dryRun=false] - Dry run mode
|
|
551
532
|
* @returns {boolean} Success status
|
|
552
533
|
*/
|
|
553
|
-
_exportMongoDB({ pod, namespace, dbName, outputPath, collections = ''
|
|
534
|
+
_exportMongoDB({ pod, namespace, dbName, outputPath, collections = '' }) {
|
|
554
535
|
try {
|
|
555
536
|
const podName = pod.NAME;
|
|
556
537
|
const containerBsonPath = `/${dbName}`;
|
|
@@ -562,7 +543,6 @@ class UnderpostDB {
|
|
|
562
543
|
podName,
|
|
563
544
|
namespace,
|
|
564
545
|
command: `rm -rf ${containerBsonPath}`,
|
|
565
|
-
dryRun,
|
|
566
546
|
});
|
|
567
547
|
|
|
568
548
|
// Dump database or specific collections
|
|
@@ -570,11 +550,11 @@ class UnderpostDB {
|
|
|
570
550
|
const collectionList = collections.split(',').map((c) => c.trim());
|
|
571
551
|
for (const collection of collectionList) {
|
|
572
552
|
const dumpCmd = `mongodump -d ${dbName} --collection ${collection} -o /`;
|
|
573
|
-
UnderpostDB.API._execInPod({ podName, namespace, command: dumpCmd
|
|
553
|
+
UnderpostDB.API._execInPod({ podName, namespace, command: dumpCmd });
|
|
574
554
|
}
|
|
575
555
|
} else {
|
|
576
556
|
const dumpCmd = `mongodump -d ${dbName} -o /`;
|
|
577
|
-
UnderpostDB.API._execInPod({ podName, namespace, command: dumpCmd
|
|
557
|
+
UnderpostDB.API._execInPod({ podName, namespace, command: dumpCmd });
|
|
578
558
|
}
|
|
579
559
|
|
|
580
560
|
// Copy BSON directory from pod
|
|
@@ -584,7 +564,6 @@ class UnderpostDB {
|
|
|
584
564
|
namespace,
|
|
585
565
|
sourcePath: containerBsonPath,
|
|
586
566
|
destPath: outputPath,
|
|
587
|
-
dryRun,
|
|
588
567
|
})
|
|
589
568
|
) {
|
|
590
569
|
return false;
|
|
@@ -782,33 +761,26 @@ class UnderpostDB {
|
|
|
782
761
|
* database connections, backup storage, and optional Git integration for version control.
|
|
783
762
|
* Supports targeting multiple specific pods, nodes, and namespaces with advanced filtering.
|
|
784
763
|
* @param {string} [deployList='default'] - Comma-separated list of deployment IDs
|
|
785
|
-
* @param {
|
|
786
|
-
* @
|
|
764
|
+
* @param {Object} options - Backup options
|
|
765
|
+
* @param {boolean} [options.import=false] - Whether to perform import operation
|
|
766
|
+
* @param {boolean} [options.export=false] - Whether to perform export operation
|
|
767
|
+
* @param {string} [options.podName=''] - Comma-separated pod name patterns to target
|
|
768
|
+
* @param {string} [options.nodeName=''] - Comma-separated node names to target
|
|
769
|
+
* @param {string} [options.ns='default'] - Kubernetes namespace
|
|
770
|
+
* @param {string} [options.collections=''] - Comma-separated MongoDB collections for export
|
|
771
|
+
* @param {string} [options.outPath=''] - Output path for backups
|
|
772
|
+
* @param {boolean} [options.drop=false] - Whether to drop existing database on import
|
|
773
|
+
* @param {boolean} [options.preserveUUID=false] - Whether to preserve UUIDs on MongoDB import
|
|
774
|
+
* @param {boolean} [options.git=false] - Whether to use Git for backup versioning
|
|
775
|
+
* @param {string} [options.hosts=''] - Comma-separated list of hosts to filter databases
|
|
776
|
+
* @param {string} [options.paths=''] - Comma-separated list of paths to filter databases
|
|
777
|
+
* @param {string} [options.labelSelector=''] - Label selector for pod filtering
|
|
778
|
+
* @param {boolean} [options.allPods=false] - Whether to target all pods in deployment
|
|
779
|
+
* @param {boolean} [options.primaryPod=false] - Whether to target MongoDB primary pod only
|
|
780
|
+
* @param {boolean} [options.stats=false] - Whether to display database statistics
|
|
781
|
+
* @param {number} [options.macroRollbackExport=1] - Number of commits to rollback in macro export
|
|
782
|
+
* @returns {Promise<void>} Resolves when operation is complete
|
|
787
783
|
* @memberof UnderpostDB
|
|
788
|
-
* @example
|
|
789
|
-
* // Export database from specific pods
|
|
790
|
-
* await UnderpostDB.API.callback('dd-myapp', {
|
|
791
|
-
* export: true,
|
|
792
|
-
* podName: 'mariadb-statefulset-0,mariadb-statefulset-1',
|
|
793
|
-
* ns: 'production'
|
|
794
|
-
* });
|
|
795
|
-
*
|
|
796
|
-
* @example
|
|
797
|
-
* // Import database to all matching pods on specific nodes
|
|
798
|
-
* await UnderpostDB.API.callback('dd-myapp', {
|
|
799
|
-
* import: true,
|
|
800
|
-
* nodeName: 'node-1,node-2',
|
|
801
|
-
* allPods: true,
|
|
802
|
-
* ns: 'staging'
|
|
803
|
-
* });
|
|
804
|
-
*
|
|
805
|
-
* @example
|
|
806
|
-
* // Import to MongoDB primary pod only
|
|
807
|
-
* await UnderpostDB.API.callback('dd-myapp', {
|
|
808
|
-
* import: true,
|
|
809
|
-
* primaryPod: true,
|
|
810
|
-
* ns: 'production'
|
|
811
|
-
* });
|
|
812
784
|
*/
|
|
813
785
|
async callback(
|
|
814
786
|
deployList = 'default',
|
|
@@ -827,9 +799,10 @@ class UnderpostDB {
|
|
|
827
799
|
paths: '',
|
|
828
800
|
labelSelector: '',
|
|
829
801
|
allPods: false,
|
|
830
|
-
dryRun: false,
|
|
831
802
|
primaryPod: false,
|
|
832
803
|
stats: false,
|
|
804
|
+
macroRollbackExport: 1,
|
|
805
|
+
forceClone: false,
|
|
833
806
|
},
|
|
834
807
|
) {
|
|
835
808
|
const newBackupTimestamp = new Date().getTime();
|
|
@@ -841,12 +814,13 @@ class UnderpostDB {
|
|
|
841
814
|
throw new Error(`Invalid namespace: ${namespace}`);
|
|
842
815
|
}
|
|
843
816
|
|
|
817
|
+
if (deployList === 'dd') deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
|
|
818
|
+
|
|
844
819
|
logger.info('Starting database operation', {
|
|
845
820
|
deployList,
|
|
846
821
|
namespace,
|
|
847
822
|
import: options.import,
|
|
848
823
|
export: options.export,
|
|
849
|
-
dryRun: options.dryRun,
|
|
850
824
|
});
|
|
851
825
|
|
|
852
826
|
for (const _deployId of deployList.split(',')) {
|
|
@@ -891,10 +865,31 @@ class UnderpostDB {
|
|
|
891
865
|
|
|
892
866
|
// Handle Git operations
|
|
893
867
|
if (options.git === true) {
|
|
894
|
-
UnderpostDB.API._manageGitRepo({ repoName, operation: 'clone' });
|
|
868
|
+
UnderpostDB.API._manageGitRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
|
|
895
869
|
UnderpostDB.API._manageGitRepo({ repoName, operation: 'pull' });
|
|
896
870
|
}
|
|
897
871
|
|
|
872
|
+
if (options.macroRollbackExport) {
|
|
873
|
+
UnderpostDB.API._manageGitRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
|
|
874
|
+
UnderpostDB.API._manageGitRepo({ repoName, operation: 'pull' });
|
|
875
|
+
|
|
876
|
+
const nCommits = parseInt(options.macroRollbackExport);
|
|
877
|
+
const repoPath = `../${repoName}`;
|
|
878
|
+
const username = process.env.GITHUB_USERNAME;
|
|
879
|
+
|
|
880
|
+
if (fs.existsSync(repoPath) && username) {
|
|
881
|
+
logger.info('Executing macro rollback export', { repoName, nCommits });
|
|
882
|
+
shellExec(`cd ${repoPath} && underpost cmt . reset ${nCommits}`);
|
|
883
|
+
shellExec(`cd ${repoPath} && git reset`);
|
|
884
|
+
shellExec(`cd ${repoPath} && git checkout .`);
|
|
885
|
+
shellExec(`cd ${repoPath} && git clean -f -d`);
|
|
886
|
+
shellExec(`cd ${repoPath} && underpost push . ${username}/${repoName} -f`);
|
|
887
|
+
} else {
|
|
888
|
+
if (!username) logger.error('GITHUB_USERNAME environment variable not set');
|
|
889
|
+
logger.warn('Repository not found for macro rollback', { repoPath });
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
898
893
|
// Process each database provider
|
|
899
894
|
for (const provider of Object.keys(dbs)) {
|
|
900
895
|
for (const dbName of Object.keys(dbs[provider])) {
|
|
@@ -1040,7 +1035,6 @@ class UnderpostDB {
|
|
|
1040
1035
|
user,
|
|
1041
1036
|
password,
|
|
1042
1037
|
sqlPath: toSqlPath,
|
|
1043
|
-
dryRun: options.dryRun,
|
|
1044
1038
|
});
|
|
1045
1039
|
}
|
|
1046
1040
|
|
|
@@ -1053,7 +1047,6 @@ class UnderpostDB {
|
|
|
1053
1047
|
user,
|
|
1054
1048
|
password,
|
|
1055
1049
|
outputPath,
|
|
1056
|
-
dryRun: options.dryRun,
|
|
1057
1050
|
});
|
|
1058
1051
|
}
|
|
1059
1052
|
break;
|
|
@@ -1080,7 +1073,6 @@ class UnderpostDB {
|
|
|
1080
1073
|
bsonPath,
|
|
1081
1074
|
drop: options.drop,
|
|
1082
1075
|
preserveUUID: options.preserveUUID,
|
|
1083
|
-
dryRun: options.dryRun,
|
|
1084
1076
|
});
|
|
1085
1077
|
}
|
|
1086
1078
|
|
|
@@ -1092,7 +1084,6 @@ class UnderpostDB {
|
|
|
1092
1084
|
dbName,
|
|
1093
1085
|
outputPath,
|
|
1094
1086
|
collections: options.collections,
|
|
1095
|
-
dryRun: options.dryRun,
|
|
1096
1087
|
});
|
|
1097
1088
|
}
|
|
1098
1089
|
break;
|
|
@@ -1136,9 +1127,9 @@ class UnderpostDB {
|
|
|
1136
1127
|
host = process.env.DEFAULT_DEPLOY_HOST,
|
|
1137
1128
|
path = process.env.DEFAULT_DEPLOY_PATH,
|
|
1138
1129
|
) {
|
|
1139
|
-
deployId = deployId
|
|
1140
|
-
host = host
|
|
1141
|
-
path = path
|
|
1130
|
+
deployId = deployId ? deployId : process.env.DEFAULT_DEPLOY_ID;
|
|
1131
|
+
host = host ? host : process.env.DEFAULT_DEPLOY_HOST;
|
|
1132
|
+
path = path ? path : process.env.DEFAULT_DEPLOY_PATH;
|
|
1142
1133
|
|
|
1143
1134
|
logger.info('Creating cluster metadata', { deployId, host, path });
|
|
1144
1135
|
|
|
@@ -1322,9 +1313,9 @@ class UnderpostDB {
|
|
|
1322
1313
|
crons: false,
|
|
1323
1314
|
},
|
|
1324
1315
|
) {
|
|
1325
|
-
deployId = deployId
|
|
1326
|
-
host = host
|
|
1327
|
-
path = path
|
|
1316
|
+
deployId = deployId ? deployId : process.env.DEFAULT_DEPLOY_ID;
|
|
1317
|
+
host = host ? host : process.env.DEFAULT_DEPLOY_HOST;
|
|
1318
|
+
path = path ? path : process.env.DEFAULT_DEPLOY_PATH;
|
|
1328
1319
|
|
|
1329
1320
|
logger.info('Starting cluster metadata backup operation', {
|
|
1330
1321
|
deployId,
|
package/src/cli/deploy.js
CHANGED
|
@@ -657,18 +657,28 @@ EOF`);
|
|
|
657
657
|
* @param {string} env - Environment for which the status is being checked.
|
|
658
658
|
* @param {string} traffic - Current traffic status for the deployment.
|
|
659
659
|
* @param {Array<string>} ignoresNames - List of pod names to ignore.
|
|
660
|
+
* @param {string} [namespace='default'] - Kubernetes namespace for the deployment.
|
|
660
661
|
* @returns {object} - Object containing the status of the deployment.
|
|
661
662
|
* @memberof UnderpostDeploy
|
|
662
663
|
*/
|
|
663
|
-
checkDeploymentReadyStatus(deployId, env, traffic, ignoresNames = []) {
|
|
664
|
+
async checkDeploymentReadyStatus(deployId, env, traffic, ignoresNames = [], namespace = 'default') {
|
|
664
665
|
const cmd = `underpost config get container-status`;
|
|
665
|
-
const pods = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}
|
|
666
|
+
const pods = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}`, 'pods', namespace);
|
|
666
667
|
const readyPods = [];
|
|
667
668
|
const notReadyPods = [];
|
|
668
669
|
for (const pod of pods) {
|
|
669
670
|
const { NAME } = pod;
|
|
670
671
|
if (ignoresNames && ignoresNames.find((t) => NAME.trim().toLowerCase().match(t.trim().toLowerCase()))) continue;
|
|
671
|
-
const out =
|
|
672
|
+
const out = await new Promise((resolve) => {
|
|
673
|
+
shellExec(`sudo kubectl exec -i ${NAME} -n ${namespace} -- sh -c "${cmd}"`, {
|
|
674
|
+
silent: true,
|
|
675
|
+
disableLog: true,
|
|
676
|
+
callback: function (code, stdout, stderr) {
|
|
677
|
+
return resolve(JSON.stringify({ code, stdout, stderr }));
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
pod.out = out;
|
|
672
682
|
const ready = out.match(`${deployId}-${env}-running-deployment`);
|
|
673
683
|
ready ? readyPods.push(pod) : notReadyPods.push(pod);
|
|
674
684
|
}
|
|
@@ -893,15 +903,18 @@ ${renderHosts}`,
|
|
|
893
903
|
* @param {string} env - Environment for which the ready status is being monitored.
|
|
894
904
|
* @param {string} targetTraffic - Target traffic status for the deployment.
|
|
895
905
|
* @param {Array<string>} ignorePods - List of pod names to ignore.
|
|
906
|
+
* @param {string} [namespace='default'] - Kubernetes namespace for the deployment.
|
|
907
|
+
* @param {string} [outLogType=''] - Type of log output.
|
|
896
908
|
* @returns {object} - Object containing the ready status of the deployment.
|
|
897
909
|
* @memberof UnderpostDeploy
|
|
898
910
|
*/
|
|
899
|
-
async monitorReadyRunner(deployId, env, targetTraffic, ignorePods = []) {
|
|
911
|
+
async monitorReadyRunner(deployId, env, targetTraffic, ignorePods = [], namespace = 'default', outLogType = '') {
|
|
900
912
|
let checkStatusIteration = 0;
|
|
901
913
|
const checkStatusIterationMsDelay = 1000;
|
|
902
914
|
const maxIterations = 500;
|
|
903
|
-
const
|
|
904
|
-
|
|
915
|
+
const deploymentId = `${deployId}-${env}-${targetTraffic}`;
|
|
916
|
+
const iteratorTag = `[${deploymentId}]`;
|
|
917
|
+
logger.info('Deployment init', { deployId, env, targetTraffic, checkStatusIterationMsDelay, namespace });
|
|
905
918
|
const minReadyOk = 3;
|
|
906
919
|
let readyOk = 0;
|
|
907
920
|
let result = {
|
|
@@ -909,7 +922,7 @@ ${renderHosts}`,
|
|
|
909
922
|
notReadyPods: [],
|
|
910
923
|
readyPods: [],
|
|
911
924
|
};
|
|
912
|
-
|
|
925
|
+
let lastMsg = {};
|
|
913
926
|
while (readyOk < minReadyOk) {
|
|
914
927
|
if (checkStatusIteration >= maxIterations) {
|
|
915
928
|
logger.error(
|
|
@@ -917,30 +930,81 @@ ${renderHosts}`,
|
|
|
917
930
|
);
|
|
918
931
|
break;
|
|
919
932
|
}
|
|
920
|
-
result = UnderpostDeploy.API.checkDeploymentReadyStatus(
|
|
933
|
+
result = await UnderpostDeploy.API.checkDeploymentReadyStatus(
|
|
934
|
+
deployId,
|
|
935
|
+
env,
|
|
936
|
+
targetTraffic,
|
|
937
|
+
ignorePods,
|
|
938
|
+
namespace,
|
|
939
|
+
);
|
|
921
940
|
if (result.ready === true) {
|
|
922
941
|
readyOk++;
|
|
923
942
|
logger.info(`${iteratorTag} | Deployment ready. Verification number: ${readyOk}`);
|
|
943
|
+
for (const pod of result.readyPods) {
|
|
944
|
+
const { NAME } = pod;
|
|
945
|
+
lastMsg[NAME] = 'Deployment ready';
|
|
946
|
+
console.log(
|
|
947
|
+
'Target pod:',
|
|
948
|
+
NAME[NAME.match('green') ? 'bgGreen' : 'bgBlue'].bold.black,
|
|
949
|
+
'| Status:',
|
|
950
|
+
lastMsg[NAME].bold.magenta,
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
switch (outLogType) {
|
|
956
|
+
case 'underpost': {
|
|
957
|
+
let indexOf = -1;
|
|
958
|
+
for (const pod of result.notReadyPods) {
|
|
959
|
+
indexOf++;
|
|
960
|
+
const { NAME, out } = pod;
|
|
961
|
+
|
|
962
|
+
if (out.match('not') && out.match('found') && checkStatusIteration <= 20 && out.match(deploymentId))
|
|
963
|
+
lastMsg[NAME] = 'Starting deployment';
|
|
964
|
+
else if (out.match('not') && out.match('found') && checkStatusIteration <= 20 && out.match('underpost'))
|
|
965
|
+
lastMsg[NAME] = 'Installing underpost cli';
|
|
966
|
+
else if (out.match('not') && out.match('found') && checkStatusIteration <= 20 && out.match('task'))
|
|
967
|
+
lastMsg[NAME] = 'Initializing setup task';
|
|
968
|
+
else if (out.match('Empty environment variables')) lastMsg[NAME] = 'Setup environment';
|
|
969
|
+
else if (out.match(`${deployId}-${env}-build-deployment`)) lastMsg[NAME] = 'Building apps/services';
|
|
970
|
+
else if (out.match(`${deployId}-${env}-initializing-deployment`))
|
|
971
|
+
lastMsg[NAME] = 'Initializing apps/services';
|
|
972
|
+
else if (!lastMsg[NAME]) lastMsg[NAME] = `Waiting for status`;
|
|
973
|
+
|
|
974
|
+
console.log(
|
|
975
|
+
'Target pod:',
|
|
976
|
+
NAME[NAME.match('green') ? 'bgGreen' : 'bgBlue'].bold.black,
|
|
977
|
+
'| Status:',
|
|
978
|
+
lastMsg[NAME].bold.magenta,
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
924
982
|
}
|
|
925
983
|
await timer(checkStatusIterationMsDelay);
|
|
926
984
|
checkStatusIteration++;
|
|
927
985
|
logger.info(
|
|
928
|
-
`${iteratorTag} | Deployment in progress... | Delay number
|
|
986
|
+
`${iteratorTag} | Deployment in progress... | Delay number monitor iterations: ${checkStatusIteration}`,
|
|
929
987
|
);
|
|
930
988
|
}
|
|
931
|
-
logger.info(
|
|
989
|
+
logger.info(
|
|
990
|
+
`${iteratorTag} | Deployment ready. | Total delay number monitor iterations: ${checkStatusIteration}`,
|
|
991
|
+
);
|
|
932
992
|
return result;
|
|
933
993
|
},
|
|
934
994
|
|
|
935
995
|
/**
|
|
936
996
|
* Retrieves the currently loaded images in the Kubernetes cluster.
|
|
997
|
+
* @param {string} [node='kind-worker'] - Node name to check for loaded images.
|
|
998
|
+
* @param {object} options - Options for the image retrieval.
|
|
999
|
+
* @param {boolean} options.spec - Whether to retrieve images from the pod specifications.
|
|
1000
|
+
* @param {string} options.namespace - Kubernetes namespace to filter pods.
|
|
937
1001
|
* @returns {Array<object>} - Array of objects containing pod names and their corresponding images.
|
|
938
1002
|
* @memberof UnderpostDeploy
|
|
939
1003
|
*/
|
|
940
|
-
getCurrentLoadedImages(node = 'kind-worker',
|
|
941
|
-
if (
|
|
1004
|
+
getCurrentLoadedImages(node = 'kind-worker', options = { spec: false, namespace: '' }) {
|
|
1005
|
+
if (options.spec) {
|
|
942
1006
|
const raw = shellExec(
|
|
943
|
-
`kubectl get pods
|
|
1007
|
+
`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}'`,
|
|
944
1008
|
{
|
|
945
1009
|
stdout: true,
|
|
946
1010
|
silent: true,
|