underpost 2.89.1 → 2.89.21

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/src/cli/deploy.js CHANGED
@@ -91,12 +91,25 @@ class UnderpostDeploy {
91
91
  * @param {Array<string>} deploymentVersions - List of deployment versions.
92
92
  * @returns {string} - YAML service configuration for the specified deployment.
93
93
  * @param {string} [serviceId] - Custom service name (optional).
94
+ * @param {Array} [pathRewritePolicy] - Path rewrite policy (optional).
94
95
  * @memberof UnderpostDeploy
95
96
  */
96
- deploymentYamlServiceFactory({ deployId, path, env, port, deploymentVersions, serviceId }) {
97
+ deploymentYamlServiceFactory({ deployId, path, env, port, deploymentVersions, serviceId, pathRewritePolicy }) {
97
98
  return `
98
99
  - conditions:
99
100
  - prefix: ${path}
101
+ ${
102
+ pathRewritePolicy
103
+ ? `pathRewritePolicy:
104
+ replacePrefix:
105
+ ${pathRewritePolicy.map(
106
+ (rd) => `- prefix: ${rd.prefix}
107
+ replacement: ${rd.replacement}
108
+ `,
109
+ ).join(`
110
+ `)}`
111
+ : ''
112
+ }
100
113
  enableWebsockets: true
101
114
  services:
102
115
  ${deploymentVersions
@@ -121,6 +134,17 @@ class UnderpostDeploy {
121
134
  */
122
135
  deploymentYamlPartsFactory({ deployId, env, suffix, resources, replicas, image }) {
123
136
  const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
137
+ let volumes = [
138
+ {
139
+ volumeMountPath: '/etc/config',
140
+ volumeName: 'config-volume',
141
+ configMap: 'underpost-config',
142
+ },
143
+ ];
144
+ const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
145
+ ? JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.volume.json`, 'utf8'))
146
+ : [];
147
+ volumes = volumes.concat(confVolume);
124
148
  return `apiVersion: apps/v1
125
149
  kind: Deployment
126
150
  metadata:
@@ -155,13 +179,10 @@ spec:
155
179
  npm install -g underpost &&
156
180
  underpost secret underpost --create-from-file /etc/config/.env.${env} &&
157
181
  underpost start --build --run ${deployId} ${env}
158
- volumeMounts:
159
- - name: config-volume
160
- mountPath: /etc/config
161
- volumes:
162
- - name: config-volume
163
- configMap:
164
- name: underpost-config
182
+ ${UnderpostDeploy.API.volumeFactory(volumes)
183
+ .render.split(`\n`)
184
+ .map((l) => ' ' + l)
185
+ .join(`\n`)}
165
186
  ---
166
187
  apiVersion: v1
167
188
  kind: Service
@@ -230,7 +251,7 @@ ${UnderpostDeploy.API.deploymentYamlPartsFactory({
230
251
 
231
252
  const pathPortAssignment = pathPortAssignmentData[host];
232
253
  // logger.info('', { host, pathPortAssignment });
233
- proxyYaml += `
254
+ let _proxyYaml = `
234
255
  ---
235
256
  apiVersion: projectcontour.io/v1
236
257
  kind: HTTPProxy
@@ -248,32 +269,31 @@ spec:
248
269
  routes:`;
249
270
  const deploymentVersions =
250
271
  options.traffic && typeof options.traffic === 'string' ? options.traffic.split(',') : ['blue'];
251
- for (const conditionObj of pathPortAssignment) {
252
- const { path, port } = conditionObj;
253
- proxyYaml += UnderpostDeploy.API.deploymentYamlServiceFactory({
254
- path,
255
- deployId,
256
- env,
257
- port,
258
- deploymentVersions,
259
- });
260
- }
272
+ let proxyRoutes = '';
273
+ if (!options.disableDeploymentProxy)
274
+ for (const conditionObj of pathPortAssignment) {
275
+ const { path, port } = conditionObj;
276
+ proxyRoutes += UnderpostDeploy.API.deploymentYamlServiceFactory({
277
+ path,
278
+ deployId,
279
+ env,
280
+ port,
281
+ deploymentVersions,
282
+ });
283
+ }
261
284
  for (const customService of customServices) {
262
- const { path: _path, port, serviceId, host: _host } = customService;
285
+ const { path: _path, port, serviceId, host: _host, pathRewritePolicy } = customService;
263
286
  if (host === _host) {
264
- switch (serviceId) {
265
- case 'mongo-express-service': {
266
- proxyYaml += UnderpostDeploy.API.deploymentYamlServiceFactory({
267
- path: _path,
268
- port,
269
- serviceId,
270
- deploymentVersions,
271
- });
272
- break;
273
- }
274
- }
287
+ proxyRoutes += UnderpostDeploy.API.deploymentYamlServiceFactory({
288
+ path: _path,
289
+ port,
290
+ serviceId,
291
+ deploymentVersions,
292
+ pathRewritePolicy,
293
+ });
275
294
  }
276
295
  }
296
+ if (proxyRoutes) proxyYaml += _proxyYaml + proxyRoutes;
277
297
  }
