underpost 2.89.37 → 2.89.44

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 (42) hide show
  1. package/README.md +3 -2
  2. package/bin/deploy.js +22 -15
  3. package/cli.md +22 -2
  4. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  5. package/manifests/deployment/dd-test-development/deployment.yaml +6 -2
  6. package/manifests/deployment/dd-test-development/proxy.yaml +2 -0
  7. package/manifests/deployment/kafka/deployment.yaml +0 -2
  8. package/manifests/deployment/spark/spark-pi-py.yaml +0 -1
  9. package/manifests/deployment/tensorflow/tf-gpu-test.yaml +0 -2
  10. package/manifests/envoy-service-nodeport.yaml +0 -1
  11. package/manifests/kubeadm-calico-config.yaml +10 -115
  12. package/manifests/letsencrypt-prod.yaml +0 -1
  13. package/manifests/mariadb/statefulset.yaml +1 -1
  14. package/manifests/mongodb/statefulset.yaml +11 -11
  15. package/manifests/mongodb-4.4/service-deployment.yaml +1 -3
  16. package/manifests/mysql/pv-pvc.yaml +1 -1
  17. package/manifests/mysql/statefulset.yaml +1 -1
  18. package/manifests/valkey/service.yaml +0 -1
  19. package/manifests/valkey/statefulset.yaml +2 -3
  20. package/package.json +1 -1
  21. package/scripts/device-scan.sh +43 -21
  22. package/scripts/rpmfusion-ffmpeg-setup.sh +1 -0
  23. package/src/cli/cluster.js +51 -26
  24. package/src/cli/deploy.js +52 -28
  25. package/src/cli/index.js +22 -1
  26. package/src/cli/monitor.js +9 -5
  27. package/src/cli/repository.js +1 -1
  28. package/src/cli/run.js +30 -18
  29. package/src/client/components/core/Logger.js +1 -1
  30. package/src/client/components/core/Modal.js +5 -0
  31. package/src/client/components/core/ObjectLayerEngineModal.js +334 -71
  32. package/src/client/components/core/ObjectLayerEngineViewer.js +170 -403
  33. package/src/client/components/core/Router.js +10 -1
  34. package/src/client/services/default/default.management.js +25 -5
  35. package/src/index.js +1 -1
  36. package/src/server/client-build.js +5 -4
  37. package/src/server/conf.js +1 -1
  38. package/manifests/kubelet-config.yaml +0 -65
  39. package/manifests/mongodb/backup-access.yaml +0 -16
  40. package/manifests/mongodb/backup-cronjob.yaml +0 -42
  41. package/manifests/mongodb/backup-pv-pvc.yaml +0 -22
  42. package/manifests/mongodb/configmap.yaml +0 -26
@@ -1,26 +1,42 @@
1
1
  #!/usr/bin/env bash
2
+ set -u -o pipefail
2
3
 
