underpost 2.8.1 → 2.8.7
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/.dockerignore +1 -0
- package/.github/workflows/ghpkg.yml +19 -49
- package/.github/workflows/npmpkg.yml +67 -0
- package/.github/workflows/publish.yml +5 -5
- package/.github/workflows/pwa-microservices-template.page.yml +12 -4
- package/.github/workflows/pwa-microservices-template.test.yml +2 -2
- package/.vscode/extensions.json +18 -71
- package/.vscode/settings.json +20 -3
- package/AUTHORS.md +16 -5
- package/CHANGELOG.md +123 -3
- package/Dockerfile +27 -70
- package/README.md +39 -29
- package/bin/build.js +186 -0
- package/bin/db.js +2 -24
- package/bin/deploy.js +1467 -236
- package/bin/file.js +67 -16
- package/bin/hwt.js +0 -10
- package/bin/index.js +1 -77
- package/bin/ssl.js +19 -11
- package/bin/util.js +9 -104
- package/bin/vs.js +26 -2
- package/cli.md +451 -0
- package/conf.js +29 -138
- package/docker-compose.yml +1 -1
- package/jsdoc.json +1 -1
- package/manifests/calico-custom-resources.yaml +25 -0
- package/manifests/deployment/adminer/deployment.yaml +32 -0
- package/manifests/deployment/adminer/kustomization.yaml +7 -0
- package/manifests/deployment/adminer/service.yaml +13 -0
- package/manifests/deployment/fastapi/backend-deployment.yml +120 -0
- package/manifests/deployment/fastapi/backend-service.yml +19 -0
- package/manifests/deployment/fastapi/frontend-deployment.yml +54 -0
- package/manifests/deployment/fastapi/frontend-service.yml +15 -0
- package/manifests/deployment/kafka/deployment.yaml +69 -0
- package/manifests/deployment/mongo-express/deployment.yaml +60 -0
- package/manifests/deployment/phpmyadmin/deployment.yaml +54 -0
- package/manifests/kind-config-dev.yaml +12 -0
- package/manifests/kind-config.yaml +12 -0
- package/manifests/kubeadm-calico-config.yaml +119 -0
- package/manifests/letsencrypt-prod.yaml +15 -0
- package/manifests/mariadb/config.yaml +10 -0
- package/manifests/mariadb/kustomization.yaml +9 -0
- package/manifests/mariadb/pv.yaml +12 -0
- package/manifests/mariadb/pvc.yaml +10 -0
- package/manifests/mariadb/secret.yaml +8 -0
- package/manifests/mariadb/service.yaml +10 -0
- package/manifests/mariadb/statefulset.yaml +55 -0
- package/manifests/mongodb/backup-access.yaml +16 -0
- package/manifests/mongodb/backup-cronjob.yaml +42 -0
- package/manifests/mongodb/backup-pv-pvc.yaml +22 -0
- package/manifests/mongodb/configmap.yaml +26 -0
- package/manifests/mongodb/headless-service.yaml +10 -0
- package/manifests/mongodb/kustomization.yaml +11 -0
- package/manifests/mongodb/pv-pvc.yaml +23 -0
- package/manifests/mongodb/statefulset.yaml +125 -0
- package/manifests/mongodb-4.4/kustomization.yaml +7 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +23 -0
- package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
- package/manifests/postgresql/configmap.yaml +9 -0
- package/manifests/postgresql/kustomization.yaml +10 -0
- package/manifests/postgresql/pv.yaml +15 -0
- package/manifests/postgresql/pvc.yaml +13 -0
- package/manifests/postgresql/service.yaml +10 -0
- package/manifests/postgresql/statefulset.yaml +37 -0
- package/manifests/valkey/kustomization.yaml +7 -0
- package/manifests/valkey/service.yaml +17 -0
- package/manifests/valkey/statefulset.yaml +41 -0
- package/package.json +127 -136
- package/src/api/core/core.service.js +1 -1
- package/src/api/default/default.service.js +1 -1
- package/src/api/user/user.model.js +16 -3
- package/src/api/user/user.service.js +15 -12
- package/src/cli/cluster.js +389 -0
- package/src/cli/cron.js +121 -0
- package/src/cli/db.js +222 -0
- package/src/cli/deploy.js +487 -0
- package/src/cli/env.js +58 -0
- package/src/cli/fs.js +161 -0
- package/src/cli/image.js +66 -0
- package/src/cli/index.js +312 -0
- package/src/cli/monitor.js +236 -0
- package/src/cli/repository.js +128 -0
- package/src/cli/script.js +53 -0
- package/src/cli/secrets.js +37 -0
- package/src/cli/test.js +118 -0
- package/src/client/components/core/Account.js +28 -24
- package/src/client/components/core/Auth.js +22 -4
- package/src/client/components/core/Blockchain.js +1 -1
- package/src/client/components/core/CalendarCore.js +128 -121
- package/src/client/components/core/CommonJs.js +283 -19
- package/src/client/components/core/CssCore.js +16 -4
- package/src/client/components/core/Docs.js +1 -2
- package/src/client/components/core/DropDown.js +5 -1
- package/src/client/components/core/EventsUI.js +3 -3
- package/src/client/components/core/FileExplorer.js +86 -78
- package/src/client/components/core/Input.js +22 -6
- package/src/client/components/core/JoyStick.js +2 -2
- package/src/client/components/core/LoadingAnimation.js +3 -12
- package/src/client/components/core/LogIn.js +3 -3
- package/src/client/components/core/LogOut.js +1 -1
- package/src/client/components/core/Modal.js +54 -20
- package/src/client/components/core/Panel.js +109 -90
- package/src/client/components/core/PanelForm.js +23 -30
- package/src/client/components/core/Recover.js +3 -3
- package/src/client/components/core/RichText.js +1 -11
- package/src/client/components/core/Router.js +3 -1
- package/src/client/components/core/Scroll.js +1 -0
- package/src/client/components/core/SignUp.js +2 -2
- package/src/client/components/core/Translate.js +47 -9
- package/src/client/components/core/Validator.js +9 -1
- package/src/client/components/core/VanillaJs.js +0 -9
- package/src/client/components/core/Worker.js +34 -31
- package/src/client/components/default/RoutesDefault.js +3 -2
- package/src/client/services/core/core.service.js +15 -10
- package/src/client/services/default/default.management.js +46 -37
- package/src/client/ssr/Render.js +6 -1
- package/src/client/ssr/body/CacheControl.js +2 -3
- package/src/client/sw/default.sw.js +3 -3
- package/src/db/mongo/MongooseDB.js +29 -1
- package/src/index.js +101 -19
- package/src/mailer/MailerProvider.js +3 -0
- package/src/runtime/lampp/Dockerfile +65 -0
- package/src/runtime/lampp/Lampp.js +1 -13
- package/src/runtime/xampp/Xampp.js +0 -13
- package/src/server/auth.js +3 -3
- package/src/server/backup.js +49 -93
- package/src/server/client-build.js +49 -46
- package/src/server/client-formatted.js +6 -3
- package/src/server/conf.js +297 -55
- package/src/server/dns.js +75 -62
- package/src/server/downloader.js +0 -8
- package/src/server/json-schema.js +77 -0
- package/src/server/logger.js +15 -10
- package/src/server/network.js +20 -161
- package/src/server/peer.js +2 -2
- package/src/server/process.js +25 -2
- package/src/server/proxy.js +7 -29
- package/src/server/runtime.js +53 -40
- package/src/server/ssl.js +1 -1
- package/src/server/start.js +122 -0
- package/src/server/valkey.js +27 -11
- package/test/api.test.js +0 -8
- package/src/dns.js +0 -22
- package/src/server/prompt-optimizer.js +0 -28
- package/startup.js +0 -11
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { loadReplicas, pathPortAssignmentFactory } from '../server/conf.js';
|
|
2
|
+
import { loggerFactory } from '../server/logger.js';
|
|
3
|
+
import UnderpostDeploy from './deploy.js';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import UnderpostRootEnv from './env.js';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import { shellExec } from '../server/process.js';
|
|
8
|
+
import { isInternetConnection } from '../server/dns.js';
|
|
9
|
+
|
|
10
|
+
const logger = loggerFactory(import.meta);
|
|
11
|
+
|
|
12
|
+
class UnderpostMonitor {
|
|
13
|
+
static API = {
|
|
14
|
+
async callback(
|
|
15
|
+
deployId,
|
|
16
|
+
env = 'development',
|
|
17
|
+
options = { now: false, single: false, msInterval: '', type: '', replicas: '', sync: false },
|
|
18
|
+
commanderOptions,
|
|
19
|
+
auxRouter,
|
|
20
|
+
) {
|
|
21
|
+
if (deployId === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`)) {
|
|
22
|
+
for (const _deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(','))
|
|
23
|
+
UnderpostMonitor.API.callback(
|
|
24
|
+
_deployId.trim(),
|
|
25
|
+
env,
|
|
26
|
+
options,
|
|
27
|
+
commanderOptions,
|
|
28
|
+
await UnderpostDeploy.API.routerFactory(_deployId, env),
|
|
29
|
+
);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const router = auxRouter ?? (await UnderpostDeploy.API.routerFactory(deployId, env));
|
|
34
|
+
|
|
35
|
+
const confServer = loadReplicas(
|
|
36
|
+
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
|
|
37
|
+
'proxy',
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
|
|
41
|
+
|
|
42
|
+
let errorPayloads = [];
|
|
43
|
+
if (options.sync === true) {
|
|
44
|
+
const currentTraffic = UnderpostDeploy.API.getCurrentTraffic(deployId);
|
|
45
|
+
if (currentTraffic) UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, currentTraffic);
|
|
46
|
+
}
|
|
47
|
+
let traffic = UnderpostRootEnv.API.get(`${deployId}-${env}-traffic`) ?? 'blue';
|
|
48
|
+
const maxAttempts = parseInt(
|
|
49
|
+
Object.keys(pathPortAssignmentData)
|
|
50
|
+
.map((host) => pathPortAssignmentData[host].length)
|
|
51
|
+
.reduce((accumulator, value) => accumulator + value, 0) * 2.5,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
logger.info(`Init deploy monitor`, {
|
|
55
|
+
pathPortAssignmentData,
|
|
56
|
+
maxAttempts,
|
|
57
|
+
deployId,
|
|
58
|
+
env,
|
|
59
|
+
traffic,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const switchTraffic = () => {
|
|
63
|
+
if (traffic === 'blue') traffic = 'green';
|
|
64
|
+
else traffic = 'blue';
|
|
65
|
+
UnderpostRootEnv.API.set(`${deployId}-${env}-traffic`, traffic);
|
|
66
|
+
shellExec(
|
|
67
|
+
`node bin deploy --info-router --build-manifest --traffic ${traffic} --replicas ${
|
|
68
|
+
options.replicas ? options.replicas : 1
|
|
69
|
+
} ${deployId} ${env}`,
|
|
70
|
+
);
|
|
71
|
+
shellExec(`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${env}/proxy.yaml`);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const monitor = async (reject) => {
|
|
75
|
+
if (UnderpostRootEnv.API.get(`monitor-init-callback-script`))
|
|
76
|
+
shellExec(UnderpostRootEnv.API.get(`monitor-init-callback-script`));
|
|
77
|
+
const currentTimestamp = new Date().getTime();
|
|
78
|
+
errorPayloads = errorPayloads.filter((e) => currentTimestamp - e.timestamp < 60 * 1000 * 5);
|
|
79
|
+
logger.info(`[${deployId}-${env}] Check server health`);
|
|
80
|
+
for (const host of Object.keys(pathPortAssignmentData)) {
|
|
81
|
+
for (const instance of pathPortAssignmentData[host]) {
|
|
82
|
+
const { port, path } = instance;
|
|
83
|
+
if (path.match('peer') || path.match('socket')) continue;
|
|
84
|
+
let urlTest = `http://localhost:${port}${path}`;
|
|
85
|
+
switch (options.type) {
|
|
86
|
+
case 'remote':
|
|
87
|
+
case 'blue-green':
|
|
88
|
+
urlTest = `https://${host}${path}`;
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
default:
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
// logger.info('Test instance', urlTest);
|
|
95
|
+
await axios.get(urlTest, { timeout: 10000 }).catch((error) => {
|
|
96
|
+
// console.log(error);
|
|
97
|
+
const errorPayload = {
|
|
98
|
+
urlTest,
|
|
99
|
+
host,
|
|
100
|
+
port,
|
|
101
|
+
path,
|
|
102
|
+
name: error.name,
|
|
103
|
+
status: error.status,
|
|
104
|
+
code: error.code,
|
|
105
|
+
errors: error.errors,
|
|
106
|
+
timestamp: new Date().getTime(),
|
|
107
|
+
};
|
|
108
|
+
if (errorPayload.status !== 404) {
|
|
109
|
+
errorPayloads.push(errorPayload);
|
|
110
|
+
if (errorPayloads.length >= maxAttempts) {
|
|
111
|
+
const message = JSON.stringify(errorPayloads, null, 4);
|
|
112
|
+
logger.error(
|
|
113
|
+
`Deployment ${deployId} ${env} has been reached max attempts error payloads`,
|
|
114
|
+
errorPayloads,
|
|
115
|
+
);
|
|
116
|
+
switch (options.type) {
|
|
117
|
+
case 'blue-green':
|
|
118
|
+
{
|
|
119
|
+
const confServer = JSON.parse(
|
|
120
|
+
fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
shellExec(`kubectl delete configmap underpost-config`);
|
|
124
|
+
shellExec(
|
|
125
|
+
`kubectl create configmap underpost-config --from-file=/home/dd/engine/engine-private/conf/dd-cron/.env.${env}`,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
for (const host of Object.keys(confServer)) {
|
|
129
|
+
shellExec(`sudo kubectl delete HTTPProxy ${host}`);
|
|
130
|
+
}
|
|
131
|
+
shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${traffic}`);
|
|
132
|
+
|
|
133
|
+
switchTraffic();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
case 'remote':
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
default:
|
|
142
|
+
if (reject) reject(message);
|
|
143
|
+
else throw new Error(message);
|
|
144
|
+
}
|
|
145
|
+
errorPayloads = [];
|
|
146
|
+
}
|
|
147
|
+
logger.error(`Error accumulator ${deployId}-${env}-${traffic}`, errorPayloads.length);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
if (options.now === true) await monitor();
|
|
154
|
+
if (options.single === true) return;
|
|
155
|
+
let optionsMsTimeout = parseInt(options.msInterval);
|
|
156
|
+
if (isNaN(optionsMsTimeout)) optionsMsTimeout = 60250; // 60.25 seconds
|
|
157
|
+
let monitorTrafficName;
|
|
158
|
+
let monitorPodName;
|
|
159
|
+
const monitorCallBack = (resolve, reject) => {
|
|
160
|
+
const envMsTimeout = UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-ms`);
|
|
161
|
+
setTimeout(
|
|
162
|
+
async () => {
|
|
163
|
+
const isOnline = await isInternetConnection();
|
|
164
|
+
if (!isOnline) {
|
|
165
|
+
logger.warn('No internet connection');
|
|
166
|
+
monitorCallBack(resolve, reject);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
switch (options.type) {
|
|
170
|
+
case 'blue-green':
|
|
171
|
+
{
|
|
172
|
+
if (monitorTrafficName !== traffic) {
|
|
173
|
+
monitorTrafficName = undefined;
|
|
174
|
+
monitorPodName = undefined;
|
|
175
|
+
}
|
|
176
|
+
const cmd = `underpost config get container-status`;
|
|
177
|
+
const checkDeploymentReadyStatus = () => {
|
|
178
|
+
const pods = UnderpostDeploy.API.get(`${deployId}-${env}-${traffic}`);
|
|
179
|
+
if (pods && pods[0]) {
|
|
180
|
+
const { NAME } = pods[0];
|
|
181
|
+
if (
|
|
182
|
+
shellExec(`sudo kubectl exec -i ${NAME} -- sh -c "${cmd}"`, { stdout: true }).match(
|
|
183
|
+
`${deployId}-${env}-running-deployment`,
|
|
184
|
+
)
|
|
185
|
+
) {
|
|
186
|
+
monitorPodName = NAME;
|
|
187
|
+
monitorTrafficName = `${traffic}`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
if (!monitorPodName) {
|
|
192
|
+
checkDeploymentReadyStatus();
|
|
193
|
+
monitorCallBack(resolve, reject);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
default:
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
for (const monitorStatus of [
|
|
204
|
+
{ key: `monitor-input`, value: UnderpostRootEnv.API.get(`monitor-input`) },
|
|
205
|
+
{
|
|
206
|
+
key: `${deployId}-${env}-monitor-input`,
|
|
207
|
+
value: UnderpostRootEnv.API.get(`${deployId}-${env}-monitor-input`),
|
|
208
|
+
},
|
|
209
|
+
])
|
|
210
|
+
switch (monitorStatus.value) {
|
|
211
|
+
case 'pause':
|
|
212
|
+
monitorCallBack(resolve, reject);
|
|
213
|
+
return;
|
|
214
|
+
case 'restart':
|
|
215
|
+
UnderpostRootEnv.API.delete(monitorStatus.key);
|
|
216
|
+
return reject();
|
|
217
|
+
case 'stop':
|
|
218
|
+
UnderpostRootEnv.API.delete(monitorStatus.key);
|
|
219
|
+
return resolve();
|
|
220
|
+
case 'blue-green-switch':
|
|
221
|
+
UnderpostRootEnv.API.delete(monitorStatus.key);
|
|
222
|
+
switchTraffic();
|
|
223
|
+
}
|
|
224
|
+
await monitor(reject);
|
|
225
|
+
monitorCallBack(resolve, reject);
|
|
226
|
+
return;
|
|
227
|
+
},
|
|
228
|
+
!isNaN(envMsTimeout) ? envMsTimeout : optionsMsTimeout,
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
return new Promise((...args) => monitorCallBack(...args));
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default UnderpostMonitor;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { commitData } from '../client/components/core/CommonJs.js';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import { pbcopy, shellExec } from '../server/process.js';
|
|
4
|
+
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import { getNpmRootPath } from '../server/conf.js';
|
|
7
|
+
import UnderpostStartUp from '../server/start.js';
|
|
8
|
+
|
|
9
|
+
dotenv.config();
|
|
10
|
+
|
|
11
|
+
const logger = loggerFactory(import.meta);
|
|
12
|
+
|
|
13
|
+
class UnderpostRepository {
|
|
14
|
+
static API = {
|
|
15
|
+
clone(gitUri = 'underpostnet/pwa-microservices-template', options = { bare: false }) {
|
|
16
|
+
const repoName = gitUri.split('/').pop();
|
|
17
|
+
if (fs.existsSync(`./${repoName}`)) fs.removeSync(`./${repoName}`);
|
|
18
|
+
shellExec(
|
|
19
|
+
`git clone ${options?.bare === true ? ` --bare ` : ''}https://${
|
|
20
|
+
process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN}@` : ''
|
|
21
|
+
}github.com/${gitUri}.git`,
|
|
22
|
+
{
|
|
23
|
+
disableLog: true,
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
pull(repoPath = './', gitUri = 'underpostnet/pwa-microservices-template') {
|
|
28
|
+
shellExec(
|
|
29
|
+
`cd ${repoPath} && git pull https://${
|
|
30
|
+
process.env.GITHUB_TOKEN ? `${process.env.GITHUB_TOKEN}@` : ''
|
|
31
|
+
}github.com/${gitUri}.git`,
|
|
32
|
+
{
|
|
33
|
+
disableLog: true,
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
commit(
|
|
38
|
+
repoPath = './',
|
|
39
|
+
commitType = 'feat',
|
|
40
|
+
subModule = '',
|
|
41
|
+
message = '',
|
|
42
|
+
options = {
|
|
43
|
+
copy: false,
|
|
44
|
+
info: false,
|
|
45
|
+
empty: false,
|
|
46
|
+
},
|
|
47
|
+
) {
|
|
48
|
+
if (commitType === 'reset') {
|
|
49
|
+
shellExec(`cd ${repoPath} && git reset --soft HEAD~${isNaN(parseInt(subModule)) ? 1 : parseInt(subModule)}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (options.info) return logger.info('', commitData);
|
|
53
|
+
const _message = `${commitType}${subModule ? `(${subModule})` : ''}${process.argv.includes('!') ? '!' : ''}: ${
|
|
54
|
+
commitData[commitType].emoji
|
|
55
|
+
} ${message ? message : commitData[commitType].description}`;
|
|
56
|
+
if (options.copy) return pbcopy(_message);
|
|
57
|
+
shellExec(`cd ${repoPath} && git commit ${options?.empty ? `--allow-empty ` : ''}-m "${_message}"`);
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
push(repoPath = './', gitUri = 'underpostnet/pwa-microservices-template', options = { f: false }) {
|
|
61
|
+
shellExec(
|
|
62
|
+
`cd ${repoPath} && git push https://${process.env.GITHUB_TOKEN}@github.com/${gitUri}.git${
|
|
63
|
+
options?.f === true ? ' --force' : ''
|
|
64
|
+
}`,
|
|
65
|
+
{
|
|
66
|
+
disableLog: true,
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
logger.info(
|
|
70
|
+
'commit url',
|
|
71
|
+
`http://github.com/${gitUri}/commit/${shellExec(`cd ${repoPath} && git rev-parse --verify HEAD`, {
|
|
72
|
+
stdout: true,
|
|
73
|
+
}).trim()}`,
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
new(repositoryName) {
|
|
78
|
+
return new Promise(async (resolve, reject) => {
|
|
79
|
+
try {
|
|
80
|
+
await logger.setUpInfo();
|
|
81
|
+
if (repositoryName === 'service')
|
|
82
|
+
return resolve(
|
|
83
|
+
await UnderpostStartUp.API.listenPortController(UnderpostStartUp.API.listenServerFactory(), ':'),
|
|
84
|
+
);
|
|
85
|
+
else actionInitLog();
|
|
86
|
+
const exeRootPath = `${getNpmRootPath()}/underpost`;
|
|
87
|
+
const destFolder = `./${repositoryName}`;
|
|
88
|
+
logger.info('Note: This process may take several minutes to complete');
|
|
89
|
+
logger.info('build app', { destFolder });
|
|
90
|
+
if (fs.existsSync(destFolder)) fs.removeSync(destFolder);
|
|
91
|
+
fs.mkdirSync(destFolder, { recursive: true });
|
|
92
|
+
fs.copySync(exeRootPath, destFolder);
|
|
93
|
+
fs.writeFileSync(`${destFolder}/.gitignore`, fs.readFileSync(`${exeRootPath}/.dockerignore`, 'utf8'), 'utf8');
|
|
94
|
+
shellExec(`cd ${destFolder} && git init && git add . && git commit -m "Base template implementation"`);
|
|
95
|
+
shellExec(`cd ${destFolder} && npm run build`);
|
|
96
|
+
shellExec(`cd ${destFolder} && npm run dev`);
|
|
97
|
+
return resolve();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
logger.error(error, error.stack);
|
|
100
|
+
return reject(error.message);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
getDeleteFiles(path = '.') {
|
|
106
|
+
const commandUntrack = `cd ${path} && git ls-files --deleted`;
|
|
107
|
+
const diffUntrackOutput = shellExec(commandUntrack, { stdout: true, silent: true });
|
|
108
|
+
return diffUntrackOutput.toString().split('\n').filter(Boolean);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
getChangedFiles(path = '.', extension = '', head = false) {
|
|
112
|
+
const extensionFilter = extension ? `-- '***.${extension}'` : '';
|
|
113
|
+
const command = `cd ${path} && git diff ${head ? 'HEAD^ HEAD ' : ''}--name-only ${extensionFilter}`;
|
|
114
|
+
const commandUntrack = `cd ${path} && git ls-files --others --exclude-standard`;
|
|
115
|
+
const diffOutput = shellExec(command, { stdout: true, silent: true });
|
|
116
|
+
const diffUntrackOutput = shellExec(commandUntrack, { stdout: true, silent: true });
|
|
117
|
+
const deleteFiles = UnderpostRepository.API.getDeleteFiles(path);
|
|
118
|
+
return diffOutput
|
|
119
|
+
.toString()
|
|
120
|
+
.split('\n')
|
|
121
|
+
.filter(Boolean)
|
|
122
|
+
.concat(diffUntrackOutput.toString().split('\n').filter(Boolean))
|
|
123
|
+
.filter((f) => !deleteFiles.includes(f));
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default UnderpostRepository;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getNpmRootPath } from '../server/conf.js';
|
|
2
|
+
import { loggerFactory } from '../server/logger.js';
|
|
3
|
+
import { shellExec } from '../server/process.js';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import UnderpostDeploy from './deploy.js';
|
|
6
|
+
|
|
7
|
+
const logger = loggerFactory(import.meta);
|
|
8
|
+
|
|
9
|
+
class UnderpostScript {
|
|
10
|
+
static API = {
|
|
11
|
+
set(key, value) {
|
|
12
|
+
const npmRoot = `${getNpmRootPath()}/underpost`;
|
|
13
|
+
const packageJson = JSON.parse(fs.readFileSync(`${npmRoot}/package.json`, 'utf8'));
|
|
14
|
+
packageJson.scripts[key] = value;
|
|
15
|
+
fs.writeFileSync(`${npmRoot}/package.json`, JSON.stringify(packageJson, null, 4));
|
|
16
|
+
},
|
|
17
|
+
run(key, value, options) {
|
|
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
|
+
}
|
|
42
|
+
shellExec(`cd ${npmRoot} && npm run ${key}`);
|
|
43
|
+
},
|
|
44
|
+
get(key) {
|
|
45
|
+
const npmRoot = `${getNpmRootPath()}/underpost`;
|
|
46
|
+
const packageJson = JSON.parse(fs.readFileSync(`${npmRoot}/package.json`, 'utf8'));
|
|
47
|
+
logger.info('[get] ' + key, packageJson.scripts[key]);
|
|
48
|
+
return packageJson.scripts[key];
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default UnderpostScript;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
import { shellExec } from '../server/process.js';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import UnderpostRootEnv from './env.js';
|
|
5
|
+
|
|
6
|
+
class UnderpostSecret {
|
|
7
|
+
static API = {
|
|
8
|
+
docker: {
|
|
9
|
+
init() {
|
|
10
|
+
shellExec(`docker swarm init`);
|
|
11
|
+
},
|
|
12
|
+
createFromEnvFile(envPath) {
|
|
13
|
+
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
14
|
+
for (const key of Object.keys(envObj)) {
|
|
15
|
+
UnderpostSecret.API.docker.set(key, envObj[key]);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
set(key, value) {
|
|
19
|
+
shellExec(`docker secret rm ${key}`);
|
|
20
|
+
shellExec(`echo "${value}" | docker secret create ${key} -`);
|
|
21
|
+
},
|
|
22
|
+
list() {
|
|
23
|
+
shellExec(`docker secret ls`);
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
underpost: {
|
|
27
|
+
createFromEnvFile(envPath) {
|
|
28
|
+
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
29
|
+
for (const key of Object.keys(envObj)) {
|
|
30
|
+
UnderpostRootEnv.API.set(key, envObj[key]);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default UnderpostSecret;
|
package/src/cli/test.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { timer } from '../client/components/core/CommonJs.js';
|
|
2
|
+
import { MariaDB } from '../db/mariadb/MariaDB.js';
|
|
3
|
+
import { getNpmRootPath } from '../server/conf.js';
|
|
4
|
+
import { actionInitLog, loggerFactory, setUpInfo } from '../server/logger.js';
|
|
5
|
+
import { pbcopy, shellExec } from '../server/process.js';
|
|
6
|
+
import UnderpostDeploy from './deploy.js';
|
|
7
|
+
|
|
8
|
+
const logger = loggerFactory(import.meta);
|
|
9
|
+
|
|
10
|
+
class UnderpostTest {
|
|
11
|
+
static API = {
|
|
12
|
+
/**
|
|
13
|
+
* Logs information about the current process environment to the console.
|
|
14
|
+
*
|
|
15
|
+
* This function is used to log details about
|
|
16
|
+
* the execution context, such as command-line arguments,
|
|
17
|
+
* environment variables, the process's administrative privileges,
|
|
18
|
+
* and the maximum available heap space size.
|
|
19
|
+
*
|
|
20
|
+
* @static
|
|
21
|
+
* @method setUpInfo
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
* @memberof Underpost
|
|
24
|
+
*/
|
|
25
|
+
async setUpInfo() {
|
|
26
|
+
return await setUpInfo(logger);
|
|
27
|
+
},
|
|
28
|
+
run() {
|
|
29
|
+
actionInitLog();
|
|
30
|
+
shellExec(`cd ${getNpmRootPath()}/underpost && npm run test`);
|
|
31
|
+
},
|
|
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
|
+
|
|
41
|
+
if (options.sh === true || options.logs === true) {
|
|
42
|
+
const [pod] = UnderpostDeploy.API.get(deployList);
|
|
43
|
+
if (pod) {
|
|
44
|
+
if (options.sh) return pbcopy(`sudo kubectl exec -it ${pod.NAME} -- sh`);
|
|
45
|
+
if (options.logs) return shellExec(`sudo kubectl logs -f ${pod.NAME}`);
|
|
46
|
+
}
|
|
47
|
+
return logger.warn(`Couldn't find pods in deployment`, deployList);
|
|
48
|
+
}
|
|
49
|
+
if (deployList) {
|
|
50
|
+
for (const _deployId of deployList.split(',')) {
|
|
51
|
+
const deployId = _deployId.trim();
|
|
52
|
+
if (!deployId) continue;
|
|
53
|
+
if (options.itc === true)
|
|
54
|
+
switch (deployId) {
|
|
55
|
+
case 'dd-lampp':
|
|
56
|
+
{
|
|
57
|
+
const { MARIADB_HOST, MARIADB_USER, MARIADB_PASSWORD, DD_LAMPP_TEST_DB_0 } = process.env;
|
|
58
|
+
|
|
59
|
+
await MariaDB.query({
|
|
60
|
+
host: MARIADB_HOST,
|
|
61
|
+
user: MARIADB_USER,
|
|
62
|
+
password: MARIADB_PASSWORD,
|
|
63
|
+
query: `SHOW TABLES FROM ${DD_LAMPP_TEST_DB_0}`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
default:
|
|
69
|
+
{
|
|
70
|
+
shellExec('npm run test');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const pods = UnderpostDeploy.API.get(deployId);
|
|
77
|
+
if (pods.length > 0)
|
|
78
|
+
for (const deployData of pods) {
|
|
79
|
+
const { NAME } = deployData;
|
|
80
|
+
shellExec(
|
|
81
|
+
`sudo kubectl exec -i ${NAME} -- sh -c "cd /home/dd/engine && underpost test ${deployId} --inside-container"`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
else logger.warn(`Couldn't find pods in deployment`, { deployId });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else return UnderpostTest.API.run();
|
|
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
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default UnderpostTest;
|
|
@@ -151,23 +151,27 @@ const Account = {
|
|
|
151
151
|
// s(`.btn-close-modal-account`).click();
|
|
152
152
|
s(`.main-btn-recover`).click();
|
|
153
153
|
};
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
154
|
+
EventsUI.onClick(
|
|
155
|
+
`.btn-account-delete-confirm`,
|
|
156
|
+
async (e) => {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
const confirmResult = await Modal.RenderConfirm({
|
|
159
|
+
html: async () => {
|
|
160
|
+
return html`
|
|
161
|
+
<div class="in section-mp" style="text-align: center">
|
|
162
|
+
${Translate.Render('confirm-delete-account')}
|
|
163
|
+
</div>
|
|
164
|
+
`;
|
|
165
|
+
},
|
|
166
|
+
id: 'delete-account-modal',
|
|
167
|
+
});
|
|
168
|
+
if (confirmResult.status === 'cancelled') return;
|
|
169
|
+
s(`.btn-account-delete-confirm`).classList.add('hide');
|
|
170
|
+
s(`.btn-account-delete`).classList.remove('hide');
|
|
171
|
+
s(`.btn-account-delete`).click();
|
|
172
|
+
},
|
|
173
|
+
{ context: 'modal' },
|
|
174
|
+
);
|
|
171
175
|
EventsUI.onClick(`.btn-account-delete`, async (e) => {
|
|
172
176
|
e.preventDefault();
|
|
173
177
|
const result = await UserService.delete({ id: user._id });
|
|
@@ -178,7 +182,7 @@ const Account = {
|
|
|
178
182
|
s(`.btn-account-delete-confirm`).classList.remove('hide');
|
|
179
183
|
s(`.btn-account-delete`).classList.add('hide');
|
|
180
184
|
if (result.status === 'success') {
|
|
181
|
-
|
|
185
|
+
Modal.onHomeRouterEvent();
|
|
182
186
|
await Auth.sessionOut();
|
|
183
187
|
}
|
|
184
188
|
});
|
|
@@ -200,7 +204,7 @@ const Account = {
|
|
|
200
204
|
disabled: false,
|
|
201
205
|
extension: async () =>
|
|
202
206
|
html`${await BtnIcon.Render({
|
|
203
|
-
class: `wfa btn-input-extension btn-account-update-username`,
|
|
207
|
+
class: `in wfa btn-input-extension btn-account-update-username`,
|
|
204
208
|
type: 'button',
|
|
205
209
|
style: 'text-align: left',
|
|
206
210
|
label: html`${Translate.Render(`update`)}`,
|
|
@@ -219,7 +223,7 @@ const Account = {
|
|
|
219
223
|
extension: !(options && options.disabled && options.disabled.includes('emailConfirm'))
|
|
220
224
|
? async () => html`<div class="in verify-email-status"></div>
|
|
221
225
|
${await BtnIcon.Render({
|
|
222
|
-
class: `wfa btn-input-extension btn-confirm-email`,
|
|
226
|
+
class: `in wfa btn-input-extension btn-confirm-email`,
|
|
223
227
|
type: 'button',
|
|
224
228
|
style: 'text-align: left',
|
|
225
229
|
label: html`<div class="in">
|
|
@@ -242,7 +246,7 @@ const Account = {
|
|
|
242
246
|
disabledEye: true,
|
|
243
247
|
extension: async () =>
|
|
244
248
|
html`${await BtnIcon.Render({
|
|
245
|
-
class: `wfa btn-input-extension btn-account-change-password`,
|
|
249
|
+
class: `in wfa btn-input-extension btn-account-change-password`,
|
|
246
250
|
type: 'button',
|
|
247
251
|
style: 'text-align: left',
|
|
248
252
|
label: html`${Translate.Render(`change-password`)}`,
|
|
@@ -252,7 +256,7 @@ const Account = {
|
|
|
252
256
|
${options?.bottomRender ? await options.bottomRender() : ``}
|
|
253
257
|
<div class="in hide">
|
|
254
258
|
${await BtnIcon.Render({
|
|
255
|
-
class: 'section-mp form-button btn-account',
|
|
259
|
+
class: 'in section-mp form-button btn-account',
|
|
256
260
|
label: Translate.Render('update'),
|
|
257
261
|
type: 'submit',
|
|
258
262
|
})}
|
|
@@ -260,13 +264,13 @@ const Account = {
|
|
|
260
264
|
</form>
|
|
261
265
|
<div class="in">
|
|
262
266
|
${await BtnIcon.Render({
|
|
263
|
-
class: 'section-mp form-button btn-account-delete hide',
|
|
267
|
+
class: 'in section-mp form-button btn-account-delete hide',
|
|
264
268
|
label: html` ${Translate.Render(`delete-account`)}`,
|
|
265
269
|
type: 'button',
|
|
266
270
|
style: 'color: #5f5f5f',
|
|
267
271
|
})}
|
|
268
272
|
${await BtnIcon.Render({
|
|
269
|
-
class: 'section-mp form-button btn-account-delete-confirm',
|
|
273
|
+
class: 'in section-mp form-button btn-account-delete-confirm',
|
|
270
274
|
label: html` ${Translate.Render(`delete-account`)}`,
|
|
271
275
|
type: 'button',
|
|
272
276
|
style: 'color: #5f5f5f',
|