278
298
  const yamlPath = `./engine-private/conf/${deployId}/build/${env}/proxy.yaml`;
279
299
  fs.writeFileSync(yamlPath, proxyYaml, 'utf8');
@@ -332,28 +352,31 @@ spec:
332
352
 
333
353
  /**
334
354
  * Callback function for handling deployment options.
335
- * @param {string} deployList - List of deployment IDs to include in the manifest.
336
- * @param {string} env - Environment for which the manifest is being built.
337
- * @param {object} options - Options for the manifest build process.
338
- * @param {string} options.remove - Whether to remove the deployment.
339
- * @param {string} options.infoRouter - Whether to display router information.
340
- * @param {string} options.sync - Whether to synchronize the deployment.
341
- * @param {string} options.buildManifest - Whether to build the manifest.
342
- * @param {string} options.infoUtil - Whether to display utility information.
343
- * @param {string} options.expose - Whether to expose the deployment.
344
- * @param {string} options.cert - Whether to create a certificate.
345
- * @param {string} options.certHosts - List of hosts for which certificates are being created.
355
+ * @param {string} deployList - List of deployment IDs to process.
356
+ * @param {string} env - Environment for which the deployment is being processed.
357
+ * @param {object} options - Options for the deployment process.
358
+ * @param {boolean} options.remove - Whether to remove the deployment.
359
+ * @param {boolean} options.infoRouter - Whether to display router information.
360
+ * @param {boolean} options.sync - Whether to synchronize deployment configurations.
361
+ * @param {boolean} options.buildManifest - Whether to build the deployment manifest.
362
+ * @param {boolean} options.infoUtil - Whether to display utility information.
363
+ * @param {boolean} options.expose - Whether to expose the deployment.
364
+ * @param {boolean} options.cert - Whether to create certificates for the deployment.
365
+ * @param {string} options.certHosts - Comma-separated list of hosts for which to create certificates.
346
366
  * @param {string} options.versions - Comma-separated list of versions to deploy.
347
367
  * @param {string} options.image - Docker image for the deployment.
348
- * @param {string} options.traffic - Current traffic status for the deployment.
368
+ * @param {string} options.traffic - Traffic status for the deployment.
349
369
  * @param {string} options.replicas - Number of replicas for the deployment.
350
370
  * @param {string} options.node - Node name for resource allocation.
351
- * @param {string} options.restoreHosts - Whether to restore hosts.
352
- * @param {string} options.disableUpdateDeployment - Whether to disable updating the deployment.
353
- * @param {string} options.disableUpdateProxy - Whether to disable updating the proxy.
354
- * @param {string} options.status - Whether to display status host machine server and traffic information.
355
- * @param {string} options.etcHosts - Whether to update /etc/hosts.
356
- * @returns {Promise<void>} - Promise that resolves when the callback is complete.
371
+ * @param {boolean} options.restoreHosts - Whether to restore the hosts file.
372
+ * @param {boolean} options.disableUpdateDeployment - Whether to disable deployment updates.
373
+ * @param {boolean} options.disableUpdateProxy - Whether to disable proxy updates.
374
+ * @param {boolean} options.disableDeploymentProxy - Whether to disable deployment proxy.
375
+ * @param {boolean} options.disableUpdateVolume - Whether to disable volume updates.
376
+ * @param {boolean} options.status - Whether to display deployment status.
377
+ * @param {boolean} options.etcHosts - Whether to display the /etc/hosts file.
378
+ * @param {boolean} options.disableUpdateUnderpostConfig - Whether to disable Underpost config updates.
379
+ * @returns {Promise<void>} - Promise that resolves when the deployment process is complete.
357
380
  * @memberof UnderpostDeploy
358
381
  */