3
4
  for iface_path in /sys/class/net/*; do
5
+ [ -e "$iface_path" ] || continue
4
6
  name=$(basename "$iface_path")
5
- mac=$(< "$iface_path/address")
6
- ip=$(ip -4 addr show dev "$name" \
7
- | grep -oP '(?<=inet\s)\d+(\.\d+){3}' || echo "—")
8
- operstate=$(< "$iface_path/operstate")
9
- mtu=$(< "$iface_path/mtu")
10
-
11
- # Driver
12
- if [ -L "$iface_path/device/driver" ]; then
7
+
8
+ # MAC address
9
+ if [ -r "$iface_path/address" ]; then
10
+ mac=$(< "$iface_path/address")
11
+ else
12
+ mac="—"
13
+ fi
14
+
15
+ # IPv4: collect all IPv4 CIDRs, strip masks, join with commas (or show —)
16
+ ip_info=$(ip -4 -o addr show dev "$name" 2>/dev/null | awk '{print $4}')
17
+ if [ -n "$ip_info" ]; then
18
+ # Use word-splitting intentionally to iterate lines from ip_info
19
+ ip=$(printf "%s\n" $ip_info | awk -F/ '{print $1}' | paste -sd, -)
20
+ else
21
+ ip="—"
22
+ fi
23
+
24
+ # operstate and mtu
25
+ operstate=$(< "$iface_path/operstate" 2>/dev/null || echo "—")
26
+ mtu=$(< "$iface_path/mtu" 2>/dev/null || echo "—")
27
+
28
+ # Driver (if available)
29
+ if [ -e "$iface_path/device/driver" ]; then
13
30
  driver=$(basename "$(readlink -f "$iface_path/device/driver")")
14
31
  else
15
32
  driver="—"
16
33
  fi
17
34
 
18
- # Vendor device ID PCI
35
+ # PCI vendor:device (if available)
19
36
  pci_dev="$iface_path/device"
20
- if [ -f "$pci_dev/vendor" ] && [ -f "$pci_dev/device" ]; then
37
+ if [ -r "$pci_dev/vendor" ] && [ -r "$pci_dev/device" ]; then
21
38
  vendor_id=$(< "$pci_dev/vendor")
22
39
  device_id=$(< "$pci_dev/device")
23
- # parse 0x8086 to 8086, etc.
24
40
  vendor_id=${vendor_id#0x}
25
41
  device_id=${device_id#0x}
26
42
  pci="${vendor_id}:${device_id}"
@@ -28,16 +44,22 @@ for iface_path in /sys/class/net/*; do
28
44
  pci="—"
29
45
  fi
30
46
 
31
- # Link Speed
47
+ # Link speed: only append unit if numeric
32
48
  speed=$(cat "$iface_path/speed" 2>/dev/null || echo "—")
49
+ if [[ "$speed" =~ ^[0-9]+$ ]]; then
50
+ speed_label="${speed} Mb/s"
51
+ else
52
+ speed_label="$speed"
53
+ fi
54
+
55
+ # Print formatted output
56
+ printf 'Interface: %s\n' "$name"
57
+ printf ' MAC: %s\n' "$mac"
58
+ printf ' IPv4: %s\n' "$ip"
59
+ printf ' State: %s\n' "$operstate"
60
+ printf ' MTU: %s\n' "$mtu"
61
+ printf ' Driver: %s\n' "$driver"
62
+ printf ' PCI Vendor:Device: %s\n' "$pci"
63
+ printf ' Link Speed: %s\n\n' "$speed_label"
33
64
 
34
- echo "Interface: $name"
35
- echo " MAC: $mac"
36
- echo " IPv4: $ip"
37
- echo " State: $operstate"
38
- echo " MTU: $mtu"
39
- echo " Driver: $driver"
40
- echo " PCI Vendor:Device ID: $pci"
41
- echo " Link Speed: ${speed}Mb/s"
42
- echo
43
65
  done
@@ -32,6 +32,7 @@ echo "6) Try to install audio helper packages that sometimes block ffmpeg (ladsp
32
32
  # These may be provided by CRB/EPEL or other compatible repos
33
33
  dnf -y install ladspa || echo "ladspa not available from enabled repos (will try later)"
34
34
  dnf -y install rubberband || echo "rubberband not available from enabled repos (will try later)"
35
+ dnf -y install libwebp-tools || echo "libwebp-tools not available from enabled repos (will try later)"
35
36
 
36
37
  echo "7) Try installing ffmpeg (several fallbacks tried)"
37
38
  if dnf -y install ffmpeg ffmpeg-devel --allowerasing; then
@@ -44,7 +44,8 @@ class UnderpostCluster {
44
44
  * @param {boolean} [options.listPods=false] - List Kubernetes pods.
45
45
  * @param {boolean} [options.reset=false] - Perform a comprehensive reset of Kubernetes and container environments.
46
46
  * @param {boolean} [options.dev=false] - Run in development mode (adjusts paths).
47
- * @param {string} [options.nsUse=''] - Set the current kubectl namespace.
47
+ * @param {string} [options.nsUse=''] - Set the current kubectl namespace (creates namespace if it doesn't exist).
48
+ * @param {string} [options.namespace='default'] - Kubernetes namespace for cluster operations.
48
49
  * @param {boolean} [options.infoCapacity=false] - Display resource capacity information for the cluster.
49
50
  * @param {boolean} [options.infoCapacityPod=false] - Display resource capacity information for pods.
50
51
  * @param {boolean} [options.pullImage=false] - Pull necessary Docker images before deployment.
@@ -79,6 +80,7 @@ class UnderpostCluster {
79
80
  reset: false,
80
81
  dev: false,
81
82
  nsUse: '',
83
+ namespace: 'default',
82
84
  infoCapacity: false,
83
85
  infoCapacityPod: false,
84
86
  pullImage: false,
@@ -116,8 +118,26 @@ class UnderpostCluster {
116
118
  if (options.infoCapacity === true)
117
119
  return logger.info('', UnderpostCluster.API.getResourcesCapacity(options.kubeadm || options.k3s)); // Adjust for k3s
118
120
  if (options.listPods === true) return console.table(UnderpostDeploy.API.get(podName ?? undefined));
121
+ // Set default namespace if not specified
122
+ if (!options.namespace) options.namespace = 'default';
123
+
119
124
  if (options.nsUse && typeof options.nsUse === 'string') {
125
+ // Verify if namespace exists, create if not
126
+ const namespaceExists = shellExec(`kubectl get namespace ${options.nsUse} --ignore-not-found -o name`, {
127
+ stdout: true,
128
+ silent: true,
129
+ }).trim();
130
+
131
+ if (!namespaceExists) {
132
+ logger.info(`Namespace '${options.nsUse}' does not exist. Creating it...`);
133
+ shellExec(`kubectl create namespace ${options.nsUse}`);
134
+ logger.info(`Namespace '${options.nsUse}' created successfully.`);
135
+ } else {
136
+ logger.info(`Namespace '${options.nsUse}' already exists.`);
137
+ }
138
+
120
139
  shellExec(`kubectl config set-context --current --namespace=${options.nsUse}`);
140
+ logger.info(`Context switched to namespace: ${options.nsUse}`);
121
141
  return;
122
142
  }
123
143
  if (options.info === true) {
@@ -242,14 +262,17 @@ class UnderpostCluster {
242
262
  shellExec(
243
263
  `sudo kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.3/manifests/tigera-operator.yaml`,
244
264
  );
245
- shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/kubeadm-calico-config.yaml`);
265
+ shellExec(
266
+ `sudo kubectl apply -f ${underpostRoot}/manifests/kubeadm-calico-config.yaml -n ${options.namespace}`,
267
+ );
268
+
246
269
  // Untaint control plane node to allow scheduling pods
247
270
  const nodeName = os.hostname();
248
271
  shellExec(`kubectl taint nodes ${nodeName} node-role.kubernetes.io/control-plane:NoSchedule-`);
249
272
  // Install local-path-provisioner for dynamic PVCs (optional but recommended)
250
273
  logger.info('Installing local-path-provisioner...');
251
274
  shellExec(
252
- `kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml`,
275
+ `kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml -n ${options.namespace}`,
253
276
  );
254
277
  } else {
255
278
  // Kind cluster initialization (if not using kubeadm or k3s)
@@ -286,13 +309,13 @@ class UnderpostCluster {
286
309
  }
287
310
 
288
311
  if (options.grafana === true) {
289
- shellExec(`kubectl delete deployment grafana --ignore-not-found`);
290
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana`);
312
+ shellExec(`kubectl delete deployment grafana -n ${options.namespace} --ignore-not-found`);
313
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana -n ${options.namespace}`);
291
314
  const yaml = `${fs
292
315
  .readFileSync(`${underpostRoot}/manifests/grafana/deployment.yaml`, 'utf8')
293
316
  .replace('{{GF_SERVER_ROOT_URL}}', options.hosts.split(',')[0])}`;
294
317
  console.log(yaml);
295
- shellExec(`kubectl apply -f - <<EOF
318
+ shellExec(`kubectl apply -f - -n ${options.namespace} <<EOF
296
319
  ${yaml}
297
320
  EOF
298
321
  `);
@@ -311,7 +334,7 @@ EOF
311
334
  .join(',')}]`,
312
335
  )}`;
313
336
  console.log(yaml);
314
- shellExec(`kubectl apply -f - <<EOF
337
+ shellExec(`kubectl apply -f - -n ${options.namespace} <<EOF
315
338
  ${yaml}
316
339
  EOF
317
340
  `);
@@ -340,15 +363,15 @@ EOF
340
363
  // For kubeadm/k3s, ensure it's available for containerd
341
364
  shellExec(`sudo crictl pull valkey/valkey:latest`);
342
365
  }
343
- shellExec(`kubectl delete statefulset valkey-service --ignore-not-found`);
344
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey`);
366
+ shellExec(`kubectl delete statefulset valkey-service -n ${options.namespace} --ignore-not-found`);
367
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey -n ${options.namespace}`);
345
368
  await UnderpostTest.API.statusMonitor('valkey-service', 'Running', 'pods', 1000, 60);
346
369
  }
347
370
  if (options.full === true || options.mariadb === true) {
348
371
  shellExec(
349
- `sudo kubectl create secret generic mariadb-secret --from-file=username=/home/dd/engine/engine-private/mariadb-username --from-file=password=/home/dd/engine/engine-private/mariadb-password --dry-run=client -o yaml | kubectl apply -f -`,
372
+ `sudo kubectl create secret generic mariadb-secret --from-file=username=/home/dd/engine/engine-private/mariadb-username --from-file=password=/home/dd/engine/engine-private/mariadb-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
350
373
  );
351
- shellExec(`kubectl delete statefulset mariadb-statefulset --ignore-not-found`);
374
+ shellExec(`kubectl delete statefulset mariadb-statefulset -n ${options.namespace} --ignore-not-found`);
352
375
 
353
376
  if (options.pullImage === true) {
354
377
  // shellExec(`sudo podman pull mariadb:latest`);
@@ -360,17 +383,17 @@ EOF
360
383
  // For kubeadm/k3s, ensure it's available for containerd
361
384
  shellExec(`sudo crictl pull mariadb:latest`);
362
385
  }
363
- shellExec(`kubectl apply -f ${underpostRoot}/manifests/mariadb/storage-class.yaml`);
364
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb`);
386
+ shellExec(`kubectl apply -f ${underpostRoot}/manifests/mariadb/storage-class.yaml -n ${options.namespace}`);
387
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb -n ${options.namespace}`);
365
388
  }
366
389
  if (options.full === true || options.mysql === true) {
367
390
  shellExec(
368
- `sudo kubectl create secret generic mysql-secret --from-file=username=/home/dd/engine/engine-private/mysql-username --from-file=password=/home/dd/engine/engine-private/mysql-password --dry-run=client -o yaml | kubectl apply -f -`,
391
+ `sudo kubectl create secret generic mysql-secret --from-file=username=/home/dd/engine/engine-private/mysql-username --from-file=password=/home/dd/engine/engine-private/mysql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
369
392
  );
370
393
  shellExec(`sudo mkdir -p /mnt/data`);
371
394
  shellExec(`sudo chmod 777 /mnt/data`);
372
395
  shellExec(`sudo chown -R root:root /mnt/data`);
373
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/mysql`);
396
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/mysql -n ${options.namespace}`);
374
397
  }
375
398
  if (options.full === true || options.postgresql === true) {
376
399
  if (options.pullImage === true) {
@@ -383,9 +406,9 @@ EOF
383
406
  shellExec(`sudo crictl pull postgres:latest`);
384
407
  }
385
408
  shellExec(
386
- `sudo kubectl create secret generic postgres-secret --from-file=password=/home/dd/engine/engine-private/postgresql-password --dry-run=client -o yaml | kubectl apply -f -`,
409
+ `sudo kubectl create secret generic postgres-secret --from-file=password=/home/dd/engine/engine-private/postgresql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
387
410
  );
388
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/postgresql`);
411
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/postgresql -n ${options.namespace}`);
389
412
  }
390
413
  if (options.mongodb4 === true) {
391
414
  if (options.pullImage === true) {
@@ -397,7 +420,7 @@ EOF
397
420
  // For kubeadm/k3s, ensure it's available for containerd
398
421
  shellExec(`sudo crictl pull mongo:4.4`);
399
422
  }
400
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4`);
423
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4 -n ${options.namespace}`);
401
424
 
402
425
  const deploymentName = 'mongodb-deployment';
403
426
 
@@ -428,14 +451,14 @@ EOF
428
451
  shellExec(`sudo crictl pull mongo:latest`);
429
452
  }
430
453
  shellExec(
431
- `sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile --dry-run=client -o yaml | kubectl apply -f -`,
454
+ `sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
432
455
  );
433
456
  shellExec(
434
- `sudo kubectl create secret generic mongodb-secret --from-file=username=/home/dd/engine/engine-private/mongodb-username --from-file=password=/home/dd/engine/engine-private/mongodb-password --dry-run=client -o yaml | kubectl apply -f -`,
457
+ `sudo kubectl create secret generic mongodb-secret --from-file=username=/home/dd/engine/engine-private/mongodb-username --from-file=password=/home/dd/engine/engine-private/mongodb-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
435
458
  );
436
- shellExec(`kubectl delete statefulset mongodb --ignore-not-found`);
437
- shellExec(`kubectl apply -f ${underpostRoot}/manifests/mongodb/storage-class.yaml`);
438
- shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb`);
459
+ shellExec(`kubectl delete statefulset mongodb -n ${options.namespace} --ignore-not-found`);
460
+ shellExec(`kubectl apply -f ${underpostRoot}/manifests/mongodb/storage-class.yaml -n ${options.namespace}`);
461
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb -n ${options.namespace}`);
439
462
 
440
463
  const successInstance = await UnderpostTest.API.statusMonitor('mongodb-0', 'Running', 'pods', 1000, 60 * 10);
441
464
 
@@ -456,10 +479,12 @@ EOF
456
479
  }
457
480
 
458
481
  if (options.full === true || options.contour === true) {
459
- shellExec(`kubectl apply -f https://projectcontour.io/quickstart/contour.yaml`);
482
+ shellExec(`kubectl apply -f https://projectcontour.io/quickstart/contour.yaml -n ${options.namespace}`);
460
483
  if (options.kubeadm === true) {
461
484
  // Envoy service might need NodePort for kubeadm
462
- shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/envoy-service-nodeport.yaml`);
485
+ shellExec(
486
+ `sudo kubectl apply -f ${underpostRoot}/manifests/envoy-service-nodeport.yaml -n ${options.namespace}`,
487
+ );
463
488
  }
464
489
  // K3s has a built-in LoadBalancer (Klipper-lb) that can expose services,
465
490
  // so a specific NodePort service might not be needed or can be configured differently.
@@ -479,7 +504,7 @@ EOF
479
504
 
480
505
  const letsEncName = 'letsencrypt-prod';
481
506
  shellExec(`sudo kubectl delete ClusterIssuer ${letsEncName} --ignore-not-found`);
482
- shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/${letsEncName}.yaml`);
507
+ shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/${letsEncName}.yaml -n ${options.namespace}`);
483
508
  }
484
509
  },
485
510
 
package/src/cli/deploy.js CHANGED
@@ -129,10 +129,11 @@ class UnderpostDeploy {
129
129
  * @param {object} resources - Resource configuration for the deployment.
130
130
  * @param {number} replicas - Number of replicas for the deployment.
131
131
  * @param {string} image - Docker image for the deployment.
132
+ * @param {string} namespace - Kubernetes namespace for the deployment.
132
133
  * @returns {string} - YAML deployment configuration for the specified deployment.
133
134
  * @memberof UnderpostDeploy
134
135
  */
