underpost 2.8.52 → 2.8.56

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 (40) hide show
  1. package/.github/workflows/ghpkg.yml +1 -1
  2. package/.github/workflows/npmpkg.yml +1 -1
  3. package/.github/workflows/pwa-microservices-template.page.yml +1 -1
  4. package/CHANGELOG.md +24 -0
  5. package/bin/build.js +29 -4
  6. package/bin/deploy.js +64 -52
  7. package/bin/hwt.js +0 -10
  8. package/bin/index.js +38 -16
  9. package/bin/util.js +0 -7
  10. package/conf.js +0 -2
  11. package/docker-compose.yml +1 -1
  12. package/manifests/kind-config-dev.yaml +12 -0
  13. package/manifests/mongodb-4.4/kustomization.yaml +7 -0
  14. package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
  15. package/package.json +9 -7
  16. package/src/cli/cluster.js +71 -45
  17. package/src/cli/cron.js +1 -1
  18. package/src/cli/db.js +6 -4
  19. package/src/cli/deploy.js +43 -30
  20. package/src/cli/fs.js +134 -0
  21. package/src/cli/image.js +1 -18
  22. package/src/cli/repository.js +13 -0
  23. package/src/cli/script.js +25 -1
  24. package/src/cli/test.js +39 -4
  25. package/src/db/mongo/MongooseDB.js +17 -1
  26. package/src/index.js +9 -1
  27. package/src/server/backup.js +2 -2
  28. package/src/server/client-formatted.js +2 -1
  29. package/src/server/conf.js +4 -10
  30. package/src/server/dns.js +39 -46
  31. package/src/server/downloader.js +0 -8
  32. package/test/api.test.js +0 -8
  33. package/manifests/core/kustomization.yaml +0 -11
  34. package/manifests/core/underpost-engine-backup-access.yaml +0 -16
  35. package/manifests/core/underpost-engine-backup-pv-pvc.yaml +0 -22
  36. package/manifests/core/underpost-engine-headless-service.yaml +0 -10
  37. package/manifests/core/underpost-engine-mongodb-backup-cronjob.yaml +0 -40
  38. package/manifests/core/underpost-engine-mongodb-configmap.yaml +0 -26
  39. package/manifests/core/underpost-engine-statefulset.yaml +0 -91
  40. /package/manifests/{core/underpost-engine-pv-pvc.yaml → mongodb-4.4/pv-pvc.yaml} +0 -0
@@ -1,29 +1,40 @@
1
1
  import { timer } from '../client/components/core/CommonJs.js';
2
- import { cliSpinner } from '../server/conf.js';
2
+ import { cliSpinner, getNpmRootPath } from '../server/conf.js';
3
3
  import { loggerFactory } from '../server/logger.js';
4
4
  import { shellExec } from '../server/process.js';
5
5
  import UnderpostDeploy from './deploy.js';
6
+ import UnderpostTest from './test.js';
6
7
 
7
8
  const logger = loggerFactory(import.meta);
8
9
 