359
382
  async callback(
@@ -376,8 +399,11 @@ spec:
376
399
  restoreHosts: false,
377
400
  disableUpdateDeployment: false,
378
401
  disableUpdateProxy: false,
402
+ disableDeploymentProxy: false,
403
+ disableUpdateVolume: false,
379
404
  status: false,
380
405
  etcHosts: false,
406
+ disableUpdateUnderpostConfig: false,
381
407
  },
382
408
  ) {
383
409
  if (options.infoUtil === true)
@@ -473,7 +499,7 @@ EOF`);
473
499
  logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
474
500
  return;
475
501
  }
476
- UnderpostDeploy.API.configMap(env);
502
+ if (!options.disableUpdateUnderpostConfig) UnderpostDeploy.API.configMap(env);
477
503
  let renderHosts = '';
478
504
  let etcHosts = [];
479
505
  if (options.restoreHosts === true) {
@@ -504,6 +530,26 @@ EOF`);
504
530
  }
505
531
 
506
532
  const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
533
+ const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
534
+ ? JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.volume.json`, 'utf8'))
535
+ : [];
536
+
537
+ if (!options.disableUpdateVolume) {
538
+ for (const volume of confVolume) {
539
+ if (options.remove) {
540
+ shellExec(`kubectl delete pvc ${volume.claimName}`);
541
+ shellExec(`kubectl delete pv ${volume.claimName.replace('pvc-', 'pv-')}`);
542
+ continue;
543
+ }
544
+ shellExec(`kubectl apply -f - <<EOF
545
+ ${UnderpostDeploy.API.persistentVolumeFactory({
546
+ hostPath: volume.volumeMountPath,
547
+ pvcId: volume.claimName,
548
+ })}
549
+ EOF
550
+ `);
551
+ }
552
+ }
507
553
 
508
554
  for (const host of Object.keys(confServer)) {
509
555
  if (!options.disableUpdateProxy) {
@@ -686,6 +732,78 @@ EOF`);
686
732
  );
687
733
  shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
688
734
  },
