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.
Files changed (35) hide show
  1. package/.github/workflows/pwa-microservices-template-page.cd.yml +5 -4
  2. package/README.md +4 -5
  3. package/bin/build.js +6 -1
  4. package/bin/deploy.js +2 -69
  5. package/cli.md +100 -92
  6. package/manifests/deployment/dd-default-development/deployment.yaml +4 -4
  7. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  8. package/package.json +1 -1
  9. package/scripts/disk-clean.sh +216 -0
  10. package/scripts/ssh-cluster-info.sh +4 -3
  11. package/src/cli/cluster.js +1 -1
  12. package/src/cli/db.js +80 -89
  13. package/src/cli/deploy.js +77 -13
  14. package/src/cli/image.js +198 -133
  15. package/src/cli/index.js +60 -81
  16. package/src/cli/lxd.js +73 -74
  17. package/src/cli/monitor.js +20 -9
  18. package/src/cli/repository.js +86 -3
  19. package/src/cli/run.js +167 -63
  20. package/src/cli/ssh.js +351 -134
  21. package/src/index.js +1 -1
  22. package/src/monitor.js +11 -1
  23. package/src/server/backup.js +1 -1
  24. package/src/server/conf.js +1 -1
  25. package/src/server/dns.js +88 -1
  26. package/src/server/process.js +6 -1
  27. package/scripts/snap-clean.sh +0 -26
  28. package/src/client/public/default/plantuml/client-conf.svg +0 -1
  29. package/src/client/public/default/plantuml/client-schema.svg +0 -1
  30. package/src/client/public/default/plantuml/cron-conf.svg +0 -1
  31. package/src/client/public/default/plantuml/cron-schema.svg +0 -1
  32. package/src/client/public/default/plantuml/server-conf.svg +0 -1
  33. package/src/client/public/default/plantuml/server-schema.svg +0 -1
  34. package/src/client/public/default/plantuml/ssr-conf.svg +0 -1
  35. 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 { dryRun = false, context = '' } = options;
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, dryRun = false }) {
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, { dryRun, context: `copy to pod ${podName}` });
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, dryRun = false }) {
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, { dryRun, context: `copy from pod ${podName}` });
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, dryRun = false }) {
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, { dryRun, context: `exec in pod ${podName}` });
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}`, { disableLog: true });
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, dryRun = false }) {
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
- { dryRun, context: `create database ${dbName}` },
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, dryRun });
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, dryRun = false }) {
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, dryRun });
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 (!dryRun && fs.existsSync(outputPath)) {
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, dryRun = false }) {
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, dryRun });
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 = '', dryRun = false }) {
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, dryRun });
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, dryRun });
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 {DatabaseOptions} [options] - Database operation options
786
- * @returns {Promise<void>}
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 ?? process.env.DEFAULT_DEPLOY_ID;
1140
- host = host ?? process.env.DEFAULT_DEPLOY_HOST;
1141
- path = path ?? process.env.DEFAULT_DEPLOY_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 ?? process.env.DEFAULT_DEPLOY_ID;
1326
- host = host ?? process.env.DEFAULT_DEPLOY_HOST;
1327
- path = path ?? process.env.DEFAULT_DEPLOY_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 = shellExec(`sudo kubectl exec -i ${NAME} -- sh -c "${cmd}"`, { stdout: true, silent: true });
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 iteratorTag = `[${deployId}-${env}-${targetTraffic}]`;
904
- logger.info('Deployment init', { deployId, env, targetTraffic, checkStatusIterationMsDelay });
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(deployId, env, targetTraffic, ignorePods);
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 check iterations: ${checkStatusIteration}`,
986
+ `${iteratorTag} | Deployment in progress... | Delay number monitor iterations: ${checkStatusIteration}`,
929
987
  );
930
988
  }
931
- logger.info(`${iteratorTag} | Deployment ready. | Total delay number check iterations: ${checkStatusIteration}`);
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', specContainers = false) {
941
- if (specContainers) {
1004
+ getCurrentLoadedImages(node = 'kind-worker', options = { spec: false, namespace: '' }) {
1005
+ if (options.spec) {
942
1006
  const raw = shellExec(
943
- `kubectl get pods --all-namespaces -o=jsonpath='{range .items[*]}{"\\n"}{.metadata.namespace}{"/"}{.metadata.name}{":\\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}'`,
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,