9
10
  class UnderpostCluster {
10
11
  static API = {
11
12
  async init(
13
+ podName,
12
14
  options = {
13
- valkey: false,
15
+ mongodb: false,
16
+ mongodb4: false,
14
17
  mariadb: false,
15
18
  valkey: false,
16
19
  full: false,
17
20
  info: false,
18
21
  certManager: false,
22
+ listPods: false,
23
+ reset: false,
24
+ dev: false,
19
25
  nsUse: '',
20
26
  },
21
27
  ) {
22
- if (options.nsUse) {
28
+ const npmRoot = getNpmRootPath();
29
+ const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
30
+ if (options.reset === true) return await UnderpostCluster.API.reset();
31
+ if (options.listPods === true) return console.table(UnderpostDeploy.API.get(podName ?? undefined));
32
+
33
+ if (options.nsUse && typeof options.nsUse === 'string') {
23
34
  shellExec(`kubectl config set-context --current --namespace=${options.nsUse}`);
24
35
  return;
25
36
  }
26
- if (options.info) {
37
+ if (options.info === true) {
27
38
  shellExec(`kubectl config get-contexts`); // config env persisente for manage multiple clusters
28
39
  shellExec(`kubectl config get-clusters`);
29
40
  shellExec(`kubectl get nodes -o wide`); // set of nodes of a cluster
@@ -54,26 +65,28 @@ class UnderpostCluster {
54
65
  shellExec(`kubectl get crd --all-namespaces -o wide`);
55
66
  return;
56
67
  }
57
- const testClusterInit = shellExec(`kubectl get pods --all-namespaces -o wide`, {
58
- disableLog: true,
59
- silent: true,
60
- stdout: true,
61
- });
62
- if (!(testClusterInit.match('kube-system') && testClusterInit.match('kube-proxy'))) {
68
+
69
+ if (!UnderpostDeploy.API.get('kube-apiserver-kind-control-plane')[0]) {
63
70
  shellExec(`containerd config default > /etc/containerd/config.toml`);
64
71
  shellExec(`sed -i -e "s/SystemdCgroup = false/SystemdCgroup = true/g" /etc/containerd/config.toml`);
65
72
  // shellExec(`cp /etc/kubernetes/admin.conf ~/.kube/config`);
66
73
  shellExec(`sudo systemctl restart kubelet`);
67
74
  shellExec(`sudo service docker restart`);
68
- shellExec(`cd ./manifests && kind create cluster --config kind-config.yaml`);
75
+ shellExec(`sudo systemctl enable --now containerd.service`);
76
+ shellExec(`sudo systemctl restart containerd`);
77
+ shellExec(
78
+ `cd ${underpostRoot}/manifests && kind create cluster --config kind-config${
79
+ options?.dev === true ? '-dev' : ''
80
+ }.yaml`,
81
+ );
69
82
  shellExec(`sudo chown $(id -u):$(id -g) $HOME/.kube/config**`);
70
83
  } else logger.warn('Cluster already initialized');
71
84
 
72
- if (options.full || options.valkey) {
85
+ if (options.full === true || options.valkey === true) {
73
86
  shellExec(`kubectl delete statefulset service-valkey`);
74
- shellExec(`kubectl apply -k ./manifests/valkey`);
87
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey`);
75
88
  }
76
- if (options.full || options.mariadb) {
89
+ if (options.full === true || options.mariadb === true) {
77
90
  shellExec(
78
91
  `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`,
79
92
  );
@@ -81,9 +94,31 @@ class UnderpostCluster {
81
94
  `sudo kubectl create secret generic github-secret --from-literal=GITHUB_TOKEN=${process.env.GITHUB_TOKEN}`,
82
95
  );
83
96
  shellExec(`kubectl delete statefulset mariadb-statefulset`);
84
- shellExec(`kubectl apply -k ./manifests/mariadb`);
97
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb`);
85
98
  }
86
- if (options.full || options.mongodb) {
99
+ if (options.mongodb4 === true) {
100
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4`);
101
+
102
+ const deploymentName = 'mongodb-deployment';
103
+
104
+ const successInstance = await UnderpostTest.API.statusMonitor(deploymentName);
105
+
106
+ if (successInstance) {
107
+ const mongoConfig = {
108
+ _id: 'rs0',
109
+ members: [{ _id: 0, host: '127.0.0.1:27017' }],
110
+ };
111
+
112
+ const [pod] = UnderpostDeploy.API.get(deploymentName);
113
+
114
+ shellExec(
115
+ `sudo kubectl exec -i ${pod.NAME} -- mongo --quiet \
116
+ --eval 'rs.initiate(${JSON.stringify(mongoConfig)})'`,
117
+ );
118
+ }
119
+
120
+ // await UnderpostTest.API.statusMonitor('mongodb-1');
121
+ } else if (options.full === true || options.mongodb === true) {
87
122
  shellExec(
88
123
  `sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile`,
89
124
  );
@@ -91,42 +126,33 @@ class UnderpostCluster {
91
126
  `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`,
92
127
  );
93
128
  shellExec(`kubectl delete statefulset mongodb`);
94
- shellExec(`kubectl apply -k ./manifests/mongodb`);
129
+ shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb`);
95
130
 
96
- await new Promise(async (resolve) => {
97
- cliSpinner(3000, `[cluster.js] `, ` Load mongodb instance`, 'yellow', 'material');
98
- await timer(3000);
131
+ const successInstance = await UnderpostTest.API.statusMonitor('mongodb-1');
99
132
 
100
- const monitor = async () => {
101
- cliSpinner(1000, `[cluster.js] `, ` Load mongodb instance`, 'yellow', 'material');
102
- await timer(1000);
103
- if (UnderpostDeploy.API.getPods('mongodb-1').find((p) => p.STATUS === 'Running')) return resolve();
104
- return monitor();
133
+ if (successInstance) {
134
+ const mongoConfig = {
135
+ _id: 'rs0',
136
+ members: [
137
+ { _id: 0, host: 'mongodb-0.mongodb-service:27017', priority: 1 },
138
+ { _id: 1, host: 'mongodb-1.mongodb-service:27017', priority: 1 },
139
+ ],
105
140
  };
106
- await monitor();
107
- });
108
-
109
- const mongoConfig = {
110
- _id: 'rs0',
111
- members: [
112
- { _id: 0, host: 'mongodb-0.mongodb-service:27017', priority: 1 },
113
- { _id: 1, host: 'mongodb-1.mongodb-service:27017', priority: 1 },
114
- ],
115
- };
116
141
 
117
- shellExec(
118
- `sudo kubectl exec -i mongodb-0 -- mongosh --quiet --json=relaxed \
119
- --eval 'use admin' \
120
- --eval 'rs.initiate(${JSON.stringify(mongoConfig)})' \
121
- --eval 'rs.status()'`,
122
- );
142
+ shellExec(
143
+ `sudo kubectl exec -i mongodb-0 -- mongosh --quiet --json=relaxed \
144
+ --eval 'use admin' \
145
+ --eval 'rs.initiate(${JSON.stringify(mongoConfig)})' \
146
+ --eval 'rs.status()'`,
147
+ );
148
+ }
123
149
  }
124
150
 
125
- if (options.full || options.contour)
151
+ if (options.full === true || options.contour === true)
126
152
  shellExec(`kubectl apply -f https://projectcontour.io/quickstart/contour.yaml`);
127
153
 
128
- if (options.full || options.certManager) {
129
- if (!UnderpostDeploy.API.getPods('cert-manager').find((p) => p.STATUS === 'Running')) {
154
+ if (options.full === true || options.certManager === true) {
155
+ if (!UnderpostDeploy.API.get('cert-manager').find((p) => p.STATUS === 'Running')) {
130
156
  shellExec(`helm repo add jetstack https://charts.jetstack.io --force-update`);
131
157
  shellExec(
132
158
  `helm install cert-manager jetstack/cert-manager \
@@ -139,7 +165,7 @@ class UnderpostCluster {
139
165
 
140
166
  const letsEncName = 'letsencrypt-prod';
141
167
  shellExec(`sudo kubectl delete ClusterIssuer ${letsEncName}`);
142
- shellExec(`sudo kubectl apply -f ./manifests/${letsEncName}.yaml`);
168
+ shellExec(`sudo kubectl apply -f ${underpostRoot}/manifests/${letsEncName}.yaml`);
143
169
  }
144
170
  },
145
171
  reset() {
package/src/cli/cron.js CHANGED
@@ -46,7 +46,7 @@ class UnderpostCron {
46
46
  callback: async function (
47
47
  deployList = 'default',
48
48
  jobList = Object.keys(UnderpostCron.JOB),
49
- options = { disableKindCluster: false, init: false },
49
+ options = { itc: false, init: false },
50
50
  ) {
51
51
  if (options.init === true) {
52
52
  await Underpost.test.setUpInfo();
package/src/cli/db.js CHANGED
@@ -7,9 +7,9 @@ const logger = loggerFactory(import.meta);
7
7
 
8
8
  class UnderpostDB {
9
9
  static API = {
10
- async callback(deployList = 'default', options = { import: false, export: false }) {
10
+ async callback(deployList = 'default', options = { import: false, export: false, podName: false, ns: false }) {
11
11
  const newBackupTimestamp = new Date().getTime();
12
- const nameSpace = 'default';
12
+ const nameSpace = options.ns && typeof options.ns === 'string' ? options.ns : 'default';
13
13
  for (const _deployId of deployList.split(',')) {
14
14
  const deployId = _deployId.trim();
15
15
  if (!deployId) continue;
@@ -74,7 +74,8 @@ class UnderpostDB {
74
74
 
75
75
  switch (provider) {
76
76
  case 'mariadb': {
77
- const podName = `mariadb-statefulset-0`;
77
+ const podName =
78
+ options.podName && typeof options.podName === 'string' ? options.podName : `mariadb-statefulset-0`;
78
79
  const serviceName = 'mariadb';
79
80
  if (options.import === true) {
80
81
  shellExec(`sudo kubectl cp ${_toSqlPath} ${nameSpace}/${podName}:/${dbName}.sql`);
@@ -95,7 +96,8 @@ class UnderpostDB {
95
96
 
96
97
  case 'mongoose': {
97
98
  if (options.import === true) {
98
- const podName = `mongodb-0`;
99
+ const podName =
100
+ options.podName && typeof options.podName === 'string' ? options.podName : `mongodb-0`;
99
101
  shellExec(`sudo kubectl cp ${_toBsonPath} ${nameSpace}/${podName}:/${dbName}`);
100
102
  const cmd = `mongorestore -d ${dbName} /${dbName}`;
101
103
  shellExec(`sudo kubectl exec -i ${podName} -- sh -c "${cmd}"`);
package/src/cli/deploy.js CHANGED
@@ -180,28 +180,42 @@ spec:
180
180
  async callback(
181
181
  deployList = 'default',
182
182
  env = 'development',
183
- options = { remove: false, infoRouter: false, sync: false, buildManifest: false },
183
+ options = { remove: false, infoRouter: false, sync: false, buildManifest: false, infoUtil: false, expose: false },
184
184
  ) {
185
+ if (options.infoUtil === true)
186
+ return logger.info(`
187
+ kubectl rollout restart deployment/deployment-name
188
+ kubectl rollout undo deployment/deployment-name
189
+ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
190
+ `);
185
191
  if (deployList === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`))
186
192
  deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
187
193
  if (options.sync) UnderpostDeploy.API.sync(deployList);
188
194
  if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env);
189
195
  if (options.infoRouter === true)
190
196
  return logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
197
+ const etcHost = (
198
+ concat,
199
+ ) => `127.0.0.1 ${concat} localhost localhost.localdomain localhost4 localhost4.localdomain4
200
+ ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6`;
201
+ let concatHots = '';
191
202
 
192
203
  for (const _deployId of deployList.split(',')) {
193
204
  const deployId = _deployId.trim();
194
205
  if (!deployId) continue;
195
-
206
+ if (options.expose === true) {
207
+ const svc = UnderpostDeploy.API.get(deployId, 'svc')[0];
208
+ const port = parseInt(svc[`PORT(S)`].split('/TCP')[0]);
209
+ logger.info(deployId, {
210
+ svc,
211
+ port,
212
+ });
213
+ shellExec(`sudo kubectl port-forward -n default svc/${svc.NAME} ${port}:${port}`, { async: true });
214
+ continue;
215
+ }
196
216
  shellExec(`sudo kubectl delete svc ${deployId}-${env}-service`);
197
217
  shellExec(`sudo kubectl delete deployment ${deployId}-${env}`);
198
218
 
199
- const etcHost = (
200
- concat,
201
- ) => `127.0.0.1 ${concat} localhost localhost.localdomain localhost4 localhost4.localdomain4
202
- ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6`;
203
- let concatHots = '';
204
-
205
219
  const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
206
220
  for (const host of Object.keys(confServer)) {
207
221
  shellExec(`sudo kubectl delete HTTPProxy ${host}`);
@@ -219,38 +233,37 @@ spec:
219
233
  shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml`);
220
234
  if (env === 'production') shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml`);
221
235
  }
236
+ }
237
+ let renderHosts;
238
+ switch (process.platform) {
239
+ case 'linux':
240
+ {
241
+ switch (env) {
242
+ case 'development':
243
+ renderHosts = etcHost(concatHots);
244
+ fs.writeFileSync(`/etc/hosts`, renderHosts, 'utf8');
222
245
 
223
- let renderHosts;
224
-
225
- switch (process.platform) {
226
- case 'linux':
227
- {
228
- switch (env) {
229
- case 'development':
230
- renderHosts = etcHost(concatHots);
231
- fs.writeFileSync(`/etc/hosts`, renderHosts, 'utf8');
232
-
233
- break;
246
+ break;
234
247
 
235
- default:
236
- break;
237
- }
248
+ default:
249
+ break;
238
250
  }
239
- break;
251
+ }
252
+ break;
240
253
 
241
- default:
242
- break;
243
- }
254
+ default:
255
+ break;
256
+ }
257
+ if (renderHosts)
244
258
  logger.info(
245
259
  `
246
260
  ` + renderHosts,
247
261
  );
248
- }
249
262
  },
250
- getPods(deployId) {
251
- const raw = shellExec(`sudo kubectl get pods --all-namespaces -o wide`, {
263
+ get(deployId, kindType = 'pods') {
264
+ const raw = shellExec(`sudo kubectl get ${kindType} --all-namespaces -o wide`, {
252
265
  stdout: true,
253
- disableLog: false,
266
+ disableLog: true,
254
267
  silent: true,
255
268
  });
256
269
 
package/src/cli/fs.js ADDED
@@ -0,0 +1,134 @@
1
+ import { v2 as cloudinary } from 'cloudinary';
2
+ import { loggerFactory } from '../server/logger.js';
3
+ import dotenv from 'dotenv';
4
+ import AdmZip from 'adm-zip';
5
+ import * as dir from 'path';
6
+ import fs from 'fs-extra';
7
+ import { Downloader } from '../server/downloader.js';
8
+ import UnderpostRepository from './repository.js';
9
+ import { shellExec } from '../server/process.js';
10
+ dotenv.config();
11
+
12
+ const logger = loggerFactory(import.meta);
13
+
14
+ class UnderpostFileStorage {
15
+ static API = {
16
+ cloudinaryConfig() {
17
+ // https://console.cloudinary.com/
18
+ cloudinary.config({
19
+ cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
20
+ api_key: process.env.CLOUDINARY_API_KEY,
21
+ api_secret: process.env.CLOUDINARY_API_SECRET,
22
+ });
23
+ },
24
+ getStorageConf(options) {
25
+ let storage, storageConf;
26
+ if (options.deployId && typeof options.deployId === 'string') {
27
+ storageConf = `./engine-private/conf/${options.deployId}/storage.json`;
28
+ if (!fs.existsSync(storageConf)) fs.writeFileSync(storageConf, JSON.stringify({}), 'utf8');
29
+ storage = JSON.parse(fs.readFileSync(storageConf, 'utf8'));
30
+ }
31
+ return { storage, storageConf };
32
+ },
33
+ writeStorageConf(storage, storageConf) {
34
+ if (storage) fs.writeFileSync(storageConf, JSON.stringify(storage, null, 4), 'utf8');
35
+ },
36
+ async recursiveCallback(
37
+ path,
38
+ options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
39
+ ) {
40
+ const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
41
+ const files =
42
+ options.git === true
43
+ ? UnderpostRepository.API.getChangedFiles(path)
44
+ : await fs.readdir(path, { recursive: true });
45
+ for (const relativePath of files) {
46
+ const _path = path + '/' + relativePath;
47
+ if (fs.statSync(_path).isDirectory()) {
48
+ if (options.pull === true && !fs.existsSync(_path)) fs.mkdirSync(_path, { recursive: true });
49
+ continue;
50
+ }
51
+ if (options.pull === true) {
52
+ await UnderpostFileStorage.API.pull(_path, options);
53
+ } else if (!(_path in storage) || options.force === true) {
54
+ await UnderpostFileStorage.API.upload(_path, options);
55
+ if (storage) storage[_path] = {};
56
+ } else logger.warn('File already exists', _path);
57
+ }
58
+ UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
59
+ if (options.git === true) {
60
+ shellExec(`cd ${path} && git add .`);
61
+ shellExec(`underpost cmt ${path} feat`);
62
+ }
63
+ },
64
+ async callback(
65
+ path,
66
+ options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
67
+ ) {
68
+ if (options.recursive === true || options.git === true)
69
+ return await UnderpostFileStorage.API.recursiveCallback(path, options);
70
+ if (options.pull === true) return await UnderpostFileStorage.API.pull(path, options);
71
+ if (options.rm === true) return await UnderpostFileStorage.API.delete(path, options);
72
+ return await UnderpostFileStorage.API.upload(path, options);
73
+ },
74
+ async upload(path, options = { rm: false, recursive: false, deployId: '', force: false, pull: false }) {
75
+ UnderpostFileStorage.API.cloudinaryConfig();
76
+ const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
77
+ // path = UnderpostFileStorage.API.file2Zip(path);
78
+ const uploadResult = await cloudinary.uploader
79
+ .upload(path, {
80
+ public_id: path,
81
+ resource_type: 'raw',
82
+ overwrite: options.force === true ? true : false,
83
+ })
84
+ .catch((error) => {
85
+ logger.error(error, { path, stack: error.stack });
86
+ });
87
+ logger.info('upload result', uploadResult);
88
+ if (storage) storage[path] = {};
89
+ UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
90
+ return uploadResult;
91
+ },
92
+ async pull(path) {
93
+ UnderpostFileStorage.API.cloudinaryConfig();
94
+ const downloadResult = await cloudinary.utils.download_archive_url({
95
+ public_ids: [path],
96
+ resource_type: 'raw',
97
+ });
98
+ logger.info('download result', downloadResult);
99
+ await Downloader(downloadResult, path + '.zip');
100
+ path = UnderpostFileStorage.API.zip2File(path + '.zip');
101
+ fs.removeSync(path + '.zip');
102
+ },
103
+ async delete(path) {
104
+ UnderpostFileStorage.API.cloudinaryConfig();
105
+ const deleteResult = await cloudinary.api
106
+ .delete_resources([path], { type: 'upload', resource_type: 'raw' })
107
+ .catch((error) => {
108
+ logger.error(error, { path, stack: error.stack });
109
+ });
110
+ logger.info('delete result', deleteResult);
111
+ return deleteResult;
112
+ },
113
+ file2Zip(path) {
114
+ const zip = new AdmZip();
115
+ zip.addLocalFile(path, '/');
116
+ path = path + '.zip';
117
+ zip.writeZip(path);
118
+ return path;
119
+ },
120
+ zip2File(path) {
121
+ const zip = new AdmZip(path);
122
+ path = path.replaceAll('.zip', '');
123
+ zip.extractEntryTo(
124
+ /*entry name*/ path.split('/').pop(),
125
+ /*target path*/ dir.dirname(path),
126
+ /*maintainEntryPath*/ false,
127
+ /*overwrite*/ true,
128
+ );
129
+ return path;
130
+ },
131
+ };
132
+ }
133
+
134
+ export default UnderpostFileStorage;
package/src/cli/image.js CHANGED
@@ -54,23 +54,6 @@ class UnderpostImage {
54
54
  {
55
55
  const lamppPublicPath = '/xampp/htdocs/online';
56
56
  shellExec(`sudo mkdir -p ${lamppPublicPath}`);
57
-
58
- {
59
- shellExec(
60
- `cd ${lamppPublicPath} && git clone https://${process.env.GITHUB_TOKEN}@github.com/${process.env.DD_LAMPP_REPO_0}`,
61
- );
62
-
63
- shellExec(`cd ${lamppPublicPath} && sudo ${process.env.DD_LAMPP_SCRIPT_0}`);
64
-
65
- shellExec(
66
- `sudo sed -i -e "s@define( 'DB_HOST', 'localhost' );@define( 'DB_HOST', '${process.env.MARIADB_HOST}' );@g" ${lamppPublicPath}/${process.env.DD_LAMPP_REPO_0_FOLDER}/wp-config.php`,
67
- );
68
- }
69
- {
70
- shellExec(
71
- `cd ${lamppPublicPath} && git clone https://${process.env.GITHUB_TOKEN}@github.com/${process.env.DD_LAMPP_REPO_1}`,
72
- );
73
- }
74
57
  }
75
58
  break;
76
59
 
@@ -109,7 +92,7 @@ class UnderpostImage {
109
92
  shellExec(`node bin/deploy conf ${deployId} ${env}`);
110
93
  shellExec(`node bin/deploy build-full-client ${deployId}`);
111
94
  if (options.run === true) {
112
- const runCmd = env === 'production' ? 'start' : 'run dev-img';
95
+ const runCmd = env === 'production' ? 'run prod-img' : 'run dev-img';
113
96
  if (fs.existsSync(`./engine-private/replica`)) {
114
97
  const replicas = await fs.readdir(`./engine-private/replica`);
115
98
  for (const replica of replicas) {
@@ -98,6 +98,19 @@ class UnderpostRepository {
98
98
  }
99
99
  });
100
100
  },
101
+
102
+ getChangedFiles(path = '.', extension = '', head = false) {
103
+ const extensionFilter = extension ? `-- '***.${extension}'` : '';
104
+ const command = `cd ${path} && git diff ${head ? 'HEAD^ HEAD ' : ''}--name-only ${extensionFilter}`;
105
+ const commandUntrack = `cd ${path} && git ls-files --others --exclude-standard`;
106
+ const diffOutput = shellExec(command, { stdout: true, silent: true });
107
+ const diffUntrackOutput = shellExec(commandUntrack, { stdout: true, silent: true });
108
+ return diffOutput
109
+ .toString()
110
+ .split('\n')
111
+ .filter(Boolean)
112
+ .concat(diffUntrackOutput.toString().split('\n').filter(Boolean));
113
+ },
101
114
  };
102
115
  }
103
116
 
package/src/cli/script.js CHANGED
@@ -2,6 +2,7 @@ import { getNpmRootPath } from '../server/conf.js';
2
2
  import { loggerFactory } from '../server/logger.js';
3
3
  import { shellExec } from '../server/process.js';
4
4
  import fs from 'fs-extra';
5
+ import UnderpostDeploy from './deploy.js';
5
6
 
6
7
  const logger = loggerFactory(import.meta);
7
8
 
@@ -13,8 +14,31 @@ class UnderpostScript {
13
14
  packageJson.scripts[key] = value;
14
15
  fs.writeFileSync(`${npmRoot}/package.json`, JSON.stringify(packageJson, null, 4));
15
16
  },
16
- run(key) {
17
+ run(key, value, options) {
17
18
  const npmRoot = `${getNpmRootPath()}/underpost`;
19
+ const packageJson = JSON.parse(fs.readFileSync(`${npmRoot}/package.json`, 'utf8'));
20
+ if (options.itc === true) {
21
+ value = packageJson.scripts[key];
22
+ const podScriptPath = `${options.itcPath && typeof options.itcPath === 'string' ? options.itcPath : '/'}${value
23
+ .split('/')
24
+ .pop()}`;
25
+ const nameSpace = options.ns && typeof options.ns === 'string' ? options.ns : 'default';
26
+ const podMatch = options.podName && typeof options.podName === 'string' ? options.podName : key;
27
+
28
+ if (fs.existsSync(`${value}`)) {
29
+ for (const pod of UnderpostDeploy.API.get(podMatch)) {
30
+ shellExec(`sudo kubectl cp ${value} ${nameSpace}/${pod.NAME}:${podScriptPath}`);
31
+ const cmd = `node ${podScriptPath}`;
32
+ shellExec(`sudo kubectl exec -i ${pod.NAME} -- sh -c "${cmd}"`);
33
+ }
34
+ } else {
35
+ for (const pod of UnderpostDeploy.API.get(podMatch)) {
36
+ shellExec(`sudo kubectl exec -i ${pod.NAME} -- sh -c "${value}"`);
37
+ }
38
+ }
39
+
40
+ return;
41
+ }
18
42
  shellExec(`cd ${npmRoot} && npm run ${key}`);
19
43
  },
20
44
  get(key) {
package/src/cli/test.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { timer } from '../client/components/core/CommonJs.js';
1
2
  import { MariaDB } from '../db/mariadb/MariaDB.js';
2
3
  import { getNpmRootPath } from '../server/conf.js';
3
4
  import { actionInitLog, loggerFactory, setUpInfo } from '../server/logger.js';
@@ -28,9 +29,17 @@ class UnderpostTest {
28
29
  actionInitLog();
29
30
  shellExec(`cd ${getNpmRootPath()}/underpost && npm run test`);
30
31
  },
31
- async callback(deployList = '', options = { insideContainer: false, sh: false, logs: false }) {
32
+ async callback(deployList = '', options = { itc: false, sh: false, logs: false }) {
33
+ if (
34
+ options.podName &&
35
+ typeof options.podName === 'string' &&
36
+ options.podStatus &&
37
+ typeof options.podStatus === 'string'
38
+ )
39
+ return await UnderpostTest.API.statusMonitor(options.podName, options.podStatus, options.kindType);
40
+
32
41
  if (options.sh === true || options.logs === true) {
33
- const [pod] = UnderpostDeploy.API.getPods(deployList);
42
+ const [pod] = UnderpostDeploy.API.get(deployList);
34
43
  if (pod) {
35
44
  if (options.sh) return pbcopy(`sudo kubectl exec -it ${pod.NAME} -- sh`);
36
45
  if (options.logs) return shellExec(`sudo kubectl logs -f ${pod.NAME}`);
@@ -41,7 +50,7 @@ class UnderpostTest {
41
50
  for (const _deployId of deployList.split(',')) {
42
51
  const deployId = _deployId.trim();
43
52
  if (!deployId) continue;
44
- if (options.insideContainer === true)
53
+ if (options.itc === true)
45
54
  switch (deployId) {
46
55
  case 'dd-lampp':
47
56
  {
@@ -64,7 +73,7 @@ class UnderpostTest {
64
73
  break;
65
74
  }
66
75
  else {
67
- const pods = UnderpostDeploy.API.getPods(deployId);
76
+ const pods = UnderpostDeploy.API.get(deployId);
68
77
  if (pods.length > 0)
69
78
  for (const deployData of pods) {
70
79
  const { NAME } = deployData;
@@ -77,6 +86,32 @@ class UnderpostTest {
77
86
  }
78
87
  } else return UnderpostTest.API.run();
79
88
  },
89
+ statusMonitor(podName, status = 'Running', kindType = '', deltaMs = 1000, maxAttempts = 60 * 5) {
90
+ if (!(kindType && typeof kindType === 'string')) kindType = 'pods';
91
+ return new Promise(async (resolve) => {
92
+ let index = 0;
93
+ logger.info(`Loading instance`, { podName, status, kindType, deltaMs, maxAttempts });
94
+ const _monitor = async () => {
95
+ await timer(deltaMs);
96
+ const pods = UnderpostDeploy.API.get(podName, kindType);
97
+ const result = pods.find((p) => p.STATUS === status);
98
+ logger.info(
99
+ `Testing pod ${podName}... ${result ? 1 : 0}/1 - elapsed time ${deltaMs * (index + 1)}s - attempt ${
100
+ index + 1
101
+ }/${maxAttempts}`,
102
+ pods[0] ? pods[0].STATUS : 'Not found kind object',
103
+ );
104
+ if (result) return resolve(true);
105
+ index++;
106
+ if (index === maxAttempts) {
107
+ logger.error(`Failed to test pod ${podName} within ${maxAttempts} attempts`);
108
+ return resolve(false);
109
+ }
110
+ return _monitor();
111
+ };
112
+ await _monitor();
113
+ });
114
+ },
80
115
  };
81
116
  }
82
117