735
+
736
+ /**
737
+ * Creates volume mounts and volumes for a deployment.
738
+ * @param {Array<volume>} volumes - List of volume configurations.
739
+ * @param {string} volume.volumeName - Name of the volume.
740
+ * @param {string} volume.volumeMountPath - Mount path of the volume in the container.
741
+ * @param {string} volume.volumeHostPath - Host path of the volume.
742
+ * @param {string} volume.volumeType - Type of the volume (e.g. 'Directory').
743
+ * @param {string|null} volume.claimName - Name of the persistent volume claim (if applicable).
744
+ * @param {string|null} volume.configMap - Name of the config map (if applicable).
745
+ * @returns {object} - Object containing the rendered volume mounts and volumes.
746
+ * @memberof UnderpostDeploy
747
+ */
748
+ volumeFactory(
749
+ volumes = [
750
+ {
751
+ volumeName: 'volume-name',
752
+ volumeMountPath: '/path/in/container',
753
+ volumeHostPath: '/path/on/host',
754
+ volumeType: 'Directory',
755
+ claimName: null,
756
+ configMap: null,
757
+ },
758
+ ],
759
+ ) {
760
+ let _volumeMounts = `
761
+ volumeMounts:`;
762
+ let _volumes = `
763
+ volumes:`;
764
+ volumes.map((volumeData) => {
765
+ const { volumeName, volumeMountPath, volumeHostPath, volumeType, claimName, configMap } = volumeData;
766
+ _volumeMounts += `
767
+ - name: ${volumeName}
768
+ mountPath: ${volumeMountPath}
769
+ `;
770
+
771
+ _volumes += `
772
+ - name: ${volumeName}
773
+ ${
774
+ configMap
775
+ ? ` configMap:
776
+ name: ${configMap}`
777
+ : claimName
778
+ ? ` persistentVolumeClaim:
779
+ claimName: ${claimName}`
780
+ : ` hostPath:
781
+ path: ${volumeHostPath}
782
+ type: ${volumeType}
783
+ `
784
+ }
785
+
786
+ `;
787
+ });
788
+ return { render: _volumeMounts + _volumes };
789
+ },
790
+
791
+ /**
792
+ * Creates a persistent volume and persistent volume claim for a deployment.
793
+ * @param {object} options - Options for the persistent volume and claim creation.
794
+ * @param {string} options.hostPath - Host path for the persistent volume.
795
+ * @param {string} options.pvcId - Persistent volume claim ID.
796
+ * @returns {string} - YAML configuration for the persistent volume and claim.
797
+ * @memberof UnderpostDeploy
798
+ */
799
+ persistentVolumeFactory({ hostPath, pvcId }) {
800
+ return fs
801
+ .readFileSync(`./manifests/pv-pvc-dd.yaml`, 'utf8')
802
+ .replace('/home/dd', hostPath)
803
+ .replace('pv-dd', pvcId.replace('pvc-', 'pv-'))
804
+ .replace('pvc-dd', pvcId);
805
+ },
806
+
689
807
  /**
690
808
  * Creates a hosts file for a deployment.
691
809
  * @param {Array<string>} hosts - List of hosts to be added to the hosts file.
@@ -789,37 +907,35 @@ ${renderHosts}`,
789
907
  }))
790
908
  .filter((o) => o.image);
791
909
  }
792
- if (node === 'kind-worker') {
793
- const raw = shellExec(`docker exec -i ${node} crictl images`, {
794
- stdout: true,
795
- silent: true,
796
- });
910
+ const raw = shellExec(node === 'kind-worker' ? `docker exec -i ${node} crictl images` : `crictl images`, {
911
+ stdout: true,
912
+ silent: true,
913
+ });
797
914
 
798
- const heads = raw
799
- .split(`\n`)[0]
800
- .split(' ')
801
- .filter((_r) => _r.trim());
915
+ const heads = raw
916
+ .split(`\n`)[0]
917
+ .split(' ')
918
+ .filter((_r) => _r.trim());
802
919
 
803
- const pods = raw
804
- .split(`\n`)
805
- .filter((r) => !r.match('IMAGE'))
806
- .map((r) => r.split(' ').filter((_r) => _r.trim()));
807
-
808
- const result = [];
809
-
810
- for (const row of pods) {
811
- if (row.length === 0) continue;
812
- const pod = {};
813
- let index = -1;
814
- for (const head of heads) {
815
- if (head in pod) continue;
816
- index++;
817
- pod[head] = row[index];
818
- }
819
- result.push(pod);
920
+ const pods = raw
921
+ .split(`\n`)
922
+ .filter((r) => !r.match('IMAGE'))
923
+ .map((r) => r.split(' ').filter((_r) => _r.trim()));
924
+
925
+ const result = [];
926
+
927
+ for (const row of pods) {
928
+ if (row.length === 0) continue;
929
+ const pod = {};
930
+ let index = -1;
931
+ for (const head of heads) {
932
+ if (head in pod) continue;
933
+ index++;
934
+ pod[head] = row[index];
820
935
  }
821
- return result;
936
+ result.push(pod);
822
937
  }
938
+ return result;
823
939
  },
824
940
  };
825
941
  }
package/src/cli/index.js CHANGED
@@ -78,6 +78,8 @@ program
78
78
  .option('--msg <msg>', 'Sets a custom commit message.')
79
79
  .option('--deploy-id <deploy-id>', 'Sets the deployment configuration ID for the commit context.')
80
80
  .option('--cached', 'Commit staged changes only or context.')
81
+ .option('--hashes <hashes>', 'Comma-separated list of specific file hashes of commits.')
82
+ .option('--extension <extension>', 'specific file extensions of commits.')
81
83
  .description('Manages commits to a GitHub repository, supporting various commit types and options.')
82
84
  .action(Underpost.repo.commit);
83
85
 
@@ -162,6 +164,8 @@ program
162
164
  .option('--worker', 'Sets the context for a worker node.')
163
165
  .option('--chown', 'Sets the appropriate ownership for Kubernetes kubeconfig files.')
164
166
  .option('--k3s', 'Initializes the cluster using K3s (Lightweight Kubernetes).')
167
+ .option('--hosts <hosts>', 'A comma-separated list of cluster hostnames or IP addresses.')
168
+ .option('--remove-volume-host-paths', 'Removes specified volume host paths after execution.')
165
169
  .action(Underpost.cluster.init)
166
170
  .description('Manages Kubernetes clusters, defaulting to Kind cluster initialization.');
167
171
 
@@ -191,6 +195,8 @@ program
191
195
  .option('--traffic <traffic-versions>', 'A comma-separated list of custom deployment traffic weights.')
192
196
  .option('--disable-update-deployment', 'Disables updates to deployments.')
193
197
  .option('--disable-update-proxy', 'Disables updates to proxies.')
198
+ .option('--disable-deployment-proxy', 'Disables proxies of deployments.')
199
+ .option('--disable-update-volume', 'Disables updates to volume mounts during deployment.')
194
200
  .option(
195
201
  '--status',
196
202
  'Retrieves current network traffic data from resource deployments and the host machine network configuration.',
@@ -198,6 +204,7 @@ program
198
204
  .option('--kubeadm', 'Enables the kubeadm context for deployment operations.')
199
205
  .option('--etc-hosts', 'Enables the etc-hosts context for deployment operations.')
200
206
  .option('--restore-hosts', 'Restores default `/etc/hosts` entries.')
207
+ .option('--disable-update-underpost-config', 'Disables updates to Underpost configuration during deployment.')
201
208
  .description('Manages application deployments, defaulting to deploying development pods.')
202
209
  .action(Underpost.deploy.callback);
203
210
 
@@ -379,13 +386,24 @@ program
379
386
  .option('--dev', 'Sets the development context environment for the script.')
380
387
  .option('--build', 'Set builder context runner')
381
388
  .option('--replicas <replicas>', 'Sets a custom number of replicas for deployment.')
382
- .option('--pod-name <pod-name>', 'Optional: Specifies the pod name for test execution.')
389
+ .option('--pod-name <pod-name>', 'Optional: Specifies the pod name for execution.')
390
+ .option('--node-name <node-name>', 'Optional: Specifies the node name for execution.')
391
+ .option('--port <port>', 'Optional: Specifies the port for execution.')
392
+ .option('--etc-hosts', 'Enables etc-hosts context for the runner execution.')
383
393
  .option('--volume-host-path <volume-host-path>', 'Optional: Specifies the volume host path for test execution.')
384
394
  .option('--volume-mount-path <volume-mount-path>', 'Optional: Specifies the volume mount path for test execution.')
385
395
  .option('--volume-type <volume-type>', 'Optional: Specifies the volume type for test execution.')
386
396
  .option('--image-name <image-name>', 'Optional: Specifies the image name for test execution.')
387
397
  .option('--container-name <container-name>', 'Optional: Specifies the container name for test execution.')
388
398
  .option('--namespace <namespace>', 'Optional: Specifies the namespace for test execution.')
399
+ .option('--tty', 'Enables TTY for the container in deploy-job.')
400
+ .option('--stdin', 'Keeps STDIN open for the container in deploy-job.')
401
+ .option('--restart-policy <policy>', 'Sets the restart policy for the job in deploy-job.')
402
+ .option('--runtime-class-name <name>', 'Sets the runtime class name for the job in deploy-job.')
403
+ .option('--image-pull-policy <policy>', 'Sets the image pull policy for the job in deploy-job.')
404
+ .option('--api-version <version>', 'Sets the API version for the job manifest in deploy-job.')
405
+ .option('--claim-name <name>', 'Optional: Specifies the claim name for volume mounting in deploy-job.')
406
+ .option('--kind <kind-type>', 'Specifies the kind of Kubernetes resource (e.g., Job, Deployment) for deploy-job.')
389
407
  .option('--kubeadm', 'Flag to indicate Kubeadm cluster type context')
390
408
  .option('--k3s', 'Flag to indicate K3s cluster type context')
391
409
  .option('--force', 'Forces operation, overriding any warnings or conflicts.')
@@ -394,6 +412,7 @@ program
394
412
  .option('--terminal', 'Enables terminal mode for interactive script execution.')
395
413
  .option('--dev-proxy-port-offset <port-offset>', 'Sets a custom port offset for development proxy.')
396
414
  .option('--conf-server-path <conf-server-path>', 'Sets a custom configuration server path.')
415
+ .option('--underpost-root <underpost-root>', 'Sets a custom Underpost root path.')
397
416
  .description('Runs a script from the specified path.')
398
417
  .action(UnderpostRun.API.callback);
399
418
 
@@ -89,6 +89,8 @@ class UnderpostRepository {
89
89
  * @param {boolean} [options.lastMsg=0] - If greater than 0, copies or show the last last single n commit message to clipboard.
90
90
  * @param {string} [options.msg=''] - If provided, outputs this message instead of committing.
91
91
  * @param {string} [options.deployId=''] - An optional deploy ID to include in the commit message.
92
+ * @param {string} [options.hashes=''] - If provided with diff option, shows the diff between two hashes.
93
+ * @param {string} [options.extension=''] - If provided with diff option, filters the diff by this file extension.
92
94
  * @memberof UnderpostRepository
93
95
  */
94
96
  commit(
@@ -107,9 +109,19 @@ class UnderpostRepository {
107
109
  log: 0,
108
110
  msg: '',
109
111
  deployId: '',
112
+ hashes: '',
113
+ extension: '',
110
114
  },
111
115
  ) {
112
116
  if (!repoPath) repoPath = '.';
117
+ if (options.diff && options.hashes) {
118
+ const hashes = options.hashes.split(',');
119
+ const cmd = `git --no-pager diff ${hashes[0]} ${hashes[1] ? hashes[1] : 'HEAD'}${options.extension ? ` -- '*.${options.extension}'` : ''}`;
120
+ if (options.copy) {
121
+ pbcopy(cmd);
122
+ } else console.log(cmd);
123
+ return;
124
+ }
113
125
  if (options.msg) {
114
126
  options.msg = options.msg.replaceAll('"', '').replaceAll(`'`, '').replaceAll('`', '');
115
127
  let key = Object.keys(commitData).find((k) => k && options.msg.toLocaleLowerCase().slice(0, 16).match(k));