135
- deploymentYamlPartsFactory({ deployId, env, suffix, resources, replicas, image }) {
136
+ deploymentYamlPartsFactory({ deployId, env, suffix, resources, replicas, image, namespace }) {
136
137
  const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
137
138
  let volumes = [
138
139
  {
@@ -149,6 +150,7 @@ class UnderpostDeploy {
149
150
  kind: Deployment
150
151
  metadata:
151
152
  name: ${deployId}-${env}-${suffix}
153
+ namespace: ${namespace ? namespace : 'default'}
152
154
  labels:
153
155
  app: ${deployId}-${env}-${suffix}
154
156
  spec:
@@ -188,6 +190,7 @@ apiVersion: v1
188
190
  kind: Service
189
191
  metadata:
190
192
  name: ${deployId}-${env}-${suffix}-service
193
+ namespace: ${namespace}
191
194
  spec:
192
195
  selector:
193
196
  app: ${deployId}-${env}-${suffix}
@@ -201,6 +204,7 @@ spec:
201
204
  * @param {object} options - Options for the manifest build process.
202
205
  * @param {string} options.replicas - Number of replicas for each deployment.
203
206
  * @param {string} options.image - Docker image for the deployment.
207
+ * @param {string} options.namespace - Kubernetes namespace for the deployment.
204
208
  * @returns {Promise<void>} - Promise that resolves when the manifest is built.
205
209
  * @memberof UnderpostDeploy
206
210
  */
@@ -208,6 +212,7 @@ spec:
208
212
  const resources = UnderpostDeploy.API.resourcesFactory();
209
213
  const replicas = options.replicas;
210
214
  const image = options.image;
215
+ if (!options.namespace) options.namespace = 'default';
211
216
 
212
217
  for (const _deployId of deployList.split(',')) {
213
218
  const deployId = _deployId.trim();
@@ -235,6 +240,7 @@ ${UnderpostDeploy.API.deploymentYamlPartsFactory({
235
240
  resources,
236
241
  replicas,
237
242
  image,
243
+ namespace: options.namespace,
238
244
  }).replace('{{ports}}', buildKindPorts(fromPort, toPort))}
239
245
  `;
240
246
  }
@@ -257,6 +263,7 @@ apiVersion: projectcontour.io/v1
257
263
  kind: HTTPProxy
258
264
  metadata:
259
265
  name: ${host}
266
+ namespace: ${options.namespace}
260
267
  spec:
261
268
  virtualhost:
262
269
  fqdn: ${host}${
@@ -316,16 +323,18 @@ spec:
316
323
  /**
317
324
  * Builds a Certificate resource for a host using cert-manager.
318
325
  * @param {string} host - Hostname for which the certificate is being built.
326
+ * @param {string} namespace - Kubernetes namespace for the certificate.
319
327
  * @returns {string} - Certificate resource YAML for the specified host.
320
328
  * @memberof UnderpostDeploy
321
329
  */
322
- buildCertManagerCertificate({ host }) {
330
+ buildCertManagerCertificate({ host, namespace }) {
323
331
  return `
324
332
  ---
325
333
  apiVersion: cert-manager.io/v1
326
334
  kind: Certificate
327
335
  metadata:
328
336
  name: ${host}
337
+ namespace: ${namespace}
329
338
  spec:
330
339
  commonName: ${host}
331
340
  dnsNames:
@@ -376,6 +385,7 @@ spec:
376
385
  * @param {boolean} options.status - Whether to display deployment status.
377
386
  * @param {boolean} options.etcHosts - Whether to display the /etc/hosts file.
378
387
  * @param {boolean} options.disableUpdateUnderpostConfig - Whether to disable Underpost config updates.
388
+ * @param {string} [options.namespace] - Kubernetes namespace for the deployment.
379
389
  * @returns {Promise<void>} - Promise that resolves when the deployment process is complete.
380
390
  * @memberof UnderpostDeploy
381
391
  */
@@ -404,6 +414,7 @@ spec:
404
414
  status: false,
405
415
  etcHosts: false,
406
416
  disableUpdateUnderpostConfig: false,
417
+ namespace: '',
407
418
  },
408
419
  ) {
409
420
  if (options.infoUtil === true)
@@ -437,7 +448,6 @@ kubectl wait --for=jsonpath='{.status.phase}'=Running pod/busybox1
437
448
  kubectl wait --for='jsonpath={.status.conditions[?(@.type=="Ready")].status}=True' pod/busybox1
438
449
  kubectl wait --for=delete pod/busybox1 --timeout=60s
439
450
 
440
- fqdn: <service>.<namespace>.<kind(svc/pod)>.<cluster-domain(cluster.local)>
441
451
  node bin run cluster-build
442
452
  node bin run template-deploy
443
453
  node bin run ssh-deploy (sync-)engine-core
@@ -447,6 +457,7 @@ node bin run promote dd-default production
447
457
  node bin dockerfile-pull-base-images --dev --path 'image-path' --kind-load
448
458
  node bin/deploy update-default-conf <deploy-id>
449
459
 
460
+ fqdn: <service>.<namespace>.<kind(svc/pod)>.<cluster-domain(cluster.local)>
450
461
  kubectl run --rm -it test-dns --image=busybox:latest --restart=Never -- /bin/sh -c "
451
462
  nslookup kubernetes.default.svc.cluster.local;
452
463
  nslookup mongodb-service.default.svc.cluster.local;
@@ -468,10 +479,11 @@ docker login nvcr.io
468
479
  Username: $oauthtoken
469
480
  Password: <Your Key>
470
481
  `);
482
+ const namespace = options.namespace ? options.namespace : 'default';
471
483
  if (!deployList && options.certHosts) {
472
484
  for (const host of options.certHosts.split(',')) {
473
- shellExec(`sudo kubectl apply -f - <<EOF
474
- ${UnderpostDeploy.API.buildCertManagerCertificate({ host })}
485
+ shellExec(`sudo kubectl apply -f - -n ${namespace} <<EOF
486
+ ${UnderpostDeploy.API.buildCertManagerCertificate({ host, namespace })}
475
487
  EOF`);
476
488
  }
477
489
  return;
@@ -540,8 +552,12 @@ EOF`);
540
552
 
541
553
  if (!options.disableUpdateDeployment)
542
554
  for (const version of options.versions.split(',')) {
543
- shellExec(`sudo kubectl delete svc ${deployId}-${env}-${version}-service`);
544
- shellExec(`sudo kubectl delete deployment ${deployId}-${env}-${version}`);
555
+ shellExec(
556
+ `sudo kubectl delete svc ${deployId}-${env}-${version}-service -n ${namespace} --ignore-not-found`,
557
+ );
558
+ shellExec(
559
+ `sudo kubectl delete deployment ${deployId}-${env}-${version} -n ${namespace} --ignore-not-found`,
560
+ );
545
561
  if (!options.disableUpdateVolume) {
546
562
  for (const volume of confVolume) {
547
563
  const pvcId = `${volume.claimName}-${deployId}-${env}-${version}`;
@@ -549,9 +565,9 @@ EOF`);
549
565
  const rootVolumeHostPath = `/home/dd/engine/volume/${pvId}`;
550
566
  if (!fs.existsSync(rootVolumeHostPath)) fs.mkdirSync(rootVolumeHostPath, { recursive: true });
551
567
  fs.copySync(volume.volumeMountPath, rootVolumeHostPath);
552
- shellExec(`kubectl delete pvc ${pvcId}`);
553
- shellExec(`kubectl delete pv ${pvId}`);
554
- shellExec(`kubectl apply -f - <<EOF
568
+ shellExec(`kubectl delete pvc ${pvcId} -n ${namespace} --ignore-not-found`);
569
+ shellExec(`kubectl delete pv ${pvId} --ignore-not-found`);
570
+ shellExec(`kubectl apply -f - -n ${namespace} <<EOF
555
571
  ${UnderpostDeploy.API.persistentVolumeFactory({
556
572
  hostPath: rootVolumeHostPath,
557
573
  pvcId,
@@ -564,9 +580,9 @@ EOF
564
580
 
565
581
  for (const host of Object.keys(confServer)) {
566
582
  if (!options.disableUpdateProxy) {
567
- shellExec(`sudo kubectl delete HTTPProxy ${host}`);
583
+ shellExec(`sudo kubectl delete HTTPProxy ${host} -n ${namespace} --ignore-not-found`);
568
584
  if (UnderpostDeploy.API.isValidTLSContext({ host, env, options }))
569
- shellExec(`sudo kubectl delete Certificate ${host}`);
585
+ shellExec(`sudo kubectl delete Certificate ${host} -n ${namespace} --ignore-not-found`);
570
586
  }
571
587
  if (!options.remove) etcHosts.push(host);
572
588
  }
@@ -577,11 +593,13 @@ EOF
577
593
  : `manifests/deployment/${deployId}-${env}`;
578
594
 
579
595
  if (!options.remove) {
580
- if (!options.disableUpdateDeployment) shellExec(`sudo kubectl apply -f ./${manifestsPath}/deployment.yaml`);
581
- if (!options.disableUpdateProxy) shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml`);
596
+ if (!options.disableUpdateDeployment)
597
+ shellExec(`sudo kubectl apply -f ./${manifestsPath}/deployment.yaml -n ${namespace}`);
598
+ if (!options.disableUpdateProxy)
599
+ shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml -n ${namespace}`);
582
600
 
583
601
  if (UnderpostDeploy.API.isValidTLSContext({ host: Object.keys(confServer)[0], env, options }))
584
- shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml`);
602
+ shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml -n ${namespace}`);
585
603
  }
586
604
  }
587
605
  if (options.etcHosts === true) {
@@ -598,15 +616,19 @@ EOF
598
616
  * Retrieves information about a deployment.
599
617
  * @param {string} deployId - Deployment ID for which information is being retrieved.
600
618
  * @param {string} kindType - Type of Kubernetes resource to retrieve information for (e.g. 'pods').
619
+ * @param {string} namespace - Kubernetes namespace to retrieve information from.
601
620
  * @returns {Array<object>} - Array of objects containing information about the deployment.
602
621
  * @memberof UnderpostDeploy
603
622
  */
604
- get(deployId, kindType = 'pods') {
605
- const raw = shellExec(`sudo kubectl get ${kindType} --all-namespaces -o wide`, {
606
- stdout: true,
607
- disableLog: true,
608
- silent: true,
609
- });
623
+ get(deployId, kindType = 'pods', namespace = '') {
624
+ const raw = shellExec(
625
+ `sudo kubectl get ${kindType}${namespace ? ` -n ${namespace}` : ` --all-namespaces`} -o wide`,
626
+ {
627
+ stdout: true,
628
+ disableLog: true,
629
+ silent: true,
630
+ },
631
+ );
610
632
 
611
633
  const heads = raw
612
634
  .split(`\n`)[0]
@@ -720,12 +742,13 @@ EOF
720
742
  /**
721
743
  * Creates a configmap for a deployment.
722
744
  * @param {string} env - Environment for which the configmap is being created.
745
+ * @param {string} [namespace='default'] - Kubernetes namespace for the configmap.
723
746
  * @memberof UnderpostDeploy
724
747
  */
725
- configMap(env) {
726
- shellExec(`kubectl delete configmap underpost-config`);
748
+ configMap(env, namespace = 'default') {
749
+ shellExec(`kubectl delete configmap underpost-config -n ${namespace} --ignore-not-found`);
727
750
  shellExec(
728
- `kubectl create configmap underpost-config --from-file=/home/dd/engine/engine-private/conf/dd-cron/.env.${env}`,
751
+ `kubectl create configmap underpost-config --from-file=/home/dd/engine/engine-private/conf/dd-cron/.env.${env} --dry-run=client -o yaml | kubectl apply -f - -n ${namespace}`,
729
752
  );
730
753
  },
731
754
  /**
@@ -734,14 +757,15 @@ EOF
734
757
  * @param {string} env - Environment for which the traffic is being switched.
735
758
  * @param {string} targetTraffic - Target traffic status for the deployment.
736
759
  * @param {number} replicas - Number of replicas for the deployment.
760
+ * @param {string} [namespace='default'] - Kubernetes namespace for the deployment.
737
761
  * @memberof UnderpostDeploy
738
762
  */
739
- switchTraffic(deployId, env, targetTraffic, replicas = 1) {
763
+ switchTraffic(deployId, env, targetTraffic, replicas = 1, namespace = 'default') {
740
764
  UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, targetTraffic);
741
765
  shellExec(
742
- `node bin deploy --info-router --build-manifest --traffic ${targetTraffic} --replicas ${replicas} ${deployId} ${env}`,
766
+ `node bin deploy --info-router --build-manifest --traffic ${targetTraffic} --replicas ${replicas} --namespace ${namespace} ${deployId} ${env}`,
743
767
  );
744
- shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
768
+ shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml -n ${namespace}`);
745
769
  },
746
770
 
747
771
  /**
@@ -909,7 +933,7 @@ ${renderHosts}`,
909
933
  getCurrentLoadedImages(node = 'kind-worker', specContainers = false) {
910
934
  if (specContainers) {
911
935
  const raw = shellExec(
912
- `kubectl get pods --all-namespaces -o=jsonpath='{range .items[*]}{"\\n"}{.metadata.name}{":\\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}'`,
936
+ `kubectl get pods --all-namespaces -o=jsonpath='{range .items[*]}{"\\n"}{.metadata.namespace}{"/"}{.metadata.name}{":\\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}'`,
913
937
  {
914
938
  stdout: true,
915
939
  silent: true,
package/src/cli/index.js CHANGED
@@ -7,6 +7,8 @@ import { commitData } from '../client/components/core/CommonJs.js';
7
7
  import UnderpostLxd from './lxd.js';
8
8
  import UnderpostBaremetal from './baremetal.js';
9
9
  import UnderpostRun from './run.js';
10
+ import Dns from '../server/dns.js';
11
+ import { pbcopy } from '../server/process.js';
10
12
 
11
13
  // Load environment variables from .env file
12
14
  const underpostRootPath = getUnderpostRootPath();
@@ -129,6 +131,16 @@ program
129
131
  .description('Displays the root path of the npm installation.')
130
132
  .action(() => console.log(getNpmRootPath()));
131
133
 
134
+ program
135
+ .command('ip')
136
+ .option('--copy', 'Copies the IP addresses to the clipboard.')
137
+ .description('Displays the current public machine IP addresses.')
138
+ .action(async (options) => {
139
+ const ip = await Dns.getPublicIp();
140
+ if (options.copy) return pbcopy(ip);
141
+ return console.log(ip);
142
+ });
143
+
132
144
  // 'cluster' command: Manage Kubernetes clusters
133
145
  program
134
146
  .command('cluster')
@@ -146,7 +158,10 @@ program
146
158
  .option('--dedicated-gpu', 'Initializes the cluster with dedicated GPU base resources and environment settings.')
147
159
  .option('--info', 'Retrieves information about all deployed Kubernetes objects.')
148
160
  .option('--full', 'Initializes the cluster with all available statefulsets and services.')
149
- .option('--ns-use <ns-name>', 'Switches the current Kubernetes context to the specified namespace.')
161
+ .option(
162
+ '--ns-use <ns-name>',
163
+ "Switches the current Kubernetes context to the specified namespace (creates if it doesn't exist).",
164
+ )
150
165
  .option('--kubeadm', 'Initializes the cluster using kubeadm for control plane management.')
151
166
  .option('--grafana', 'Initializes the cluster with a Grafana deployment.')
152
167
  .option(
@@ -166,6 +181,7 @@ program
166
181
  .option('--k3s', 'Initializes the cluster using K3s (Lightweight Kubernetes).')
167
182
  .option('--hosts <hosts>', 'A comma-separated list of cluster hostnames or IP addresses.')
168
183
  .option('--remove-volume-host-paths', 'Removes specified volume host paths after execution.')
184
+ .option('--namespace <namespace>', 'Kubernetes namespace for cluster operations (defaults to "default").')
169
185
  .action(Underpost.cluster.init)
170
186
  .description('Manages Kubernetes clusters, defaulting to Kind cluster initialization.');
171
187
 
@@ -205,6 +221,7 @@ program
205
221
  .option('--etc-hosts', 'Enables the etc-hosts context for deployment operations.')
206
222
  .option('--restore-hosts', 'Restores default `/etc/hosts` entries.')
207
223
  .option('--disable-update-underpost-config', 'Disables updates to Underpost configuration during deployment.')
224
+ .option('--namespace <namespace>', 'Kubernetes namespace for deployment operations (defaults to "default").')
208
225
  .description('Manages application deployments, defaulting to deploying development pods.')
209
226
  .action(Underpost.deploy.callback);
210
227
 
@@ -402,6 +419,10 @@ program
402
419
  .option('--runtime-class-name <name>', 'Sets the runtime class name for the job in deploy-job.')
403
420
  .option('--image-pull-policy <policy>', 'Sets the image pull policy for the job in deploy-job.')
404
421
  .option('--api-version <version>', 'Sets the API version for the job manifest in deploy-job.')
422
+ .option(
423
+ '--labels <labels>',
424
+ 'Optional: Specifies a comma-separated list of key-value pairs for labels (e.g., "app=my-app,env=prod").',
425
+ )
405
426
  .option('--claim-name <name>', 'Optional: Specifies the claim name for volume mounting in deploy-job.')
406
427
  .option('--kind <kind-type>', 'Specifies the kind of Kubernetes resource (e.g., Job, Deployment) for deploy-job.')
407
428
  .option('--kubeadm', 'Flag to indicate Kubeadm cluster type context')
@@ -95,12 +95,13 @@ class UnderpostMonitor {
95
95
  if (traffic === 'blue') traffic = 'green';
96
96
  else traffic = 'blue';
97
97
  UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, traffic);
98
+ const namespace = options.namespace || 'default';
98
99
  shellExec(
99
100
  `node bin deploy --info-router --build-manifest --traffic ${traffic} --replicas ${
100
101
  options.replicas ? options.replicas : 1
101
- } ${deployId} ${env}`,
102
+ } --namespace ${namespace} ${deployId} ${env}`,
102
103
  );
103
- shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
104
+ shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml -n ${namespace}`);
104
105
  };
105
106
 
106
107
  const monitor = async (reject) => {
@@ -152,12 +153,15 @@ class UnderpostMonitor {
152
153
  fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'),
153
154
  );
154
155
 
155
- UnderpostDeploy.API.configMap(env);
156
+ const namespace = options.namespace || 'default';
157
+ UnderpostDeploy.API.configMap(env, namespace);
156
158
 
157
159
  for (const host of Object.keys(confServer)) {
158
- shellExec(`sudo kubectl delete HTTPProxy ${host}`);
160
+ shellExec(`sudo kubectl delete HTTPProxy ${host} -n ${namespace} --ignore-not-found`);
159
161
  }
160
- shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${traffic}`);
162
+ shellExec(
163
+ `sudo kubectl rollout restart deployment/${deployId}-${env}-${traffic} -n ${namespace}`,
164
+ );
161
165
 
162
166
  switchTraffic();
163
167
  }
@@ -179,7 +179,7 @@ class UnderpostRepository {
179
179
  return;
180
180
  }
181
181
  if (options.info) return logger.info('', commitData);
182
- const _message = `${commitType}${subModule ? `(${subModule})` : ''}${process.argv.includes('!') ? '!' : ''}: ${
182
+ const _message = `${commitType}${subModule ? `(${subModule})` : ''}: ${
183
183
  commitData[commitType].emoji
184
184
  } ${message ? message : commitData[commitType].description}`;
185
185
  if (options.copy) return pbcopy(_message);