underpost 2.8.531 → 2.8.611
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/.vscode/settings.json +3 -0
- package/bin/build.js +29 -4
- package/bin/deploy.js +2 -1
- package/bin/index.js +34 -6
- package/bin/vs.js +1 -0
- package/conf.js +0 -2
- package/docker-compose.yml +1 -1
- package/manifests/kind-config-dev.yaml +12 -0
- package/manifests/mongodb/kustomization.yaml +2 -2
- package/manifests/mongodb-4.4/service-deployment.yaml +5 -4
- package/package.json +8 -7
- package/src/cli/cluster.js +58 -32
- package/src/cli/cron.js +1 -1
- package/src/cli/db.js +102 -38
- package/src/cli/deploy.js +71 -38
- package/src/cli/fs.js +149 -0
- package/src/cli/image.js +36 -6
- package/src/cli/repository.js +21 -0
- package/src/cli/script.js +12 -6
- package/src/cli/test.js +19 -8
- package/src/client/components/core/CalendarCore.js +12 -1
- package/src/client/components/core/CommonJs.js +52 -1
- package/src/client/components/core/CssCore.js +2 -4
- package/src/client/components/core/Docs.js +1 -2
- package/src/client/components/core/Input.js +4 -2
- package/src/client/components/core/LoadingAnimation.js +8 -1
- package/src/client/components/core/Modal.js +30 -6
- package/src/client/components/core/Panel.js +8 -6
- package/src/client/components/core/PanelForm.js +23 -7
- package/src/client/services/core/core.service.js +15 -10
- package/src/client/ssr/Render.js +4 -1
- package/src/client/ssr/body/CacheControl.js +2 -3
- package/src/client/sw/default.sw.js +3 -3
- package/src/index.js +9 -1
- package/src/server/backup.js +2 -2
- package/src/server/client-build.js +32 -23
- package/src/server/client-formatted.js +2 -1
- package/src/server/conf.js +1 -1
- package/src/server/dns.js +2 -2
- package/src/server/runtime.js +16 -1
- package/manifests/valkey/underpost-engine-valkey-service.yaml +0 -17
- package/manifests/valkey/underpost-engine-valkey-statefulset.yaml +0 -39
package/src/cli/db.js
CHANGED
|
@@ -2,14 +2,28 @@ import { mergeFile, splitFileFactory } 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
|
|
|
8
9
|
class UnderpostDB {
|
|
9
10
|
static API = {
|
|
10
|
-
async callback(
|
|
11
|
+
async callback(
|
|
12
|
+
deployList = 'default',
|
|
13
|
+
options = {
|
|
14
|
+
import: false,
|
|
15
|
+
export: false,
|
|
16
|
+
podName: false,
|
|
17
|
+
ns: false,
|
|
18
|
+
collection: '',
|
|
19
|
+
outPath: '',
|
|
20
|
+
drop: false,
|
|
21
|
+
preserveUUID: false,
|
|
22
|
+
git: false,
|
|
23
|
+
},
|
|
24
|
+
) {
|
|
11
25
|
const newBackupTimestamp = new Date().getTime();
|
|
12
|
-
const nameSpace = 'default';
|
|
26
|
+
const nameSpace = options.ns && typeof options.ns === 'string' ? options.ns : 'default';
|
|
13
27
|
for (const _deployId of deployList.split(',')) {
|
|
14
28
|
const deployId = _deployId.trim();
|
|
15
29
|
if (!deployId) continue;
|
|
@@ -43,10 +57,12 @@ class UnderpostDB {
|
|
|
43
57
|
logger.info('', { hostFolder, provider, dbName });
|
|
44
58
|
|
|
45
59
|
const backUpPath = `../${repoName}/${hostFolder}`;
|
|
60
|
+
if (!fs.existsSync(backUpPath)) fs.mkdirSync(backUpPath, { recursive: true });
|
|
61
|
+
shellExec(`cd ${backUpPath} && find . -type d -empty -delete`); // delete empty folders
|
|
46
62
|
const times = await fs.readdir(backUpPath);
|
|
47
|
-
const currentBackupTimestamp = Math.max(...times.map((t) => parseInt(t)));
|
|
63
|
+
const currentBackupTimestamp = Math.max(...times.map((t) => parseInt(t)).filter((t) => !isNaN(t)));
|
|
48
64
|
dbs[provider][dbName].currentBackupTimestamp = currentBackupTimestamp;
|
|
49
|
-
const removeBackupTimestamp = Math.min(...times.map((t) => parseInt(t)));
|
|
65
|
+
const removeBackupTimestamp = Math.min(...times.map((t) => parseInt(t)).filter((t) => !isNaN(t)));
|
|
50
66
|
|
|
51
67
|
const sqlContainerPath = `/home/${dbName}.sql`;
|
|
52
68
|
const _fromPartsParts = `../${repoName}/${hostFolder}/${currentBackupTimestamp}/${dbName}-parths.json`;
|
|
@@ -68,57 +84,105 @@ class UnderpostDB {
|
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
if (options.export === true && times.length >= 5) {
|
|
87
|
+
logger.info('remove', `../${repoName}/${hostFolder}/${removeBackupTimestamp}`);
|
|
71
88
|
fs.removeSync(`../${repoName}/${hostFolder}/${removeBackupTimestamp}`);
|
|
89
|
+
logger.info('create', `../${repoName}/${hostFolder}/${newBackupTimestamp}`);
|
|
72
90
|
fs.mkdirSync(`../${repoName}/${hostFolder}/${newBackupTimestamp}`, { recursive: true });
|
|
73
91
|
}
|
|
74
92
|
|
|
75
93
|
switch (provider) {
|
|
76
94
|
case 'mariadb': {
|
|
77
|
-
const
|
|
95
|
+
const podNames =
|
|
96
|
+
options.podName && typeof options.podName === 'string'
|
|
97
|
+
? options.podName.split(',')
|
|
98
|
+
: UnderpostDeploy.API.get('mariadb'); // `mariadb-statefulset-0`;
|
|
78
99
|
const serviceName = 'mariadb';
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
`kubectl
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
for (const podNameData of [podNames[0]]) {
|
|
101
|
+
const podName = podNameData.NAME;
|
|
102
|
+
if (options.import === true) {
|
|
103
|
+
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "rm -rf /${dbName}.sql"`);
|
|
104
|
+
shellExec(`sudo kubectl cp ${_toSqlPath} ${nameSpace}/${podName}:/${dbName}.sql`);
|
|
105
|
+
const cmd = `mariadb -u ${user} -p${password} ${dbName} < /${dbName}.sql`;
|
|
106
|
+
shellExec(
|
|
107
|
+
`kubectl exec -i ${podName} -- ${serviceName} -p${password} -e 'CREATE DATABASE ${dbName};'`,
|
|
108
|
+
);
|
|
109
|
+
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "${cmd}"`);
|
|
110
|
+
}
|
|
111
|
+
if (options.export === true) {
|
|
112
|
+
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "rm -rf ${sqlContainerPath}"`);
|
|
113
|
+
const cmd = `mariadb-dump --user=${user} --password=${password} --lock-tables ${dbName} > ${sqlContainerPath}`;
|
|
114
|
+
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "${cmd}"`);
|
|
115
|
+
shellExec(
|
|
116
|
+
`sudo kubectl cp ${nameSpace}/${podName}:${sqlContainerPath} ${
|
|
117
|
+
options.outPath ? options.outPath : _toNewSqlPath
|
|
118
|
+
}`,
|
|
119
|
+
);
|
|
120
|
+
await splitFileFactory(dbName, options.outPath ? options.outPath : _toNewSqlPath);
|
|
121
|
+
}
|
|
92
122
|
}
|
|
93
123
|
break;
|
|
94
124
|
}
|
|
95
125
|
|
|
96
126
|
case 'mongoose': {
|
|
97
127
|
if (options.import === true) {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
const podNames =
|
|
129
|
+
options.podName && typeof options.podName === 'string'
|
|
130
|
+
? options.podName.split(',')
|
|
131
|
+
: UnderpostDeploy.API.get('mongo');
|
|
132
|
+
// `mongodb-0`;
|
|
133
|
+
for (const podNameData of [podNames[0]]) {
|
|
134
|
+
const podName = podNameData.NAME;
|
|
135
|
+
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "rm -rf /${dbName}"`);
|
|
136
|
+
shellExec(
|
|
137
|
+
`sudo kubectl cp ${
|
|
138
|
+
options.outPath ? options.outPath : _toBsonPath
|
|
139
|
+
} ${nameSpace}/${podName}:/${dbName}`,
|
|
140
|
+
);
|
|
141
|
+
const cmd = `mongorestore -d ${dbName} /${dbName}${options.drop ? ' --drop' : ''}${
|
|
142
|
+
options.preserveUUID ? ' --preserveUUID' : ''
|
|
143
|
+
}`;
|
|
144
|
+
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "${cmd}"`);
|
|
145
|
+
}
|
|
102
146
|
}
|
|
103
147
|
if (options.export === true) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else {
|
|
148
|
+
const podNames =
|
|
149
|
+
options.podName && typeof options.podName === 'string'
|
|
150
|
+
? options.podName.split(',')
|
|
151
|
+
: UnderpostDeploy.API.get('mongo'); // `backup-access`;
|
|
152
|
+
for (const podNameData of [podNames[0]]) {
|
|
153
|
+
const podName = podNameData.NAME;
|
|
154
|
+
shellExec(`sudo kubectl exec -i ${podName} -- sh -c "rm -rf /${dbName}"`);
|
|
155
|
+
if (options.collections)
|
|
156
|
+
for (const collection of options.collections)
|
|
157
|
+
shellExec(
|
|
158
|
+
`sudo kubectl exec -i ${podName} -- sh -c "mongodump -d ${dbName} --collection ${collection} -o /"`,
|
|
159
|
+
);
|
|
160
|
+
else shellExec(`sudo kubectl exec -i ${podName} -- sh -c "mongodump -d ${dbName} -o /"`);
|
|
118
161
|
shellExec(
|
|
119
|
-
`sudo kubectl cp ${nameSpace}/${podName}
|
|
162
|
+
`sudo kubectl cp ${nameSpace}/${podName}:/${dbName} ${
|
|
163
|
+
options.outPath ? options.outPath : _toNewBsonPath
|
|
164
|
+
}`,
|
|
120
165
|
);
|
|
121
166
|
}
|
|
167
|
+
if (false) {
|
|
168
|
+
const containerBaseBackupPath = '/backup';
|
|
169
|
+
let timeFolder = shellExec(
|
|
170
|
+
`sudo kubectl exec -i ${podName} -- sh -c "cd ${containerBaseBackupPath} && ls -a"`,
|
|
171
|
+
{
|
|
172
|
+
stdout: true,
|
|
173
|
+
disableLog: false,
|
|
174
|
+
silent: true,
|
|
175
|
+
},
|
|
176
|
+
).split(`\n`);
|
|
177
|
+
timeFolder = timeFolder[timeFolder.length - 2];
|
|
178
|
+
if (timeFolder === '..') {
|
|
179
|
+
logger.warn(`Cannot backup available`, { timeFolder });
|
|
180
|
+
} else {
|
|
181
|
+
shellExec(
|
|
182
|
+
`sudo kubectl cp ${nameSpace}/${podName}:${containerBaseBackupPath}/${timeFolder}/${dbName} ${_toNewBsonPath}`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
122
186
|
}
|
|
123
187
|
break;
|
|
124
188
|
}
|
|
@@ -129,7 +193,7 @@ class UnderpostDB {
|
|
|
129
193
|
}
|
|
130
194
|
}
|
|
131
195
|
}
|
|
132
|
-
if (options.export === true) {
|
|
196
|
+
if (options.export === true && options.git === true) {
|
|
133
197
|
shellExec(`cd ../${repoName} && git add .`);
|
|
134
198
|
shellExec(
|
|
135
199
|
`underpost cmt ../${repoName} backup '' '${new Date(newBackupTimestamp).toLocaleDateString()} ${new Date(
|
package/src/cli/deploy.js
CHANGED
|
@@ -32,7 +32,7 @@ class UnderpostDeploy {
|
|
|
32
32
|
await Config.build(undefined, 'proxy', deployList);
|
|
33
33
|
return buildPortProxyRouter(env === 'development' ? 80 : 443, buildProxyRouter());
|
|
34
34
|
},
|
|
35
|
-
async buildManifest(deployList, env) {
|
|
35
|
+
async buildManifest(deployList, env, version) {
|
|
36
36
|
for (const _deployId of deployList.split(',')) {
|
|
37
37
|
const deployId = _deployId.trim();
|
|
38
38
|
if (!deployId) continue;
|
|
@@ -69,7 +69,18 @@ spec:
|
|
|
69
69
|
spec:
|
|
70
70
|
containers:
|
|
71
71
|
- name: ${deployId}-${env}
|
|
72
|
-
image: localhost
|
|
72
|
+
image: localhost/underpost-engine:${version && typeof version === 'string' ? version : Underpost.version}
|
|
73
|
+
lifecycle:
|
|
74
|
+
postStart:
|
|
75
|
+
exec:
|
|
76
|
+
command:
|
|
77
|
+
- /bin/sh
|
|
78
|
+
- -c
|
|
79
|
+
- >
|
|
80
|
+
sleep 60 &&
|
|
81
|
+
underpost config set deploy-id ${deployId} &&
|
|
82
|
+
underpost config set deploy-env ${env}
|
|
83
|
+
# image: localhost/${deployId}-${env}:${version && typeof version === 'string' ? version : Underpost.version}
|
|
73
84
|
---
|
|
74
85
|
apiVersion: v1
|
|
75
86
|
kind: Service
|
|
@@ -180,32 +191,55 @@ spec:
|
|
|
180
191
|
async callback(
|
|
181
192
|
deployList = 'default',
|
|
182
193
|
env = 'development',
|
|
183
|
-
options = {
|
|
194
|
+
options = {
|
|
195
|
+
remove: false,
|
|
196
|
+
infoRouter: false,
|
|
197
|
+
sync: false,
|
|
198
|
+
buildManifest: false,
|
|
199
|
+
infoUtil: false,
|
|
200
|
+
expose: false,
|
|
201
|
+
cert: false,
|
|
202
|
+
version: '',
|
|
203
|
+
},
|
|
184
204
|
) {
|
|
205
|
+
if (options.infoUtil === true)
|
|
206
|
+
return logger.info(`
|
|
207
|
+
kubectl rollout restart deployment/deployment-name
|
|
208
|
+
kubectl rollout undo deployment/deployment-name
|
|
209
|
+
kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
|
|
210
|
+
`);
|
|
185
211
|
if (deployList === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`))
|
|
186
212
|
deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
|
|
187
213
|
if (options.sync) UnderpostDeploy.API.sync(deployList);
|
|
188
|
-
if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env);
|
|
214
|
+
if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options.version);
|
|
189
215
|
if (options.infoRouter === true)
|
|
190
216
|
return logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
|
|
217
|
+
const etcHost = (
|
|
218
|
+
concat,
|
|
219
|
+
) => `127.0.0.1 ${concat} localhost localhost.localdomain localhost4 localhost4.localdomain4
|
|
220
|
+
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6`;
|
|
221
|
+
let concatHots = '';
|
|
191
222
|
|
|
192
223
|
for (const _deployId of deployList.split(',')) {
|
|
193
224
|
const deployId = _deployId.trim();
|
|
194
225
|
if (!deployId) continue;
|
|
195
|
-
|
|
226
|
+
if (options.expose === true) {
|
|
227
|
+
const svc = UnderpostDeploy.API.get(deployId, 'svc')[0];
|
|
228
|
+
const port = parseInt(svc[`PORT(S)`].split('/TCP')[0]);
|
|
229
|
+
logger.info(deployId, {
|
|
230
|
+
svc,
|
|
231
|
+
port,
|
|
232
|
+
});
|
|
233
|
+
shellExec(`sudo kubectl port-forward -n default svc/${svc.NAME} ${port}:${port}`, { async: true });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
196
236
|
shellExec(`sudo kubectl delete svc ${deployId}-${env}-service`);
|
|
197
237
|
shellExec(`sudo kubectl delete deployment ${deployId}-${env}`);
|
|
198
238
|
|
|
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
239
|
const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
|
|
206
240
|
for (const host of Object.keys(confServer)) {
|
|
207
241
|
shellExec(`sudo kubectl delete HTTPProxy ${host}`);
|
|
208
|
-
if (env === 'production') shellExec(`sudo kubectl delete Certificate ${host}`);
|
|
242
|
+
if (env === 'production' && options.cert === true) shellExec(`sudo kubectl delete Certificate ${host}`);
|
|
209
243
|
if (!options.remove === true && env === 'development') concatHots += ` ${host}`;
|
|
210
244
|
}
|
|
211
245
|
|
|
@@ -217,39 +251,38 @@ spec:
|
|
|
217
251
|
if (!options.remove === true) {
|
|
218
252
|
shellExec(`sudo kubectl apply -f ./${manifestsPath}/deployment.yaml`);
|
|
219
253
|
shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml`);
|
|
220
|
-
if (env === 'production'
|
|
254
|
+
if (env === 'production' && options.cert === true)
|
|
255
|
+
shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml`);
|
|
221
256
|
}
|
|
257
|
+
}
|
|
258
|
+
let renderHosts;
|
|
259
|
+
switch (process.platform) {
|
|
260
|
+
case 'linux':
|
|
261
|
+
{
|
|
262
|
+
switch (env) {
|
|
263
|
+
case 'development':
|
|
264
|
+
renderHosts = etcHost(concatHots);
|
|
265
|
+
fs.writeFileSync(`/etc/hosts`, renderHosts, 'utf8');
|
|
222
266
|
|
|
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;
|
|
267
|
+
break;
|
|
234
268
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
269
|
+
default:
|
|
270
|
+
break;
|
|
238
271
|
}
|
|
239
|
-
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
240
274
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
if (renderHosts)
|
|
245
|
-
logger.info(
|
|
246
|
-
`
|
|
247
|
-
` + renderHosts,
|
|
248
|
-
);
|
|
275
|
+
default:
|
|
276
|
+
break;
|
|
249
277
|
}
|
|
278
|
+
if (renderHosts)
|
|
279
|
+
logger.info(
|
|
280
|
+
`
|
|
281
|
+
` + renderHosts,
|
|
282
|
+
);
|
|
250
283
|
},
|
|
251
|
-
|
|
252
|
-
const raw = shellExec(`sudo kubectl get
|
|
284
|
+
get(deployId, kindType = 'pods') {
|
|
285
|
+
const raw = shellExec(`sudo kubectl get ${kindType} --all-namespaces -o wide`, {
|
|
253
286
|
stdout: true,
|
|
254
287
|
disableLog: true,
|
|
255
288
|
silent: true,
|
package/src/cli/fs.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
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 deleteFiles = UnderpostRepository.API.getDeleteFiles(path);
|
|
42
|
+
for (const relativePath of deleteFiles) {
|
|
43
|
+
const _path = path + '/' + relativePath;
|
|
44
|
+
if (_path in storage) {
|
|
45
|
+
await UnderpostFileStorage.API.delete(_path);
|
|
46
|
+
delete storage[_path];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const files =
|
|
50
|
+
options.git === true
|
|
51
|
+
? UnderpostRepository.API.getChangedFiles(path)
|
|
52
|
+
: await fs.readdir(path, { recursive: true });
|
|
53
|
+
if (options.pull === true) {
|
|
54
|
+
for (const _path of Object.keys(storage)) {
|
|
55
|
+
if (!fs.existsSync(_path) || options.force === true) {
|
|
56
|
+
if (options.force === true && fs.existsSync(_path)) fs.removeSync(_path);
|
|
57
|
+
await UnderpostFileStorage.API.pull(_path, options);
|
|
58
|
+
} else logger.warn(`Pull path already exists`, _path);
|
|
59
|
+
}
|
|
60
|
+
} else
|
|
61
|
+
for (const relativePath of files) {
|
|
62
|
+
const _path = path + '/' + relativePath;
|
|
63
|
+
if (fs.statSync(_path).isDirectory()) {
|
|
64
|
+
if (options.pull === true && !fs.existsSync(_path)) fs.mkdirSync(_path, { recursive: true });
|
|
65
|
+
continue;
|
|
66
|
+
} else if (!(_path in storage) || options.force === true) {
|
|
67
|
+
await UnderpostFileStorage.API.upload(_path, options);
|
|
68
|
+
if (storage) storage[_path] = {};
|
|
69
|
+
} else logger.warn('File already exists', _path);
|
|
70
|
+
}
|
|
71
|
+
UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
|
|
72
|
+
if (options.git === true) {
|
|
73
|
+
shellExec(`cd ${path} && git add .`);
|
|
74
|
+
shellExec(`underpost cmt ${path} feat`);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async callback(
|
|
78
|
+
path,
|
|
79
|
+
options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
|
|
80
|
+
) {
|
|
81
|
+
if (options.recursive === true || options.git === true)
|
|
82
|
+
return await UnderpostFileStorage.API.recursiveCallback(path, options);
|
|
83
|
+
if (options.pull === true) return await UnderpostFileStorage.API.pull(path, options);
|
|
84
|
+
if (options.rm === true) return await UnderpostFileStorage.API.delete(path, options);
|
|
85
|
+
return await UnderpostFileStorage.API.upload(path, options);
|
|
86
|
+
},
|
|
87
|
+
async upload(path, options = { rm: false, recursive: false, deployId: '', force: false, pull: false }) {
|
|
88
|
+
UnderpostFileStorage.API.cloudinaryConfig();
|
|
89
|
+
const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
|
|
90
|
+
// path = UnderpostFileStorage.API.file2Zip(path);
|
|
91
|
+
const uploadResult = await cloudinary.uploader
|
|
92
|
+
.upload(path, {
|
|
93
|
+
public_id: path,
|
|
94
|
+
resource_type: 'raw',
|
|
95
|
+
overwrite: options.force === true ? true : false,
|
|
96
|
+
})
|
|
97
|
+
.catch((error) => {
|
|
98
|
+
logger.error(error, { path, stack: error.stack });
|
|
99
|
+
});
|
|
100
|
+
logger.info('upload result', uploadResult);
|
|
101
|
+
if (storage) storage[path] = {};
|
|
102
|
+
UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
|
|
103
|
+
return uploadResult;
|
|
104
|
+
},
|
|
105
|
+
async pull(path) {
|
|
106
|
+
UnderpostFileStorage.API.cloudinaryConfig();
|
|
107
|
+
const folder = dir.dirname(path);
|
|
108
|
+
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
109
|
+
const downloadResult = await cloudinary.utils.download_archive_url({
|
|
110
|
+
public_ids: [path],
|
|
111
|
+
resource_type: 'raw',
|
|
112
|
+
});
|
|
113
|
+
logger.info('download result', downloadResult);
|
|
114
|
+
await Downloader(downloadResult, path + '.zip');
|
|
115
|
+
path = UnderpostFileStorage.API.zip2File(path + '.zip');
|
|
116
|
+
fs.removeSync(path + '.zip');
|
|
117
|
+
},
|
|
118
|
+
async delete(path) {
|
|
119
|
+
UnderpostFileStorage.API.cloudinaryConfig();
|
|
120
|
+
const deleteResult = await cloudinary.api
|
|
121
|
+
.delete_resources([path], { type: 'upload', resource_type: 'raw' })
|
|
122
|
+
.catch((error) => {
|
|
123
|
+
logger.error(error, { path, stack: error.stack });
|
|
124
|
+
});
|
|
125
|
+
logger.info('delete result', deleteResult);
|
|
126
|
+
return deleteResult;
|
|
127
|
+
},
|
|
128
|
+
file2Zip(path) {
|
|
129
|
+
const zip = new AdmZip();
|
|
130
|
+
zip.addLocalFile(path, '/');
|
|
131
|
+
path = path + '.zip';
|
|
132
|
+
zip.writeZip(path);
|
|
133
|
+
return path;
|
|
134
|
+
},
|
|
135
|
+
zip2File(path) {
|
|
136
|
+
const zip = new AdmZip(path);
|
|
137
|
+
path = path.replaceAll('.zip', '');
|
|
138
|
+
zip.extractEntryTo(
|
|
139
|
+
/*entry name*/ path.split('/').pop(),
|
|
140
|
+
/*target path*/ dir.dirname(path),
|
|
141
|
+
/*maintainEntryPath*/ false,
|
|
142
|
+
/*overwrite*/ true,
|
|
143
|
+
);
|
|
144
|
+
return path;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default UnderpostFileStorage;
|
package/src/cli/image.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import Underpost from '../index.js';
|
|
3
|
-
import { shellExec } from '../server/process.js';
|
|
3
|
+
import { shellCd, shellExec } from '../server/process.js';
|
|
4
4
|
import dotenv from 'dotenv';
|
|
5
5
|
import { getNpmRootPath } from '../server/conf.js';
|
|
6
6
|
import { timer } from '../client/components/core/CommonJs.js';
|
|
7
|
+
import UnderpostRootEnv from './env.js';
|
|
7
8
|
|
|
8
9
|
dotenv.config();
|
|
9
10
|
|
|
@@ -17,9 +18,13 @@ class UnderpostImage {
|
|
|
17
18
|
deployId = 'default',
|
|
18
19
|
env = 'development',
|
|
19
20
|
path = '.',
|
|
20
|
-
options = { imageArchive: false, podmanSave: false },
|
|
21
|
+
options = { imageArchive: false, podmanSave: false, imageName: '', imageVersion: '' },
|
|
21
22
|
) {
|
|
22
|
-
const imgName = `${
|
|
23
|
+
const imgName = `${
|
|
24
|
+
options.imageName && typeof options.imageName === 'string' ? options.imageName : `${deployId}-${env}`
|
|
25
|
+
}:${
|
|
26
|
+
options.imageVersion && typeof options.imageVersions === 'string' ? options.imageVersion : Underpost.version
|
|
27
|
+
}`;
|
|
23
28
|
const podManImg = `localhost/${imgName}`;
|
|
24
29
|
const imagesStoragePath = `/images`;
|
|
25
30
|
if (!fs.existsSync(`${path}${imagesStoragePath}`))
|
|
@@ -48,7 +53,32 @@ class UnderpostImage {
|
|
|
48
53
|
shellExec(`cd ${path} && podman save -o ${tarFile} ${podManImg}`);
|
|
49
54
|
shellExec(`cd ${path} && sudo kind load image-archive ${tarFile}`);
|
|
50
55
|
},
|
|
51
|
-
async script(deployId = 'default', env = 'development', options = { run: false }) {
|
|
56
|
+
async script(deployId = 'default', env = 'development', options = { run: false, build: false }) {
|
|
57
|
+
if (deployId === 'deploy') {
|
|
58
|
+
const _deployId = UnderpostRootEnv.API.get('deploy-id');
|
|
59
|
+
const _env = UnderpostRootEnv.API.get('deploy-env');
|
|
60
|
+
if (_deployId) {
|
|
61
|
+
deployId = _deployId;
|
|
62
|
+
if (_env) env = _env;
|
|
63
|
+
} else {
|
|
64
|
+
await timer(30 * 1000);
|
|
65
|
+
return await UnderpostImage.API.dockerfile.script(deployId, env, options);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (options.build === true) {
|
|
69
|
+
const buildBasePath = `/home/dd`;
|
|
70
|
+
const repoName = `engine-${deployId.split('-')[1]}`;
|
|
71
|
+
fs.mkdirSync(buildBasePath, { recursive: true });
|
|
72
|
+
shellExec(`cd ${buildBasePath} && underpost clone underpostnet/${repoName}`);
|
|
73
|
+
shellExec(`cd ${buildBasePath} && sudo mv ./${repoName} ./engine`);
|
|
74
|
+
shellExec(`cd ${buildBasePath}/engine && underpost clone underpostnet/${repoName}-private`);
|
|
75
|
+
shellExec(`cd ${buildBasePath}/engine && sudo mv ./${repoName}-private ./engine-private`);
|
|
76
|
+
shellCd(`${buildBasePath}/engine`);
|
|
77
|
+
shellExec(`npm install`);
|
|
78
|
+
const itcScripts = fs.readdir('./engine-private/itc-scripts');
|
|
79
|
+
for (const itcScript of itcScripts)
|
|
80
|
+
if (itcScript.match(deployId)) shellExec(`node ./engine-private/itc-scripts/${itcScript}`);
|
|
81
|
+
}
|
|
52
82
|
switch (deployId) {
|
|
53
83
|
case 'dd-lampp':
|
|
54
84
|
{
|
|
@@ -97,7 +127,7 @@ class UnderpostImage {
|
|
|
97
127
|
const replicas = await fs.readdir(`./engine-private/replica`);
|
|
98
128
|
for (const replica of replicas) {
|
|
99
129
|
shellExec(`node bin/deploy conf ${replica} ${env}`);
|
|
100
|
-
shellExec(`npm ${runCmd}
|
|
130
|
+
shellExec(`npm ${runCmd} deploy deploy-id:${replica}`, { async: true });
|
|
101
131
|
fs.writeFileSync(`./tmp/await-deploy`, '', 'utf8');
|
|
102
132
|
const monitor = async () => {
|
|
103
133
|
await timer(1000);
|
|
@@ -107,7 +137,7 @@ class UnderpostImage {
|
|
|
107
137
|
}
|
|
108
138
|
shellExec(`node bin/deploy conf ${deployId} ${env}`);
|
|
109
139
|
}
|
|
110
|
-
shellExec(`npm ${runCmd}
|
|
140
|
+
shellExec(`npm ${runCmd} deploy deploy-id:${deployId}`);
|
|
111
141
|
}
|
|
112
142
|
},
|
|
113
143
|
},
|
package/src/cli/repository.js
CHANGED
|
@@ -98,6 +98,27 @@ class UnderpostRepository {
|
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
},
|
|
101
|
+
|
|
102
|
+
getDeleteFiles(path = '.') {
|
|
103
|
+
const commandUntrack = `cd ${path} && git ls-files --deleted`;
|
|
104
|
+
const diffUntrackOutput = shellExec(commandUntrack, { stdout: true, silent: true });
|
|
105
|
+
return diffUntrackOutput.toString().split('\n').filter(Boolean);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
getChangedFiles(path = '.', extension = '', head = false) {
|
|
109
|
+
const extensionFilter = extension ? `-- '***.${extension}'` : '';
|
|
110
|
+
const command = `cd ${path} && git diff ${head ? 'HEAD^ HEAD ' : ''}--name-only ${extensionFilter}`;
|
|
111
|
+
const commandUntrack = `cd ${path} && git ls-files --others --exclude-standard`;
|
|
112
|
+
const diffOutput = shellExec(command, { stdout: true, silent: true });
|
|
113
|
+
const diffUntrackOutput = shellExec(commandUntrack, { stdout: true, silent: true });
|
|
114
|
+
const deleteFiles = UnderpostRepository.API.getDeleteFiles(path);
|
|
115
|
+
return diffOutput
|
|
116
|
+
.toString()
|
|
117
|
+
.split('\n')
|
|
118
|
+
.filter(Boolean)
|
|
119
|
+
.concat(diffUntrackOutput.toString().split('\n').filter(Boolean))
|
|
120
|
+
.filter((f) => !deleteFiles.includes(f));
|
|
121
|
+
},
|
|
101
122
|
};
|
|
102
123
|
}
|
|
103
124
|
|
package/src/cli/script.js
CHANGED
|
@@ -19,16 +19,22 @@ class UnderpostScript {
|
|
|
19
19
|
const packageJson = JSON.parse(fs.readFileSync(`${npmRoot}/package.json`, 'utf8'));
|
|
20
20
|
if (options.itc === true) {
|
|
21
21
|
value = packageJson.scripts[key];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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;
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
if (fs.existsSync(`${value}`)) {
|
|
29
|
+
for (const pod of UnderpostDeploy.API.get(podMatch)) {
|
|
28
30
|
shellExec(`sudo kubectl cp ${value} ${nameSpace}/${pod.NAME}:${podScriptPath}`);
|
|
29
|
-
const cmd = `
|
|
31
|
+
const cmd = `node ${podScriptPath}`;
|
|
30
32
|
shellExec(`sudo kubectl exec -i ${pod.NAME} -- sh -c "${cmd}"`);
|
|
31
33
|
}
|
|
34
|
+
} else {
|
|
35
|
+
for (const pod of UnderpostDeploy.API.get(podMatch)) {
|
|
36
|
+
shellExec(`sudo kubectl exec -i ${pod.NAME} -- sh -c "${value}"`);
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
return;
|