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.
- package/.github/workflows/ghpkg.yml +1 -1
- package/.github/workflows/npmpkg.yml +1 -1
- package/.github/workflows/pwa-microservices-template.page.yml +1 -1
- package/CHANGELOG.md +24 -0
- package/bin/build.js +29 -4
- package/bin/deploy.js +64 -52
- package/bin/hwt.js +0 -10
- package/bin/index.js +38 -16
- package/bin/util.js +0 -7
- package/conf.js +0 -2
- package/docker-compose.yml +1 -1
- package/manifests/kind-config-dev.yaml +12 -0
- package/manifests/mongodb-4.4/kustomization.yaml +7 -0
- package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
- package/package.json +9 -7
- package/src/cli/cluster.js +71 -45
- package/src/cli/cron.js +1 -1
- package/src/cli/db.js +6 -4
- package/src/cli/deploy.js +43 -30
- package/src/cli/fs.js +134 -0
- package/src/cli/image.js +1 -18
- package/src/cli/repository.js +13 -0
- package/src/cli/script.js +25 -1
- package/src/cli/test.js +39 -4
- package/src/db/mongo/MongooseDB.js +17 -1
- package/src/index.js +9 -1
- package/src/server/backup.js +2 -2
- package/src/server/client-formatted.js +2 -1
- package/src/server/conf.js +4 -10
- package/src/server/dns.js +39 -46
- package/src/server/downloader.js +0 -8
- package/test/api.test.js +0 -8
- package/manifests/core/kustomization.yaml +0 -11
- package/manifests/core/underpost-engine-backup-access.yaml +0 -16
- package/manifests/core/underpost-engine-backup-pv-pvc.yaml +0 -22
- package/manifests/core/underpost-engine-headless-service.yaml +0 -10
- package/manifests/core/underpost-engine-mongodb-backup-cronjob.yaml +0 -40
- package/manifests/core/underpost-engine-mongodb-configmap.yaml +0 -26
- package/manifests/core/underpost-engine-statefulset.yaml +0 -91
- /package/manifests/{core/underpost-engine-pv-pvc.yaml → mongodb-4.4/pv-pvc.yaml} +0 -0
package/src/cli/cluster.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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(`
|
|
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
|
|
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
|
|
97
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb`);
|
|
85
98
|
}
|
|
86
|
-
if (options.
|
|
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
|
|
129
|
+
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb`);
|
|
95
130
|
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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.
|
|
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
|
|
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 = {
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
248
|
+
default:
|
|
249
|
+
break;
|
|
238
250
|
}
|
|
239
|
-
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
240
253
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
default:
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
if (renderHosts)
|
|
244
258
|
logger.info(
|
|
245
259
|
`
|
|
246
260
|
` + renderHosts,
|
|
247
261
|
);
|
|
248
|
-
}
|
|
249
262
|
},
|
|
250
|
-
|
|
251
|
-
const raw = shellExec(`sudo kubectl get
|
|
263
|
+
get(deployId, kindType = 'pods') {
|
|
264
|
+
const raw = shellExec(`sudo kubectl get ${kindType} --all-namespaces -o wide`, {
|
|
252
265
|
stdout: true,
|
|
253
|
-
disableLog:
|
|
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' ? '
|
|
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) {
|
package/src/cli/repository.js
CHANGED
|
@@ -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 = {
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|