Compare commits
488 Commits
vmsselect
...
tryingsess
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ee489ef5a | ||
|
|
c2f26ae478 | ||
|
|
19e94b031a | ||
|
|
7843a26962 | ||
|
|
7040d5d030 | ||
|
|
66111af16f | ||
|
|
1eae012704 | ||
|
|
6f7db33e89 | ||
|
|
af323e1f91 | ||
|
|
b85d5d1a2d | ||
|
|
6c6e1c27b1 | ||
|
|
08714427b3 | ||
|
|
1aabf5dd45 | ||
|
|
03ed7b74db | ||
|
|
036ab4077c | ||
|
|
1888a447d7 | ||
|
|
4e99e55373 | ||
|
|
a31d3324ad | ||
|
|
092726c3f9 | ||
|
|
630a35d528 | ||
|
|
f82c844f6b | ||
|
|
24f9c51559 | ||
|
|
3ceffc97fb | ||
|
|
dcc48ae5d3 | ||
|
|
b03fc86168 | ||
|
|
0784f7bdfb | ||
|
|
71d26dbb9a | ||
|
|
dd4487edcd | ||
|
|
f805f78ef3 | ||
|
|
73206b556d | ||
|
|
397f3c1251 | ||
|
|
d5d7a128d2 | ||
|
|
1f1e9f5b21 | ||
|
|
d262735c4e | ||
|
|
ecac686e25 | ||
|
|
f1c9a6e87e | ||
|
|
fcba362cc0 | ||
|
|
c3d23d7415 | ||
|
|
84303d0632 | ||
|
|
44b6a6d658 | ||
|
|
4e90027630 | ||
|
|
b207b9b3c4 | ||
|
|
213867ed77 | ||
|
|
4412216b6c | ||
|
|
70014e9021 | ||
|
|
a3704bd4f0 | ||
|
|
ca0b30f37e | ||
|
|
7e9ec60b8e | ||
|
|
adb4f54c54 | ||
|
|
eacba70919 | ||
|
|
a68143af97 | ||
|
|
a95e4e691d | ||
|
|
d4578bef28 | ||
|
|
ae04d5f62d | ||
|
|
c3ea7ba384 | ||
|
|
bca55b62df | ||
|
|
36741b06ad | ||
|
|
e7cb9b149d | ||
|
|
8129378fc8 | ||
|
|
f3d34321cd | ||
|
|
2a1525670c | ||
|
|
a7e36d0119 | ||
|
|
2ba046310f | ||
|
|
6ed942d954 | ||
|
|
0af1c4d4c9 | ||
|
|
811220f882 | ||
|
|
caeef42000 | ||
|
|
90a3cf176f | ||
|
|
44cec9cc31 | ||
|
|
e04cec01e6 | ||
|
|
9df38c142e | ||
|
|
51bf4c8064 | ||
|
|
cca7714f19 | ||
|
|
fc6d9437a9 | ||
|
|
537123c6aa | ||
|
|
373258f402 | ||
|
|
6f2a3b75d4 | ||
|
|
ed4cd383df | ||
|
|
3cd7e1d4ae | ||
|
|
7264710bb5 | ||
|
|
004c425738 | ||
|
|
cd50c47b4a | ||
|
|
93d240e9f2 | ||
|
|
01640adde7 | ||
|
|
c3c1c76acb | ||
|
|
7e58cbc685 | ||
|
|
5e7ad89fdb | ||
|
|
f1be3b7730 | ||
|
|
08286e3172 | ||
|
|
f16683a421 | ||
|
|
ce7f97839b | ||
|
|
7e39db8dbd | ||
|
|
5c029d8976 | ||
|
|
bc76634bca | ||
|
|
fcb8365fe4 | ||
|
|
b2f4a41c0d | ||
|
|
811ca7ca70 | ||
|
|
1fcec290b6 | ||
|
|
d9d2ac29b9 | ||
|
|
28e3c12b23 | ||
|
|
1eff5089f2 | ||
|
|
b0520064e8 | ||
|
|
b3262d9acb | ||
|
|
272cb32e74 | ||
|
|
577246830d | ||
|
|
7fed166c29 | ||
|
|
294a3a8d2d | ||
|
|
835d162ede | ||
|
|
8f8c18490a | ||
|
|
dbcdd6974b | ||
|
|
61071e1d00 | ||
|
|
1aa83a1d5b | ||
|
|
f9c27e4b3d | ||
|
|
cb10e425d7 | ||
|
|
8827940789 | ||
|
|
30e099ee2e | ||
|
|
834aff2a9d | ||
|
|
975b84a3b2 | ||
|
|
14d1690100 | ||
|
|
4bbd86e82f | ||
|
|
ba30495cc8 | ||
|
|
368d58070c | ||
|
|
54f05b11c4 | ||
|
|
77b599ec83 | ||
|
|
2cbcb9dd5d | ||
|
|
0f0d050599 | ||
|
|
99d9a1a053 | ||
|
|
1c9d5e5ce2 | ||
|
|
56748c4478 | ||
|
|
41cdb46fc2 | ||
|
|
515fd749e6 | ||
|
|
2574278b68 | ||
|
|
395c495130 | ||
|
|
075815a6b5 | ||
|
|
29c6bcad1e | ||
|
|
35408b7b1f | ||
|
|
04d8d41b05 | ||
|
|
7700675a76 | ||
|
|
2b57b2535d | ||
|
|
ddc36dbfcf | ||
|
|
2ab78335a2 | ||
|
|
fed9ad156c | ||
|
|
b8e06f91ef | ||
|
|
de56713e96 | ||
|
|
3515158bb0 | ||
|
|
a5ed3edd84 | ||
|
|
e2d32d954e | ||
|
|
baf2b3776c | ||
|
|
f48e32230b | ||
|
|
3338f66c0b | ||
|
|
8d34b3d4af | ||
|
|
023a3fa89d | ||
|
|
ad6d7a1082 | ||
|
|
8e8a23454a | ||
|
|
2cea8c6c8a | ||
|
|
ce795f99f0 | ||
|
|
fcda1da75b | ||
|
|
199d11d22e | ||
|
|
dd819b17ce | ||
|
|
2b93f3f986 | ||
|
|
1cf448c823 | ||
|
|
6618b80964 | ||
|
|
3215f559d4 | ||
|
|
cb634636f5 | ||
|
|
c9b8ed3e58 | ||
|
|
6e6233232c | ||
|
|
642ac66b9f | ||
|
|
319ab11274 | ||
|
|
5e928f2e67 | ||
|
|
26b65b5752 | ||
|
|
b677044fa6 | ||
|
|
bafcd504f2 | ||
|
|
f41a9f76c1 | ||
|
|
37c8f97493 | ||
|
|
9aac529d70 | ||
|
|
3d59581432 | ||
|
|
469e1244cc | ||
|
|
1735edf494 | ||
|
|
b02b258d77 | ||
|
|
222056574c | ||
|
|
6748b3518f | ||
|
|
29ccb721f5 | ||
|
|
26706a154b | ||
|
|
131be49e5f | ||
|
|
b7e57ef49c | ||
|
|
6f57285db0 | ||
|
|
6694637fc1 | ||
|
|
e84ffa16d0 | ||
|
|
10b492b3d2 | ||
|
|
ab0866d36a | ||
|
|
deb29c0224 | ||
|
|
c8944543cc | ||
|
|
6ae1472d6c | ||
|
|
9c2c0393e1 | ||
|
|
062e3c89fd | ||
|
|
c08db405f4 | ||
|
|
e7ebfa7f4c | ||
|
|
46a908fb3c | ||
|
|
36a9fbed56 | ||
|
|
63965ec26e | ||
|
|
8b09158148 | ||
|
|
fc10564ab5 | ||
|
|
4c5b65aa38 | ||
|
|
94e392d3ef | ||
|
|
ebc7924049 | ||
|
|
689b789864 | ||
|
|
c3b0470c20 | ||
|
|
3501cf72aa | ||
|
|
3464bfa85a | ||
|
|
671b1b79ed | ||
|
|
b823e82411 | ||
|
|
25c1462848 | ||
|
|
fb1308556c | ||
|
|
d85f521b53 | ||
|
|
9105755afe | ||
|
|
b3b157641e | ||
|
|
55678c9e91 | ||
|
|
7e173a2a5f | ||
|
|
41b0001886 | ||
|
|
75efcbc3a1 | ||
|
|
3c3641040c | ||
|
|
fce686972c | ||
|
|
d28a7b5f26 | ||
|
|
9372e47589 | ||
|
|
b5af6c0959 | ||
|
|
7ec3c6b1c5 | ||
|
|
12f0e627a1 | ||
|
|
ec6149c127 | ||
|
|
06050b2796 | ||
|
|
704319b27a | ||
|
|
c868e7000e | ||
|
|
85f9c826cf | ||
|
|
7d65d736bc | ||
|
|
2208e362c2 | ||
|
|
ac1933d881 | ||
|
|
b95570b769 | ||
|
|
b52b5a0df7 | ||
|
|
1cedcb791d | ||
|
|
a20e677954 | ||
|
|
2ea8877a76 | ||
|
|
f272b030fb | ||
|
|
0de582a750 | ||
|
|
df6d28fd37 | ||
|
|
d3149ad1b3 | ||
|
|
e35873ab54 | ||
|
|
7071eb6907 | ||
|
|
852b68eaba | ||
|
|
9063951aa7 | ||
|
|
f69abbcda9 | ||
|
|
45f936ee7c | ||
|
|
39663ba9ad | ||
|
|
58d50b2552 | ||
|
|
32a1abce22 | ||
|
|
30388bf064 | ||
|
|
a4bc5e4e7b | ||
|
|
1e450bf019 | ||
|
|
e65215448a | ||
|
|
fbd2d8d829 | ||
|
|
d531463e67 | ||
|
|
28b792fe76 | ||
|
|
c19e4ca60a | ||
|
|
9749e98aee | ||
|
|
b49895045d | ||
|
|
ca21757199 | ||
|
|
2e98be3fca | ||
|
|
3b86ac7777 | ||
|
|
295d1ffb30 | ||
|
|
26eb86193c | ||
|
|
9e2cf17ab7 | ||
|
|
4600969f16 | ||
|
|
332df80b1d | ||
|
|
0a534eed37 | ||
|
|
900a6abeb7 | ||
|
|
9b81646219 | ||
|
|
ca8d26c119 | ||
|
|
245bd0d90e | ||
|
|
67ec379e1d | ||
|
|
3e73e1a561 | ||
|
|
55ccfab66c | ||
|
|
23ae12bf2e | ||
|
|
e8de86da66 | ||
|
|
13a297d58d | ||
|
|
f2aa774acd | ||
|
|
721b478d00 | ||
|
|
4ee672ea3b | ||
|
|
acd210da93 | ||
|
|
4c853a7b20 | ||
|
|
51f20a9e2d | ||
|
|
18a17ea761 | ||
|
|
60c5bc543a | ||
|
|
0aa1dc0c74 | ||
|
|
f5b85b25ed | ||
|
|
4245694b10 | ||
|
|
24c99df65a | ||
|
|
6283dddb22 | ||
|
|
c3b8bd119f | ||
|
|
2e31fe279c | ||
|
|
899fd2fda7 | ||
|
|
dc116c9f7f | ||
|
|
455cc3e75d | ||
|
|
16f574f744 | ||
|
|
1486e2fd21 | ||
|
|
c0349f440e | ||
|
|
1a99589c8b | ||
|
|
b54a590f51 | ||
|
|
eafd78f91c | ||
|
|
243c65c64b | ||
|
|
672ba4194a | ||
|
|
9f539bd96a | ||
|
|
55b9f0c032 | ||
|
|
00dcb33872 | ||
|
|
429838cfc5 | ||
|
|
3a2d6eb0b6 | ||
|
|
4290406c19 | ||
|
|
9057f58342 | ||
|
|
6d03a4f6ed | ||
|
|
5d914c890f | ||
|
|
b1286a1b2b | ||
|
|
a5b5f2a8ca | ||
|
|
8bb7856102 | ||
|
|
8280b32872 | ||
|
|
39ab3fe6dd | ||
|
|
fbe30b740c | ||
|
|
ab5ab80765 | ||
|
|
180a20c8d6 | ||
|
|
91d7017b03 | ||
|
|
5b6cb73b09 | ||
|
|
2003a039f2 | ||
|
|
96b1302520 | ||
|
|
891acb714e | ||
|
|
7cf4e7b5e7 | ||
|
|
0c78992958 | ||
|
|
ed44eb7fef | ||
|
|
8ed6612686 | ||
|
|
793b8f8739 | ||
|
|
a9b3b57414 | ||
|
|
69b92a64bc | ||
|
|
f84ecd7212 | ||
|
|
5b6bb90119 | ||
|
|
d5637065c2 | ||
|
|
6d322c3cc2 | ||
|
|
e0ba340ca1 | ||
|
|
79ec7af773 | ||
|
|
752a3329be | ||
|
|
36a246504f | ||
|
|
b2a4b41023 | ||
|
|
852c943f99 | ||
|
|
96e33ca765 | ||
|
|
bae80ec847 | ||
|
|
ce55788a26 | ||
|
|
17e128099e | ||
|
|
17f391ffbd | ||
|
|
23bf5f58f6 | ||
|
|
b36616f28a | ||
|
|
5e266a4461 | ||
|
|
0dd199d5f8 | ||
|
|
0f020e96a7 | ||
|
|
0dcba31636 | ||
|
|
04221eb03a | ||
|
|
c12039d354 | ||
|
|
c5dcc547a9 | ||
|
|
5c3e10f9df | ||
|
|
e1848da829 | ||
|
|
a4ae065d8b | ||
|
|
8e0ffd72fa | ||
|
|
fafe697af5 | ||
|
|
2cf319175d | ||
|
|
643cb9775d | ||
|
|
66085e49c5 | ||
|
|
fd85d65aeb | ||
|
|
24a3573f73 | ||
|
|
048fc6d89d | ||
|
|
bf20f4efd4 | ||
|
|
2fd05a2b54 | ||
|
|
03ce50c255 | ||
|
|
171f552571 | ||
|
|
1843907b42 | ||
|
|
d88a6f2359 | ||
|
|
d273bc937e | ||
|
|
6777d2f0d8 | ||
|
|
3d2506639d | ||
|
|
a9806bd94e | ||
|
|
783642f083 | ||
|
|
2a7fec03d7 | ||
|
|
aed18cccfa | ||
|
|
0f12ee6649 | ||
|
|
fbc727a66e | ||
|
|
897fa2a631 | ||
|
|
43a428592e | ||
|
|
c3796b478f | ||
|
|
e12f60516b | ||
|
|
84fe322c96 | ||
|
|
84263b08dd | ||
|
|
895e069326 | ||
|
|
da4c940055 | ||
|
|
f9e608c06c | ||
|
|
15e8c12508 | ||
|
|
1e543b1e6f | ||
|
|
00739082f2 | ||
|
|
aacf23f57b | ||
|
|
ca037b42ef | ||
|
|
96300e1e99 | ||
|
|
721fbe24b8 | ||
|
|
b20a6fe858 | ||
|
|
658bd6a131 | ||
|
|
be5c9ef85e | ||
|
|
5cc6f7a3c6 | ||
|
|
5fdd26306a | ||
|
|
0e74d8873f | ||
|
|
3f8e9290de | ||
|
|
034dde5e1d | ||
|
|
d0e274cd5f | ||
|
|
b3de18bbda | ||
|
|
e02ac30cde | ||
|
|
49b444cfc8 | ||
|
|
1ab9055fc5 | ||
|
|
fd8e542467 | ||
|
|
7699978f39 | ||
|
|
902db15c17 | ||
|
|
49bd59df62 | ||
|
|
b7e44f436c | ||
|
|
aa26928716 | ||
|
|
8509a520cf | ||
|
|
0037634b70 | ||
|
|
6dfab50871 | ||
|
|
fcc46cdb1a | ||
|
|
06c4fae9c2 | ||
|
|
8b18a65303 | ||
|
|
e4ad1dde54 | ||
|
|
a2b82501aa | ||
|
|
ad0c4cbbd1 | ||
|
|
1a638c16bf | ||
|
|
7dd9fc79c2 | ||
|
|
d492c5e4a8 | ||
|
|
c2a14e5f79 | ||
|
|
c888c068b4 | ||
|
|
7b0bacc990 | ||
|
|
4d66c40c07 | ||
|
|
8a86130fe6 | ||
|
|
b7db4a5f00 | ||
|
|
f3b9869d4f | ||
|
|
0801f84b10 | ||
|
|
9dd091f982 | ||
|
|
190967a889 | ||
|
|
510254922b | ||
|
|
91da6199b3 | ||
|
|
9374a60e2a | ||
|
|
0e1817947e | ||
|
|
870ad47cbc | ||
|
|
4304c56685 | ||
|
|
b7dfee506f | ||
|
|
022b414766 | ||
|
|
05143c78c0 | ||
|
|
04ed3a22ef | ||
|
|
d3f0513224 | ||
|
|
d5efa608bc | ||
|
|
040d2187cb | ||
|
|
2d254ae238 | ||
|
|
82861f2130 | ||
|
|
e0270e6925 | ||
|
|
dc2180af03 | ||
|
|
d240b06c5c | ||
|
|
2434d2d157 | ||
|
|
c631fb141e | ||
|
|
4e27a35e4e | ||
|
|
1b458ab2df | ||
|
|
082e98c01a | ||
|
|
c6d1f84c57 | ||
|
|
c3b9e8532f | ||
|
|
4194973884 | ||
|
|
57a47d7794 | ||
|
|
953930bc42 | ||
|
|
35d78b4db0 | ||
|
|
b8a8b8bace | ||
|
|
e09d03f1ef | ||
|
|
dd2f328f45 | ||
|
|
b3895be44a | ||
|
|
7516f46903 | ||
|
|
3c7327ed57 | ||
|
|
8e1c027038 | ||
|
|
f8fe33039c | ||
|
|
bf6ea3cd51 | ||
|
|
e3aeeea51f | ||
|
|
1fba40289e | ||
|
|
b32c18a349 | ||
|
|
9fc6bdd5a3 | ||
|
|
755fd11f1a | ||
|
|
82984673aa |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -48,4 +48,4 @@ secrets.json
|
||||
|
||||
qmi-cloud-tf-modules/
|
||||
*.pfx
|
||||
|
||||
/photos/*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Stage 1:
|
||||
FROM node:13.8-alpine AS sources
|
||||
FROM node:15.12.0-alpine AS sources
|
||||
|
||||
RUN apk --no-cache add yarn
|
||||
RUN apk --no-cache add yarn git
|
||||
|
||||
WORKDIR /var/www/app
|
||||
|
||||
@@ -12,7 +12,7 @@ ADD ./qmi-cloud-common ./qmi-cloud-common
|
||||
RUN yarn install --production
|
||||
|
||||
# Stage 2:
|
||||
FROM node:13.8-alpine AS production
|
||||
FROM node:15.12.0-alpine AS production
|
||||
WORKDIR /var/www/app
|
||||
COPY --from=sources /var/www/app/node_modules ./node_modules
|
||||
COPY --from=sources /var/www/app/package.json ./package.json
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# QMI Cloud
|
||||
|
||||
[](https://gitlab.com/qmi/qmi-cloud/-/commits/master)
|
||||
|
||||
## Pre-requisites
|
||||
- Docker
|
||||
- Docker-Compose
|
||||
@@ -7,9 +9,9 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"AZURE_TENANT_ID" : "xxxxxxxx",
|
||||
"AZURE_CLIENT_ID": "yyyyyyyy",
|
||||
"AZURE_CLIENT_SECRET": "zzzzzzzz"
|
||||
"IDENTITY_METADATA" : "xxxxxxxx",
|
||||
"CLIENT_ID": "yyyyyyyy",
|
||||
"CLIENT_SECRET": "zzzzzzzz"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
11
angular.json
11
angular.json
@@ -23,8 +23,10 @@
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
"src/favicon.svg",
|
||||
"src/assets",
|
||||
"src/env.js",
|
||||
"src/oauth-callback.html"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss",
|
||||
@@ -79,7 +81,8 @@
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "qmi-cloud:build"
|
||||
"browserTarget": "qmi-cloud:build",
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -105,7 +108,7 @@
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/favicon.svg",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
|
||||
3
dist/qmi-cloud/assets/faq.md
vendored
3
dist/qmi-cloud/assets/faq.md
vendored
@@ -10,7 +10,7 @@ Just an assorted selection of questions and answers.
|
||||
|
||||
### 2\. Are these QMI scenarios ready to go End-To-End demos?
|
||||
|
||||
- Not at this moment, a global environment is being built for PreSales by GEAR.
|
||||
- Not at this moment, a global environment is being built for PreSales by Innovation & Excellence Team.
|
||||
- Provided scenarios are meant for (See 1). QMI works by installing licensing the products. QMI completes the main setup for the scenario to properly work where possible. You may need to put custom data, Qlik Sense applications or whatever you need for your customer engagement.
|
||||
|
||||
* * *
|
||||
@@ -117,7 +117,6 @@ Just an assorted selection of questions and answers.
|
||||
### 15\. How are we managing Costs?
|
||||
|
||||
- Excessive use for instances, will need to be justified, every instance logged it tagged with your trigram. The usage of QMI Cloud will be made public within a Qlik Sense Application.
|
||||
- GEAR is looking at estimating the price for each server that you initiate.
|
||||
|
||||
* * *
|
||||
|
||||
|
||||
BIN
dist/qmi-cloud/assets/favicon.ico
vendored
BIN
dist/qmi-cloud/assets/favicon.ico
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
8
dist/qmi-cloud/assets/favicon.svg
vendored
Normal file
8
dist/qmi-cloud/assets/favicon.svg
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M23.3002 25.1984C21.3002 26.7984 18.7002 27.6984 16.0002 27.6984C9.5002 27.7984 4.2002 22.4984 4.2002 15.9984C4.2002 13.1984 5.2002 10.5984 6.8002 8.59844L4.7002 6.39844C2.5002 8.99844 1.2002 12.2984 1.2002 15.9984C1.2002 24.1984 7.8002 30.7984 16.0002 30.7984C19.6002 30.7984 22.9002 29.4984 25.5002 27.3984L23.3002 25.1984Z"
|
||||
fill="#54565A" />
|
||||
<path
|
||||
d="M27.9002 30.0992H32.0002L27.6002 25.2992C29.6002 22.7992 30.8002 19.4992 30.8002 15.9992C30.8002 7.79922 24.2002 1.19922 16.0002 1.19922C11.5002 1.19922 7.4002 3.29922 4.7002 6.49922L6.9002 8.69922C9.0002 5.89922 12.3002 4.19922 16.0002 4.19922C22.5002 4.19922 27.8002 9.49922 27.8002 15.9992C27.8002 19.6992 26.1002 23.0992 23.3002 25.1992L25.3002 27.1992L25.4002 27.2992L27.9002 30.0992Z"
|
||||
fill="#009845" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 914 B |
1
dist/qmi-cloud/assets/logo.svg
vendored
Normal file
1
dist/qmi-cloud/assets/logo.svg
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="30" fill="none"><path fill="#54565A" d="M38.188.66h-2.985v28.562h2.985V.66ZM46.298 9.27h-2.974v19.953h2.974V9.27ZM46.842 3.02a2.033 2.033 0 1 0-4.014-.652 2.033 2.033 0 0 0 4.014.651ZM22.408 24.334A11.885 11.885 0 0 1 15 26.907C8.425 26.918 3.093 21.587 3.093 15c0-2.833.984-5.429 2.628-7.473L3.536 5.332A14.912 14.912 0 0 0 0 15c0 8.285 6.716 15 15 15 3.645 0 6.986-1.297 9.582-3.46l-2.174-2.206Z"/><path fill="#009845" d="M27.035 29.232h4.132l-4.456-4.856A14.951 14.951 0 0 0 30 15c0-8.284-6.716-15-15-15A14.974 14.974 0 0 0 3.535 5.332L5.72 7.527a11.878 11.878 0 0 1 9.29-4.445c6.586 0 11.917 5.332 11.917 11.918 0 3.785-1.762 7.16-4.51 9.333l2.045 2.055.086.097.022.022 2.465 2.725Z"/><path fill="#54565A" d="M67.191 9.269H63.05l-8.597 7.527L54.44.66h-2.974v28.562h2.974v-9.214l8.825 9.214h4.13L56.875 18.548l10.317-9.28ZM71.495 27.816v1.417h-.248v-.973l-.422.973h-.13l-.433-.973v.973h-.248v-1.417h.303l.443 1.039.432-1.039h.303ZM69.776 27.816v.206h-.433v1.211h-.248v-1.211h-.433v-.206h1.114Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
dist/qmi-cloud/assets/user1.png
vendored
Normal file
BIN
dist/qmi-cloud/assets/user1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
5
dist/qmi-cloud/env.js
vendored
Normal file
5
dist/qmi-cloud/env.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
(function (window) {
|
||||
window.__env = window.__env || {};
|
||||
|
||||
window.__env.disabledProvisions = false;
|
||||
}(this));
|
||||
BIN
dist/qmi-cloud/favicon.ico
vendored
BIN
dist/qmi-cloud/favicon.ico
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
26
dist/qmi-cloud/index.html
vendored
26
dist/qmi-cloud/index.html
vendored
@@ -5,9 +5,29 @@
|
||||
<title>QMI Cloud</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="assets/favicon.ico">
|
||||
<link rel="stylesheet" href="styles.b2e9059fb6aa834811ea.css"></head>
|
||||
<link rel="icon" href="assets/favicon.svg">
|
||||
<!-- Load environment variables -->
|
||||
<script src="env.js"></script>
|
||||
|
||||
<!--
|
||||
<script crossorigin="anonymous"
|
||||
type="application/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
|
||||
data-host="https://qmicloud.qliktech.com/qcsproxy"></script>
|
||||
-->
|
||||
|
||||
<script crossorigin="anonymous"
|
||||
type="application/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
|
||||
data-host="https://innovation.us.qlikcloud.com"
|
||||
data-auth-type="Oauth2"
|
||||
data-client-id="21be5044bba1072c16a803a3e6e4dca0"
|
||||
data-redirect-uri="https://qmicloud-dev.qliktech.com/oauth-callback.html"
|
||||
data-access-token-storage="session"
|
||||
data-auto-redirect="true"></script>
|
||||
|
||||
<link rel="stylesheet" href="styles.3b2b6672156f20378f8f.css"></head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script src="runtime.c51bd5b1c616d9ffddc1.js" defer></script><script src="polyfills-es5.6fef7e679f78bcc42760.js" nomodule defer></script><script src="polyfills.51f5cc3d1309de3a873d.js" defer></script><script src="scripts.1af868998801499c8755.js" defer></script><script src="main.5c4c2f7e4461741ce9f1.js" defer></script></body>
|
||||
<script src="runtime.c51bd5b1c616d9ffddc1.js" defer></script><script src="polyfills-es5.6fef7e679f78bcc42760.js" nomodule defer></script><script src="polyfills.51f5cc3d1309de3a873d.js" defer></script><script src="scripts.1af868998801499c8755.js" defer></script><script src="main.0eb476bbc192df0d888a.js" defer></script></body>
|
||||
</html>
|
||||
|
||||
1
dist/qmi-cloud/main.0eb476bbc192df0d888a.js
vendored
Normal file
1
dist/qmi-cloud/main.0eb476bbc192df0d888a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/qmi-cloud/main.5c4c2f7e4461741ce9f1.js
vendored
1
dist/qmi-cloud/main.5c4c2f7e4461741ce9f1.js
vendored
File diff suppressed because one or more lines are too long
12
dist/qmi-cloud/oauth-callback.html
vendored
Normal file
12
dist/qmi-cloud/oauth-callback.html
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script
|
||||
crossorigin="anonymous"
|
||||
type="application/javascript"
|
||||
data-host="https://innovation.us.qlikcloud.com"
|
||||
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components@0/dist/oauth-callback.js"
|
||||
></script>
|
||||
</head>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -77,6 +77,7 @@ services:
|
||||
- ../qmi-cloud-provisions:/provisions
|
||||
- ./logs:/logs
|
||||
- ./costexport:/var/www/app/costexport
|
||||
- ./photos:/var/www/app/photos
|
||||
#- ./certs:/var/www/app/server/certs
|
||||
depends_on:
|
||||
- mongo
|
||||
@@ -95,6 +96,7 @@ services:
|
||||
container_name: qmi-cloud-worker
|
||||
restart: on-failure
|
||||
environment:
|
||||
- HOSTNAME_URL=http://localhost:3000
|
||||
- REDIS_URL=redis://redis
|
||||
- MONGO_URI=mongodb://root:example@mongo/qmicloud?authSource=admin
|
||||
- PROJECT_PATH=%PWD%
|
||||
@@ -102,11 +104,11 @@ services:
|
||||
- GIT_TAG=dev
|
||||
- SSHPATH=/Users/aor/.ssh
|
||||
- DOCKERIMAGE_AZURE_POWERSHELL=mcr.microsoft.com/azure-powershell:4.2.0-ubuntu-18.04
|
||||
- DOCKERIMAGE_TERRAFORM=qlikgear/terraform:1.0.1
|
||||
- DOCKERIMAGE_TERRAFORM=qlikgear/terraform:1.3.5
|
||||
command: "sh -c 'npm run start:dev'"
|
||||
volumes:
|
||||
# -- Dev only volumes
|
||||
- ./worker:/app/worker
|
||||
- ./qmi-cloud-worker:/app/worker
|
||||
# -------------------
|
||||
- ./logs:/logs
|
||||
- /var/run/docker.sock:/home/docker.sock
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
|
||||
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
<link rel="shortcut icon" href="favicon.svg" />
|
||||
<!-- <script type="text/javascript" src="https://qdt-apps.qlik.com/qdt-components/v3/3.0.0/qdt-components.js"></script> -->
|
||||
<script type="text/javascript" src="qdt-components.js"></script>
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
|
||||
15214
package-lock.json
generated
15214
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "qmi-cloud-app",
|
||||
"version": "1.1.8",
|
||||
"version": "5.0.1",
|
||||
"scripts": {
|
||||
"start": "node -r esm server/server.js",
|
||||
"start:dev": "nodemon -r esm server/server.js",
|
||||
@@ -27,11 +27,13 @@
|
||||
"adal-angular4": "^4.0.12",
|
||||
"angular-bootstrap-md": "9.0.0",
|
||||
"animate.css": "^3.7.2",
|
||||
"axios": "^0.21.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"bootstrap": "^4.3.1",
|
||||
"bull-arena": "^2.6.4",
|
||||
"chart.js": "^2.9.3",
|
||||
"connect-mongo": "^3.0.0",
|
||||
"cookie": "^0.6.0",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"core-js": "^2.5.4",
|
||||
"esm": "^3.2.25",
|
||||
@@ -40,20 +42,23 @@
|
||||
"font-awesome": "^4.7.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"js-sha1": "^0.6.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"leonardo-ui": "^1.7.1",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.31",
|
||||
"mongoose": "^5.7.4",
|
||||
"mongoose": "^6.11.1",
|
||||
"ngx-markdown": "^9.0.0",
|
||||
"nodemon": "^1.19.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-azure-ad": "^4.1.0",
|
||||
"passport-openidconnect": "^0.1.2",
|
||||
"qdt-components": "^2.5.5",
|
||||
"qmi-cloud-common": "./qmi-cloud-common",
|
||||
"rxjs": "~6.5.4",
|
||||
"swagger-jsdoc": "3.5.0",
|
||||
"swagger-ui-express": "4.1.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.14.2",
|
||||
"zone.js": "~0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
0
photos/.keep
Normal file
0
photos/.keep
Normal file
9
proxy.conf.json
Normal file
9
proxy.conf.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"/api/*": {
|
||||
"target": "http://localhost:3000/api",
|
||||
"secure": false,
|
||||
"logLevel": "debug",
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": { "^/api": "" }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# Stage 1: NOTE: context is actually ../
|
||||
FROM node:13.8-alpine AS sources
|
||||
FROM node:15.12.0-alpine AS sources
|
||||
|
||||
RUN apk --no-cache add yarn
|
||||
RUN apk --no-cache add yarn git
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -11,7 +11,7 @@ ADD ./qmi-cloud-common ../qmi-cloud-common
|
||||
RUN yarn install --production
|
||||
|
||||
# Stage 2:
|
||||
FROM node:13.8-alpine AS production
|
||||
FROM node:15.12.0-alpine AS production
|
||||
WORKDIR /app
|
||||
COPY --from=sources /app ./
|
||||
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
# Examples
|
||||
|
||||
## Check Destroy
|
||||
```
|
||||
docker run --net=host \
|
||||
-e MONGO_URI="mongodb://root:example@localhost:27017/qmicloud?authSource=admin" \
|
||||
-e API_KEY="c229219ccdd72d11e8ea253fd3876d247e5f489c9c84922cabdfb0cc194d8ff398a8d8d6528d8241efc99add2207e0ec75122a1b2c5598cc340cbe6b7c3c0dbf" \
|
||||
qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -p 4 -r test
|
||||
qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -r test
|
||||
```
|
||||
|
||||
## Init DB
|
||||
```
|
||||
docker run --net=host \
|
||||
-e MONGO_URI="mongodb://root:example@localhost:27017/qmicloud?authSource=admin" \
|
||||
qlikgear/qmi-cloud-cli:latest -s initdb
|
||||
```
|
||||
|
||||
## Check Destroy
|
||||
```
|
||||
docker run --net=host -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -p 20 -r test
|
||||
docker run --net=host -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -r test
|
||||
```
|
||||
|
||||
## Check Stop
|
||||
```
|
||||
docker run --net=host -e SMTP_EMAIL_SENDER=http://172.20.16.4:9200/sendemail -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkstop -t warning -r test
|
||||
```
|
||||
|
||||
## Check Errors
|
||||
```
|
||||
docker run --net=host -e MONGO_URI=$MONGO_URI qlikgear/qmi-cloud-cli:latest -s checkerror -r test
|
||||
```
|
||||
|
||||
## Move Destroyed
|
||||
```
|
||||
docker run --net=host -e MONGO_URI=$MONGO_URI -v $PWD/qmi-cloud-provisions:/provisions qlikgear/qmi-cloud-cli:latest -s movedestroyed -r "2020-31-12T00:00:00.000Z"
|
||||
```
|
||||
docker run --net=host -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkstop -t warning -p 4 -r test
|
||||
```
|
||||
@@ -4,12 +4,12 @@
|
||||
"isWafPolicyAppGw": true,
|
||||
"isExternal": true,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Qlik Sense",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"index": "vm1",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "Qlik Sense February 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qliksense-base-feb20-2"
|
||||
@@ -35,12 +35,12 @@
|
||||
"isWafPolicyAppGw": true,
|
||||
"isExternal": true,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Qlik Sense",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"index": "vm1",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "Qlik Sense February 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qliksense-base-feb20-2"
|
||||
@@ -55,7 +55,7 @@
|
||||
"product": "QDC",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"index": "vm2",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "QDC Single Server December 2019",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qdc-base-4.4-2"
|
||||
@@ -81,12 +81,12 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Oracle",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"name": "azqmi-database-lkn",
|
||||
@@ -102,12 +102,12 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "packer",
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"name": "azqmi-box-builder",
|
||||
@@ -123,12 +123,12 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": true,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "QDC",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"index": "vm1",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "QDC Single Server December 2019",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qdc-base-4.4-2"
|
||||
@@ -154,13 +154,13 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Windows",
|
||||
"vmTypeDefault": "Standard_D2s_v3",
|
||||
"diskSizeGbDefault": 128,
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D2s_v3",
|
||||
@@ -177,12 +177,12 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Windows",
|
||||
"vmTypeDefault": "Standard_D2s_v3",
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D2s_v3",
|
||||
@@ -198,13 +198,13 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Ubuntu 18.04",
|
||||
"vmTypeDefault": "Standard_B2s",
|
||||
"diskSizeGbDefault": 128,
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_B2s",
|
||||
@@ -221,13 +221,13 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Centos 7.5",
|
||||
"vmTypeDefault": "Standard_B2s",
|
||||
"diskSizeGbDefault": 128,
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_B2s",
|
||||
@@ -244,13 +244,13 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Centos 7.5",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"diskSizeGbDefault": 128,
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
@@ -268,13 +268,13 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Windows",
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
"diskSizeGbDefault": 128,
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
@@ -292,12 +292,12 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "QlikView",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
@@ -314,13 +314,13 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "each node",
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
"diskSizeGbDefault": 250,
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
@@ -337,12 +337,12 @@
|
||||
"isWafPolicyAppGw": true,
|
||||
"isExternal": true,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "QIB",
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
"index": "vm1",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "Qlik Sense Feb 2020 and QIB Feb 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qliksense-qib-base-feb20-1"
|
||||
@@ -363,12 +363,12 @@
|
||||
"isWafPolicyAppGw": true,
|
||||
"isExternal": true,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Windows VM",
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
@@ -384,13 +384,13 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Ubuntu 18.04",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"diskSizeGbDefault": 128,
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
@@ -406,12 +406,12 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Attunity Replicate/Compose DW",
|
||||
"vmTypeDefault": "Standard_D2s_v3",
|
||||
"index": "vm1",
|
||||
"versions": []
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"vmTypeDefault": "Standard_D2s_v3",
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Qlik Sense",
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"diskSizeGbDefault": "500",
|
||||
"index": "vm1",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "Qlik Sense April 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/versions/13.72.3"
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/values/13.72.3"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -22,10 +22,10 @@
|
||||
"vmTypeDefault": "Standard_D4s_v3",
|
||||
"diskSizeGbDefault": "500",
|
||||
"index": "vm2",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "QDC Single Server April 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/versions/4.5.0"
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/values/4.5.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -42,16 +42,16 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "QDC",
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
"diskSizeGbDefault": 500,
|
||||
"index": "vm1",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "QDC Single Server April 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/versions/4.5.0"
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/values/4.5.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -68,20 +68,20 @@
|
||||
"isWafPolicyAppGw": false,
|
||||
"isExternal": false,
|
||||
"isDisabled": false,
|
||||
"availableProductVersions": [
|
||||
"availableProductvalues": [
|
||||
{
|
||||
"product": "Qlik Sense",
|
||||
"vmTypeDefault": "Standard_D8s_v3",
|
||||
"diskSizeGbDefault": 250,
|
||||
"index": "vm1",
|
||||
"versions": [
|
||||
"values": [
|
||||
{
|
||||
"name": "Qlik Sense February 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/versions/13.62.0"
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/values/13.62.0"
|
||||
},
|
||||
{
|
||||
"name": "Qlik Sense April 2020",
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/versions/13.72.3"
|
||||
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/values/13.72.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
var myArgs = process.argv.slice(2);
|
||||
|
||||
if ( myArgs.length < 3 ) {
|
||||
if ( myArgs.length < 2 ) {
|
||||
console.log("Missing args", myArgs);
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -12,9 +12,7 @@ const moment = require('moment');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
|
||||
const IS_REAL = myArgs[2] !== 'test';
|
||||
const STOPPED_PERIOD = myArgs[1]; //20 Days
|
||||
const STOPPED_PERIOD_IS_EXTERNAL = Math.ceil(myArgs[1]/2); //10 Days
|
||||
const IS_REAL = myArgs[1] !== 'test';
|
||||
const WARNING_DAYS = 2;
|
||||
|
||||
//---
|
||||
@@ -52,6 +50,15 @@ function timeRunning(p) {
|
||||
};
|
||||
}
|
||||
|
||||
function timeFromCreated(p) {
|
||||
let totalTime = Math.abs(new Date().getTime() - new Date(p.created).getTime());
|
||||
let duration = moment.duration(totalTime);
|
||||
p.duration = {
|
||||
hours: Math.floor(duration.asHours()),
|
||||
complete: Math.floor(duration.asDays()) +"d "+duration.hours()+"h "+duration.minutes()
|
||||
};
|
||||
}
|
||||
|
||||
async function asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
@@ -63,7 +70,12 @@ async function init(type) {
|
||||
var filter = {
|
||||
"isDestroyed":false,
|
||||
"isDeleted": false,
|
||||
"statusVms": "Stopped"
|
||||
"$or": [ {"runForever":{ "$exists": false }}, {"runForever": false}],
|
||||
"statusVms": "Stopped",
|
||||
"$and": [{"$or": [
|
||||
{ "options": {"$exists": true}, "options.vm1": { "$exists": true } },
|
||||
{ "vmImage": {"$exists": true}, "vmImage.vm1": { "$exists": true } },
|
||||
]}]
|
||||
};
|
||||
if ( type === "warning" ) {
|
||||
filter.pendingNextAction = {$ne: "destroy"};
|
||||
@@ -84,11 +96,15 @@ async function init(type) {
|
||||
typeSchedule = 'OnSchedule';
|
||||
}
|
||||
timeRunning(p);
|
||||
|
||||
let stoppedPeriod = p._scenarioDoc.allowedInnactiveDays || 20;
|
||||
let stoppedPeriodExternal = Math.ceil(stoppedPeriod/2);
|
||||
|
||||
var limit;
|
||||
if ( type === "warning" ) {
|
||||
limit = p.isExternalAccess? 24*(STOPPED_PERIOD_IS_EXTERNAL-WARNING_DAYS) : 24*(STOPPED_PERIOD-WARNING_DAYS);
|
||||
limit = p.isExternalAccess? 24*(stoppedPeriodExternal-WARNING_DAYS) : 24*(stoppedPeriod-WARNING_DAYS);
|
||||
} else if ( type === "exec" ) {
|
||||
limit = p.isExternalAccess? (24*STOPPED_PERIOD_IS_EXTERNAL) : (24*STOPPED_PERIOD);
|
||||
limit = p.isExternalAccess? (24*stoppedPeriodExternal) : (24*stoppedPeriod);
|
||||
}
|
||||
if ( !IS_REAL ) {
|
||||
console.log(`${p._id} (${typeSchedule}) - limit: ${limit} hs - actual duration: ${p.duration.hours} hs`);
|
||||
@@ -99,6 +115,55 @@ async function init(type) {
|
||||
});
|
||||
}
|
||||
|
||||
async function initOthers() {
|
||||
const WARN_DAYS = 82;
|
||||
const DEST_DAYS = 91;
|
||||
var dWarning = new Date();
|
||||
var dDestroy = new Date();
|
||||
dWarning.setDate(dWarning.getDate() - WARN_DAYS);
|
||||
dDestroy.setDate(dDestroy.getDate() - DEST_DAYS);
|
||||
|
||||
dWarning = dWarning.getTime();
|
||||
dDestroy = dDestroy.getTime();
|
||||
|
||||
var filter = {
|
||||
"isDestroyed":false,
|
||||
"isDeleted": false,
|
||||
"$or": [ {"runForever":{ "$exists": false }}, {"runForever": false}],
|
||||
"scenario": { "$in": ["awsqmi-s3bucket"] }
|
||||
};
|
||||
|
||||
|
||||
let provisions = await db.provision.get(filter);
|
||||
|
||||
await asyncForEach(provisions.results, async function(p) {
|
||||
var typeSchedule = "24x7";
|
||||
timeFromCreated(p);
|
||||
|
||||
let pCreatedAt = new Date(p.created);
|
||||
pCreatedAt = pCreatedAt.getTime();
|
||||
var limit;
|
||||
|
||||
if ( pCreatedAt <= dDestroy ) {
|
||||
//TODO Destroy
|
||||
limit = DEST_DAYS*24;
|
||||
await doDestroy(p, limit, "24x7");
|
||||
} else if ( pCreatedAt > dDestroy && pCreatedAt <= dWarning && !p.pendingNextAction) {
|
||||
//TODO Warning
|
||||
limit = WARN_DAYS*24;
|
||||
let msg = `Send warning DESTROY email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Inactive' more than ${limit} hours, (exactly ${p.duration.complete})`;
|
||||
console.log(msg);
|
||||
if ( IS_REAL ) {
|
||||
db.event.add({ provision: p._id, type: 'vms.warning-destroy' });
|
||||
await db.provision.update(p._id, {"pendingNextAction": "destroy"});
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'warningDestroy', message: msg });
|
||||
await sendEmail.sendWillDestroyInDays(p, p._scenarioDoc, WARN_DAYS, 10);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const doSendEmailDestroyWarning = async function(p, limit, typeSchedule) {
|
||||
if ( p.pendingNextAction === 'destroy') {
|
||||
console.log(`Warning email Destroy already sent. Wait for pending action to complete.`);
|
||||
@@ -106,9 +171,11 @@ const doSendEmailDestroyWarning = async function(p, limit, typeSchedule) {
|
||||
let msg = `Send warning DESTROY email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Inactive' more than ${limit} hours, (exactly ${p.duration.complete})`;
|
||||
console.log(msg);
|
||||
if ( IS_REAL ) {
|
||||
db.event.add({ provision: p._id, type: 'vms.warning-destroy' });
|
||||
await db.provision.update(p._id, {"pendingNextAction": "destroy"});
|
||||
await sendEmail.sendWillDestroyIn24(p, p._scenarioDoc, Math.floor(limit/24), WARNING_DAYS);
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'warningDestroy', message: msg });
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'warningDestroy', message: msg });
|
||||
await sendEmail.sendWillDestroyIn24(p, p._scenarioDoc, Math.floor(limit/24), WARNING_DAYS);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -117,9 +184,10 @@ const doDestroy = async function(p, limit, typeSchedule) {
|
||||
try {
|
||||
let msg = `Provision destroyed - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Inactive' more than ${limit} hours (exactly ${p.duration.complete})`
|
||||
console.log(msg);
|
||||
if ( IS_REAL ) {
|
||||
if ( IS_REAL ) {
|
||||
db.event.add({ provision: p._id, type: 'vms.exec-destroy' });
|
||||
await postDestroy(p);
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'destroy', message: msg });
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'destroy', message: msg });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("doDestroy Error", error);
|
||||
@@ -138,6 +206,18 @@ function check(type) {
|
||||
});
|
||||
}
|
||||
|
||||
function checkOthers() {
|
||||
initOthers().then(function(){
|
||||
db.mongoose.connection.close()
|
||||
process.exit(0);
|
||||
|
||||
}).catch(function(e){
|
||||
db.mongoose.connection.close()
|
||||
console.log("Error", e);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------
|
||||
switch (myArgs[0]) {
|
||||
@@ -147,6 +227,9 @@ switch (myArgs[0]) {
|
||||
case 'exec':
|
||||
check("exec");
|
||||
break;
|
||||
case 'others':
|
||||
checkOthers();
|
||||
break;
|
||||
default:
|
||||
console.log('Sorry, that is not something I know how to do.');
|
||||
process.exit(0);
|
||||
|
||||
59
qmi-cloud-cli/jobs/error5.js
Normal file
59
qmi-cloud-cli/jobs/error5.js
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
var myArgs = process.argv.slice(2);
|
||||
|
||||
if ( myArgs.length < 1 ) {
|
||||
console.log("Missing args", myArgs);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const IS_REAL = myArgs[0] !== 'test';
|
||||
|
||||
|
||||
// ---
|
||||
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const sendEmail = require("qmi-cloud-common/send-email");
|
||||
|
||||
async function asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
}
|
||||
}
|
||||
|
||||
const doSendEmailErrorProvision = async function(p) {
|
||||
let msg = `Send prrovision ERROR email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id}) provisioned with 'Errors'`;
|
||||
console.log(msg);
|
||||
if ( IS_REAL ) {
|
||||
db.event.add({ provision: p._id, type: 'vms.warning-error' });
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'warningError', message: msg });
|
||||
await sendEmail.sendProvisionError(p, p._scenarioDoc);
|
||||
}
|
||||
};
|
||||
|
||||
async function init() {
|
||||
let provisions = await db.provision.get({
|
||||
"isDestroyed":false,
|
||||
"isDeleted": false,
|
||||
"status": "error"
|
||||
});
|
||||
|
||||
await asyncForEach(provisions.results, async function(p) {
|
||||
await doSendEmailErrorProvision(p);
|
||||
});
|
||||
}
|
||||
|
||||
function check() {
|
||||
init().then(function(){
|
||||
db.mongoose.connection.close()
|
||||
process.exit(0);
|
||||
|
||||
}).catch(function(e){
|
||||
db.mongoose.connection.close()
|
||||
console.log("Error", e);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------
|
||||
check();
|
||||
61
qmi-cloud-cli/jobs/move_destroyed.js
Normal file
61
qmi-cloud-cli/jobs/move_destroyed.js
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
var myArgs = process.argv.slice(2);
|
||||
|
||||
if ( myArgs.length < 1 ) {
|
||||
console.log("Missing args", myArgs);
|
||||
process.exit(0);
|
||||
}
|
||||
console.log("myArgs", myArgs);
|
||||
const ISODATE = new Date(myArgs[0]).toISOString();
|
||||
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
async function asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
let provisions = await db.provision.get({
|
||||
"isDestroyed":true,
|
||||
"isDeleted": false,
|
||||
"created": { "$lt" : ISODATE }
|
||||
});
|
||||
await asyncForEach(provisions.results, async function(p) {
|
||||
await doMoveDestroyed(p);
|
||||
});
|
||||
}
|
||||
|
||||
const doMoveDestroyed = async function(provision) {
|
||||
var scenarioFolder = `${provision.scenario}_${provision._id}`;
|
||||
console.log(`Moving scenario: /provisions/${scenarioFolder}`);
|
||||
if (fs.existsSync(`/provisions/${scenarioFolder}`)) {
|
||||
fs.moveSync(`/provisions/${scenarioFolder}`, `/provisions/deleted/${scenarioFolder}`, { overwrite: true })
|
||||
console.log(`OK.`);
|
||||
} else {
|
||||
console.log(`NOK: It does not exist.`);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function check() {
|
||||
init().then(function(){
|
||||
db.mongoose.connection.close()
|
||||
process.exit(0);
|
||||
|
||||
}).catch(function(e){
|
||||
db.mongoose.connection.close()
|
||||
console.log("Error", e);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------
|
||||
check();
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
|
||||
var myArgs = process.argv.slice(2);
|
||||
|
||||
if ( myArgs.length < 3 ) {
|
||||
if ( myArgs.length < 2 ) {
|
||||
console.log("Missing args", myArgs);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const IS_REAL = myArgs[2] !== 'test';
|
||||
|
||||
const RUNNING_PERIOD = myArgs[1]; // 7 Days
|
||||
const RUNNING_PERIOD_ON_SCHEDULE = Math.ceil(myArgs[1]/2); // ~ 4 Days
|
||||
const IS_REAL = myArgs[1] !== 'test';
|
||||
const WARNING_DAYS = 1;
|
||||
|
||||
// ---
|
||||
@@ -17,7 +14,7 @@ const WARNING_DAYS = 1;
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const sendEmail = require("qmi-cloud-common/send-email");
|
||||
const moment = require('moment');
|
||||
const azurecli = require('qmi-cloud-common/azurecli');
|
||||
const cli = require('qmi-cloud-common/cli');
|
||||
|
||||
function timeRunningIs24x7(p) {
|
||||
let runningFromTime = p.runningFrom? new Date(p.runningFrom).getTime() : new Date(p.created).getTime();
|
||||
@@ -41,6 +38,18 @@ function timeRunningOnSchedule(p) {
|
||||
};
|
||||
}
|
||||
|
||||
function timeRunningOnSchedule2(p) {
|
||||
|
||||
let startTimestamp = p.startDateOnSchedule? new Date(p.startDateOnSchedule).getTime() : 0;
|
||||
let endTimestamp = p.endDateOnSchedule? new Date(p.endDateOnSchedule).getTime() : 0;
|
||||
let totalTimeOnschedule = Math.abs(endTimestamp - startTimestamp);
|
||||
let duration = moment.duration(totalTimeOnschedule);
|
||||
p.duration = {
|
||||
hours: Math.floor(duration.asHours()),
|
||||
complete: Math.floor(duration.asDays()) +"d "+duration.hours()+"h "+duration.minutes()
|
||||
};
|
||||
}
|
||||
|
||||
async function asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
@@ -50,12 +59,18 @@ async function asyncForEach(array, callback) {
|
||||
async function init(type) {
|
||||
var cb;
|
||||
var filter = {
|
||||
"isDestroyed":false,
|
||||
"isDestroyed": false,
|
||||
"isDeleted": false,
|
||||
"statusVms": "Running"
|
||||
"$or": [ {"runForever":{ "$exists": false }}, {"runForever": false}],
|
||||
"statusVms": "Running",
|
||||
"$and": [{"$or": [
|
||||
{ "options": {"$exists": true}, "options.vm1": { "$exists": true } },
|
||||
{ "vmImage": {"$exists": true}, "vmImage.vm1": { "$exists": true } },
|
||||
]}]
|
||||
};
|
||||
|
||||
if ( type === "warning" ) {
|
||||
filter.pendingNextAction = {$ne: "stopVms"};
|
||||
filter.pendingNextAction = { $ne: "stopVms" };
|
||||
cb = doSendEmailWarning;
|
||||
} else if ( type === "exec" ) {
|
||||
filter.pendingNextAction = "stopVms";
|
||||
@@ -69,18 +84,19 @@ async function init(type) {
|
||||
|
||||
await asyncForEach(provisions.results, async function(p) {
|
||||
//Only if 24x7
|
||||
var typeSchedule = "24x7";
|
||||
var periodDays = RUNNING_PERIOD;
|
||||
let typeSchedule = "24x7";
|
||||
let periodDays = p._scenarioDoc.allowed24x7RunningDays;
|
||||
if ( !p.schedule || p.schedule.is24x7 ) {
|
||||
// 24x7
|
||||
timeRunningIs24x7(p);
|
||||
periodDays = RUNNING_PERIOD;
|
||||
} else if ( p.schedule && !p.schedule.is24x7 ) {
|
||||
timeRunningOnSchedule(p);
|
||||
periodDays = p._scenarioDoc.allowed24x7RunningDays;
|
||||
} else if ( p.schedule && !p.schedule.is24x7 ) {
|
||||
// OnSchedule
|
||||
timeRunningOnSchedule2(p);
|
||||
typeSchedule = 'OnSchedule';
|
||||
let onScheduleRenewed = p.schedule.onScheduleRenewed? p.schedule.onScheduleRenewed : 1;
|
||||
periodDays = RUNNING_PERIOD_ON_SCHEDULE * onScheduleRenewed;
|
||||
periodDays = p._scenarioDoc.allowedOnScheduleRunningDays;
|
||||
}
|
||||
var limit;
|
||||
let limit;
|
||||
if ( type === "warning" ) {
|
||||
limit = 24*(periodDays-WARNING_DAYS);
|
||||
} else if ( type === "exec" ) {
|
||||
@@ -102,9 +118,10 @@ const doSendEmailWarning = async function(p, limit, typeSchedule) {
|
||||
let msg = `Send warning STOP email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Running' more than ${limit} hours, (exactly ${p.duration.complete})`;
|
||||
console.log(msg);
|
||||
if ( IS_REAL ) {
|
||||
db.event.add({ provision: p._id, type: 'vms.warning-stop' });
|
||||
await db.provision.update(p._id, {"pendingNextAction": "stopVms"});
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'warningStop', message: msg });
|
||||
await sendEmail.sendWillStopIn24(p, p._scenarioDoc, Math.floor(limit/24), WARNING_DAYS);
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'warningStop', message: msg });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -118,14 +135,15 @@ const doStop = async function(p, limit, typeSchedule) {
|
||||
if (p.schedule){
|
||||
//Disable Divvy
|
||||
await db.schedule.update(p.schedule._id, {"isStartupTimeEnable": false});
|
||||
await azurecli.updateVmsTags(p._id, {
|
||||
await cli.updateVmsTags(p._id, {
|
||||
"24x7": false,
|
||||
"StartupTime": false,
|
||||
"ShutdownTime": false
|
||||
});
|
||||
}
|
||||
//Stop VMs indefinitely
|
||||
await azurecli.deallocate(p._id, true);
|
||||
db.event.add({ provision: p._id, type: 'vms.exec-stop' });
|
||||
await cli.deallocate(p._id, null, true);
|
||||
await db.notification.add({ provision: p._id.toString(), type: 'stop', message: msg });
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ async function getProvisions(filter){
|
||||
console.log(res.results.length);
|
||||
await asyncForEach(res.results, async function(prov){
|
||||
if ( prov.vmType ) {
|
||||
//console.log("vmType", prov.vmType, prov.vmImage)
|
||||
prov["vmImage"] = {
|
||||
//console.log("vmType", prov.vmType, prov.options)
|
||||
prov["options"] = {
|
||||
"vm1": {
|
||||
"vmType": prov.vmType,
|
||||
"diskSizeGb": 128
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"name": "qmi-cloud-cli",
|
||||
"version": "1.1.5",
|
||||
"version": "2.0.5",
|
||||
"scripts": {
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"qmi-cloud-common": "../qmi-cloud-common",
|
||||
"node-fetch": "^2.6.0",
|
||||
"moment": "^2.24.0"
|
||||
"moment": "^2.24.0",
|
||||
"axios": "^0.21.1",
|
||||
"fs-extra": "^8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,33 @@ helpFunction()
|
||||
{
|
||||
echo ""
|
||||
echo "Usage: $0 -s script"
|
||||
echo -e "\t-s Type of script ( checkstop | checkdestroy | initdb )"
|
||||
echo -e "\t-s Type of script ( checkstop | checkdestroy | checkerror | movedestroyed | initdb )"
|
||||
exit 1 # Exit script after printing help
|
||||
}
|
||||
|
||||
helpFunctionCheck()
|
||||
{
|
||||
echo ""
|
||||
echo "Usage: $0 -s script -t type -p period -r realortest"
|
||||
echo -e "\t-s Type of script ( checkstop | checkdestroy | initdb )"
|
||||
echo -e "\t-t Type of script (warning | exec)"
|
||||
echo -e "\t-p Period (number of days)"
|
||||
echo -e "\t-r Type of script (test | real)"
|
||||
echo "Usage: $0 -s script [-t type -r realortest]"
|
||||
echo -e "\t-s Type of script ( checkstop | checkdestroy | checkerror )"
|
||||
echo -e "\t-t Type of script ( warning | exec )"
|
||||
echo -e "\t-r Indicate whether is for test or real execution ( test | real )"
|
||||
exit 1 # Exit script after printing help
|
||||
}
|
||||
|
||||
helpFunctionCheck2()
|
||||
{
|
||||
echo ""
|
||||
echo "Usage: $0 -s checkerror [-r realortest]"
|
||||
echo -e "\t-r Indicate whether is for test or real execution ( test | real )"
|
||||
exit 1 # Exit script after printing help
|
||||
}
|
||||
|
||||
helpFunctionCheck3()
|
||||
{
|
||||
echo ""
|
||||
echo "Usage: $0 -s movedestroyed [-r isodate]"
|
||||
echo -e "\t-r An ISODATE (ie: '2020-31-12T00:00:00.000Z') to movedestroyed backwards"
|
||||
exit 1 # Exit script after printing help
|
||||
}
|
||||
|
||||
@@ -25,7 +40,6 @@ do
|
||||
case "$opt" in
|
||||
s ) script="$OPTARG" ;;
|
||||
t ) type="$OPTARG" ;;
|
||||
p ) period="$OPTARG" ;;
|
||||
r ) realortest="$OPTARG" ;;
|
||||
? ) helpFunction ;; # Print helpFunction in case parameter is non-existent
|
||||
esac
|
||||
@@ -33,25 +47,25 @@ done
|
||||
|
||||
if [ -z "$script" ]
|
||||
then
|
||||
echo "Some or all of the parameters are empty";
|
||||
echo "Script not recognised";
|
||||
helpFunction
|
||||
fi
|
||||
|
||||
if [ "$script" = "checkstop" ] || [ "$script" = "checkdestroy" ]
|
||||
then
|
||||
if [ -z "$type" ] || [ -z "$period" ] || [ -z "$realortest" ]
|
||||
if [ -z "$type" ] || [ -z "$realortest" ]
|
||||
then
|
||||
echo "Some or all of the parameters are empty";
|
||||
helpFunctionCheck
|
||||
else
|
||||
if [ "$script" = "checkstop" ]
|
||||
then
|
||||
$BASEDIR/checkstop.sh $type $period $realortest
|
||||
$BASEDIR/checkstop.sh $type $realortest
|
||||
fi
|
||||
|
||||
if [ "$script" = "checkdestroy" ]
|
||||
then
|
||||
$BASEDIR/checkdestroy.sh $type $period $realortest
|
||||
$BASEDIR/checkdestroy.sh $type $realortest
|
||||
fi
|
||||
fi
|
||||
elif [ "$script" = "initdb" ]
|
||||
@@ -64,5 +78,24 @@ then
|
||||
echo "\$MONGO_URI=$MONGO_URI"
|
||||
node $BASEDIR/../jobs/db-init.js
|
||||
fi
|
||||
elif [ "$script" = "checkerror" ]
|
||||
then
|
||||
if [ -z "$realortest" ]
|
||||
then
|
||||
echo "Some or all of the parameters are empty";
|
||||
helpFunctionCheck2
|
||||
else
|
||||
node $BASEDIR/../jobs/error5.js $realortest
|
||||
fi
|
||||
elif [ "$script" = "movedestroyed" ]
|
||||
then
|
||||
if [ -z "$realortest" ]
|
||||
then
|
||||
echo "Some or all of the parameters are empty";
|
||||
helpFunctionCheck3
|
||||
else
|
||||
node $BASEDIR/../jobs/move_destroyed.js $realortest
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@ d=`date`
|
||||
echo "------ $d"
|
||||
echo "------ SCRIPT: $0"
|
||||
echo "------ TYPE: $1"
|
||||
echo "------ PERIOD: $2"
|
||||
echo "------ TEST/REAL: $3"
|
||||
echo "------ TEST/REAL: $2"
|
||||
|
||||
if [ -z "$MONGO_URI" ]
|
||||
then
|
||||
@@ -22,4 +21,4 @@ else
|
||||
echo "\$API_KEY=$API_KEY"
|
||||
fi
|
||||
|
||||
node $BASEDIR/../jobs/destroy5.js $1 $2 $3
|
||||
node $BASEDIR/../jobs/destroy5.js $1 $2
|
||||
|
||||
14
qmi-cloud-cli/shell-utils/checkerror.sh
Executable file
14
qmi-cloud-cli/shell-utils/checkerror.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
BASEDIR=$(dirname "$0")
|
||||
d=`date`
|
||||
echo "------ $d"
|
||||
echo "------ SCRIPT: $0"
|
||||
|
||||
if [ -z "$MONGO_URI" ]
|
||||
then
|
||||
echo "\$MONGO_URI is empty"
|
||||
exit
|
||||
else
|
||||
echo "\$MONGO_URI=$MONGO_URI"
|
||||
fi
|
||||
|
||||
node $BASEDIR/../jobs/error5.js $1
|
||||
@@ -3,8 +3,7 @@ d=`date`
|
||||
echo "------ $d"
|
||||
echo "------ SCRIPT: $0"
|
||||
echo "------ TYPE: $1"
|
||||
echo "------ PERIOD: $2"
|
||||
echo "------ TEST/REAL: $3"
|
||||
echo "------ TEST/REAL: $2"
|
||||
|
||||
if [ -z "$MONGO_URI" ]
|
||||
then
|
||||
@@ -22,4 +21,4 @@ else
|
||||
echo "\$API_KEY=$API_KEY"
|
||||
fi
|
||||
|
||||
node $BASEDIR/../jobs/stop5.js $1 $2 $3
|
||||
node $BASEDIR/../jobs/stop5.js $1 $2
|
||||
@@ -2,6 +2,70 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@azure/abort-controller@^1.0.0":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd"
|
||||
integrity sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@azure/arm-compute@^15.0.0":
|
||||
version "15.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/arm-compute/-/arm-compute-15.0.0.tgz#5d0d0c1db16adbb6db3d33ca376b120c68e6ae23"
|
||||
integrity sha512-ElV2MuYZ+B2+ygMx2iiM/u3C7WDRruZjkXzfk5p2O+UbWxjG6j/686OH3T+VSDqmzg+47AnIOTLu2v0u0H8FOw==
|
||||
dependencies:
|
||||
"@azure/ms-rest-azure-js" "^2.0.1"
|
||||
"@azure/ms-rest-js" "^2.0.4"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@azure/core-auth@^1.1.4":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.2.0.tgz#a5a181164e99f8446a3ccf9039345ddc9bb63bb9"
|
||||
integrity sha512-KUl+Nwn/Sm6Lw5d3U90m1jZfNSL087SPcqHLxwn2T6PupNKmcgsEbDjHB25gDvHO4h7pBsTlrdJAY7dz+Qk8GA==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.0.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@azure/ms-rest-azure-env@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz#45809f89763a480924e21d3c620cd40866771625"
|
||||
integrity sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==
|
||||
|
||||
"@azure/ms-rest-azure-js@^2.0.1":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.1.0.tgz#8c90b31468aeca3146b06c7144b386fd4827f64c"
|
||||
integrity sha512-CjZjB8apvXl5h97Ck6SbeeCmU0sk56YPozPtTyGudPp1RGoHXNjFNtoOvwOG76EdpmMpxbK10DqcygI16Lu60Q==
|
||||
dependencies:
|
||||
"@azure/core-auth" "^1.1.4"
|
||||
"@azure/ms-rest-js" "^2.2.0"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@azure/ms-rest-js@^2.0.4", "@azure/ms-rest-js@^2.2.0":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-2.2.3.tgz#8f0085f7768c69f17b3cdb20ce95728b452dc304"
|
||||
integrity sha512-sXOhOu/37Tr8428f32Jwuwga975Xw64pYg1UeUwOBMhkNgtn5vUuNRa3fhmem+I6f8EKoi6hOsYDFlaHeZ52jA==
|
||||
dependencies:
|
||||
"@azure/core-auth" "^1.1.4"
|
||||
"@types/node-fetch" "^2.3.7"
|
||||
"@types/tunnel" "0.0.1"
|
||||
abort-controller "^3.0.0"
|
||||
form-data "^2.5.0"
|
||||
node-fetch "^2.6.0"
|
||||
tough-cookie "^3.0.1"
|
||||
tslib "^1.10.0"
|
||||
tunnel "0.0.6"
|
||||
uuid "^3.3.2"
|
||||
xml2js "^0.4.19"
|
||||
|
||||
"@azure/ms-rest-nodeauth@^3.0.7":
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.0.7.tgz#73c399b0aef45c75104324b6617aa4e0a6c27875"
|
||||
integrity sha512-7Q1MyMB+eqUQy8JO+virSIzAjqR2UbKXE/YQZe+53gC8yakm8WOQ5OzGfPP+eyHqeRs6bQESyw2IC5feLWlT2A==
|
||||
dependencies:
|
||||
"@azure/ms-rest-azure-env" "^2.0.0"
|
||||
"@azure/ms-rest-js" "^2.0.4"
|
||||
adal-node "^0.1.28"
|
||||
|
||||
"@hapi/boom@^9.1.0":
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.0.tgz#0d9517657a56ff1e0b42d0aca9da1b37706fec56"
|
||||
@@ -14,11 +78,38 @@
|
||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010"
|
||||
integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==
|
||||
|
||||
"@types/node-fetch@^2.3.7":
|
||||
version "2.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb"
|
||||
integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*":
|
||||
version "14.14.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313"
|
||||
integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==
|
||||
|
||||
"@types/node@^8.0.47":
|
||||
version "8.10.61"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.61.tgz#d299136ce54bcaf1abaa4a487f9e4bedf6b0d393"
|
||||
integrity sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==
|
||||
|
||||
"@types/tunnel@0.0.1":
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c"
|
||||
integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
||||
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
|
||||
dependencies:
|
||||
event-target-shim "^5.0.0"
|
||||
|
||||
adal-node@^0.1.28:
|
||||
version "0.1.28"
|
||||
resolved "https://registry.yarnpkg.com/adal-node/-/adal-node-0.1.28.tgz#468c4bb3ebbd96b1270669f4b9cba4e0065ea485"
|
||||
@@ -56,13 +147,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||
|
||||
async@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
|
||||
integrity sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==
|
||||
dependencies:
|
||||
lodash "^4.14.0"
|
||||
|
||||
async@>=0.6.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
|
||||
@@ -83,13 +167,12 @@ aws4@^1.8.0:
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
|
||||
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
|
||||
|
||||
azure-arm-compute@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/azure-arm-compute/-/azure-arm-compute-10.0.0.tgz#ce9ba2e4d6dd6b1174c34da2219700a8080f389b"
|
||||
integrity sha512-ehafNtcMKI6c00FT+xhPWPtzhYgHCZ675TUsaJ1FJ2bSpznih5EUrpir/4w519T4zbFBigszhnRK6eBkl78I9g==
|
||||
axios@^0.21.1:
|
||||
version "0.21.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
||||
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
||||
dependencies:
|
||||
ms-rest "^2.5.0"
|
||||
ms-rest-azure "^2.5.5"
|
||||
follow-redirects "^1.10.0"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
@@ -147,7 +230,7 @@ cluster-key-slot@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
@@ -215,11 +298,6 @@ denque@^1.1.0, denque@^1.4.1:
|
||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
|
||||
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
|
||||
|
||||
duplexer@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
|
||||
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
@@ -261,6 +339,11 @@ es-to-primitive@^1.2.1:
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
event-target-shim@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||
|
||||
extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
@@ -286,11 +369,34 @@ fast-json-stable-stringify@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
follow-redirects@^1.10.0:
|
||||
version "1.13.3"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
|
||||
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
||||
|
||||
form-data@^2.5.0:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
|
||||
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
@@ -300,6 +406,15 @@ form-data@~2.3.2:
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-extra@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
@@ -317,6 +432,11 @@ getpass@^0.1.1:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
version "4.2.6"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
|
||||
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
|
||||
|
||||
har-schema@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
@@ -371,10 +491,10 @@ ioredis@^4.14.1:
|
||||
redis-parser "^3.0.0"
|
||||
standard-as-callback "^2.0.1"
|
||||
|
||||
is-buffer@^1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||
ip-regex@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
|
||||
|
||||
is-callable@^1.1.4, is-callable@^1.1.5:
|
||||
version "1.2.0"
|
||||
@@ -400,11 +520,6 @@ is-regex@^1.0.5:
|
||||
dependencies:
|
||||
has-symbols "^1.0.1"
|
||||
|
||||
is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||
|
||||
is-symbol@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
|
||||
@@ -447,6 +562,13 @@ json-stringify-safe@~5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
@@ -489,7 +611,7 @@ lodash.flatten@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||
|
||||
lodash@^4.14.0, lodash@^4.17.15:
|
||||
lodash@^4.17.15:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
@@ -518,7 +640,7 @@ moment-timezone@^0.5.31:
|
||||
dependencies:
|
||||
moment ">= 2.9.0"
|
||||
|
||||
"moment@>= 2.9.0", moment@^2.21.0, moment@^2.22.2, moment@^2.24.0:
|
||||
"moment@>= 2.9.0", moment@^2.24.0:
|
||||
version "2.26.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
|
||||
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
|
||||
@@ -574,44 +696,6 @@ mquery@3.2.2:
|
||||
safe-buffer "5.1.2"
|
||||
sliced "1.0.1"
|
||||
|
||||
ms-rest-azure@^2.5.5:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-2.6.0.tgz#2098efec529eecfa0c6e215b69143abcaba12140"
|
||||
integrity sha512-J6386a9krZ4VtU7CRt+Ypgo9RGf8+d3gjMBkH7zbkM4zzkhbbMOYiPRaZ+bHZcfihkKLlktTgA6rjshTjF329A==
|
||||
dependencies:
|
||||
adal-node "^0.1.28"
|
||||
async "2.6.0"
|
||||
moment "^2.22.2"
|
||||
ms-rest "^2.3.2"
|
||||
request "^2.88.0"
|
||||
uuid "^3.2.1"
|
||||
|
||||
ms-rest-azure@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-3.0.0.tgz#54d0341c2aeef7b9f17f2a46258788740b2f0ec5"
|
||||
integrity sha512-cttN01/TtMDB4v3rt/WQ/slgffB6jcUYxcPzcL0VNSB+WFPE1j4y5ICNHMuD1RaNNekCYMI4Pv51BDQ/BXNq7Q==
|
||||
dependencies:
|
||||
adal-node "^0.1.28"
|
||||
async "2.6.0"
|
||||
moment "^2.22.2"
|
||||
ms-rest "^2.3.2"
|
||||
request "^2.88.0"
|
||||
uuid "^3.2.1"
|
||||
|
||||
ms-rest@^2.3.2, ms-rest@^2.5.0:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/ms-rest/-/ms-rest-2.5.4.tgz#57b42299cf302e45d5e1a734220bf7d4a110167a"
|
||||
integrity sha512-VeqCbawxRM6nhw0RKNfj7TWL7SL8PB6MypqwgylXCi+u412uvYoyY/kSmO8n06wyd8nIcnTbYToCmSKFMI1mCg==
|
||||
dependencies:
|
||||
duplexer "^0.1.1"
|
||||
is-buffer "^1.1.6"
|
||||
is-stream "^1.1.0"
|
||||
moment "^2.21.0"
|
||||
request "^2.88.0"
|
||||
through "^2.3.8"
|
||||
tunnel "0.0.5"
|
||||
uuid "^3.2.1"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
@@ -707,13 +791,14 @@ punycode@^2.1.0, punycode@^2.1.1:
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qmi-cloud-common@../qmi-cloud-common:
|
||||
version "1.1.2"
|
||||
version "1.1.6"
|
||||
dependencies:
|
||||
"@azure/arm-compute" "^15.0.0"
|
||||
"@azure/ms-rest-nodeauth" "^3.0.7"
|
||||
"@hapi/boom" "^9.1.0"
|
||||
azure-arm-compute "^10.0.0"
|
||||
axios "^0.21.1"
|
||||
bull "^3.11.0"
|
||||
mongoose "^5.7.4"
|
||||
ms-rest-azure "^3.0.0"
|
||||
nodemailer "^6.4.2"
|
||||
|
||||
qs@~6.5.2:
|
||||
@@ -756,7 +841,7 @@ regexp-clone@1.0.0, regexp-clone@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
|
||||
integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==
|
||||
|
||||
"request@>= 2.52.0", request@^2.88.0:
|
||||
"request@>= 2.52.0":
|
||||
version "2.88.2"
|
||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
||||
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
|
||||
@@ -817,6 +902,11 @@ saslprep@^1.0.0:
|
||||
dependencies:
|
||||
sparse-bitfield "^3.0.3"
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
semver@^5.1.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
@@ -905,10 +995,14 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
tough-cookie@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
|
||||
integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
|
||||
dependencies:
|
||||
ip-regex "^2.1.0"
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tough-cookie@~2.5.0:
|
||||
version "2.5.0"
|
||||
@@ -918,6 +1012,16 @@ tough-cookie@~2.5.0:
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tslib@^1.10.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
@@ -925,10 +1029,10 @@ tunnel-agent@^0.6.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tunnel@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.5.tgz#d1532254749ed36620fcd1010865495a1fa9d0ae"
|
||||
integrity sha512-gj5sdqherx4VZKMcBA4vewER7zdK25Td+z1npBqpbDys4eJrLx+SlYjJvq1bDXs2irkuJM5pf8ktaEQVipkrbA==
|
||||
tunnel@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
@@ -940,6 +1044,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf"
|
||||
integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||
@@ -962,7 +1071,7 @@ util.promisify@^1.0.1:
|
||||
has-symbols "^1.0.1"
|
||||
object.getownpropertydescriptors "^2.1.0"
|
||||
|
||||
uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.4.0:
|
||||
uuid@^3.1.0, uuid@^3.3.2, uuid@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
@@ -976,6 +1085,19 @@ verror@1.10.0:
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
xml2js@^0.4.19:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
|
||||
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xmlbuilder@~11.0.0:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
"xmldom@>= 0.1.x":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a"
|
||||
|
||||
222
qmi-cloud-common/awscli.js
Normal file
222
qmi-cloud-common/awscli.js
Normal file
@@ -0,0 +1,222 @@
|
||||
const AWS = require("aws-sdk");
|
||||
const db = require("./mongo");
|
||||
const utils = require("./utils");
|
||||
|
||||
function _getRgName(provision) {
|
||||
let rgName = provision.scenario.toUpperCase();
|
||||
rgName = rgName.replace(/AZQMI/g, 'QMI');
|
||||
rgName = rgName + "-" + provision._id.toString();
|
||||
return rgName;
|
||||
}
|
||||
|
||||
function _getRegion(provision) {
|
||||
let region = "eu-west-1";
|
||||
if ( provision.deployOpts.location === 'East US') {
|
||||
region = "us-east-1";
|
||||
} else if ( provision.deployOpts.location === 'Southeast Asia' ) {
|
||||
region = "ap-southeast-1";
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
function _getDbIndentifier(provision) {
|
||||
var out;
|
||||
if ( provision.outputs['db_instance_id'] ) {
|
||||
out = provision.outputs['db_instance_id'];
|
||||
} else if (provision.outputs['db_instance_endpoint']) {
|
||||
out = provision.outputs['db_instance_endpoint'].split(".")[0];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
async function stopDbInstance(provision, triggerUserId, isSendEmailAfter) {
|
||||
if (provision.statusVms === 'Stopped' || provision.statusVms === 'Stopping'){
|
||||
return;
|
||||
}
|
||||
var identifier = _getDbIndentifier(provision);
|
||||
|
||||
if ( !identifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Stopping" });
|
||||
|
||||
db.event.add({ user: triggerUserId, provision: provision._id, type: `db.stopping` });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let region = _getRegion(provision);
|
||||
var rds = new AWS.RDS({apiVersion: '2014-10-31', region: region});
|
||||
var params = {
|
||||
DBInstanceIdentifier: identifier
|
||||
};
|
||||
console.log(`AWSCLI# DB (${identifier}) stopping...`);
|
||||
rds.stopDBInstance(params, async function(err, data) {
|
||||
if (err) {
|
||||
console.log("AWSCLI# ERROR stopping DB: "+identifier, err.stack);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`AWSCLI# DB (${identifier}) stopped!`);
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async function startDbInstance(provision, triggerUserId) {
|
||||
if (provision.statusVms === 'Running' || provision.statusVms === 'Starting'){
|
||||
return;
|
||||
}
|
||||
|
||||
var identifier = _getDbIndentifier(provision);
|
||||
|
||||
if ( !identifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Starting" });
|
||||
db.event.add({ user: triggerUserId, provision: provision._id, type: `db.starting` });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let region = _getRegion(provision);
|
||||
var rds = new AWS.RDS({apiVersion: '2014-10-31', region: region});
|
||||
var params = {
|
||||
DBInstanceIdentifier: identifier
|
||||
};
|
||||
console.log(`AWSCLI# DB (${identifier}) starting...`);
|
||||
|
||||
rds.startDBInstance(params, async function(err, data) {
|
||||
|
||||
if (err) {
|
||||
console.log("AWSCLI# ERROR starting DB: "+identifier, err.stack);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`AWSCLI# DB (${identifier}) starting (2)...`);
|
||||
rds.waitFor('dBInstanceAvailable', params, async function(err, data) {
|
||||
if (err) {
|
||||
console.log("AWSCLI# ERROR waiting for DB to become available: "+identifier, err.stack);
|
||||
reject(err);
|
||||
} else {
|
||||
console.log(`AWSCLI# DB (${identifier}) started!`);
|
||||
await utils.afterStartVms( provision, triggerUserId, "db" );
|
||||
}
|
||||
});
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function deallocate(provision, triggerUserId, isSendEmailAfter) {
|
||||
|
||||
let rgName = _getRgName(provision);
|
||||
let region = _getRegion(provision);
|
||||
|
||||
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
var ec2 = new AWS.EC2({apiVersion: '2016-11-15', region: region});
|
||||
|
||||
var params = {
|
||||
DryRun: false,
|
||||
Filters: [
|
||||
{
|
||||
Name: 'tag:Name',
|
||||
Values: [ `fort-${provision._id}` ]
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
ec2.describeInstances(params, async function (err, data) {
|
||||
if (err) {
|
||||
console.log("AWSCLI# ERROR describing EC2s: "+rgName, err.stack);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
|
||||
reject(err);
|
||||
} else if (data && data.Reservations && data.Reservations.length) {
|
||||
|
||||
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
|
||||
console.log("AWSCLI# Stopping Ec2s ids...", ec2Ids);
|
||||
params = {
|
||||
DryRun: false,
|
||||
InstanceIds: ec2Ids
|
||||
};
|
||||
|
||||
ec2.stopInstances(params, async function(err1, data) {
|
||||
if (err1) {
|
||||
console.log("AWSCLI# ERROR stopping EC2s: "+rgName, err1.stack);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
|
||||
reject(err1);
|
||||
} else {
|
||||
console.log("AWSCLI# Ec2s stopped!");
|
||||
await utils.afterStopVms( provision, triggerUserId, isSendEmailAfter );
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("AWSCLI# No Ec2 Instances found: "+rgName);
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function start(provision, triggerUserId) {
|
||||
|
||||
let rgName = _getRgName(provision);
|
||||
let region = _getRegion(provision);
|
||||
|
||||
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
var ec2 = new AWS.EC2({apiVersion: '2016-11-15', region: region});
|
||||
var params = {
|
||||
DryRun: false,
|
||||
Filters: [
|
||||
{
|
||||
Name: 'tag:Name',
|
||||
Values: [ `fort-${provision._id}` ]
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
ec2.describeInstances(params, async function (err, data) {
|
||||
if (err) {
|
||||
console.log("AWSCLI# ERROR describing EC2s: "+rgName, err.stack);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
|
||||
reject(err);
|
||||
} else if (data && data.Reservations && data.Reservations.length) {
|
||||
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
|
||||
console.log("AWSCLI# Starting Ec2s ids...", ec2Ids);
|
||||
params = {
|
||||
DryRun: false,
|
||||
InstanceIds: ec2Ids
|
||||
};
|
||||
|
||||
ec2.startInstances(params, async function(err1, data) {
|
||||
if (err1) {
|
||||
console.log("AWSCLI# ERROR starting EC2s: "+rgName, err1.stack);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
|
||||
reject(err1);
|
||||
} else {
|
||||
console.log("AWSCLI# Ec2s started!");
|
||||
await utils.afterStartVms( provision, triggerUserId );
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("AWSCLI# No Ec2 Instances found: "+rgName);
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.deallocate = deallocate;
|
||||
module.exports.start = start;
|
||||
module.exports.stopDbInstance = stopDbInstance;
|
||||
module.exports.startDbInstance = startDbInstance;
|
||||
@@ -1,7 +1,8 @@
|
||||
const MsRest = require('ms-rest-azure');
|
||||
const computeManagementClient = require('azure-arm-compute');
|
||||
const loginWithVmMSI = require("@azure/ms-rest-nodeauth").loginWithVmMSI;
|
||||
const computeManagementClient = require("@azure/arm-compute").ComputeManagementClient;
|
||||
const dnsManagementClient = require("@azure/arm-dns").DnsManagementClient;
|
||||
const db = require("./mongo");
|
||||
const sendEmail = require("./send-email");
|
||||
const utils = require("./utils");
|
||||
|
||||
const SUBSCRIPTION_ID = "62ebff8f-c40b-41be-9239-252d6c0c8ad9";
|
||||
|
||||
@@ -18,10 +19,22 @@ async function _getClient(scenarioName) {
|
||||
var scenario = await db.scenario.getOne({"name": scenarioName});
|
||||
id = scenario.subscription? scenario.subscription.subsId : SUBSCRIPTION_ID;
|
||||
}
|
||||
var credentials = await MsRest.loginWithMSI({ port: 50342 });
|
||||
var credentials = await loginWithVmMSI({ port: 50342 });
|
||||
//console.log("AzureCLI# authenticated", credentials);
|
||||
return new computeManagementClient(credentials, id);
|
||||
return new computeManagementClient(credentials, id);
|
||||
}
|
||||
async function _getDNSClient(scenarioName) {
|
||||
var id = SUBSCRIPTION_ID;
|
||||
if (scenarioName){
|
||||
var scenario = await db.scenario.getOne({"name": scenarioName});
|
||||
id = scenario.subscription? scenario.subscription.subsId : SUBSCRIPTION_ID;
|
||||
}
|
||||
var credentials = await loginWithVmMSI({ port: 50342 });
|
||||
//console.log("AzureCLI# authenticated", credentials);
|
||||
return new dnsManagementClient(credentials, id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
@@ -29,60 +42,74 @@ async function asyncForEach(array, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deallocate(provId, isSendEmailAfter ) {
|
||||
let provision = await db.provision.getById(provId);
|
||||
if ( !provision ) return;
|
||||
|
||||
async function deallocate(provision, triggerUserId, isSendEmailAfter ) {
|
||||
|
||||
let rgName = _getRgName(provision);
|
||||
console.log("AzureCLI# Deallocating VMs for resource group: "+rgName);
|
||||
var computeClient = await _getClient(provision.scenario);
|
||||
let finalResult = await computeClient.virtualMachines.list(rgName);
|
||||
if ( finalResult && finalResult.length > 0 ) {
|
||||
db.provision.update(provision._id, {"statusVms": "Stopping"});
|
||||
}
|
||||
await asyncForEach(finalResult, async function(vm) {
|
||||
await computeClient.virtualMachines.deallocate(rgName, vm.name);
|
||||
});
|
||||
|
||||
let timeRunning = db.utils.getNewTimeRunning(provision);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped", "timeRunning": timeRunning, "stoppedFrom": new Date(), "pendingNextAction": undefined});
|
||||
|
||||
if ( isSendEmailAfter && provision._scenarioDoc ) {
|
||||
await sendEmail.sendVMsStopped(provision, provision._scenarioDoc);
|
||||
try {
|
||||
var computeClient = await _getClient(provision.scenario);
|
||||
let finalResult = await computeClient.virtualMachines.list(rgName);
|
||||
if ( finalResult && finalResult.length > 0 ) {
|
||||
db.provision.update(provision._id, {"statusVms": "Stopping"});
|
||||
}
|
||||
|
||||
await asyncForEach(finalResult, async function(vm) {
|
||||
await computeClient.virtualMachines.deallocate(rgName, vm.name);
|
||||
});
|
||||
|
||||
await utils.afterStopVms(provision, triggerUserId, isSendEmailAfter);
|
||||
|
||||
console.log("AzureCLI# All VMs DEALLOCATED for resource group: "+rgName);
|
||||
} catch ( error ) {
|
||||
console.log("AzureCLI# ERROR stopping VMs: "+rgName, error);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
|
||||
}
|
||||
|
||||
console.log("AzureCLI# All VMs DEALLOCATED for resource group: "+rgName);
|
||||
}
|
||||
|
||||
async function start(provId){
|
||||
let provision = await db.provision.getById(provId);
|
||||
if ( !provision ) return;
|
||||
|
||||
async function start(provision, triggerUserId){
|
||||
|
||||
let rgName = _getRgName(provision);
|
||||
console.log("AzureCLI# Starting VMs for resource group: "+rgName);
|
||||
|
||||
var computeClient = await _getClient(provision.scenario);
|
||||
let finalResult = await computeClient.virtualMachines.list(rgName);
|
||||
try {
|
||||
|
||||
if ( finalResult && finalResult.length > 0 ) {
|
||||
db.provision.update(provision._id, {"statusVms": "Starting"});
|
||||
var computeClient = await _getClient(provision.scenario);
|
||||
let finalResult = await computeClient.virtualMachines.list(rgName);
|
||||
|
||||
if ( finalResult && finalResult.length > 0 ) {
|
||||
db.provision.update(provision._id, {"statusVms": "Starting"});
|
||||
}
|
||||
|
||||
await asyncForEach(finalResult, async function(vm) {
|
||||
await computeClient.virtualMachines.start(rgName, vm.name);
|
||||
});
|
||||
|
||||
await utils.afterStartVms( provision, triggerUserId );
|
||||
console.log("AzureCLI# All VMs RUNNING for resource group: "+rgName);
|
||||
|
||||
} catch ( error ) {
|
||||
console.log("AzureCLI# ERROR starting VMs: "+rgName, error);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
|
||||
}
|
||||
await asyncForEach(finalResult, async function(vm) {
|
||||
await computeClient.virtualMachines.start(rgName, vm.name);
|
||||
});
|
||||
let countExtend = db.utils.getNewCountExtend(provision);
|
||||
await db.provision.update(provision._id.toString(), {"statusVms": "Running", "runningFrom": new Date(), "countExtend": countExtend, "pendingNextAction": undefined});
|
||||
console.log("AzureCLI# All VMs RUNNING for resource group: "+rgName);
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function getResourceGroupVms(rgName){
|
||||
var computeClient = await _getClient();
|
||||
let computeClient = await _getClient();
|
||||
return await computeClient.virtualMachines.list(rgName);
|
||||
}
|
||||
|
||||
async function getAllVms(){
|
||||
var computeClient = await _getClient();
|
||||
return await computeClient.virtualMachines.listAll(rgName);
|
||||
let computeClient = await _getClient();
|
||||
console.log("AzureCLI# Retrieving all VMS from subscription");
|
||||
return await computeClient.virtualMachines.listAll();
|
||||
}
|
||||
async function getAllVmsNext(nextLink){
|
||||
let computeClient = await _getClient();
|
||||
console.log("AzureCLI# Retrieving all VMS from subscription (next): " +nextLink);
|
||||
return await computeClient.virtualMachines.listAllNext(nextLink)
|
||||
}
|
||||
|
||||
async function updateVmsTags(provId, tagsEdit) {
|
||||
@@ -94,8 +121,16 @@ async function updateVmsTags(provId, tagsEdit) {
|
||||
let rgName = _getRgName(provision);
|
||||
console.log("AzureCLI# Updating TAGS in VMs for resource group: "+rgName);
|
||||
|
||||
var computeClient = await _getClient(provision.scenario);
|
||||
let finalResult = await computeClient.virtualMachines.list(rgName);
|
||||
|
||||
var computeClient;
|
||||
var finalResult;
|
||||
try {
|
||||
computeClient = await _getClient(provision.scenario);
|
||||
finalResult = await computeClient.virtualMachines.list(rgName);
|
||||
} catch (e2) {
|
||||
console.log(`AzureCLI# ERROR: Resource group '${rgName}' could not be found`, e2);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (finalResult && finalResult.length > 0) {
|
||||
var toDelete = [];
|
||||
@@ -111,6 +146,8 @@ async function updateVmsTags(provId, tagsEdit) {
|
||||
if ( toDelete.length > 0 || toAdd.length > 0 ) {
|
||||
|
||||
await asyncForEach(finalResult, async function(vm) {
|
||||
|
||||
var vmActualTags = JSON.stringify(vm.tags);
|
||||
var tags = vm.tags;
|
||||
toDelete.forEach(t=>{
|
||||
delete tags[t];
|
||||
@@ -120,11 +157,23 @@ async function updateVmsTags(provId, tagsEdit) {
|
||||
});
|
||||
tags["ProvId"] = provision._id.toString();
|
||||
result[vm.name] = tags;
|
||||
console.log(`AzureCLI# VM ${vm.name} new tags: `, tags );
|
||||
computeClient.virtualMachines.update(rgName, vm.name, {"tags": tags} );
|
||||
|
||||
if ( JSON.stringify(tags) !== vmActualTags ) {
|
||||
try {
|
||||
console.log(`AzureCLI# setting new tags to VM ${vm.name} (${rgName})`);
|
||||
await computeClient.virtualMachines.update(rgName, vm.name, {"tags": tags} );
|
||||
} catch (e1) {
|
||||
console.log(`AzureCLI# ERROR setting tags to VM ${vm.name} (${rgName})`, e1);
|
||||
}
|
||||
} else {
|
||||
console.log(`AzureCLI# Same tags, no need to update tags for VM ${vm.name} (${rgName})`);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//db.event.add({ user: provision.user._id, provision: provision._id, type: 'provision.vms-tags-update' });
|
||||
|
||||
return result;
|
||||
|
||||
} else {
|
||||
@@ -132,8 +181,53 @@ async function updateVmsTags(provId, tagsEdit) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* DNS Records
|
||||
*/
|
||||
|
||||
const DNS_RESOURCE_GROUP = "QMI-Infra-DNS";
|
||||
const DNS_ZONE_NAME = "qmi.qlik-poc.com";
|
||||
|
||||
async function getDNSRecords(type = "CNAME") {
|
||||
let dnsClient = await _getDNSClient();
|
||||
console.log(`AzureCLI# Retrieving '${type}' DNS records`);
|
||||
return await dnsClient.recordSets.listByType(DNS_RESOURCE_GROUP, DNS_ZONE_NAME, type);
|
||||
}
|
||||
|
||||
async function createDNSRecord(provision, cname, type = "CNAME"){
|
||||
|
||||
if ( !provision ) return;
|
||||
|
||||
let rgName = _getRgName(provision).toLowerCase();
|
||||
|
||||
let dnsClient = await _getDNSClient();
|
||||
console.log(`AzureCLI# Creating '${type}' DNS record: ${rgName}.${DNS_ZONE_NAME} --> ${cname}`);
|
||||
let parameters = {
|
||||
"cnameRecord": {
|
||||
"cname": cname
|
||||
},
|
||||
"tTL": 60
|
||||
};
|
||||
return await dnsClient.recordSets.createOrUpdate(DNS_RESOURCE_GROUP, DNS_ZONE_NAME, rgName, type, parameters );
|
||||
}
|
||||
|
||||
async function deleteDNSRecord(provision, type = "CNAME"){
|
||||
|
||||
if ( !provision ) return;
|
||||
|
||||
let rgName = _getRgName(provision).toLowerCase();
|
||||
let dnsClient = await _getDNSClient();
|
||||
console.log(`AzureCLI# Deleting '${type}' DNS record: ${rgName}.${DNS_ZONE_NAME}`);
|
||||
return await dnsClient.recordSets.deleteMethod(DNS_RESOURCE_GROUP, DNS_ZONE_NAME, rgName, type );
|
||||
}
|
||||
|
||||
module.exports.start = start;
|
||||
module.exports.deallocate = deallocate;
|
||||
module.exports.getResourceGroupVms = getResourceGroupVms;
|
||||
module.exports.getAllVms = getAllVms;
|
||||
module.exports.updateVmsTags = updateVmsTags;
|
||||
module.exports.getAllVmsNext = getAllVmsNext;
|
||||
module.exports.updateVmsTags = updateVmsTags;
|
||||
module.exports.getDNSRecords = getDNSRecords;
|
||||
module.exports.createDNSRecord = createDNSRecord;
|
||||
module.exports.deleteDNSRecord = deleteDNSRecord
|
||||
|
||||
85
qmi-cloud-common/barracuda.js
Normal file
85
qmi-cloud-common/barracuda.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const BarracudaAPI = require('barracuda-api');
|
||||
const azurecli = require('./azurecli');
|
||||
const db = require('./mongo');
|
||||
|
||||
const DOMAIN = "qmi.qlik-poc.com";
|
||||
|
||||
async function createApp(prov) {
|
||||
|
||||
if ( !prov._scenarioDoc.barracudaTemplate || prov._scenarioDoc.barracudaTemplate === 'qs-only-443' ) {
|
||||
_createApp(prov, "443", "HTTPS");
|
||||
} else if ( prov._scenarioDoc.barracudaTemplate === 'qdc-only-8443' ) {
|
||||
_createApp(prov, "8443", "HTTPS");
|
||||
} else if (prov._scenarioDoc.barracudaTemplate) {
|
||||
var spl = prov._scenarioDoc.barracudaTemplate.split("-");
|
||||
_createApp(prov, spl[0], spl[1]);
|
||||
}
|
||||
}
|
||||
|
||||
async function _createApp(provision, backendPort, backendType) {
|
||||
|
||||
|
||||
if ( !provision || !provision.barracudaAzureFqdn ) {
|
||||
console.log(`Barracuda# Provision does not exist or does not have a barracudaAzureFqdn value. Do nothing.`);
|
||||
return;
|
||||
}
|
||||
|
||||
let appName = provision.scenario.toLowerCase();
|
||||
appName = appName.replace(/azqmi/g, 'qmi');
|
||||
appName = `${appName}-${provision._id}`
|
||||
|
||||
console.log(`Barracuda# Creating Barracuda App for provision (${appName})`);
|
||||
|
||||
const client = new BarracudaAPI.QlikSenseNPrintingApp();
|
||||
client.create(appName, `${appName}.${DOMAIN}`, provision.barracudaAzureFqdn, backendPort, backendType).then(function(result) {
|
||||
let bApp = result.app;
|
||||
console.log("Barracuda# Barracuda App created! ID: "+ bApp.id);
|
||||
let cname = bApp.waas_services && bApp.waas_services.length? bApp.waas_services[0].cname : null;
|
||||
db.provision.update(provision._id, {"barracudaAppId": bApp.id, "barracudaAppCname":cname});
|
||||
|
||||
console.log("Barracuda# Creating DNS record in Azure: "+cname);
|
||||
azurecli.createDNSRecord(provision, cname);
|
||||
|
||||
db.event.add({ provision: provision._id, type: 'provision.is-public' });
|
||||
|
||||
}).catch(function(err){
|
||||
console.log("Barracuda# Error creating Barracuda App", err);
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteApp(provision) {
|
||||
|
||||
|
||||
if ( !provision || !provision.barracudaAppId ) {
|
||||
console.log(`Barracuda# Provision does not exist or does not have a barracudaAppId value. Do nothing.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Barracuda# Deleting DNS record in Azure and Barracuda App ID (${provision.barracudaAppId}) for provision (${provision._id})`);
|
||||
|
||||
azurecli.deleteDNSRecord(provision);
|
||||
const client = new BarracudaAPI.QlikSenseNPrintingApp();
|
||||
client.delete(provision.barracudaAppId).then(function() {
|
||||
console.log("Barracuda# Barracuda App deleted! ID: "+ provision.barracudaAppId);
|
||||
|
||||
db.event.add({ provision: provision._id, type: 'provision.is-private' });
|
||||
}).catch(function(err){
|
||||
console.log("Barracuda# Error deleting Barracuda App", err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async function getApp(provision) {
|
||||
|
||||
if ( !provision || !provision.barracudaAppId ) {
|
||||
console.log(`Barracuda# Provision does not exist or does not have a barracudaAppId value. Do nothing.`);
|
||||
return;
|
||||
}
|
||||
const client = new BarracudaAPI.QlikSenseNPrintingApp();
|
||||
return await client.getDetails(provision.barracudaAppId);
|
||||
|
||||
}
|
||||
|
||||
module.exports.getApp = getApp;
|
||||
module.exports.createApp = createApp;
|
||||
module.exports.deleteApp = deleteApp;
|
||||
72
qmi-cloud-common/cli.js
Normal file
72
qmi-cloud-common/cli.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const awscli = require("./awscli");
|
||||
const azurecli = require("./azurecli");
|
||||
const db = require("./mongo");
|
||||
|
||||
|
||||
async function deallocate(provId, userId, isSendEmailAfter ) {
|
||||
try {
|
||||
let provision = await db.provision.getById(provId);
|
||||
if ( !provision ) return;
|
||||
|
||||
if (provision.scenario === 'azqmi-fort'){
|
||||
return awscli.deallocate(provision, userId, isSendEmailAfter);
|
||||
} else {
|
||||
return azurecli.deallocate(provision, userId, isSendEmailAfter);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("CLI# ERROR stopping VMs", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function start(provId, userId){
|
||||
|
||||
try {
|
||||
let provision = await db.provision.getById(provId);
|
||||
|
||||
if ( !provision ) return;
|
||||
|
||||
if (provision.scenario === 'azqmi-fort'){
|
||||
return awscli.start(provision, userId);
|
||||
} else {
|
||||
return azurecli.start(provision, userId);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("CLI# ERROR starting VMs", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateVmsTags(provId, tagsEdit) {
|
||||
try {
|
||||
return azurecli.updateVmsTags(provId, tagsEdit);
|
||||
} catch (err) {
|
||||
console.log("CLI# ERROR updateVmsTags", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopDb(provId, userId, isSendEmailAfter) {
|
||||
try {
|
||||
let provision = await db.provision.getById(provId);
|
||||
if ( !provision ) return;
|
||||
|
||||
return awscli.stopDbInstance(provision, userId, isSendEmailAfter);
|
||||
} catch (err) {
|
||||
console.log("CLI# ERROR stopping DB", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function startDb(provId, userId) {
|
||||
try {
|
||||
let provision = await db.provision.getById(provId);
|
||||
if ( !provision ) return;
|
||||
|
||||
return awscli.startDbInstance(provision, userId);
|
||||
} catch (err) {
|
||||
console.log("CLI# ERROR stopping DB", err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.deallocate = deallocate;
|
||||
module.exports.start = start;
|
||||
module.exports.updateVmsTags = updateVmsTags;
|
||||
module.exports.stopDb = stopDb;
|
||||
module.exports.startDb = startDb;
|
||||
@@ -1,13 +1,14 @@
|
||||
const mongoose = require('mongoose');
|
||||
mongoose.set('useFindAndModify', false);
|
||||
const crypto = require("crypto");
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
|
||||
const schema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.Types.ObjectId, ref: 'User'
|
||||
},
|
||||
description: {
|
||||
type: String
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
const mongoose = require('mongoose');
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
|
||||
const destroySchema = new mongoose.Schema({
|
||||
|
||||
27
qmi-cloud-common/models/Event.js
Normal file
27
qmi-cloud-common/models/Event.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const schema = new mongoose.Schema({
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
index : true
|
||||
},
|
||||
user: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
provision: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'Provision',
|
||||
index : true
|
||||
},
|
||||
type: {
|
||||
type: String
|
||||
},
|
||||
message: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = mongoose.model('Event', schema)
|
||||
@@ -1,7 +1,4 @@
|
||||
const mongoose = require('mongoose');
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
|
||||
const sc = new mongoose.Schema({
|
||||
created: {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
const mongoose = require('mongoose');
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
const provisionSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: mongoose.Types.ObjectId, ref: 'User',
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'User',
|
||||
index: true
|
||||
},
|
||||
created: {
|
||||
@@ -26,6 +25,7 @@ const provisionSchema = new mongoose.Schema({
|
||||
},
|
||||
description: String,
|
||||
vmImage: Object,
|
||||
options: Object,
|
||||
status: {
|
||||
type: String,
|
||||
default: "queued"
|
||||
@@ -38,6 +38,15 @@ const provisionSchema = new mongoose.Schema({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
barracudaAppId: {
|
||||
type: String
|
||||
},
|
||||
barracudaAppCname: {
|
||||
type: String
|
||||
},
|
||||
barracudaAzureFqdn: {
|
||||
type: String
|
||||
},
|
||||
isDestroyed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -66,6 +75,12 @@ const provisionSchema = new mongoose.Schema({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
startDateOnSchedule: {
|
||||
type: Date
|
||||
},
|
||||
endDateOnSchedule: {
|
||||
type: Date
|
||||
},
|
||||
countExtend: {
|
||||
type: Number,
|
||||
default: 0
|
||||
@@ -82,8 +97,21 @@ const provisionSchema = new mongoose.Schema({
|
||||
ref: "Subscription"
|
||||
},
|
||||
terraformImage: {
|
||||
type: String,
|
||||
default: "qlikgear/terraform:0.13.4"
|
||||
type: String
|
||||
},
|
||||
version: {
|
||||
type: Number
|
||||
},
|
||||
parent: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'Provision'
|
||||
},
|
||||
runForever: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
guacaConnId: {
|
||||
type: String
|
||||
}
|
||||
},{
|
||||
toObject: {virtuals:true},
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
const mongoose = require('mongoose')
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
|
||||
const scenarioSchema = new mongoose.Schema({
|
||||
@@ -35,6 +33,10 @@ const scenarioSchema = new mongoose.Schema({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
forceExternalAccess: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isDivvyEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -50,6 +52,9 @@ const scenarioSchema = new mongoose.Schema({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
gitBranch: {
|
||||
type: String
|
||||
},
|
||||
description: String,
|
||||
availableProductVersions: Array,
|
||||
productVersionDefault: String,
|
||||
@@ -65,7 +70,33 @@ const scenarioSchema = new mongoose.Schema({
|
||||
}],
|
||||
labels: [{
|
||||
type: String
|
||||
}]
|
||||
}],
|
||||
allowedUsers: [{
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
}],
|
||||
support: [{
|
||||
type: String
|
||||
}],
|
||||
barracudaTemplate: {
|
||||
type: String
|
||||
},
|
||||
allowedInnactiveDays: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
allowed24x7RunningDays: {
|
||||
type: Number,
|
||||
default: 7
|
||||
},
|
||||
allowedOnScheduleRunningDays: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
terraformImage: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
const mongoose = require('mongoose');
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
|
||||
const sc = new mongoose.Schema({
|
||||
@@ -13,10 +11,6 @@ const sc = new mongoose.Schema({
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
onScheduleRenewed: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
localeShutdownTime: {
|
||||
type: String
|
||||
},
|
||||
|
||||
35
qmi-cloud-common/models/SharedProvision.js
Normal file
35
qmi-cloud-common/models/SharedProvision.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const schema = new mongoose.Schema({
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
index : true
|
||||
},
|
||||
updated: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
user: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'User',
|
||||
index: true
|
||||
},
|
||||
provision: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'Provision',
|
||||
index: true
|
||||
},
|
||||
sharedWithUser: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'User',
|
||||
index: true
|
||||
},
|
||||
canManage: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = mongoose.model('sharedProvision', schema);
|
||||
@@ -1,6 +1,4 @@
|
||||
const mongoose = require('mongoose')
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
const subSchema = new mongoose.Schema({
|
||||
created: {
|
||||
|
||||
67
qmi-cloud-common/models/TrainingSession.js
Normal file
67
qmi-cloud-common/models/TrainingSession.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const mongoose = require('mongoose');
|
||||
const crypto = require("crypto");
|
||||
|
||||
|
||||
const schema = new mongoose.Schema({
|
||||
description: {
|
||||
type: String
|
||||
},
|
||||
user: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'User'
|
||||
},
|
||||
template: {
|
||||
type: mongoose.Types.ObjectId,
|
||||
ref: 'TrainingTemplate'
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
updated: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: "inactive" //active, inactive, terminated
|
||||
},
|
||||
qcsTenantHost: {
|
||||
type: String
|
||||
},
|
||||
qcsApiKey: {
|
||||
type: String
|
||||
},
|
||||
qaUrl: {
|
||||
type: String
|
||||
},
|
||||
qaToken: {
|
||||
type: String
|
||||
},
|
||||
cloudshareClass: {
|
||||
type: String
|
||||
},
|
||||
qcsSharedSpace: {
|
||||
type: String
|
||||
},
|
||||
qcsDataSpace: {
|
||||
type: String
|
||||
},
|
||||
passwd: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return crypto.randomBytes(4).toString('hex');
|
||||
}
|
||||
},
|
||||
studentsCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
studentEmailFilter: {
|
||||
type: String // qlik.com,talend.com,gmail.com
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
module.exports = mongoose.model('TrainingSession', schema);
|
||||
22
qmi-cloud-common/models/TrainingStudent.js
Normal file
22
qmi-cloud-common/models/TrainingStudent.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
|
||||
const schema = new mongoose.Schema({
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
email: {
|
||||
type: String
|
||||
},
|
||||
session: {
|
||||
type: mongoose.Types.ObjectId, ref: 'TrainingSession'
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: 'pending'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = mongoose.model('TrainingStudent', schema);
|
||||
35
qmi-cloud-common/models/TrainingTemplate.js
Normal file
35
qmi-cloud-common/models/TrainingTemplate.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const schema = new mongoose.Schema({
|
||||
index: {
|
||||
type: String
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
description: {
|
||||
type: String
|
||||
},
|
||||
publicDescription: {
|
||||
type: String
|
||||
},
|
||||
cloudshare: {
|
||||
type: Boolean
|
||||
},
|
||||
qcs: {
|
||||
type: Boolean
|
||||
},
|
||||
needQcsAutomation: {
|
||||
type: Array //['main']
|
||||
},
|
||||
needQcsApiKey: {
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = mongoose.model('TrainingTemplate', schema);
|
||||
@@ -1,6 +1,4 @@
|
||||
const mongoose = require('mongoose')
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
@@ -14,7 +12,11 @@ const userSchema = new mongoose.Schema({
|
||||
default: Date.now
|
||||
},
|
||||
displayName: String,
|
||||
upn: String,
|
||||
upn: {
|
||||
type: String,
|
||||
index: true
|
||||
},
|
||||
sub: String,
|
||||
oid: {
|
||||
type: String,
|
||||
index: true
|
||||
@@ -31,6 +33,19 @@ const userSchema = new mongoose.Schema({
|
||||
},
|
||||
qcsUserSubject: {
|
||||
type: String
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
jobTitle: {
|
||||
type: String
|
||||
},
|
||||
mail: {
|
||||
type: String
|
||||
},
|
||||
featureFlags: {
|
||||
type: Array
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
const mongoose = require('mongoose')
|
||||
mongoose.set('useFindAndModify', false);
|
||||
//mongoose.set('debug', true)
|
||||
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
type: String,
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
const mongoose = require('mongoose');
|
||||
const boom = require('@hapi/boom');
|
||||
const options = {
|
||||
loggerLevel: 'error',
|
||||
useNewUrlParser: true,
|
||||
//reconnectInterval: 2000,
|
||||
//reconnectTries: 30, // Retry up to 30 times
|
||||
useCreateIndex: true,
|
||||
useUnifiedTopology: true
|
||||
};
|
||||
|
||||
console.log("--- MongoDB connecting... ", process.env.MONGO_URI);
|
||||
|
||||
// Connect to DB
|
||||
mongoose.connect(process.env.MONGO_URI, options);
|
||||
mongoose.connect(process.env.MONGO_URI);
|
||||
|
||||
// When successfully connected
|
||||
mongoose.connection.on('connected', () => {
|
||||
@@ -42,7 +34,11 @@ const VmType = require('./models/VmType');
|
||||
const ApiKey = require('./models/ApiKey');
|
||||
const Notification = require('./models/Notification');
|
||||
const Subscription = require('./models/Subscription');
|
||||
|
||||
const Event = require('./models/Event');
|
||||
const SharedProvision = require('./models/SharedProvision');
|
||||
const TrainingTemplate = require('./models/TrainingTemplate');
|
||||
const TrainingSession = require('./models/TrainingSession');
|
||||
const TrainingStudent = require('./models/TrainingStudent');
|
||||
|
||||
const getNewCountExtend = function(provision) {
|
||||
return provision.countExtend !== undefined? (provision.countExtend + 1) : 1;
|
||||
@@ -93,14 +89,25 @@ const getPage = async ( model, filter, page, populates, select ) => {
|
||||
if ( model === Provision ) {
|
||||
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "schedule"}).populate('deployOpts');
|
||||
}
|
||||
if ( model === SharedProvision ) {
|
||||
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
|
||||
}
|
||||
|
||||
if ( model === ApiKey ) {
|
||||
exec = exec.populate('user');
|
||||
}
|
||||
|
||||
if ( model = Scenario ) {
|
||||
if ( model === Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts');
|
||||
}
|
||||
|
||||
if ( model === Event ) {
|
||||
exec = exec.populate({path: 'user', select: 'displayName'});
|
||||
}
|
||||
|
||||
if ( model === TrainingSession ) {
|
||||
exec = exec.populate('user').populate('template');
|
||||
}
|
||||
}
|
||||
|
||||
const entity = await exec;
|
||||
@@ -128,8 +135,15 @@ const getPage = async ( model, filter, page, populates, select ) => {
|
||||
const get = async (model, filter, select, skip, limit, populates, reply) => {
|
||||
var sort = {};
|
||||
var modelAttributes = Object.keys(model.schema.tree);
|
||||
|
||||
if ( model === Scenario ) {
|
||||
if ( modelAttributes.indexOf("updated") !== -1) {
|
||||
sort.updated = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( modelAttributes.indexOf("created") !== -1) {
|
||||
sort = {created: -1};
|
||||
sort.created = -1;
|
||||
}
|
||||
try {
|
||||
var exec = model.find(filter, select).sort(sort);
|
||||
@@ -152,13 +166,32 @@ const get = async (model, filter, select, skip, limit, populates, reply) => {
|
||||
if ( model === Provision ) {
|
||||
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "schedule"}).populate('deployOpts');
|
||||
}
|
||||
|
||||
if ( model === SharedProvision ) {
|
||||
exec = exec.populate({path:'provision', populate: [{
|
||||
path: 'schedule',
|
||||
},{
|
||||
path: '_scenarioDoc',
|
||||
select: "-availableProductVersions -updated -created"
|
||||
},{
|
||||
path: 'destroy',
|
||||
select: "-user -jobId"
|
||||
}]}).populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
|
||||
}
|
||||
|
||||
if ( model === ApiKey ) {
|
||||
exec = exec.populate('user');
|
||||
}
|
||||
|
||||
if ( model = Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts');
|
||||
if ( model === Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
|
||||
}
|
||||
|
||||
if ( model === Event ) {
|
||||
exec = exec.populate({path: 'user', select: 'displayName'});
|
||||
}
|
||||
if ( model === TrainingSession ) {
|
||||
exec = exec.populate('user').populate('template');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,11 +218,20 @@ const getById = async (model, id, reply) => {
|
||||
if ( model === Provision ) {
|
||||
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
|
||||
}
|
||||
if ( model === SharedProvision ) {
|
||||
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
|
||||
}
|
||||
if ( model === ApiKey ) {
|
||||
exec = exec.populate('user');
|
||||
}
|
||||
if ( model = Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts');
|
||||
if ( model === Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
|
||||
}
|
||||
if ( model === Event ) {
|
||||
exec = exec.populate({path: 'user', select: 'displayName'});
|
||||
}
|
||||
if ( model === TrainingSession ) {
|
||||
exec = exec.populate('user').populate('template');
|
||||
}
|
||||
const entity = await exec;
|
||||
return entity;
|
||||
@@ -204,11 +246,20 @@ const getOne = async (model, filter, reply) => {
|
||||
if ( model === Provision ) {
|
||||
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
|
||||
}
|
||||
if ( model === SharedProvision ) {
|
||||
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
|
||||
}
|
||||
if ( model === ApiKey ) {
|
||||
exec = exec.populate('user');
|
||||
}
|
||||
if ( model = Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts');
|
||||
if ( model === Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
|
||||
}
|
||||
if ( model === Event ) {
|
||||
exec = exec.populate({path: 'user', select: 'displayName'});
|
||||
}
|
||||
if ( model === TrainingSession ) {
|
||||
exec = exec.populate('user').populate('template');
|
||||
}
|
||||
const entity = await exec;
|
||||
return entity;
|
||||
@@ -235,12 +286,18 @@ const update = async (model, id, body, reply) => {
|
||||
if ( model === Provision ) {
|
||||
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
|
||||
}
|
||||
if ( model === SharedProvision ) {
|
||||
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
|
||||
}
|
||||
if ( model === ApiKey ) {
|
||||
exec = exec.populate('user');
|
||||
}
|
||||
if ( model = Scenario ) {
|
||||
if ( model === Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts');
|
||||
}
|
||||
if ( model === TrainingSession ) {
|
||||
exec = exec.populate('user').populate('template');
|
||||
}
|
||||
const update = await exec;
|
||||
return update;
|
||||
} catch (err) {
|
||||
@@ -248,10 +305,57 @@ const update = async (model, id, body, reply) => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateMany = async (model, filter, body, reply) => {
|
||||
try {
|
||||
const { ...updateData } = body;
|
||||
|
||||
updateData.updated = new Date();
|
||||
|
||||
var exec = model.updateMany(filter, updateData);
|
||||
|
||||
if ( model === Provision ) {
|
||||
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
|
||||
}
|
||||
if ( model === SharedProvision ) {
|
||||
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
|
||||
}
|
||||
if ( model === ApiKey ) {
|
||||
exec = exec.populate('user');
|
||||
}
|
||||
if ( model === Scenario ) {
|
||||
exec = exec.populate('subscription').populate('deployOpts');
|
||||
}
|
||||
if ( model === TrainingSession ) {
|
||||
exec = exec.populate('user').populate('template');
|
||||
}
|
||||
|
||||
return await exec;
|
||||
|
||||
} catch (err) {
|
||||
throw boom.boomify(err)
|
||||
}
|
||||
};
|
||||
|
||||
const del = async (model, id, reply) => {
|
||||
try {
|
||||
const entity = await model.findByIdAndRemove(id)
|
||||
return entity;
|
||||
return await model.findByIdAndRemove(id);
|
||||
} catch (err) {
|
||||
throw boom.boomify(err)
|
||||
}
|
||||
}
|
||||
|
||||
const delMany = async(model, filter, reply) => {
|
||||
try {
|
||||
return await model.deleteMany(filter);
|
||||
} catch (err) {
|
||||
throw boom.boomify(err)
|
||||
}
|
||||
}
|
||||
|
||||
const count = async (model, filter, reply) => {
|
||||
try {
|
||||
var totalDocs = await model.countDocuments(filter);
|
||||
return totalDocs;
|
||||
} catch (err) {
|
||||
throw boom.boomify(err)
|
||||
}
|
||||
@@ -277,8 +381,17 @@ function _m(model) {
|
||||
update: async (id, data, reply) => {
|
||||
return update(model, id, data, reply);
|
||||
},
|
||||
updateMany: async(filter, data, reply) => {
|
||||
return updateMany(model, filter, data, reply);
|
||||
},
|
||||
del: async (id, reply) => {
|
||||
return del(model, id, reply);
|
||||
},
|
||||
count: async (filter, reply) => {
|
||||
return count(model, filter, reply);
|
||||
},
|
||||
delMany: async(filter, reply) => {
|
||||
return delMany(model, filter, reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,7 +406,12 @@ module.exports = {
|
||||
apiKey: _m(ApiKey),
|
||||
notification: _m(Notification),
|
||||
subscription: _m(Subscription),
|
||||
event: _m(Event),
|
||||
user: _m(User),
|
||||
sharedProvision: _m(SharedProvision),
|
||||
trainingSession: _m(TrainingSession),
|
||||
trainingTemplate: _m(TrainingTemplate),
|
||||
trainingStudent: _m(TrainingStudent),
|
||||
utils: {
|
||||
getNewTimeRunning: getNewTimeRunning,
|
||||
getNewCountExtend: getNewCountExtend
|
||||
@@ -308,7 +426,12 @@ module.exports = {
|
||||
VmType: VmType,
|
||||
Notification: Notification,
|
||||
ApiKey: ApiKey,
|
||||
Subscription: Subscription
|
||||
Subscription: Subscription,
|
||||
Event: Event,
|
||||
SharedProvision: SharedProvision,
|
||||
TrainingSession: TrainingSession,
|
||||
TrainingTemplate: TrainingTemplate,
|
||||
TrainingStudent: TrainingStudent
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
1315
qmi-cloud-common/package-lock.json
generated
Normal file
1315
qmi-cloud-common/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,17 @@
|
||||
{
|
||||
"name": "qmi-cloud-common",
|
||||
"version": "1.1.5",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"@azure/arm-compute": "^15.0.0",
|
||||
"@azure/arm-dns": "^4.0.0",
|
||||
"@azure/ms-rest-nodeauth": "^3.0.7",
|
||||
"@hapi/boom": "^9.1.0",
|
||||
"mongoose": "^5.7.4",
|
||||
"aws-sdk": "^2.942.0",
|
||||
"axios": "^0.21.1",
|
||||
"barracuda-api": "https://gitlab.com/qlik_gear/barracuda-api-node.git#1.1.0",
|
||||
"bull": "^3.11.0",
|
||||
"mongoose": "^6.11.1",
|
||||
"nodemailer": "^6.4.2",
|
||||
"azure-arm-compute": "^10.0.0",
|
||||
"ms-rest-azure": "^3.0.0",
|
||||
"bull": "^3.11.0"
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,25 +4,30 @@ const Queue = require('bull');
|
||||
export const TF_APPLY_QUEUE = 'TF_APPLY_QUEUE';
|
||||
export const TF_DESTROY_QUEUE = 'TF_DESTROY_QUEUE';
|
||||
export const TF_APPLY_QSEOK_QUEUE = 'TF_APPLY_QSEOK_QUEUE';
|
||||
export const STOP_CONTAINER_QUEUE = 'STOP_CONTAINER_QUEUE';
|
||||
export const SYNAPSE_QUEUE = 'SYNAPSE_QUEUE';
|
||||
|
||||
|
||||
var terraformApplyQueue = new Queue(TF_APPLY_QUEUE, process.env.REDIS_URL);
|
||||
var terraformDestroyQueue = new Queue(TF_DESTROY_QUEUE, process.env.REDIS_URL);
|
||||
var terraformApplyQseokQueue = new Queue(TF_APPLY_QSEOK_QUEUE, process.env.REDIS_URL);
|
||||
|
||||
var stopContainerQueue = new Queue(STOP_CONTAINER_QUEUE, process.env.REDIS_URL);
|
||||
var synapseQueue = new Queue(SYNAPSE_QUEUE, process.env.REDIS_URL);
|
||||
|
||||
|
||||
export const queues = {
|
||||
[TF_APPLY_QUEUE]: terraformApplyQueue,
|
||||
[TF_DESTROY_QUEUE]: terraformDestroyQueue,
|
||||
[TF_APPLY_QSEOK_QUEUE]: terraformApplyQseokQueue
|
||||
[TF_APPLY_QSEOK_QUEUE]: terraformApplyQseokQueue,
|
||||
[STOP_CONTAINER_QUEUE]: stopContainerQueue,
|
||||
[SYNAPSE_QUEUE]: synapseQueue
|
||||
};
|
||||
|
||||
|
||||
for (let key in queues) {
|
||||
queues[key].on('completed', function(job, result) {
|
||||
//console.log(`Queues# Job ${job.id} completed! Result`, result);
|
||||
console.log(`Queues# Job ${job.id} completed!`);
|
||||
console.log(`Queue ${key}# Job ${job.id} completed!`);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,13 +36,13 @@ for (let key in queues) {
|
||||
});
|
||||
|
||||
queues[key].on('waiting', function(jobId){
|
||||
console.log(`Queues# Job ${jobId} is waiting...`);
|
||||
console.log(`Queue ${key}# Job ${jobId} is waiting...`);
|
||||
// A Job is waiting to be processed as soon as a worker is idling.
|
||||
});
|
||||
|
||||
queues[key].on('active', function(job, jobPromise){
|
||||
// A job has started. You can use `jobPromise.cancel()`` to abort it.
|
||||
console.log(`Queues# Job ${job.id} is now active`);
|
||||
console.log(`Queue ${key}# Job ${job.id} is now active`);
|
||||
});
|
||||
|
||||
queues[key].on('stalled', function(job){
|
||||
@@ -47,12 +52,12 @@ for (let key in queues) {
|
||||
|
||||
queues[key].on('progress', function(job, progress){
|
||||
// A job's progress was updated!
|
||||
console.log(`Queues# Job ${job.id} is ${progress * 100}% ready!`);
|
||||
console.log(`Queue ${key}# Job ${job.id} is ${progress * 100}% ready!`);
|
||||
});
|
||||
|
||||
queues[key].on('failed', function(job, err){
|
||||
// A job failed with reason `err`!
|
||||
console.log(`Queues# Job ${job.id} has failed:`, err);
|
||||
console.log(`Queue ${key}# Job ${job.id} has failed:`, err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
278
qmi-cloud-common/send-email copy.js
Normal file
278
qmi-cloud-common/send-email copy.js
Normal file
@@ -0,0 +1,278 @@
|
||||
'use strict';
|
||||
const nodemailer = require('nodemailer');
|
||||
const FROM = '"Qlik" <no-reply@qlik.com>';
|
||||
var transporter;
|
||||
|
||||
const HOSTNAME_URL = process.env.HOSTNAME_URL || "https://qmicloud.qliktech.com";
|
||||
const FOOTER = `<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
|
||||
</div>`;
|
||||
|
||||
if ( process.env.GMAIL_USERNAME && process.env.GMAIL_PASSWORD ) {
|
||||
//GMAIL
|
||||
transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.GMAIL_USERNAME,
|
||||
pass: process.env.GMAIL_PASSWORD
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//QLIK
|
||||
transporter = nodemailer.createTransport({
|
||||
host: 'smtp.qliktech.com',
|
||||
port: 587,
|
||||
secure: false, // true for 465, false for other ports
|
||||
});
|
||||
}
|
||||
|
||||
async function _doSend(to, subject, htmlText) {
|
||||
// send mail with defined transport object
|
||||
|
||||
try {
|
||||
|
||||
let info = await transporter.sendMail( {
|
||||
from: FROM, // sender address
|
||||
to: to, // list of receivers
|
||||
subject: subject, // Subject line
|
||||
text: subject, // plain text body
|
||||
html: htmlText // html body
|
||||
} );
|
||||
|
||||
console.log('SendEmail# message id ('+info.messageId+') sent to: ' + to);
|
||||
|
||||
return info;
|
||||
} catch (err) {
|
||||
|
||||
console.log('SendEmail# ERROR!! -> could not send email to: ' + to);
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
function _getCommonDetails(provision, scenario){
|
||||
var description = decodeURI(scenario.description);
|
||||
var externalAccess = provision.isExternalAccess? 'Yes' : 'No';
|
||||
var schedule = "";
|
||||
if ( !provision.schedule || provision.schedule.is24x7 ) {
|
||||
schedule = "24x7";
|
||||
} else if ( provision.schedule && !provision.schedule.is24x7 ) {
|
||||
schedule = `from ${provision.schedule.localeStartupTime}h until ${provision.schedule.localeShutdownTime}h (${provision.schedule.localTimezone})`;
|
||||
}
|
||||
return `<div style="color:#404040;font-size:18px;margin:20px 0px">
|
||||
<p style="margin:0px">Provision information:</p>
|
||||
</div>
|
||||
<div>
|
||||
<span style="color:#404040">ID: </span> ${provision._id}
|
||||
</div>
|
||||
<div>
|
||||
<span style="color:#404040">VMs Running schedule: </span> ${schedule}
|
||||
</div>
|
||||
<div>
|
||||
<span style="color:#404040">Purpose: </span> ${provision.description}
|
||||
</div>
|
||||
<div>
|
||||
<span style="color:#404040">Scenario: </span> ${scenario.title}
|
||||
</div>
|
||||
<div>
|
||||
<span style="color:#404040">With external access: </span> ${externalAccess}
|
||||
</div>
|
||||
<div>
|
||||
<span style="color:#404040">Description: </span> ${description}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays) {
|
||||
var common = _getCommonDetails(provision,scenario);
|
||||
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
|
||||
<p style="margin:0px">Provision '${scenario.title}' inactive more than ${period} days</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:18px;margin:10px 0px">
|
||||
<p style="margin:0px;color: #FF2020">This scenario will be automatically DESTROYED in ${(warningDays*24)} hours.</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">If you don't want this to happen, you've got ${(warningDays*24)} hours (from when this email was sent) as a grace period to get back at 'Running' status this provision.</p>
|
||||
</div>
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
function getHtmlScenarioVMsStopped( provision, scenario) {
|
||||
var common = _getCommonDetails(provision,scenario);
|
||||
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
|
||||
<p style="margin:0px">Provision '${scenario.title}'</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:18px;margin:10px 0px">
|
||||
<p style="margin:0px;color: #FF2020">All VMs for this provision <b>stopped</b> automatically.</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">You can start them up again from <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
|
||||
</div>
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays ) {
|
||||
var common = _getCommonDetails(provision,scenario);
|
||||
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
|
||||
<p style="margin:0px">Provision '${scenario.title}' - VMs running for ${period} days</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:18px;margin:10px 0px">
|
||||
<p style="margin:0px;color: #FF2020">This scenario will automatically stop its VMs in ${warningDays*24} hours.</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:18px;margin:20px 0px 10px 0px">
|
||||
<p style="margin:0px;color: #FF2020">Take action and extend the period ${(period+warningDays)} extra days.</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">If you don't want the VMs to automatically stop, you've got ${warningDays*24} hours (from when this email was sent) as a grace period to extend this scenario's <b style="color: #009845">Running</b> VMs for ${(period+warningDays)} extra days.</p>
|
||||
</div>
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
function getHtmlNewProvision(provision, scenario) {
|
||||
var htmlint;
|
||||
if ( provision && provision.outputs ) {
|
||||
htmlint = `<div style="color:#404040;font-size:18px;padding: 10px 0px;">Connection resources</div>`;
|
||||
} else {
|
||||
htmlint = "";
|
||||
}
|
||||
for (let key in provision.outputs) {
|
||||
htmlint += `<div>
|
||||
<span style="color:#404040">${key}</span>
|
||||
<pre style="color:#404040;">${provision.outputs[key]}</pre>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
var common = _getCommonDetails(provision, scenario);
|
||||
|
||||
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:22px;margin:20px 0px">
|
||||
<p style="margin:0px">Scenario '${scenario.title}' successfully provisioned!</p>
|
||||
</div>
|
||||
|
||||
${common}
|
||||
|
||||
<div style="margin: 30px 0px;">
|
||||
${htmlint}
|
||||
</div>
|
||||
${FOOTER}
|
||||
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getHtmlErrorProvision(provision, scenario) {
|
||||
var common = _getCommonDetails(provision, scenario);
|
||||
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:20px;margin:20px 0px">
|
||||
<p style="margin:0px;color: #FF2020">Oops! Something didn't work.</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:20px;margin:20px 0px 50px 0px">
|
||||
<p style="margin:0px">Scenario '${scenario.title}' failed during provision.</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Please, follow these steps:</p>
|
||||
<ul>
|
||||
<li>Reach out the person responsible for this scenario for support.</li>
|
||||
<li>As soon as it's possible, consider destroy this provision since it's taking valuable cloud resources which might imply relevant cost.</li>
|
||||
</ul>
|
||||
</div>
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getHtmlDestroyProvision(provision, scenario) {
|
||||
var common = _getCommonDetails(provision, scenario);
|
||||
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:20px;margin:40px 0px">
|
||||
<p style="margin:0px">Scenario '${scenario.title}' successfully destroyed!</p>
|
||||
</div>
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
// async..await is not allowed in global scope, must use a wrapper
|
||||
async function sendProvisionSuccess( provision, scenario ) {
|
||||
const htmlText = getHtmlNewProvision(provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - Provision finished successfully', htmlText);
|
||||
}
|
||||
|
||||
async function sendProvisionError(provision, scenario ) {
|
||||
const htmlText = getHtmlErrorProvision(provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - Provision with errors', htmlText);
|
||||
}
|
||||
|
||||
async function sendDestroyedSuccess(provision, scenario ) {
|
||||
|
||||
const htmlText = getHtmlDestroyProvision(provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - Provision destroyed successfully', htmlText);
|
||||
|
||||
}
|
||||
|
||||
async function sendWillStopIn24( provision, scenario, period, warningDays ) {
|
||||
|
||||
const htmlText = getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays);
|
||||
await _doSend(provision.user.upn, `QMI Cloud - VMs will stop in ${warningDays*24} hours`, htmlText);
|
||||
|
||||
}
|
||||
|
||||
async function sendWillDestroyIn24( provision, scenario, period, warningDays ) {
|
||||
|
||||
const htmlText = getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays);
|
||||
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${(warningDays*24)} hours`, htmlText);
|
||||
}
|
||||
|
||||
async function sendVMsStopped( provision, scenario ) {
|
||||
const htmlText = getHtmlScenarioVMsStopped( provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - VMs stopped automatically', htmlText);
|
||||
}
|
||||
|
||||
module.exports.sendProvisionSuccess = sendProvisionSuccess;
|
||||
module.exports.sendProvisionError = sendProvisionError;
|
||||
module.exports.sendDestroyedSuccess = sendDestroyedSuccess;
|
||||
module.exports.sendWillStopIn24 = sendWillStopIn24;
|
||||
module.exports.sendVMsStopped = sendVMsStopped;
|
||||
module.exports.sendWillDestroyIn24 = sendWillDestroyIn24;
|
||||
module.exports._doSend = _doSend;
|
||||
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
'use strict';
|
||||
const nodemailer = require('nodemailer');
|
||||
const FROM = '"Qlik" <no-reply@qlik.com>';
|
||||
var transporter;
|
||||
const axios = require('axios');
|
||||
const https = require("https");
|
||||
const SMTP_EMAIL_SENDER = process.env.SMTP_EMAIL_SENDER;
|
||||
|
||||
const HOSTNAME_URL = process.env.HOSTNAME_URL || "https://qmicloud.qliktech.com";
|
||||
const FOOTER = `<div style="color:#404040;font-size:16px;padding:30px 0px 50px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
|
||||
</div>`;
|
||||
|
||||
if ( process.env.GMAIL_USERNAME && process.env.GMAIL_PASSWORD ) {
|
||||
//GMAIL
|
||||
transporter = nodemailer.createTransport({
|
||||
service: 'gmail',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.GMAIL_USERNAME,
|
||||
pass: process.env.GMAIL_PASSWORD
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//QLIK
|
||||
transporter = nodemailer.createTransport({
|
||||
host: 'smtp.qliktech.com',
|
||||
port: 587,
|
||||
secure: false, // true for 465, false for other ports
|
||||
});
|
||||
}
|
||||
|
||||
async function _doSend(to, subject, htmlText) {
|
||||
// send mail with defined transport object
|
||||
let info = await transporter.sendMail( {
|
||||
from: FROM, // sender address
|
||||
to: to, // list of receivers
|
||||
subject: subject, // Subject line
|
||||
text: subject, // plain text body
|
||||
html: htmlText // html body
|
||||
} );
|
||||
try {
|
||||
var body = {
|
||||
"to": to,
|
||||
"subject": subject,
|
||||
"html": htmlText
|
||||
};
|
||||
await axios({
|
||||
url: SMTP_EMAIL_SENDER,
|
||||
method: "post",
|
||||
data: body,
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
});
|
||||
console.log('SendEmail (Qlik SMTP)# message sent to: ' + to);
|
||||
|
||||
console.log('SendEmail# message id ('+info.messageId+') sent to: ' + to);
|
||||
} catch (err) {
|
||||
// Handle Error Here
|
||||
console.log("SendEmail (Qlik SMTP) _doSend error: could not send the email to: " +to);
|
||||
}
|
||||
}
|
||||
|
||||
function _getCommonDetails(provision, scenario){
|
||||
@@ -85,9 +81,26 @@ function getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays) {
|
||||
<p style="margin:0px">If you don't want this to happen, you've got ${(warningDays*24)} hours (from when this email was sent) as a grace period to get back at 'Running' status this provision.</p>
|
||||
</div>
|
||||
${common}
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getHtmlScenarioIndays( provision, scenario, period, warningDays) {
|
||||
var common = _getCommonDetails(provision,scenario);
|
||||
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
|
||||
<p style="margin:0px">Provision '${scenario.title}' running more than ${period} days</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:18px;margin:10px 0px">
|
||||
<p style="margin:0px;color: #FF2020">This scenario was meant to live active no more than 90 days. It's been 80 days since creation and it will be automatically DESTROYED in ${warningDays} days. Please, consider to backup it up manually if you don't want to lose data.</p>
|
||||
</div>
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -107,12 +120,30 @@ function getHtmlScenarioVMsStopped( provision, scenario) {
|
||||
<p style="margin:0px;color: #FF2020">All VMs for this provision <b>stopped</b> automatically.</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">You can start them up again from <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
|
||||
<p style="margin:0px">You can start them up again from <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
|
||||
</div>
|
||||
${common}
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function getHtmlSharedProvision( provision, scenario) {
|
||||
var common = _getCommonDetails(provision,scenario);
|
||||
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
|
||||
<div style="background-color:white;height:100%;padding:20px 10px">
|
||||
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
|
||||
<p style="margin:0px">QMI Cloud</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
|
||||
<p style="margin:0px">Provision '${scenario.title}'</p>
|
||||
</div>
|
||||
<div style="color:#404040;font-size:18px;margin:30px 0px">
|
||||
<p style="margin:0px;color: #FF2020">'${provision.user.displayName}' is sharing with you!</p>
|
||||
</div>
|
||||
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -137,24 +168,27 @@ function getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays )
|
||||
<p style="margin:0px">If you don't want the VMs to automatically stop, you've got ${warningDays*24} hours (from when this email was sent) as a grace period to extend this scenario's <b style="color: #009845">Running</b> VMs for ${(period+warningDays)} extra days.</p>
|
||||
</div>
|
||||
${common}
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
|
||||
</div>
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
function getHtmlNewProvision(provision, scenario) {
|
||||
var htmlint;
|
||||
if ( provision && provision.outputs ) {
|
||||
htmlint = `<div style="color:#404040;font-size:18px;padding: 10px 0px;">Connection resources</div>`;
|
||||
htmlint = `<div style="color:#404040;font-size:20px;padding: 10px 0px;">Connection resources</div>`;
|
||||
htmlint += `<table style="width:100%" border="0">`;
|
||||
} else {
|
||||
htmlint = "";
|
||||
}
|
||||
for (let key in provision.outputs) {
|
||||
htmlint += `<div>
|
||||
<span style="color:#404040">${key}</span>
|
||||
<pre style="color:#404040;">${provision.outputs[key]}</pre>
|
||||
</div>`;
|
||||
htmlint += `<tr>
|
||||
<td style="padding-right: 15px"><b style="color:#404040">${key}</b></td>
|
||||
<td><pre style="color:#404040;">${provision.outputs[key]}</pre></td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
if ( provision && provision.outputs ) {
|
||||
htmlint += `</table>`;
|
||||
}
|
||||
|
||||
var common = _getCommonDetails(provision, scenario);
|
||||
@@ -173,9 +207,7 @@ function getHtmlNewProvision(provision, scenario) {
|
||||
<div style="margin: 30px 0px;">
|
||||
${htmlint}
|
||||
</div>
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
|
||||
</div>
|
||||
${FOOTER}
|
||||
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -194,10 +226,15 @@ function getHtmlErrorProvision(provision, scenario) {
|
||||
<div style="color:#404040;font-size:20px;margin:20px 0px 50px 0px">
|
||||
<p style="margin:0px">Scenario '${scenario.title}' failed during provision.</p>
|
||||
</div>
|
||||
${common}
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
|
||||
<p style="margin:0px">Please, follow these steps:</p>
|
||||
<ul>
|
||||
<li>Reach out the person responsible for this scenario for support.</li>
|
||||
<li>As soon as it's possible, consider destroy this provision since it's taking valuable cloud resources which might imply relevant cost.</li>
|
||||
</ul>
|
||||
</div>
|
||||
${common}
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -213,26 +250,24 @@ function getHtmlDestroyProvision(provision, scenario) {
|
||||
<p style="margin:0px">Scenario '${scenario.title}' successfully destroyed!</p>
|
||||
</div>
|
||||
${common}
|
||||
<div style="color:#404040;font-size:16px;margin:30px 0px">
|
||||
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
|
||||
</div>
|
||||
${FOOTER}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
// async..await is not allowed in global scope, must use a wrapper
|
||||
async function send( provision, scenario ) {
|
||||
async function sendProvisionSuccess( provision, scenario ) {
|
||||
const htmlText = getHtmlNewProvision(provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - Provision finished successfully', htmlText);
|
||||
}
|
||||
|
||||
async function sendError(provision, scenario ) {
|
||||
async function sendProvisionError(provision, scenario ) {
|
||||
const htmlText = getHtmlErrorProvision(provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - Provision with failures', htmlText);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - Provision with errors', htmlText);
|
||||
}
|
||||
|
||||
async function sendDestroyed(provision, scenario ) {
|
||||
async function sendDestroyedSuccess(provision, scenario ) {
|
||||
|
||||
const htmlText = getHtmlDestroyProvision(provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - Provision destroyed successfully', htmlText);
|
||||
@@ -252,16 +287,30 @@ async function sendWillDestroyIn24( provision, scenario, period, warningDays ) {
|
||||
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${(warningDays*24)} hours`, htmlText);
|
||||
}
|
||||
|
||||
async function sendWillDestroyInDays( provision, scenario, period, warningDays ) {
|
||||
|
||||
const htmlText = getHtmlScenarioIndays( provision, scenario, period, warningDays);
|
||||
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${warningDays} days`, htmlText);
|
||||
}
|
||||
|
||||
async function sendVMsStopped( provision, scenario ) {
|
||||
const htmlText = getHtmlScenarioVMsStopped( provision, scenario);
|
||||
await _doSend(provision.user.upn, 'QMI Cloud - VMs stopped automatically', htmlText);
|
||||
}
|
||||
|
||||
module.exports.send = send;
|
||||
module.exports.sendError = sendError;
|
||||
module.exports.sendDestroyed = sendDestroyed;
|
||||
async function sendSharedProvision(provision, shareWithUser) {
|
||||
const htmlText = getHtmlSharedProvision( provision, provision._scenarioDoc);
|
||||
await _doSend(shareWithUser.upn, `${provision.user.displayName} shared a QMI Cloud provision with you.`, htmlText);
|
||||
}
|
||||
|
||||
module.exports.sendProvisionSuccess = sendProvisionSuccess;
|
||||
module.exports.sendProvisionError = sendProvisionError;
|
||||
module.exports.sendDestroyedSuccess = sendDestroyedSuccess;
|
||||
module.exports.sendWillStopIn24 = sendWillStopIn24;
|
||||
module.exports.sendWillDestroyInDays = sendWillDestroyInDays;
|
||||
module.exports.sendVMsStopped = sendVMsStopped;
|
||||
module.exports.sendWillDestroyIn24 = sendWillDestroyIn24;
|
||||
module.exports.sendSharedProvision= sendSharedProvision;
|
||||
module.exports._doSend = _doSend;
|
||||
|
||||
|
||||
|
||||
7
qmi-cloud-common/test-sendemail.js
Normal file
7
qmi-cloud-common/test-sendemail.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var sendEmail = require("./send-email");
|
||||
|
||||
function test(){
|
||||
|
||||
sendEmail._doSend("AOR@qlik.com", "Test subject", "Hi world");
|
||||
}
|
||||
test();
|
||||
74
qmi-cloud-common/utils.js
Normal file
74
qmi-cloud-common/utils.js
Normal file
@@ -0,0 +1,74 @@
|
||||
const db = require("./mongo");
|
||||
const sendEmail = require("./send-email");
|
||||
|
||||
async function afterStopVms( provision, triggerUserId, auto = false, type = 'vms' ) {
|
||||
let timeRunning = db.utils.getNewTimeRunning(provision);
|
||||
const dateNow = new Date();
|
||||
let patch = {
|
||||
"statusVms": "Stopped",
|
||||
"timeRunning": timeRunning,
|
||||
"stoppedFrom": dateNow,
|
||||
"pendingNextAction": null
|
||||
};
|
||||
|
||||
if ( auto && provision._scenarioDoc ) { //From CLI (auto stop)
|
||||
let msg = `[CLI] TotalTimeRunning: ${timeRunning} mins`;
|
||||
// Actual onschedule reset
|
||||
if ( provision.schedule && !provision.schedule.is24x7 ) {
|
||||
patch["startDateOnSchedule"] = dateNow;
|
||||
patch["endDateOnSchedule"] = dateNow;
|
||||
msg += " - (Schedule) accumlated running time has been reset.";
|
||||
} else {
|
||||
msg += " - (24x7) accumlated running time was reached.";
|
||||
}
|
||||
|
||||
await db.provision.update(provision._id.toString(), patch);
|
||||
await sendEmail.sendVMsStopped(provision, provision._scenarioDoc);
|
||||
db.event.add({ provision: provision._id, type: 'vms.stop', message: msg });
|
||||
|
||||
} else { //On Demand stop
|
||||
|
||||
|
||||
if ( provision.schedule && !provision.schedule.is24x7 ) {
|
||||
patch["endDateOnSchedule"] = dateNow;
|
||||
|
||||
//This is temporary, only to make sure there is some initial value soon
|
||||
if ( !provision["startDateOnSchedule"] ) {
|
||||
patch["startDateOnSchedule"] = dateNow;
|
||||
}
|
||||
|
||||
}
|
||||
await db.provision.update(provision._id.toString(), patch);
|
||||
db.event.add({ user: triggerUserId, provision: provision._id, type: `${type}.stop`, message: `[Manual] TotalTimeRunning: ${timeRunning} mins` });
|
||||
}
|
||||
}
|
||||
|
||||
async function afterStartVms( provision, triggerUserId, type = 'vms' ) {
|
||||
const dateNow = new Date();
|
||||
let countExtend = db.utils.getNewCountExtend(provision);
|
||||
var patch = {
|
||||
"statusVms": "Running",
|
||||
"runningFrom": dateNow,
|
||||
"countExtend": countExtend,
|
||||
"pendingNextAction": null
|
||||
};
|
||||
|
||||
// Actual onschedule reset
|
||||
|
||||
let msg = `[Manual] TotalTimeRunning: ${provision.timeRunning} mins`;
|
||||
if ( provision.schedule && !provision.schedule.is24x7 ) {
|
||||
msg += " - Schedule time has been reset.";
|
||||
patch["startDateOnSchedule"] = dateNow;
|
||||
patch["endDateOnSchedule"] = dateNow;
|
||||
} else {
|
||||
msg += ` - 24x7, New count extend: ${countExtend}`;
|
||||
}
|
||||
|
||||
await db.provision.update(provision._id.toString(), patch);
|
||||
|
||||
db.event.add({ user: triggerUserId, provision: provision._id, type: `${type}.start`, message: msg });
|
||||
|
||||
}
|
||||
|
||||
module.exports.afterStopVms = afterStopVms;
|
||||
module.exports.afterStartVms = afterStartVms;
|
||||
1994
qmi-cloud-common/yarn.lock
Normal file
1994
qmi-cloud-common/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
# Stage 1: NOTE: context is actually ../
|
||||
FROM node:13.8-alpine AS sources
|
||||
FROM node:15.12.0-alpine AS sources
|
||||
|
||||
RUN apk --no-cache add yarn
|
||||
RUN apk --no-cache add yarn git
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -11,8 +11,9 @@ ADD ./qmi-cloud-common ../qmi-cloud-common
|
||||
RUN yarn install --production
|
||||
|
||||
# Stage 2:
|
||||
FROM node:13.8-alpine AS production
|
||||
FROM node:15.12.0-alpine AS production
|
||||
WORKDIR /app
|
||||
COPY --from=sources /app ./
|
||||
|
||||
|
||||
CMD ["node", "-r", "esm", "index.js"]
|
||||
@@ -4,8 +4,11 @@ const PROJECT_PATH = process.env.PROJECT_PATH;
|
||||
const tf = require("./docker/tf");
|
||||
const azure = require("./docker/azure");
|
||||
const sendEmail = require("qmi-cloud-common/send-email");
|
||||
const guaca = require("./guacamole");
|
||||
|
||||
module.exports = async function(job) {
|
||||
|
||||
const triggerUser = job.data.user;
|
||||
|
||||
var prov = await db.provision.update(job.data.id, {
|
||||
"status": "initializing",
|
||||
@@ -21,6 +24,8 @@ module.exports = async function(job) {
|
||||
|
||||
var idProv = prov._id.toString();
|
||||
|
||||
db.event.add({ user: triggerUser._id, provision: prov._id, type: 'provision.init' });
|
||||
|
||||
// TERRAFORM INIT
|
||||
return tf.init(prov)
|
||||
.then(async function(res) {
|
||||
@@ -29,7 +34,7 @@ module.exports = async function(job) {
|
||||
return Promise.reject({"success": false, "error": "Error at Terraform Init", provStatus: "error_init"});
|
||||
} else {
|
||||
// TERRAFORM PLAN
|
||||
return tf.plan(prov, job.data._scenario);
|
||||
return tf.plan(prov);
|
||||
}
|
||||
} )
|
||||
.then( async function(res) {
|
||||
@@ -37,38 +42,85 @@ module.exports = async function(job) {
|
||||
console.log(`ProcessorApply# Error at Terraform PLAN for provision (${idProv}) `);
|
||||
return Promise.reject({"success": false, "error": "Error at Terraform Plan", provStatus: "error_plan"});
|
||||
} else {
|
||||
return await db.provision.update(prov._id,{"status": "provisioning", "statusVms": "Running", "runningFrom": new Date(), "runningTime": 0, "countExtend": 0});
|
||||
const dateNow = new Date();
|
||||
let patch = {
|
||||
"status": "provisioning",
|
||||
"statusVms": (prov.scenario === 'azqmi-synapse' || prov.scenario === 'awsqmi-rds' || prov.options && prov.options.vm1)? "Running" : "N/A",
|
||||
"runningFrom": dateNow,
|
||||
"runningTime": 0,
|
||||
"countExtend": 0
|
||||
};
|
||||
if ( prov.schedule && !prov.schedule.is24x7 ) {
|
||||
patch["startDateOnSchedule"] = dateNow;
|
||||
patch["endDateOnSchedule"] = dateNow;
|
||||
}
|
||||
return await db.provision.update(prov._id, patch);
|
||||
}
|
||||
} ).then( function(prov) {
|
||||
// TERRAFORM APPLY
|
||||
return tf.apply(prov);
|
||||
} ).then( async function(res) {
|
||||
// Save Provision status
|
||||
if ( res.statusCode === 1 ) {
|
||||
console.log(`ProcessorApply# Error at Terraform APPLY for provision (${idProv})`);
|
||||
} else if ( res.statusCode === 137 ){
|
||||
console.log(`ProcessorApply# Apply container must have been killed!`);
|
||||
return Promise.reject({"success": false, "error": "Apply container must have been killed", provStatus: "aborted"});
|
||||
}
|
||||
|
||||
var status = ( res.output.indexOf("Error:") !== -1 )? "error" : "provisioned";
|
||||
return await db.provision.update(prov._id, {"status": status});
|
||||
|
||||
} ).then( async function(prov) {
|
||||
// Generate Terraform OUTPUTS and store in provision object
|
||||
return tf.outputs(prov).then( async function(outputs){
|
||||
return await db.provision.update(prov._id, {"outputs": outputs});
|
||||
//if ( outputs.barracudaAzureFqdn ) {
|
||||
// let fqdn = outputs.barracudaAzureFqdn;
|
||||
// delete outputs['barracudaAzureFqdn'];
|
||||
// return await db.provision.update(prov._id, {"barracudaAzureFqdn": fqdn, "outputs": outputs});
|
||||
//} else {
|
||||
return await db.provision.update(prov._id, {"outputs": outputs});
|
||||
//}
|
||||
});
|
||||
} ).then( async function(prov) {
|
||||
|
||||
} )
|
||||
//.then( async function(prov) {
|
||||
// if ( prov.isExternalAccess && prov.barracudaAzureFqdn ) {
|
||||
// console.log(`ProcessorApply# Calling Barracuda service to create App and DNS CName for provision (${prov._id})`);
|
||||
// barracuda.createApp(prov);
|
||||
// }
|
||||
// return prov;
|
||||
//} )
|
||||
.then( async function(prov) {
|
||||
// Application Gateway assign policy
|
||||
return azure.appgateway(prov, job.data._scenario);
|
||||
} ).then( async function(prov) {
|
||||
// Create Image
|
||||
return azure.createimage(prov, job.data._scenario);
|
||||
} ).then( async function(prov) {
|
||||
// Guacamole Web access
|
||||
return await guaca.setUserConnection(prov, job.data._scenario);
|
||||
|
||||
} ).then( function(prov) {
|
||||
if (prov.status === "provisioned") {
|
||||
sendEmail.send(prov, job.data._scenario);
|
||||
db.event.add({ provision: prov._id, type: 'provision.finished' });
|
||||
sendEmail.sendProvisionSuccess(prov, job.data._scenario);
|
||||
} else {
|
||||
sendEmail.sendError(prov, job.data._scenario);
|
||||
db.event.add({ provision: prov._id, type: 'provision.error' });
|
||||
sendEmail.sendProvisionError(prov, job.data._scenario);
|
||||
}
|
||||
|
||||
return Promise.resolve({"success": true, provMongo: prov});
|
||||
} ).catch( function(err) {
|
||||
console.log("ProcessorApply# Provision: error", err);
|
||||
db.provision.update(prov._id, {"status": err.provStatus? err.provStatus : 'error'});
|
||||
sendEmail.sendError(prov, job.data._scenario);
|
||||
|
||||
var errormsg = err.provStatus? err.provStatus : 'error'
|
||||
db.provision.update(prov._id, {"status": errormsg});
|
||||
db.event.add({ user: triggerUser._id, provision: prov._id, type: 'provision.'+errormsg });
|
||||
|
||||
if ( errormsg !== "aborted") {
|
||||
sendEmail.sendProvisionError(prov, job.data._scenario);
|
||||
}
|
||||
return Promise.reject({"success": false, "error": err});
|
||||
} );
|
||||
}
|
||||
170
qmi-cloud-worker/docker/synapse.js
Normal file
170
qmi-cloud-worker/docker/synapse.js
Normal file
@@ -0,0 +1,170 @@
|
||||
const Docker = require('dockerode');
|
||||
const docker = new Docker({
|
||||
'socketPath': '/home/docker.sock'
|
||||
//'socketPath': '/var/run/docker.sock'
|
||||
});
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const SSHPATH = process.env.SSHPATH;
|
||||
const DOCKERIMAGE = "mcr.microsoft.com/azure-cli";
|
||||
const PROJECT_PATH = process.env.PROJECT_PATH;
|
||||
|
||||
const pause = function(provision) {
|
||||
|
||||
if ( provision.scenario !== 'azqmi-synapse' ) {
|
||||
console.log(`SynapseAzCLI# provision (${provision._id}) is not AZ SYNAPSE`);
|
||||
return {"message": `Won't do anything, provision (${provision._id}) is not AZ SYNAPSE`};
|
||||
}
|
||||
|
||||
if ( !provision.outputs ) {
|
||||
console.log(`SynapseAzCLI# provision (${provision._id}) AZ SYNAPSE has NO outputs`);
|
||||
return {"message": `Won't do anything, provision (${provision._id}) AZ SYNAPSE has NO outputs`};
|
||||
}
|
||||
|
||||
const name = `qmi-synpause-${provision._id}`;
|
||||
console.log(`SynapseAzCLI# Pause Synapse: ${name}`);
|
||||
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
|
||||
var exec = ['synpause', provision.outputs.Synapse_WS_ID, provision.outputs.Synapse_Database];
|
||||
console.log('SynapseAzCLI# Pause Synapse: exec: '+exec.join(" "));
|
||||
|
||||
return docker.run(DOCKERIMAGE, exec, processStream, {
|
||||
//"Env": vars.envs,
|
||||
"name": name,
|
||||
//"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
"Binds": [
|
||||
`${provision.path}/synpause.sh:/bin/synpause`,
|
||||
`${SSHPATH}:/root/.ssh`
|
||||
]
|
||||
}
|
||||
}).then(function(data) {
|
||||
var container = data[1];
|
||||
console.log(`SynapseAzCLI# Pause Synapse: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
|
||||
return container.remove().then(function(){
|
||||
console.log(`SynapseAzCLI# Pause Synapse: Container '${name}' removed!`);
|
||||
return data[0].StatusCode;
|
||||
});
|
||||
}).then(async function(statusCode) {
|
||||
|
||||
return {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const resume = function(provision) {
|
||||
|
||||
if ( provision.scenario !== 'azqmi-synapse' ) {
|
||||
console.log(`SynapseAzCLI# provision (${provision._id}) is not AZ SYNAPSE`);
|
||||
return {"message": `Won't do anything, provision (${provision._id}) is not AZ SYNAPSE`};
|
||||
}
|
||||
|
||||
if ( !provision.outputs ) {
|
||||
console.log(`SynapseAzCLI# provision (${provision._id}) AZ SYNAPSE has NO outputs`);
|
||||
return {"message": `Won't do anything, provision (${provision._id}) AZ SYNAPSE has NO outputs`};
|
||||
}
|
||||
|
||||
const name = `qmi-synresume-${provision._id}`;
|
||||
console.log(`SynapseAzCLI# Resume Synapse: ${name}`);
|
||||
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
|
||||
var exec = ['synresume', provision.outputs.Synapse_WS_ID, provision.outputs.Synapse_Database];
|
||||
console.log('SynapseAzCLI# Resume Synapse: exec: '+exec.join(" "));
|
||||
|
||||
return docker.run(DOCKERIMAGE, exec, processStream, {
|
||||
//"Env": vars.envs,
|
||||
"name": name,
|
||||
//"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
"Binds": [
|
||||
`${provision.path}/synresume.sh:/bin/synresume`,
|
||||
`${SSHPATH}:/root/.ssh`
|
||||
]
|
||||
}
|
||||
}).then(function(data) {
|
||||
var container = data[1];
|
||||
console.log(`SynapseAzCLI# Resume Synapse: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
|
||||
return container.remove().then(function(){
|
||||
console.log(`SynapseAzCLI# Resume Synapse: Container '${name}' removed!`);
|
||||
return data[0].StatusCode;
|
||||
});
|
||||
}).then(async function(statusCode) {
|
||||
|
||||
return {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const copySnapshots = function(data) {
|
||||
|
||||
let snapName = data.snapName;
|
||||
let regions = data.regions;
|
||||
|
||||
const name = `qmi-copysnaps`;
|
||||
console.log(`CopySnapsAzCLI# container: ${name}`);
|
||||
var processStream = fs.createWriteStream("/logs/general/qmi-snapshots.log", {flags:'a'});
|
||||
var exec = ['copy-snapshots-to-sa', snapName, regions];
|
||||
console.log('CopySnapsAzCLI# exec: '+exec.join(" "));
|
||||
let scriptPath = path.join(PROJECT_PATH,'..');
|
||||
return docker.run(DOCKERIMAGE, exec, processStream, {
|
||||
//"Env": vars.envs,
|
||||
"name": name,
|
||||
//"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
"Binds": [
|
||||
`${scriptPath}/copy-snapshots-to-sa.sh:/bin/copy-snapshots-to-sa`,
|
||||
`${SSHPATH}:/root/.ssh`
|
||||
]
|
||||
}
|
||||
}).then(function(data) {
|
||||
var container = data[1];
|
||||
console.log(`CopySnapsAzCLI# '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
|
||||
return container.remove().then(function(){
|
||||
console.log(`CopySnapsAzCLI# Container '${name}' removed!`);
|
||||
return data[0].StatusCode;
|
||||
});
|
||||
}).then(async function(statusCode) {
|
||||
|
||||
return {"output": fs.readFileSync("/logs/general/qmi-snapshots.log"), "statusCode": statusCode};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const checkCopySnapshots = function(data) {
|
||||
|
||||
let snapName = data.snapName;
|
||||
|
||||
const name = `qmi-checkcopysnaps`;
|
||||
console.log(`CheckCopySnapsAzCLI# container: ${name}`);
|
||||
var processStream = fs.createWriteStream("/logs/general/qmi-snapshots.log", {flags:'a'});
|
||||
var exec = ['checkcopystatus', snapName];
|
||||
console.log('CheckCopySnapsAzCLI# exec: '+exec.join(" "));
|
||||
|
||||
let scriptPath = path.join(PROJECT_PATH,'..');
|
||||
|
||||
return docker.run(DOCKERIMAGE, exec, processStream, {
|
||||
//"Env": vars.envs,
|
||||
"name": name,
|
||||
//"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
"Binds": [
|
||||
`${scriptPath}/checkcopystatus.sh:/bin/checkcopystatus`,
|
||||
`${SSHPATH}:/root/.ssh`
|
||||
]
|
||||
}
|
||||
}).then(function(data) {
|
||||
var container = data[1];
|
||||
console.log(`CheckCopySnapsAzCLI# '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
|
||||
return container.remove().then(function(){
|
||||
console.log(`CheckCopySnapsAzCLI# Container '${name}' removed!`);
|
||||
return data[0].StatusCode;
|
||||
});
|
||||
}).then(async function(statusCode) {
|
||||
|
||||
return {"output": fs.readFileSync("/logs/general/qmi-snapshots.log"), "statusCode": statusCode};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports.pause = pause;
|
||||
module.exports.resume = resume;
|
||||
module.exports.copySnapshots = copySnapshots;
|
||||
module.exports.checkCopySnapshots = checkCopySnapshots;
|
||||
@@ -5,8 +5,9 @@ const docker = new Docker({
|
||||
const fs = require('fs');
|
||||
const GIT_SCENARIOS = process.env.GIT_SCENARIOS;
|
||||
const GIT_TAG = process.env.GIT_TAG || "master";
|
||||
const DOCKERIMAGE = process.env.DOCKERIMAGE_TERRAFORM || "qlikgear/terraform:0.12.18";
|
||||
const SSHPATH = process.env.SSHPATH;
|
||||
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
|
||||
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
|
||||
|
||||
function hook_stdout(callback) {
|
||||
var old_write = process.stdout.write
|
||||
@@ -23,94 +24,129 @@ function hook_stdout(callback) {
|
||||
}
|
||||
}
|
||||
|
||||
function _buildVarsExec( exec, provision, scenario ) {
|
||||
function _buildVarsExec( exec, provision ) {
|
||||
|
||||
let prefix = scenario.name.toUpperCase();
|
||||
|
||||
prefix = prefix.replace(/AZQMI/g, 'QMI');
|
||||
exec.push('-var');
|
||||
exec.push(`prefix=${prefix}`);
|
||||
|
||||
if ( provision.deployOpts && provision.deployOpts.subsId ) {
|
||||
exec.push('-var');
|
||||
exec.push(`subscription_id=${provision.deployOpts.subsId}`);
|
||||
}
|
||||
//Deprecated
|
||||
else if ( scenario.subscription && scenario.subscription.subsId ) {
|
||||
exec.push('-var');
|
||||
exec.push(`subscription_id=${scenario.subscription.subsId}`);
|
||||
let gitBranch = GIT_TAG;
|
||||
if ( provision._scenarioDoc && provision._scenarioDoc.gitBranch && provision._scenarioDoc.gitBranch.trim() !== "") {
|
||||
gitBranch = provision._scenarioDoc.gitBranch;
|
||||
}
|
||||
let prefix = provision.scenario.toUpperCase();
|
||||
prefix = prefix.replace(/AZQMI/g, 'QMI');
|
||||
|
||||
let envs = [
|
||||
`AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}`,
|
||||
`AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}`,
|
||||
`AWS_DEFAULT_REGION=us-east-1`,
|
||||
`TF_VAR_envbranch=${gitBranch}`,
|
||||
`TF_VAR_user_email=${provision.user.upn}`,
|
||||
`TF_VAR_user_oid=${provision.user.oid}`,
|
||||
`TF_VAR_prefix=${prefix}`
|
||||
];
|
||||
|
||||
if ( provision.deployOpts ) {
|
||||
|
||||
if ( provision.deployOpts.subsId ) {
|
||||
exec.push('-var');
|
||||
exec.push(`subscription_id=${provision.deployOpts.subsId}`);
|
||||
}
|
||||
|
||||
if ( provision.deployOpts.location ) {
|
||||
exec.push('-var');
|
||||
exec.push(`location=${provision.deployOpts.location}`);
|
||||
}
|
||||
|
||||
if ( provision.deployOpts.vnetExists ) {
|
||||
exec.push('-var');
|
||||
exec.push(`subnet_id=${provision.deployOpts.subnetId}`);
|
||||
|
||||
envs.push(`TF_VAR_subnet_id=${provision.deployOpts.subnetId}`);
|
||||
if ( provision.isExternalAccess ) {
|
||||
exec.push('-var');
|
||||
exec.push(`app_gw_subnet=${provision.deployOpts.appGwSubnetId}`);
|
||||
envs.push(`TF_VAR_app_gw_subnet=${provision.deployOpts.appGwSubnetId}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//Deprecated
|
||||
else if ( scenario.subscription && scenario.subscription.vnetExists ) {
|
||||
exec.push('-var');
|
||||
exec.push(`subnet_id=${scenario.subscription.subnetId}`);
|
||||
|
||||
if ( scenario.isExternal || provision.isExternalAccess) {
|
||||
exec.push('-var');
|
||||
exec.push(`app_gw_subnet=${scenario.subscription.appGwSubnetId}`);
|
||||
}
|
||||
}
|
||||
|
||||
exec.push('-var');
|
||||
exec.push(`provision_id=${provision._id}`);
|
||||
exec.push('-var');
|
||||
exec.push(`user_id=${provision.user.displayName}`);
|
||||
|
||||
if (!provision.vmImage) {
|
||||
//Old way
|
||||
if ( provision.vmType ) {
|
||||
exec.push('-var');
|
||||
exec.push(`vm_type=${provision.vmType}`);
|
||||
}
|
||||
|
||||
if ( provision.nodeCount) {
|
||||
exec.push('-var');
|
||||
exec.push(`agent_count=${provision.nodeCount}`);
|
||||
}
|
||||
} else if ( provision.vmImage ) {
|
||||
//New way
|
||||
|
||||
//DEPRECATED VMIMAGE
|
||||
if ( provision.vmImage ) {
|
||||
for ( let key in provision.vmImage ) {
|
||||
if ( provision.vmImage[key].nodeCount ) {
|
||||
exec.push('-var');
|
||||
exec.push(`agent_count_${key}=${provision[key].nodeCount}`);
|
||||
}
|
||||
|
||||
if ( !provision.vmImage[key].disabled ) {
|
||||
|
||||
if ( provision.vmImage[key].vmType ) {
|
||||
if ( provision.vmImage[key].nodeCount ) {
|
||||
exec.push('-var');
|
||||
exec.push(`agent_count_${key}=${provision[key].nodeCount}`);
|
||||
}
|
||||
|
||||
if ( !provision.vmImage[key].disabled ) {
|
||||
if ( provision.vmImage[key].vmType ) {
|
||||
exec.push('-var');
|
||||
exec.push(`vm_type_${key}=${provision.vmImage[key].vmType}`);
|
||||
} else {
|
||||
exec.push('-var');
|
||||
exec.push(`vm_type_${key}=ENABLED`);
|
||||
}
|
||||
}
|
||||
|
||||
if ( provision.vmImage[key].diskSizeGb ) {
|
||||
exec.push('-var');
|
||||
exec.push(`disk_size_gb_${key}=${provision.vmImage[key].diskSizeGb}`);
|
||||
}
|
||||
if ( provision.vmImage[key].diskSizeGb ) {
|
||||
exec.push('-var');
|
||||
exec.push(`disk_size_gb_${key}=${provision.vmImage[key].diskSizeGb}`);
|
||||
}
|
||||
|
||||
if ( provision.vmImage[key].version && provision.vmImage[key].version.image) {
|
||||
exec.push('-var');
|
||||
exec.push(`image_reference_${key}=${provision.vmImage[key].version.image}`);
|
||||
}
|
||||
|
||||
if ( provision.vmImage[key].version ) {
|
||||
exec.push('-var');
|
||||
exec.push(`image_reference_${key}=${provision.vmImage[key].version.image}`);
|
||||
}
|
||||
if ( provision.vmImage[key].version && provision.vmImage[key].version.init_password) {
|
||||
exec.push('-var');
|
||||
exec.push(`image_reference_${key}_init_password=${provision.vmImage[key].version.init_password}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NEW ATTRS
|
||||
if ( provision.options ) {
|
||||
for ( let key in provision.options ) {
|
||||
|
||||
if ( !provision.options[key].disabled ) {
|
||||
|
||||
if ( key.indexOf("vm") !== -1 ) {
|
||||
if ( provision.options[key].nodeCount ) {
|
||||
exec.push('-var');
|
||||
exec.push(`agent_count_${key}=${provision[key].nodeCount}`);
|
||||
}
|
||||
if ( provision.options[key].vmType ) {
|
||||
exec.push('-var');
|
||||
exec.push(`vm_type_${key}=${provision.options[key].vmType}`);
|
||||
}else {
|
||||
exec.push('-var');
|
||||
exec.push(`vm_type_${key}=ENABLED`);
|
||||
}
|
||||
if ( provision.options[key].diskSizeGb ) {
|
||||
exec.push('-var');
|
||||
exec.push(`disk_size_gb_${key}=${provision.options[key].diskSizeGb}`);
|
||||
}
|
||||
if ( provision.options[key].selected && provision.options[key].selected.value) {
|
||||
exec.push('-var');
|
||||
exec.push(`image_reference_${key}=${provision.options[key].selected.value}`);
|
||||
}
|
||||
|
||||
if ( provision.options[key].selected && provision.options[key].selected.init_password) {
|
||||
exec.push('-var');
|
||||
exec.push(`image_reference_${key}_init_password=${provision.options[key].selected.init_password}`);
|
||||
}
|
||||
} else {
|
||||
if ( provision.options[key].selected && provision.options[key].selected.value) {
|
||||
exec.push('-var');
|
||||
exec.push(`attr_${key}=${provision.options[key].selected.value}`);
|
||||
} else {
|
||||
exec.push('-var');
|
||||
exec.push(`attr_${key}=ENABLED`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +173,7 @@ function _buildVarsExec( exec, provision, scenario ) {
|
||||
}
|
||||
}
|
||||
|
||||
return exec;
|
||||
return {"exec": exec, "envs": envs};
|
||||
}
|
||||
|
||||
const init = function( provision ) {
|
||||
@@ -145,11 +181,15 @@ const init = function( provision ) {
|
||||
const name = `qmi-tf-init-${provision._id}`;
|
||||
console.log(`Terraform# Init: will spin up container: ${name}`);
|
||||
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
|
||||
let exec = ['terraform', 'init', '-no-color', `-from-module=${GIT_SCENARIOS}//${provision.scenario}?ref=${GIT_TAG}`];
|
||||
let gitBranch = GIT_TAG;
|
||||
if ( provision._scenarioDoc && provision._scenarioDoc.gitBranch && provision._scenarioDoc.gitBranch.trim() !== "") {
|
||||
gitBranch = provision._scenarioDoc.gitBranch;
|
||||
}
|
||||
let exec = ['terraform', 'init', '-no-color', `-from-module=${GIT_SCENARIOS}//${provision.scenario}?ref=${gitBranch}`];
|
||||
console.log('Terraform# Init: exec: '+exec.join(" "));
|
||||
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
|
||||
return docker.run(terraformImage, exec, processStream, {
|
||||
//"Env": ["VAR_ENV=whatever"],
|
||||
console.log('Terraform# Init: version to use is : '+provision.terraformImage);
|
||||
return docker.run(provision.terraformImage, exec, processStream, {
|
||||
//"Env": [],
|
||||
"name": name,
|
||||
"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
@@ -170,18 +210,19 @@ const init = function( provision ) {
|
||||
});
|
||||
};
|
||||
|
||||
const plan = function( provision, scenario ) {
|
||||
const plan = function( provision ) {
|
||||
|
||||
const name = `qmi-tf-plan-${provision._id}`;
|
||||
console.log(`Terraform# Plan: will spin up container: ${name}`);
|
||||
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
|
||||
//var processStream = process.stdout;
|
||||
var exec = ['terraform', 'plan', '-no-color', '-input=false', '-out=tfplan' ];
|
||||
exec = _buildVarsExec(exec, provision, scenario);
|
||||
var vars = _buildVarsExec(exec, provision);
|
||||
exec = vars.exec;
|
||||
console.log('Terraform# Plan: exec: '+exec.join(" "));
|
||||
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
|
||||
return docker.run(terraformImage, exec, processStream, {
|
||||
//"Env": ["VAR_ENV=whatever"],
|
||||
console.log('Terraform# Plan: version to use is : '+provision.terraformImage);
|
||||
return docker.run(provision.terraformImage, exec, processStream, {
|
||||
"Env": vars.envs,
|
||||
"name": name,
|
||||
"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
@@ -212,9 +253,13 @@ const apply = function( provision ) {
|
||||
|
||||
var exec = ['terraform', 'apply', 'tfplan', '-no-color'];
|
||||
console.log('Terraform# Apply: exec: '+exec.join(" "));
|
||||
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
|
||||
return docker.run(terraformImage, exec, processStream, {
|
||||
//"Env": ["VAR_ENV=whatever"],
|
||||
console.log('Terraform# Apply: version to use is : '+provision.terraformImage);
|
||||
return docker.run(provision.terraformImage, exec, processStream, {
|
||||
"Env": [
|
||||
`AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}`,
|
||||
`AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}`,
|
||||
`AWS_DEFAULT_REGION=us-east-1`
|
||||
],
|
||||
"name": name,
|
||||
"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
@@ -228,7 +273,7 @@ const apply = function( provision ) {
|
||||
let container = data[1];
|
||||
console.log(`Terraform# Apply: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`);
|
||||
return container.remove().then(function(){
|
||||
console.log(`Terraform# Apply: ${name} removed!`);
|
||||
console.log(`Terraform# Apply: Container '${name}' removed!`);
|
||||
return data[0].StatusCode;
|
||||
});
|
||||
}).then(function(statusCode) {
|
||||
@@ -236,19 +281,19 @@ const apply = function( provision ) {
|
||||
})
|
||||
}
|
||||
|
||||
const destroy = function(destroyMongo, provision, scenario) {
|
||||
const destroy = function(destroyMongo, provision) {
|
||||
|
||||
const name = `qmi-tf-destroy-${destroyMongo._id}`;
|
||||
console.log(`Terraform# Destroy: will spin up container: ${name}`);
|
||||
var processStream = fs.createWriteStream(destroyMongo.logFile, {flags:'a'});
|
||||
var exec = ['Terraform# terraform', 'destroy', '-auto-approve', '-no-color'];
|
||||
exec = _buildVarsExec(exec, provision, scenario);
|
||||
var exec = ['terraform', 'destroy', '-auto-approve', '-no-color'];
|
||||
var vars = _buildVarsExec(exec, provision);
|
||||
exec = vars.exec;
|
||||
console.log('Terraform# Destroy: exec: '+exec.join(" "));
|
||||
|
||||
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
|
||||
|
||||
return docker.run(terraformImage, exec, processStream, {
|
||||
//"Env": ["VAR_ENV=whatever"],
|
||||
console.log('Terraform# Destroy: version to use is : '+provision.terraformImage);
|
||||
|
||||
return docker.run(provision.terraformImage, exec, processStream, {
|
||||
"Env": vars.envs,
|
||||
"name": name,
|
||||
"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
@@ -261,7 +306,7 @@ const destroy = function(destroyMongo, provision, scenario) {
|
||||
var container = data[1];
|
||||
console.log(`Terraform# Destroy: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
|
||||
return container.remove().then(function(){
|
||||
console.log(`Terraform# Destroy: '${name}' removed!`);
|
||||
console.log(`Terraform# Destroy: Container '${name}' removed!`);
|
||||
return data[0].StatusCode;
|
||||
});
|
||||
}).then(async function(statusCode) {
|
||||
@@ -283,11 +328,8 @@ const outputs = function(provision) {
|
||||
var unhook = hook_stdout(function(string, encoding, fd) {
|
||||
tfout += string.trim();
|
||||
});
|
||||
|
||||
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
|
||||
|
||||
return docker.run(terraformImage, exec, process.stdout, {
|
||||
//"Env": ["VAR_ENV=whatever"],
|
||||
return docker.run(provision.terraformImage, exec, process.stdout, {
|
||||
//"Env": [],
|
||||
"name": name,
|
||||
"WorkingDir": "/app",
|
||||
"HostConfig": {
|
||||
@@ -301,8 +343,8 @@ const outputs = function(provision) {
|
||||
console.log(`Terraform# Output: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
|
||||
return container.remove();
|
||||
}).then(async function(data) {
|
||||
console.log(`Terraform# Output: '${name}' removed!`);
|
||||
console.log("Terraform# Output: tfout: " + tfout);
|
||||
console.log(`Terraform# Output: Container '${name}' removed!`);
|
||||
//console.log("Terraform# Output: tfout: " + tfout);
|
||||
var out = JSON.parse(tfout);
|
||||
var o = {};
|
||||
for (var key in out) {
|
||||
@@ -312,8 +354,48 @@ const outputs = function(provision) {
|
||||
});
|
||||
};
|
||||
|
||||
const stop = function(provision) {
|
||||
|
||||
if ( provision.status !== "provisioning" ) {
|
||||
console.log(`Terraform# Stop: provision (${provision._id}) is not provisioning`);
|
||||
return {"message": `Won't stop container: provision (${provision._id}) is not provisioning`};
|
||||
}
|
||||
|
||||
const name = `qmi-tf-apply-${provision._id}`;
|
||||
|
||||
console.log(`Terraform# Stop: will try to stop container: ${name}`);
|
||||
|
||||
return docker.listContainers().then(function(containers) {
|
||||
|
||||
var containerID;
|
||||
|
||||
for (let i=0;i<containers.length;i++) {
|
||||
if ( containers[i].Names && containers[i].Names.length ) {
|
||||
//console.log(`Terraform# Stop: debug container Names: ${containers[i].Names[0]}`);
|
||||
if ( containers[i].Names[0] === ("/"+name) ) {
|
||||
console.log(`Terraform# Stop: container found: ${name}`);
|
||||
containerID = containers[i].Id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( containerID ) {
|
||||
console.log(`Terraform# Stop: stopping container: ${name} with ID ${containerID}`);
|
||||
return docker.getContainer(containerID).kill().then(function(){
|
||||
console.log(`Terraform# Stop: container stopped!!: ${name}`);
|
||||
return {"message": `container ${name} stopped`}
|
||||
});
|
||||
} else {
|
||||
console.log(`Terraform# Stop: container ${name} not found!`);
|
||||
return {"message": `container ${name} not found`};
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports.init = init;
|
||||
module.exports.plan = plan;
|
||||
module.exports.apply = apply;
|
||||
module.exports.destroy = destroy;
|
||||
module.exports.outputs = outputs;
|
||||
module.exports.outputs = outputs;
|
||||
module.exports.stop = stop;
|
||||
302
qmi-cloud-worker/guacamole.js
Normal file
302
qmi-cloud-worker/guacamole.js
Normal file
@@ -0,0 +1,302 @@
|
||||
'use strict';
|
||||
const axios = require('axios');
|
||||
const https = require("https");
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const URL = "https://qmicloud-dev.qliktech.com:8443/api/session/data/postgresql";
|
||||
const GUACA_USERNAME = process.env.GUACA_USERNAME || "guacadmin";
|
||||
const GUACA_PASSWORD = process.env.GUACA_PASSWORD;
|
||||
|
||||
|
||||
const base64urlEncode = function(value) {
|
||||
|
||||
// Translate padded standard base64 to unpadded base64url
|
||||
return Buffer.from(value).toString('base64').replace(/[+/=]/g,
|
||||
(str) => ({
|
||||
'+' : '-',
|
||||
'/' : '_',
|
||||
'=' : ''
|
||||
})[str]
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
const guacamoleClientId = function(id, type, dataSource) {
|
||||
return base64urlEncode([
|
||||
id,
|
||||
type,
|
||||
dataSource
|
||||
].join('\0'));
|
||||
};
|
||||
|
||||
async function _auth() {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
params.append('username', GUACA_USERNAME);
|
||||
params.append('password', GUACA_PASSWORD);
|
||||
|
||||
var res = await axios.post('https://qmicloud-dev.qliktech.com:8443/api/tokens', params, {
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
});
|
||||
|
||||
return res.data.authToken;
|
||||
|
||||
|
||||
} catch (err) {
|
||||
// Handle Error Here
|
||||
console.log("Guacamole# Could not auth", err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function _createUser(email, token) {
|
||||
try {
|
||||
var body = {
|
||||
"username": email,
|
||||
"attributes": {
|
||||
"disabled": "",
|
||||
"expired": "",
|
||||
"access-window-start": "",
|
||||
"access-window-end": "",
|
||||
"valid-from": "",
|
||||
"valid-until": "",
|
||||
"timezone": null
|
||||
}
|
||||
};
|
||||
var res = await axios({
|
||||
url: `${URL}/users`,
|
||||
method: "post",
|
||||
data: body,
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
}),
|
||||
headers: {
|
||||
'Guacamole-Token': token
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Guacamole# User created: ", email);
|
||||
|
||||
return email;
|
||||
|
||||
|
||||
} catch (err) {
|
||||
// Handle Error Here
|
||||
|
||||
console.log("Guacamole# User already existed: ", email);
|
||||
|
||||
return email;
|
||||
}
|
||||
}
|
||||
|
||||
async function _createConnection(type, name, ip, username, password, token) {
|
||||
try {
|
||||
var body = {
|
||||
"parentIdentifier": "ROOT",
|
||||
"name": name,
|
||||
"protocol": type,
|
||||
"parameters": {
|
||||
"port": type === "ssh"? "22" : "3389",
|
||||
"read-only": "",
|
||||
"swap-red-blue": "",
|
||||
"cursor": "",
|
||||
"color-depth": "",
|
||||
"force-lossless": "",
|
||||
"clipboard-encoding": "",
|
||||
"disable-copy": "",
|
||||
"disable-paste": "",
|
||||
"dest-port": "",
|
||||
"recording-exclude-output": "",
|
||||
"recording-exclude-mouse": "",
|
||||
"recording-include-keys": "",
|
||||
"create-recording-path": "",
|
||||
"enable-sftp": "",
|
||||
"sftp-port": "",
|
||||
"sftp-server-alive-interval": "",
|
||||
"sftp-disable-download": "",
|
||||
"sftp-disable-upload": "",
|
||||
"enable-audio": "",
|
||||
"wol-send-packet": "",
|
||||
"wol-udp-port": "",
|
||||
"wol-wait-time": "",
|
||||
"security": "nla",
|
||||
"disable-auth": "",
|
||||
"ignore-cert": "true",
|
||||
"gateway-port": "",
|
||||
"server-layout": "",
|
||||
"timezone": null,
|
||||
"enable-touch": "",
|
||||
"console": "",
|
||||
"width": "",
|
||||
"height": "",
|
||||
"dpi": "",
|
||||
"resize-method": "",
|
||||
"normalize-clipboard": "",
|
||||
"console-audio": "",
|
||||
"disable-audio": "",
|
||||
"enable-audio-input": "",
|
||||
"enable-printing": "",
|
||||
"enable-drive": "",
|
||||
"disable-download": "",
|
||||
"disable-upload": "",
|
||||
"create-drive-path": "",
|
||||
"enable-wallpaper": "",
|
||||
"enable-theming": "",
|
||||
"enable-font-smoothing": "",
|
||||
"enable-full-window-drag": "",
|
||||
"enable-desktop-composition": "",
|
||||
"enable-menu-animations": "",
|
||||
"disable-bitmap-caching": "",
|
||||
"disable-offscreen-caching": "",
|
||||
"disable-glyph-caching": "",
|
||||
"preconnection-id": "",
|
||||
"recording-exclude-touch": "",
|
||||
"hostname": ip,
|
||||
"username": username,
|
||||
"password": password
|
||||
},
|
||||
"attributes": {
|
||||
"max-connections": "2",
|
||||
"max-connections-per-user": "2",
|
||||
"weight": "",
|
||||
"failover-only": "",
|
||||
"guacd-port": "",
|
||||
"guacd-encryption": ""
|
||||
}
|
||||
};
|
||||
var res = await axios({
|
||||
url: `${URL}/connections`,
|
||||
method: "post",
|
||||
data: body,
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
}),
|
||||
headers: {
|
||||
'Guacamole-Token': token
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Guacamole# Connection created: ", name);
|
||||
|
||||
return res.data;
|
||||
|
||||
|
||||
} catch (err) {
|
||||
// Handle Error Here
|
||||
console.log("Guacamole# Could not create Guaca Connection", err.config.data);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function _addConnectionToUser(email, identifier, token) {
|
||||
try {
|
||||
var body = [
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/connectionPermissions/"+identifier,
|
||||
"value": "READ"
|
||||
}
|
||||
];
|
||||
var res = await axios({
|
||||
url: `${URL}/users/${email}/permissions`,
|
||||
method: "patch",
|
||||
data: body,
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
}),
|
||||
headers: {
|
||||
'Guacamole-Token': token
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Guacamole# Connection identifier ("+identifier+") added to user: ", email);
|
||||
|
||||
return res.data;
|
||||
|
||||
} catch (err) {
|
||||
// Handle Error Here
|
||||
console.log("Guacamole# Could not add connection "+identifier+" to Guaca User " + email);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function setUserConnection(provision, scenario){
|
||||
|
||||
var resultProvision = provision;
|
||||
|
||||
if ( provision && provision.status === 'provisioned' && provision.options && provision.options.vm1 ) {
|
||||
var token = await _auth();
|
||||
|
||||
if (token) {
|
||||
await _createUser(provision.user.mail, token);
|
||||
|
||||
|
||||
let ip = provision.outputs["RDP-ip"] || provision.outputs["Replicate__RDP_IP"] || provision.outputs["Private_IP"] || provision.outputs["IP"];
|
||||
let credentials = provision.outputs["RDP-credentials"] || provision.outputs["Replicate__RDP-credentials"] || provision.outputs["User-and-Password"] || provision.outputs["All_DBs_User-and-Password"];
|
||||
|
||||
if (ip && credentials){
|
||||
let username = credentials.split(" / ")[0];
|
||||
let password = credentials.split(" / ")[1];
|
||||
|
||||
if (username && password){
|
||||
let connection;
|
||||
let connName = `(${scenario.name}_${provision._id}) - ${provision.description}`;
|
||||
let type;
|
||||
if ( scenario.labels.indexOf("linux") === -1 ) {
|
||||
connection = await _createConnection("rdp",connName, ip, username, password, token);
|
||||
type = "RDP";
|
||||
} else {
|
||||
connection = await _createConnection("ssh", connName, ip, username, password, token);
|
||||
type = "SSH";
|
||||
}
|
||||
if (connection) {
|
||||
let outputs = provision.outputs || {};
|
||||
const guacClient = guacamoleClientId(connection.identifier,"c", "postgresql");
|
||||
outputs[`WEB_${type}_ACCESS_WITH_GUACAMOLE`] = `https://qmicloud-dev.qliktech.com:8443/#/client/${guacClient}`;
|
||||
|
||||
_addConnectionToUser(provision.user.mail, connection.identifier, token);
|
||||
|
||||
resultProvision = await db.provision.update(provision._id, {"guacaConnId": connection.identifier, outputs: outputs});
|
||||
|
||||
} else {
|
||||
console.log("Guacamole# No connection was created for provision: ", provision._id);
|
||||
}
|
||||
} else {
|
||||
console.log("Guacamole# Could not find username or password for provision:", provision._id);
|
||||
}
|
||||
} else {
|
||||
console.log("Guacamole# Could not find ip or credentials for provision:", provision._id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return resultProvision;
|
||||
|
||||
}
|
||||
|
||||
async function deleteConnection(provision){
|
||||
if ( provision.guacaConnId ) {
|
||||
try {
|
||||
var token = await _auth();
|
||||
await axios({
|
||||
url: `${URL}/connections/${provision.guacaConnId}`,
|
||||
method: "delete",
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false
|
||||
}),
|
||||
headers: {
|
||||
'Guacamole-Token': token
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// Handle Error Here
|
||||
console.log("Guacamole# Could not delete connection ("+provision.guacaConnId+")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.setUserConnection = setUserConnection;
|
||||
module.exports.deleteConnection = deleteConnection;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from 'qmi-cloud-common/queues';
|
||||
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
|
||||
|
||||
var path = require("path");
|
||||
|
||||
@@ -6,6 +6,8 @@ var path = require("path");
|
||||
queues[TF_APPLY_QUEUE].process("tf_apply_job", 10, path.resolve(__dirname, 'processor-apply.js'));
|
||||
queues[TF_APPLY_QSEOK_QUEUE].process("tf_apply_qseok_job", 10, path.resolve(__dirname, 'processor-apply-qseok.js'));
|
||||
queues[TF_DESTROY_QUEUE].process("tf_destroy_job", 10, path.resolve(__dirname, 'processor-destroy.js'));
|
||||
queues[STOP_CONTAINER_QUEUE].process("tf_abort_apply_job", 10, path.resolve(__dirname, 'processor-stop-container.js'));
|
||||
queues[SYNAPSE_QUEUE].process("synapse_job", 10, path.resolve(__dirname, 'processor-synapse.js'));
|
||||
|
||||
|
||||
console.log(`Worker queues started!`);
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "qmi-cloud-worker",
|
||||
"version": "1.1.6",
|
||||
"version": "2.1.0",
|
||||
"scripts": {
|
||||
"start": "node -r esm index.js",
|
||||
"start:dev": "nodemon -r esm index.js",
|
||||
@@ -11,6 +11,7 @@
|
||||
"qmi-cloud-common": "../qmi-cloud-common",
|
||||
"dockerode": "^3.0.2",
|
||||
"esm": "^3.2.25",
|
||||
"nodemon": "^1.19.1"
|
||||
"nodemon": "^1.19.1",
|
||||
"axios": "^0.21.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,14 @@ const tf = require('./docker/tf');
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const path = require('path');
|
||||
const sendEmail = require("qmi-cloud-common/send-email");
|
||||
const barracuda = require("qmi-cloud-common/barracuda");
|
||||
const guaca = require("./guacamole");
|
||||
|
||||
|
||||
module.exports = async function(job){
|
||||
|
||||
var triggerUser = job.data.user;
|
||||
|
||||
var destroyMongo = await db.destroy.update(job.data.id, {
|
||||
"status": "destroying",
|
||||
"jobId": job.id,
|
||||
@@ -17,25 +22,53 @@ module.exports = async function(job){
|
||||
}
|
||||
|
||||
var provMongo = await db.provision.getById(job.data.provId);
|
||||
|
||||
// Deleting Barracuda App if existed
|
||||
if ( provMongo.barracudaAppId ) {
|
||||
console.log(`ProcessorDestroy# Deleting Barracuda App...` );
|
||||
barracuda.deleteApp(provMongo);
|
||||
} else {
|
||||
console.log(`ProcessorDestroy# There is no barracuda to be deleted` );
|
||||
}
|
||||
|
||||
return tf.destroy(destroyMongo, provMongo, job.data._scenario)
|
||||
.then(async function(res) {
|
||||
db.event.add({ user: triggerUser._id, provision: provMongo._id, type: 'provision.destroy-init' });
|
||||
|
||||
return tf.destroy(destroyMongo, provMongo).then(async function(res) {
|
||||
let update, update2;
|
||||
if ( res.output.indexOf("Error:") !== -1 ) {
|
||||
update = await db.destroy.update(destroyMongo._id,{"status": "error"});
|
||||
update2 = await db.provision.update(provMongo._id, {"isDestroyed": false});
|
||||
|
||||
} else {
|
||||
update = await db.destroy.update(destroyMongo._id, {"status": "destroyed"});
|
||||
let timeRunning = db.utils.getNewTimeRunning(provMongo);
|
||||
update2 = await db.provision.update(provMongo._id, {"isDestroyed": true, "timeRunning": timeRunning, "pendingNextAction": undefined, "actualDestroyDate": new Date()});
|
||||
sendEmail.sendDestroyed(update2, job.data._scenario);
|
||||
update2 = await db.provision.update(provMongo._id, {"isDestroyed": true, "timeRunning": timeRunning, "pendingNextAction": null, "actualDestroyDate": new Date()});
|
||||
sendEmail.sendDestroyedSuccess(update2, job.data._scenario);
|
||||
guaca.deleteConnection(provMongo);
|
||||
|
||||
|
||||
//console.log(`ProcessorDestroy# Deleting shares of this provision` );
|
||||
//db.sharedProvision.delMany({"provision":update2._id});
|
||||
}
|
||||
return { destroy: update, provision: update2 };
|
||||
}).then(async function(res) {
|
||||
|
||||
}).then(async function(res) {
|
||||
if ( res.provision.isDestroyed ) {
|
||||
console.log(`ProcessorDestroy# Provision (${res.provision._id}) destroyed!` );
|
||||
db.event.add({ provision: provMongo._id, type: 'provision.destroy-finished' });
|
||||
if ( res.provision.scenario === "azqmi-qdi" ) {
|
||||
let tempApiKey = await db.apiKey.getOne({"description": res.provision._id});
|
||||
if (tempApiKey && tempApiKey._id){
|
||||
db.apiKey.del(tempApiKey._id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`ProcessorDestroy# Provision (${res.provision._id}) NOT destroyed - Error!` );
|
||||
db.event.add({ provision: provMongo._id, type: 'provision.destroy-error' });
|
||||
}
|
||||
return Promise.resolve({"success": true, job: res});
|
||||
}).catch(function(err) {
|
||||
console.log("ProcessorDestroy# err", err);
|
||||
db.event.add({ provision: provMongo._id, type: 'provision.destroy-error' });
|
||||
db.destroy.update(destroyMongo._id, {"status": "error", "isDestroyed": false});
|
||||
return Promise.reject({"success": false, "err": err});
|
||||
});
|
||||
|
||||
21
qmi-cloud-worker/processor-stop-container.js
Normal file
21
qmi-cloud-worker/processor-stop-container.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const tf = require("./docker/tf");
|
||||
|
||||
module.exports = async function(job) {
|
||||
|
||||
var prov = await db.provision.getById(job.data.provId);
|
||||
|
||||
if ( !prov ) {
|
||||
console.log(`ProcessorStopContainer# Error: Not found Provision object in Database (it should exist!), provisionId is: ${job.data.id}` );
|
||||
return Promise.reject({"success": false, "err": "Not found Provision object in Worker"});
|
||||
}
|
||||
|
||||
// TERRAFORM INIT
|
||||
return tf.stop(prov)
|
||||
.then( function(res) {
|
||||
return Promise.resolve( { "success": true, "output": res });
|
||||
} ).catch( function(err) {
|
||||
console.log("ProcessorStopContainer# Error:", err);
|
||||
return Promise.reject({"success": false, "error": err});
|
||||
} );
|
||||
}
|
||||
67
qmi-cloud-worker/processor-synapse.js
Normal file
67
qmi-cloud-worker/processor-synapse.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const synapse = require("./docker/synapse");
|
||||
|
||||
module.exports = async function(job) {
|
||||
|
||||
console.log(`ProcessorAzCLI# will execute ${job.data.tasktype} task`);
|
||||
|
||||
if (job.data.tasktype === 'pause') {
|
||||
|
||||
var prov = await db.provision.getById(job.data.provId);
|
||||
|
||||
if ( !prov ) {
|
||||
console.log(`ProcessorAzCLI# Error: Not found Provision object in Database (it should exist!), provisionId is: ${job.data.id}` );
|
||||
return Promise.reject({"success": false, "err": "Not found Provision object in Worker"});
|
||||
}
|
||||
|
||||
// Synapse Pause
|
||||
db.provision.update(prov._id, {"statusVms": "Stopping"});
|
||||
return synapse.pause(prov)
|
||||
.then( function(res) {
|
||||
db.provision.update(prov._id, {"statusVms": "Stopped"});
|
||||
return Promise.resolve( { "success": true, "output": res });
|
||||
} ).catch( function(err) {
|
||||
console.log("ProcessorAzCLI# Error:", err);
|
||||
db.provision.update(prov._id, {"statusVms": "Running"});
|
||||
return Promise.reject({"success": false, "error": err});
|
||||
} );
|
||||
} else if (job.data.tasktype === 'resume') {
|
||||
|
||||
var prov = await db.provision.getById(job.data.provId);
|
||||
|
||||
if ( !prov ) {
|
||||
console.log(`ProcessorAzCLI# Error: Not found Provision object in Database (it should exist!), provisionId is: ${job.data.id}` );
|
||||
return Promise.reject({"success": false, "err": "Not found Provision object in Worker"});
|
||||
}
|
||||
|
||||
// Synapse Resume
|
||||
db.provision.update(prov._id, {"statusVms": "Starting"});
|
||||
return synapse.resume(prov)
|
||||
.then( function(res) {
|
||||
db.provision.update(prov._id, {"statusVms": "Running"});
|
||||
return Promise.resolve( { "success": true, "output": res });
|
||||
} ).catch( function(err) {
|
||||
console.log("ProcessorAzCLI# Error:", err);
|
||||
db.provision.update(prov._id, {"statusVms": "Stopped"});
|
||||
return Promise.reject({"success": false, "error": err});
|
||||
} );
|
||||
} else if (job.data.tasktype === 'copy-snapshots') {
|
||||
|
||||
return synapse.copySnapshots(job.data)
|
||||
.then( function(res) {
|
||||
return Promise.resolve( { "success": true, "output": res });
|
||||
} ).catch( function(err) {
|
||||
console.log("ProcessorAzCLI# Error:", err);
|
||||
return Promise.reject({"success": false, "error": err});
|
||||
} );
|
||||
} else if (job.data.tasktype === 'check-copy-snapshots') {
|
||||
|
||||
return synapse.checkCopySnapshots(job.data)
|
||||
.then( function(res) {
|
||||
return Promise.resolve( { "success": true, "output": res });
|
||||
} ).catch( function(err) {
|
||||
console.log("ProcessorAzCLI# Error:", err);
|
||||
return Promise.reject({"success": false, "error": err});
|
||||
} );
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
52
server/certs/privatekey.pem
Normal file
52
server/certs/privatekey.pem
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCzYn0nGRdheWZf
|
||||
X9Gvz5Fc0RH0oeJe1HfC0E7HrDFlogkmErJyvr6vZMwjx7jxSYDbYgNJtUXmalKZ
|
||||
a3fgF4nRmlv+aPG1pNjeCThoTHTof7hMS70iAIyrbubU6v+SKvbmcWM/RkEkEXXa
|
||||
7TCOi/ZbN7S+dFQAA9ObNF6dsYHLWBQO4O8ZoydTcxTc1enc+v1Q6/Bqm7//fBY+
|
||||
VV8tIi4hiIgLTEGnsBCnH34a3Ie8i6JDXEMmD+NiZjeYLrr5YToniDu4drkrpzIp
|
||||
2P9X2wbdwI25WC6RcWLwSIkjiRY8Z/jDgowjieCDNXHVCYv47+8peeOx/Z//YuKy
|
||||
Hul2ob25KjK51k6t55NKNj3kTrzXyRIgGOdI6Q+IBl85lfhIiXPRSCobLoMVbZgG
|
||||
5qTZpEHyp2RN5CPLGlshslrQigunUzuA6sPGR+qnT8ZwGndiV8n5oaWx7fmIoK1k
|
||||
SYBe5zKehYHh4hprThvzvqqPfJwzAFAOCSJJsch7SuZGlz/2xSx0P1e27vRJ9at/
|
||||
zSKL3FIFqHpk3I0FKf/FjNZu29RPmyLIyX8uaB/Iud6cg45THaUs+GXeiLY3mt/u
|
||||
3BRADZF9AXzJZR02Ubtre1gE5PSu0NOnkudoL+9fnCK5PGFlifdtp68R8UiKAnXj
|
||||
OjMljbq+yqn5tf6DB0y82rEk0qXCoQIDAQABAoICAAhFqkF4zz1f+fVmTPGfZUko
|
||||
nPwxJj1ll9uPJpWKSRbc44YdcPGIqUYbpFb2uVPAe3riaLC9SsARjBs7ZkzvrkZ7
|
||||
qJ1b8orZU2Td0PuXoavXuUR6GPqDQvlkL6yG5QqKWlYNyYaxSIS6HLtf77rLFS0S
|
||||
Aw7l/LpUHYMikEW+WfQUwjazbw3kGlv8n8F/8ye3wqG4156FmH4sFzcq/FdvpD1I
|
||||
ogQUsVG4dUl3qCWN9jZ0IU3w6GnOFsLCtZ1EyRDXR8rAkLHKIRzfD274QkInaAgm
|
||||
09ebC5QKbLEURJAJHNI5SzKc3QswgFQcoqdb0tgZHGgcZlXJZ9eWPvT5fEj2Xsd3
|
||||
i0Q46D6y7r+dAMhhE9hdZA/Ke1fkcBgANLRRdoHHFUey1en7uQsliGfNzm1i/OR4
|
||||
AW9XdyNiB5F2LqYfnrRXEsG7FHAnCVkN1k5ur8GsBZpfYtzWof9yKXCaVUQODKqW
|
||||
0gUndI38omx8liZoFruP5VteRJgVYEIkcyAAfeG3OlIaywGmwdTGyViXhWZcihse
|
||||
3pJMsUI7P72SkSjGKAjVxCf08bwCIT6ls8V79epiUzhMnORAZC4hZxayZrBphS8U
|
||||
sVzrMGQeVexlXso3EoqjWCaM7XkbvE/8ZOBbFxyJWAkIMuD4vUuW19Fk8IOTzAL4
|
||||
VKlcq+vfZxWbA7Mq6w7vAoIBAQDeJF82Lv/IUXL3JkgYmSnze7rGg+rwsuUxBXd5
|
||||
CNPjrlwbCEIVEreQHUPOzih2Rvhrd2iCVpyCO8Kdo2AE5B7wnPePAitnEwZ6hFcD
|
||||
xMubuiRqlaILfDi8Ph7juL0mmSaz5D+HkzUdCUfvBUFXxvLyeDsNFjgqBGOahL0b
|
||||
1E1FDct/6Mltc+ATlavWxQxn+UEs8/3ssy+f4gmeLgwgevt+A8cB3ppwSFCOnu4I
|
||||
iX/vdti5MHjdUBjJGV/qjTOsUWioRXY1ay87H5RiQju3GpTQ8zlkyRv9MMll0lqi
|
||||
OQwtVvVpRd6O4qHFmgJOfPt+Z81b6cPrA0StbwAG/t5qYH7/AoIBAQDOucsdyj5k
|
||||
XmlcDo51VyEr95FsJ/SZOPOBh4MpRm4IYUgLsQTacUueTCiY17TUR6CUc+si76gb
|
||||
YYAcF0KV/17lvPosoWkdZNsqq1L8nrVq3+z2WsTkCs8m10FZ0aajf3N8WTT0v6VX
|
||||
DsOrZLsD2xxIU4f3Lf4FnL6PI5BDT83Up5paHSIhE+omAp+x7Bzf0KSpsDu2uZTH
|
||||
r8yJf1ghet+Z923/yUUTHDM8Vpt8hhoEjXWJd3r96OSIoFUwHzLgNFpiPgHkGJUU
|
||||
xRZV4Au+GeHuz9nLWPRaYVG5CxX/IbPyMzGJHchVFywIySqYK2l+VvVM17M7AuCq
|
||||
V4SZ3y7Ky15fAoIBAHahp/MwwEqDLMlOOVxhl2S/Y+yWEIbAkuNODxKlIztJJ0kM
|
||||
bPYCC+O7rTWpJTSdDBegKkDI7kYikflLgYC7LsbCnPZTa0hdga02NZ3+n9mnW8FL
|
||||
7cECcu4corRsOR9+1ItnToIhnFDIXxEHlnDA/4d7q9V+UzolI+gmETPmeelxx4ak
|
||||
k8WPB1COMrm8e7afBy5xkt6whrN0rDw8TR+fbeVLMSEPdxyVkefIekg23grNRkoH
|
||||
19Qg7Uuf8Hg7NihFRYXvqoQ2nH+Pite6lVdgq6625aSsPfVF85gb8WkG3DjuYpr4
|
||||
xDU8VLZJXAf8ePZ1itcWDRnZofiY+cPCopbet5MCggEAGb7l3wXrE1D2yjI957s8
|
||||
NF+WyuOHAPYozX71BNTyqzSCZoJbWmE1y7csbyyeJrns89Aj/qveQdq4u8bh0hCF
|
||||
3xLUDW7kynZfHUdNBI03huHwfxX643O9LNcuGmOT31TmKxxpDfo4O0lpcRUQfYBy
|
||||
W0eb7VrbAhPtX6JMOzXbKprdDFAIihoS1T0Kanw/dFhlyYRbS3x9XQk17gHgFftZ
|
||||
kbFRD8QfSCwA7YjTwIRrBRohA0fQF4NDwwhE08Nu8KFUiFu0nJW7K2UITRWkIL7U
|
||||
douIUlz3wbHRHbyVtrqZ0JYzmyIMaxyBrW5wUZdGgieOUU2j0rufA1f2+brj9vmw
|
||||
/QKCAQBDuCwPOBuZ6qFu1zBAfDyElTsIve1Uh32q+jbL5w8kkBhsiGaxFcmC3gG6
|
||||
WkdtKZKSRaf23wl4LgjvAYD5lPX+p3127pHxG+haWFkXweDMw4WrvGamm/X0H8JD
|
||||
8CX9PNWi2yxVDjNXiJ5tSbsFnhBf2CijHinrZexxlVTId6yuptOWDaq4XeYnMmKt
|
||||
FRgpnwnKAkxZKmoKIXOeSQOUVJzzxufkX3QG3CJqu60DGEDsLxM00HoP/Vxa81un
|
||||
XT+DxB6kgSYLC0a7KsHsXm8CbCoikqVEtANzIvahbsKDLNah/OWmOo9O1UphhYN+
|
||||
HQe3R2QQtfmWZ320cdhukaok4yj3
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -2,16 +2,14 @@
|
||||
const secrets = require("/run/secrets/config.json");
|
||||
|
||||
const HOSTNAME_URL = process.env.HOSTNAME_URL || "http://localhost:3000"
|
||||
const TENANT_ID = secrets.AZURE_TENANT_ID;
|
||||
const CLIENT_ID = secrets.AZURE_CLIENT_ID;
|
||||
const CLIENT_SECRET = secrets.AZURE_CLIENT_SECRET;
|
||||
const CLIENT_ID = secrets.CLIENT_ID;
|
||||
const CLIENT_SECRET = secrets.CLIENT_SECRET;
|
||||
const LOGOUT_URL = HOSTNAME_URL;
|
||||
const REDIRECT_URL = `${HOSTNAME_URL}/auth/openid/return`;
|
||||
|
||||
exports.creds = {
|
||||
// Required
|
||||
identityMetadata: `https://login.microsoftonline.com/${TENANT_ID}/.well-known/openid-configuration`,
|
||||
// or equivalently: 'https://login.microsoftonline.com/${TENANT_ID}/.well-known/openid-configuration'
|
||||
//identityMetadata: IDENTITY_METADATA,
|
||||
//
|
||||
// or you can use the common endpoint
|
||||
// 'https://login.microsoftonline.com/common/.well-known/openid-configuration'
|
||||
@@ -85,12 +83,13 @@ exports.creds = {
|
||||
exports.resourceURL = 'https://graph.microsoft.com';
|
||||
|
||||
// The url you need to go to destroy the session with AAD
|
||||
exports.destroySessionUrl = `https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=${LOGOUT_URL}`;
|
||||
|
||||
//exports.destroySessionUrl = `https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=${LOGOUT_URL}`;
|
||||
//exports.destroySessionUrl = `https://qlik.okta.com/logout?id_token_hint=${id_token}&post_logout_redirect_uri=${LOGOUT_URL}&state=${state}`
|
||||
exports.destroySessionUrl = `https://qlik.okta.com/logout?post_logout_redirect_uri=${LOGOUT_URL}`
|
||||
// If you want to use the mongoDB session store for session middleware, set to true; otherwise we will use the default
|
||||
// session store provided by express-session.
|
||||
// Note that the default session store is designed for development purpose only.
|
||||
exports.useMongoDBSessionStore = true;
|
||||
exports.useMongoDBSessionStore = false;
|
||||
|
||||
// If you want to use mongoDB, provide the uri here for the database.
|
||||
exports.databaseUri = process.env.MONGO_URI;
|
||||
|
||||
@@ -6,6 +6,17 @@ const config = require('./config');
|
||||
const MongoStore = require('connect-mongo')(expressSession);
|
||||
const mongoose = require('mongoose');
|
||||
const db = require("qmi-cloud-common/mongo");
|
||||
const axios = require('axios');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const sessionStore = new MongoStore({
|
||||
mongooseConnection: mongoose.connection,
|
||||
url: process.env.MONGO_URI,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 10
|
||||
//clear_interval: config.mongoDBSessionMaxAge
|
||||
});
|
||||
|
||||
// Start QuickStart here
|
||||
|
||||
@@ -77,37 +88,85 @@ passport.use(new OIDCStrategy({
|
||||
cookieEncryptionKeys: config.creds.cookieEncryptionKeys,
|
||||
clockSkew: config.creds.clockSkew,
|
||||
},
|
||||
function(iss, sub, profile, jwtClaims, accessToken, refreshToken, params, done) {
|
||||
async function(iss, sub, profile, jwtClaims, accessToken, refreshToken, params, done) {
|
||||
|
||||
if ( !profile.oid ) {
|
||||
return done(new Error("No oid found"), null);
|
||||
}
|
||||
|
||||
let email = profile.upn;
|
||||
let isPermitterEmail = (email.toLowerCase().includes("@qlik") || email.toLowerCase().includes("@talend"));
|
||||
if ( !email || !isPermitterEmail) {
|
||||
return done(new Error("Not a valid Qlik email"), null);
|
||||
}
|
||||
|
||||
//console.log("accessToken", accessToken);
|
||||
//console.log("iss", iss);
|
||||
//console.log("sub", sub);
|
||||
//console.log("refreshToken", refreshToken);
|
||||
//console.log("jwtClaims", jwtClaims);
|
||||
//console.log("params", params);
|
||||
console.log(`Passport# new login from: ${profile.upn} (${profile.displayName})` );
|
||||
console.log(`Passport# new login from: ${profile.upn} (${profile.displayName}) - sub: ${sub}` );
|
||||
|
||||
//Save user photo
|
||||
axios({
|
||||
method: 'GET',
|
||||
url: 'https://graph.microsoft.com/v1.0/me/photo/$value',
|
||||
responseType: 'stream',
|
||||
headers: { 'Authorization' : 'Bearer '+accessToken }
|
||||
}).then(function (response) {
|
||||
response.data.pipe(fs.createWriteStream(path.resolve(__dirname, '..', 'photos', `${profile.oid}.jpg`)));
|
||||
}).catch(function(err){
|
||||
console.log('Passport# Warning: No picture found');
|
||||
});
|
||||
|
||||
let mail, jobTitle;
|
||||
|
||||
try {
|
||||
let fullProfile = await axios({
|
||||
method: 'GET',
|
||||
url: `https://graph.microsoft.com/v1.0/users/${profile.oid}`,
|
||||
responseType: 'json',
|
||||
headers: { 'Authorization' : 'Bearer '+accessToken }
|
||||
});
|
||||
if ( fullProfile.data ) {
|
||||
mail = fullProfile.data.mail;
|
||||
jobTitle = fullProfile.data.jobTitle;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Passport# Warning: Could not get user entire profile');
|
||||
}
|
||||
|
||||
// asynchronous verification, for effect...
|
||||
process.nextTick(function () {
|
||||
_findByOid(profile.oid, async function(err, user) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
if (!user) {
|
||||
// "Auto-registration"
|
||||
user = await db.user.add({
|
||||
"oid": profile.oid,
|
||||
"upn": profile.upn,
|
||||
"displayName": profile.displayName,
|
||||
"lastLogin": new Date()
|
||||
_findByOid(profile.oid, async function(err, user) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
if (!user) {
|
||||
// "Auto-registration"
|
||||
user = await db.user.add({
|
||||
"oid": profile.oid,
|
||||
"upn": profile.upn,
|
||||
"displayName": profile.displayName,
|
||||
"lastLogin": new Date(),
|
||||
"sub": sub,
|
||||
"active": true,
|
||||
"mail": mail,
|
||||
"jobTitle": jobTitle
|
||||
});
|
||||
return done(null, user);
|
||||
}
|
||||
db.user.update(user._id, {
|
||||
"lastLogin": new Date(),
|
||||
"sub": sub,
|
||||
"active": true,
|
||||
"mail": mail,
|
||||
"jobTitle": jobTitle
|
||||
});
|
||||
return done(null, user);
|
||||
}
|
||||
db.user.update(user._id, {"lastLogin": new Date()});
|
||||
return done(null, user);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
));
|
||||
@@ -120,15 +179,16 @@ module.exports.init = function(app){
|
||||
app.use(expressSession({
|
||||
secret: 'secret',
|
||||
cookie: {maxAge: config.mongoDBSessionMaxAge * 1000},
|
||||
store: new MongoStore({
|
||||
mongooseConnection: mongoose.connection,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 10
|
||||
//clear_interval: config.mongoDBSessionMaxAge
|
||||
})
|
||||
store: sessionStore,
|
||||
resave: true,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
} else {
|
||||
app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false }));
|
||||
app.use(expressSession({
|
||||
secret: 'keyboard cat',
|
||||
resave: true,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -240,11 +300,14 @@ module.exports.ensureAuthenticatedAndAdmin = async function(req, res, next) {
|
||||
|
||||
module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
|
||||
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
|
||||
if ( req.user._id == req.params.userId || req.user.role === 'admin' || req.user.role === 'superadmin' ) {
|
||||
var userId = (req.params.userId === 'me')? req.user._id : req.params.userId;
|
||||
if ( req.user._id == userId || req.user.role === 'admin' || req.user.role === 'superadmin' ) {
|
||||
return next();
|
||||
} else {
|
||||
return res.status(401).send("Error: Unauthorized");
|
||||
}
|
||||
}
|
||||
return res.status(401).send("Error: Unauthorized");
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.sessionStore = sessionStore;
|
||||
268
server/passport-okta.js
Normal file
268
server/passport-okta.js
Normal file
@@ -0,0 +1,268 @@
|
||||
const passport = require('passport');
|
||||
const expressSession = require('express-session');
|
||||
const config = require('./config');
|
||||
|
||||
// set up database for express session
|
||||
const MongoStore = require('connect-mongo')(expressSession);
|
||||
const mongoose = require('mongoose');
|
||||
const db = require("qmi-cloud-common/mongo");
|
||||
|
||||
const sessionStore = config.useMongoDBSessionStore? new MongoStore({
|
||||
mongooseConnection: mongoose.connection,
|
||||
url: process.env.MONGO_URI,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 10
|
||||
//clear_interval: config.mongoDBSessionMaxAge
|
||||
}) : new expressSession.MemoryStore();
|
||||
|
||||
var OpenIDConnectStrategy = require('passport-openidconnect');
|
||||
|
||||
const OKTA_DOMAIN = "qlik.okta.com";
|
||||
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(user, done) {
|
||||
_findByUpn(user.upn, function (err, user) {
|
||||
done(err, user);
|
||||
});
|
||||
});
|
||||
|
||||
var _findByUpn = async function(upn, fn) {
|
||||
var mongouser = await db.user.getOne({"upn": { $regex: new RegExp(upn, 'i') } });
|
||||
if (mongouser){
|
||||
return fn(null, mongouser);
|
||||
} else {
|
||||
return fn(null, null);
|
||||
}
|
||||
};
|
||||
|
||||
// set up passport
|
||||
passport.use('oidc', new OpenIDConnectStrategy({
|
||||
issuer: "https://qlik.okta.com",
|
||||
authorizationURL: `https://${OKTA_DOMAIN}/oauth2/v1/authorize`,
|
||||
tokenURL: `https://${OKTA_DOMAIN}/oauth2/v1/token`,
|
||||
clientID: config.creds.clientID,
|
||||
clientSecret: config.creds.clientSecret,
|
||||
callbackURL: config.creds.redirectUrl,
|
||||
scope: config.creds.scope,
|
||||
passReqToCallback: true
|
||||
}, async (req, issuer, profile, context, idToken, accessToken, refreshToken, done) => {
|
||||
|
||||
//console.log("OKTA ISSUER ", issuer)
|
||||
//console.log("OKTA PROFILE ", profile)
|
||||
//console.log("OKTA context ", context)
|
||||
//console.log("OKTA idToken ", idToken)
|
||||
//console.log("OKTA accessToken ", accessToken)
|
||||
//console.log("OKTA refreshToken ", refreshToken)
|
||||
|
||||
|
||||
req.session.oktaAccessToken = accessToken;
|
||||
|
||||
if ( !profile.id ) {
|
||||
return done(new Error("No profile id found"), null);
|
||||
}
|
||||
|
||||
console.log(`Passport# new login from: ${profile.displayName} - sub: ${profile.id}` );
|
||||
|
||||
// asynchronous verification, for effect...
|
||||
process.nextTick(function () {
|
||||
_findByUpn(profile.username, async function(err, user) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
if (!user) {
|
||||
// "Auto-registration"
|
||||
user = await db.user.add({
|
||||
//"oid": profile.id,
|
||||
"upn": profile.username.toLowerCase(),
|
||||
"displayName": profile.displayName,
|
||||
"lastLogin": new Date(),
|
||||
"sub": profile.id,
|
||||
"active": true,
|
||||
"mail": profile.emails[0].value,
|
||||
//"jobTitle": jobTitle
|
||||
});
|
||||
return done(null, user);
|
||||
}
|
||||
db.user.update(user._id, {
|
||||
"upn": profile.username.toLowerCase(),
|
||||
"lastLogin": new Date(),
|
||||
"sub": profile.id,
|
||||
"active": true,
|
||||
"mail": profile.emails[0].value,
|
||||
//"jobTitle": jobTitle
|
||||
});
|
||||
return done(null, user);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//return done(null, profile);
|
||||
}));
|
||||
|
||||
|
||||
|
||||
module.exports.init = function(app, isSecure){
|
||||
|
||||
const cookieName = 'qmiconnect.sid';
|
||||
|
||||
var cookieConf = {
|
||||
maxAge: config.mongoDBSessionMaxAge * 1000,
|
||||
httpOnly: false,
|
||||
secure: isSecure,
|
||||
sameSite: isSecure? 'none' : undefined
|
||||
};
|
||||
|
||||
|
||||
console.log("--- SESSION", "Cookie Conf = ", cookieConf , "Cookie Name = " + cookieName);
|
||||
|
||||
|
||||
// set up session middleware
|
||||
if (config.useMongoDBSessionStore) {
|
||||
app.use(expressSession({
|
||||
name: 'qmiconnect.sid',
|
||||
secret: 'secret',
|
||||
cookie: cookieConf,
|
||||
store: sessionStore,
|
||||
resave: true,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
} else {
|
||||
app.use(expressSession({
|
||||
name: 'qmiconnect.sid',
|
||||
secret: 'keyboard cat',
|
||||
cookie: cookieConf,
|
||||
store: sessionStore,
|
||||
resave: true,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
// Initialize Passport! Also use passport.session() middleware, to support
|
||||
// persistent login sessions (recommended).
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
app.get('/sessioninfo', function(req, res){
|
||||
|
||||
if (req.session.oktaAccessToken && req.user ) {
|
||||
res.json({
|
||||
"oktaAccessToken": req.session.oktaAccessToken,
|
||||
"user": req.user
|
||||
});
|
||||
} else {
|
||||
res.status(401).json({});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.get('/login',
|
||||
function(req, res, next) {
|
||||
passport.authenticate('oidc',
|
||||
{
|
||||
response: res, // required
|
||||
resourceURL: config.resourceURL, // optional. Provide a value if you want to specify the resource.
|
||||
//customState: 'my_state', // optional. Provide a value if you want to provide custom state value.
|
||||
failureRedirect: '/error',
|
||||
session: true,
|
||||
//assignProperty: "weee",
|
||||
authInfo: true
|
||||
}
|
||||
)(req, res, next);
|
||||
},
|
||||
function(req, res) {
|
||||
res.redirect('/home');
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/auth/openid/return',
|
||||
function(req, res, next) {
|
||||
passport.authenticate('oidc', {
|
||||
response: res,
|
||||
failureRedirect: '/error' ,
|
||||
session: true,
|
||||
//assignProperty: "weee",
|
||||
authInfo: true
|
||||
}
|
||||
)(req, res, next);
|
||||
|
||||
},
|
||||
function(req, res) {
|
||||
console.log('Passport# We received a return from OKTA ');
|
||||
res.redirect('/provisions');
|
||||
}
|
||||
);
|
||||
|
||||
/*app.use('/auth/openid/return', passport.authenticate('oidc', { failureRedirect: '/error' }), (req, res) => {
|
||||
|
||||
console.log('Passport# We received a return from OKTA ');
|
||||
res.redirect('/provisions');
|
||||
});*/
|
||||
|
||||
app.get('/logout', function(req, res){
|
||||
req.session.destroy(function(err) {
|
||||
req.logOut();
|
||||
res.redirect("/home");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
async function isApiKeyAuthenticated(req) {
|
||||
//console.log("oktaToken", req.headers.oktatoken);
|
||||
//console.log("okta session token", req.session.oktaAccessToken);
|
||||
if (req.headers && req.headers.oktatoken !== undefined && req.session && req.headers.oktatoken === req.session.oktaAccessToken) {
|
||||
console.log("OKTA TOKEN PROVIDED and is GOOD!!!!");
|
||||
return true;
|
||||
}
|
||||
let key = req.query.apiKey || req.get('QMI-ApiKey');
|
||||
if ( key ) {
|
||||
var result = await db.apiKey.getOne({"apiKey": key});
|
||||
if ( result ) {
|
||||
req.user = result.user;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.ensureAuthenticatedDoLogin = async function(req, res, next) {
|
||||
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
|
||||
return next();
|
||||
}
|
||||
res.redirect('/login');
|
||||
};
|
||||
|
||||
module.exports.ensureAuthenticated = async function(req, res, next) {
|
||||
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
|
||||
return next();
|
||||
}
|
||||
res.status(401).send({"error": "Unauthorized"});
|
||||
};
|
||||
|
||||
module.exports.ensureAuthenticatedAndAdmin = async function(req, res, next) {
|
||||
if ( ( await isApiKeyAuthenticated(req) || req.isAuthenticated()) && (req.user.role === 'admin' || req.user.role === 'superadmin') ) {
|
||||
return next();
|
||||
}
|
||||
res.status(401).send({"error": "Unauthorized"});
|
||||
};
|
||||
|
||||
module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
|
||||
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
|
||||
var userId = (req.params.userId === 'me')? req.user._id : req.params.userId;
|
||||
if ( req.user._id == userId || req.user.role === 'admin' || req.user.role === 'superadmin' ) {
|
||||
return next();
|
||||
} else {
|
||||
return res.status(401).send("Error: Unauthorized");
|
||||
}
|
||||
}
|
||||
return res.status(401).send("Error: Unauthorized");
|
||||
};
|
||||
|
||||
module.exports.sessionStore = sessionStore;
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const passport = require('../passport');
|
||||
const passport = require('../passport-okta');
|
||||
|
||||
|
||||
/**
|
||||
@@ -12,6 +12,15 @@ const passport = require('../passport');
|
||||
* summary: Get all API keys
|
||||
* tags:
|
||||
* - admin
|
||||
* parameters:
|
||||
* - name: filter
|
||||
* in: query
|
||||
* required: false
|
||||
* type: object
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
@@ -20,7 +29,8 @@ const passport = require('../passport');
|
||||
*/
|
||||
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const result = await db.apiKey.get();
|
||||
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
|
||||
const result = await db.apiKey.get(filter);
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -42,19 +52,27 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* description:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: API KEY
|
||||
*/
|
||||
router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const user = await db.user.getById(req.params.userId);
|
||||
const user = await db.user.getById(req.params.userId);
|
||||
if ( !user ) {
|
||||
res.status(404).json({"err": "user not found"});
|
||||
}
|
||||
var body = {
|
||||
user: req.params.userId
|
||||
}
|
||||
var body = req.body;
|
||||
body.user = req.params.userId;
|
||||
const result = await db.apiKey.add(body);
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
@@ -64,10 +82,10 @@ router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, n
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /apikeys/{id}:
|
||||
* /apikeys/{id}/revoke:
|
||||
* put:
|
||||
* description: Deactivate API Key
|
||||
* summary: Deactivate API Key
|
||||
* description: Revoke API Key
|
||||
* summary: Revoke API Key
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
@@ -81,7 +99,7 @@ router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, n
|
||||
* 200:
|
||||
* description: API KEY
|
||||
*/
|
||||
router.put('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
router.put('/:id/revoke', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const result = await db.apiKey.update(req.params.id, {"isActive": false});
|
||||
return res.json(result);
|
||||
@@ -90,4 +108,32 @@ router.put('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next)
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /apikeys/{id}:
|
||||
* delete:
|
||||
* description: Delete API Key
|
||||
* summary: Delete API Key
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: API KEY
|
||||
*/
|
||||
router.delete('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const result = await db.apiKey.del(req.params.id);
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const passport = require('../passport');
|
||||
const passport = require('../passport-okta');
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const passport = require('../passport');
|
||||
const passport = require('../passport-okta');
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const passport = require('../passport');
|
||||
const passport = require('../passport-okta');
|
||||
|
||||
import { queues, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
@@ -23,15 +26,18 @@ const passport = require('../passport');
|
||||
* description: Notifications
|
||||
*/
|
||||
router.post('/updates', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const dateNow = new Date();
|
||||
const now = dateNow.toISOString();
|
||||
try {
|
||||
|
||||
let event = req.body;
|
||||
const event = req.body;
|
||||
let logEvent = event.logEvent || 'DivvyCloud';
|
||||
|
||||
|
||||
if ( event.cloudName === 'QMI Automation' ) {
|
||||
|
||||
console.log(`DivvyCloud (${now})# event received for subscription (${event.cloudName}) - provision (${event.provID}) -> new status (${event.instanceState})`);
|
||||
console.log(`${logEvent} (${now})# event received for subscription (${event.cloudName}) - provision (${event.provID}) -> new status (${event.instanceState})`);
|
||||
|
||||
if ( event.provID && event.provID !== 'None' ) {
|
||||
|
||||
@@ -39,54 +45,108 @@ router.post('/updates', passport.ensureAuthenticatedAndAdmin, async (req, res, n
|
||||
|
||||
if ( provision ) {
|
||||
|
||||
var type = "vms";
|
||||
if ( logEvent.indexOf("RDS")!== -1 ){
|
||||
type = "db";
|
||||
}
|
||||
|
||||
let id = provision._id.toString();
|
||||
|
||||
console.log(`DivvyCloud (${now})# provision (${id}) - scenario is (${provision.scenario} - v${provision.scenarioVersion})`);
|
||||
console.log(`${logEvent} (${now})# provision (${id}) - scenario is (${provision.scenario} - v${provision.scenarioVersion})`);
|
||||
|
||||
if ( provision.status === 'provisioned' ) {
|
||||
if ( provision.status === 'provisioned' || provision.status === 'error' ) {
|
||||
|
||||
if ( event.instanceState === 'Stopped' ) {
|
||||
|
||||
//Check active children provisions
|
||||
let children = await db.provision.get({ "parent": provision._id, "isDestroyed": false, "isDeleted": false });
|
||||
if (children.results.length > 0 ) {
|
||||
children.results.forEach(function(child) {
|
||||
|
||||
if ( child.scenario === 'azqmi-synapse') {
|
||||
queues[SYNAPSE_QUEUE].add("synapse_job", {
|
||||
provId: child._id,
|
||||
user: req.user,
|
||||
tasktype: 'pause'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ( provision.statusVms === 'Stopped' ) {
|
||||
console.log(`DivvyCloud (${now})# provision (${id}) - VMs were already Stopped!`);
|
||||
console.log(`${logEvent} (${now})# provision (${id}) - were already Stopped!`);
|
||||
} else {
|
||||
|
||||
let timeRunning = db.utils.getNewTimeRunning(provision);
|
||||
await db.provision.update(id, {"statusVms": "Stopped", "timeRunning": timeRunning, "stoppedFrom": new Date(), "pendingNextAction": undefined});
|
||||
console.log(`DivvyCloud (${now})# provision (${id}) - VMs changed to Stopped!`);
|
||||
let patch = {
|
||||
"statusVms": "Stopped",
|
||||
"timeRunning": timeRunning,
|
||||
"stoppedFrom": dateNow,
|
||||
"pendingNextAction": null
|
||||
};
|
||||
|
||||
if ( provision.schedule && !provision.schedule.is24x7 ) {
|
||||
patch["endDateOnSchedule"] = dateNow;
|
||||
|
||||
//This is temporary, only to make sure there is value
|
||||
if ( !provision["startDateOnSchedule"] ) {
|
||||
patch["startDateOnSchedule"] = dateNow;
|
||||
}
|
||||
}
|
||||
|
||||
await db.provision.update(id, patch);
|
||||
console.log(`${logEvent} (${now})# provision (${id}) - changed to Stopped!`);
|
||||
db.event.add({ provision: provision._id, type: `${type}.stop`, message: `[Divvy] TotalTimeRunning: ${timeRunning} mins` });
|
||||
}
|
||||
|
||||
} else if ( event.instanceState === 'Running' ) {
|
||||
|
||||
if ( provision.statusVms === 'Running' ) {
|
||||
console.log(`DivvyCloud (${now})# provision (${id}) - VMs were already Running!`);
|
||||
console.log(`${logEvent} (${now})# provision (${id}) - were already Running!`);
|
||||
} else {
|
||||
await db.provision.update(id, {"statusVms": "Running", "runningFrom": new Date(), "pendingNextAction": undefined});
|
||||
console.log(`DivvyCloud (${now})# provision (${id}) - VMs changed to Running!`);
|
||||
let patch = {
|
||||
"statusVms": "Running",
|
||||
"runningFrom": dateNow,
|
||||
"pendingNextAction": null
|
||||
};
|
||||
|
||||
// This is temporary, only to make sure there are values soon
|
||||
if ( provision.schedule && !provision.schedule.is24x7 ) {
|
||||
if ( !provision["startDateOnSchedule"] ) {
|
||||
patch["startDateOnSchedule"] = dateNow;
|
||||
patch["endDateOnSchedule"] = dateNow;
|
||||
}
|
||||
}
|
||||
|
||||
await db.provision.update(id, patch);
|
||||
console.log(`${logEvent} (${now})# provision (${id}) - changed to Running!`);
|
||||
|
||||
db.event.add({ provision: provision._id, type: `${type}.start`, message: `[Divvy] TotalTimeRunning: ${provision.timeRunning} mins` });
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log(`DivvyCloud (${now})# provision (${event.provID}) - Scenario not yet 'provisioned'`);
|
||||
console.log(`${logEvent} (${now})# provision (${event.provID}) - Scenario not yet 'provisioned'`);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log(`DivvyCloud (${now})# provision (${event.provID}) - Provision not found.`);
|
||||
console.log(`${logEvent} (${now})# provision (${event.provID}) - Provision not found.`);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log(`DivvyCloud (${now})# 'provID' attribute is missing.`);
|
||||
console.log(`${logEvent} (${now})# 'provID' attribute is missing.`);
|
||||
}
|
||||
|
||||
} else {
|
||||
//console.log(`DivvyCloud (${now}): event received for subscription (${event.cloudName}) --> won't be processed`);
|
||||
//console.log(`${logEvent} (${now}): event received for subscription (${event.cloudName}) --> won't be processed`);
|
||||
}
|
||||
|
||||
return res.json(req.body);
|
||||
|
||||
} catch (error) {
|
||||
console.log(`DivvyCloud (${now})# error!!!!`, error);
|
||||
console.log(`${logEvent} (${now})# error!!!!`, error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,7 +1,8 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const passport = require('../passport');
|
||||
const passport = require('../passport-okta');
|
||||
const sendEmail = require('qmi-cloud-common/send-email');
|
||||
|
||||
|
||||
/**
|
||||
@@ -14,6 +15,15 @@ const passport = require('../passport');
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
* - name: page
|
||||
* in: query
|
||||
* required: false
|
||||
* type: integer
|
||||
* - name: size
|
||||
* in: query
|
||||
* required: false
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Notifications
|
||||
@@ -34,4 +44,27 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /notifications/testsendemail:
|
||||
* post:
|
||||
* description: Test email service
|
||||
* summary: Test email service
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Notifications
|
||||
*/
|
||||
router.post('/testsendemail', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const result = await sendEmail._doSend(req.user.upn, "QMI - Test email", "Hi! This is a test email from QMI Cloud.");
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,17 +1,19 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const azurecli = require('qmi-cloud-common/azurecli');
|
||||
const passport = require('../passport');
|
||||
const passport = require('../passport-okta');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
|
||||
import { queues, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /provisions:
|
||||
* get:
|
||||
* description: Get all Provisions
|
||||
* summary: Get all Provisions)
|
||||
* summary: Get all Provisions
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
@@ -63,7 +65,8 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
|
||||
const result = await db.provision.getPage(filter, page, req.query.populates, req.query.select);
|
||||
|
||||
if (result.next){
|
||||
result.nextUrl = new URL(req.protocol + '://' + req.get('Host') + req.baseUrl);
|
||||
|
||||
result.nextUrl = new URL('https://' + req.hostname + req.baseUrl);
|
||||
if ( req.query.filter ) {
|
||||
result.nextUrl.searchParams.append("filter", req.query.filter);
|
||||
}
|
||||
@@ -85,10 +88,121 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /provisions/vmImagesLogs:
|
||||
* /provisions/copysnapshots:
|
||||
* post:
|
||||
* description: Copy snapshots into regions
|
||||
* summary: Copy snapshots into regions
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
* - name: snap
|
||||
* in: query
|
||||
* required: true
|
||||
* type: string
|
||||
* - name: regions
|
||||
* in: query
|
||||
* required: true
|
||||
* type: string
|
||||
* - name: rg
|
||||
* in: query
|
||||
* required: true
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 404:
|
||||
* description: Not found
|
||||
*
|
||||
*/
|
||||
router.post('/copysnapshots', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
|
||||
queues[SYNAPSE_QUEUE].add("synapse_job", {
|
||||
snapName: req.query.snap,
|
||||
rGroup: req.query.rg,
|
||||
regions: req.query.regions,
|
||||
tasktype: "copy-snapshots"
|
||||
});
|
||||
|
||||
return res.json({"msg": "Snapshots are being craeted", "success": true});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /provisions/copysnapshots:
|
||||
* get:
|
||||
* description: Get all vmImages
|
||||
* summary: Get all vmImages
|
||||
* description: Get Status of copy snapshots into regions
|
||||
* summary: Get Status of copy snapshots into regions
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
* - name: snap
|
||||
* in: query
|
||||
* required: true
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 404:
|
||||
* description: Not found
|
||||
*
|
||||
*/
|
||||
router.get('/copysnapshots', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
|
||||
queues[SYNAPSE_QUEUE].add("synapse_job", {
|
||||
snapName: req.query.snap,
|
||||
tasktype: "check-copy-snapshots"
|
||||
});
|
||||
|
||||
return res.json({"msg": "Snapshots are being checked", "success": true});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /provisions/snapshotslogs:
|
||||
* get:
|
||||
* description: Get logs for snapshots
|
||||
* summary: Get logs for snapshots
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* 404:
|
||||
* description: Not found
|
||||
*
|
||||
*/
|
||||
router.get('/snapshotslogs', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
|
||||
return res.sendFile("/logs/general/qmi-snapshots.log");
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /provisions/optionssLogs:
|
||||
* get:
|
||||
* description: Get all optionss
|
||||
* summary: Get all optionss
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
@@ -112,25 +226,25 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
|
||||
* 200:
|
||||
* description: JSON Array
|
||||
*/
|
||||
router.get('/vmImagesLogs', passport.ensureAuthenticated, async (req, res, next) => {
|
||||
router.get('/optionssLogs', passport.ensureAuthenticated, async (req, res, next) => {
|
||||
try {
|
||||
|
||||
let filter = req.query.filter? JSON.parse(req.query.filter) : {};
|
||||
filter.isDeleted = {"$exists": true};
|
||||
let select = "_id vmImage"
|
||||
let select = "_id options"
|
||||
let populates = "[]";
|
||||
|
||||
const result = await db.provision.get(filter, select, req.query.skip, req.query.limit, populates);
|
||||
var final = [];
|
||||
result.results.forEach(p=>{
|
||||
for( let key in p.vmImage ) {
|
||||
for( let key in p.options ) {
|
||||
var o = {
|
||||
"_id": p._id.toString(),
|
||||
"vmIndex": key,
|
||||
"vmType": p.vmImage[key].vmType? p.vmImage[key].vmType : null,
|
||||
"diskSizeGb": p.vmImage[key].diskSizeGb? p.vmImage[key].diskSizeGb : null,
|
||||
"versionProduct": p.vmImage[key].version? p.vmImage[key].version.name : null,
|
||||
"versionImage": p.vmImage[key].version? p.vmImage[key].version.image : null
|
||||
"vmType": p.options[key].vmType? p.options[key].vmType : null,
|
||||
"diskSizeGb": p.options[key].diskSizeGb? p.options[key].diskSizeGb : null,
|
||||
"versionProduct": p.options[key].selected? p.options[key].selected.name : null,
|
||||
"versionImage": p.options[key].selected? p.options[key].selected.value : null
|
||||
}
|
||||
final.push(o);
|
||||
}
|
||||
@@ -140,7 +254,7 @@ router.get('/vmImagesLogs', passport.ensureAuthenticated, async (req, res, next)
|
||||
count: result.count
|
||||
};
|
||||
if ( result.nextSkip && result.nextLimit ) {
|
||||
out.nextUrl = new URL(req.protocol + '://' + req.get('Host') + req.baseUrl);
|
||||
out.nextUrl = new URL('https://' + req.hostname + req.baseUrl);
|
||||
if ( req.query.filter ) {
|
||||
out.nextUrl.searchParams.append("filter", req.query.filter);
|
||||
}
|
||||
@@ -338,7 +452,7 @@ router.post('/:id/updatetagsvms', passport.ensureAuthenticatedAndAdmin, async (r
|
||||
if (!provision) {
|
||||
return res.status(404).json({"msg": "Not found provision with id: "+req.params.id, "success": false});
|
||||
}
|
||||
var result = await azurecli.updateVmsTags(provision._id, tagsEdit);
|
||||
var result = await cli.updateVmsTags(provision._id, tagsEdit);
|
||||
return res.json({"msg": "Tags are being updated", "result": result, "success": true});
|
||||
|
||||
} catch (error) {
|
||||
@@ -348,4 +462,5 @@ router.post('/:id/updatetagsvms', passport.ensureAuthenticatedAndAdmin, async (r
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const passport = require('../passport');
|
||||
const passport = require('../passport-okta');
|
||||
|
||||
|
||||
router.get('/all', passport.ensureAuthenticated, async (req, res, next) => {
|
||||
@@ -203,7 +203,7 @@ router.delete('/vmtypes/:id', passport.ensureAuthenticatedAndAdmin, async (req,
|
||||
* type: string
|
||||
* index:
|
||||
* type: string
|
||||
* versions:
|
||||
* values:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
|
||||
227
server/routes/api-stats.js
Normal file
227
server/routes/api-stats.js
Normal file
@@ -0,0 +1,227 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const moment = require('moment');
|
||||
const azurecli = require('qmi-cloud-common/azurecli');
|
||||
|
||||
const CACHED_PERIOD = 30; //minutes
|
||||
var cachedTime;
|
||||
var cachedTimeVms;
|
||||
var cachedStats;
|
||||
var cachedVms;
|
||||
/**
|
||||
* @swagger
|
||||
* /stats:
|
||||
* get:
|
||||
* description: Get overall stats
|
||||
* summary: Get overall stats
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Stats
|
||||
* 404:
|
||||
* description: Not found
|
||||
*
|
||||
*/
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
var now = new Date().getTime();
|
||||
var nowMinus5mins = now - CACHED_PERIOD*60*1000;
|
||||
|
||||
if ( (!req.query.disablecache || req.query.disablecache === 'no') && cachedStats && cachedTime && cachedTime > nowMinus5mins ) {
|
||||
console.log("APIStats# Stats: return cached value");
|
||||
cachedTime = now;
|
||||
return res.json(cachedStats);
|
||||
|
||||
} else {
|
||||
console.log("APIStats# Stats: new value");
|
||||
let filterActiveP = { "isDestroyed": false, "status": "provisioned" };
|
||||
let filterPRunning = { "isDestroyed": false, "status": "provisioned", "statusVms" : "Running" };
|
||||
let initCurrentMonth = moment().startOf('month');
|
||||
let initLastMonth = moment(initCurrentMonth).add(-1, 'months');
|
||||
let today = moment();
|
||||
let todayLastMonth = moment().add(-1, "months");
|
||||
let filterTotalPCurrentMonth = { "status": "provisioned", "created": {
|
||||
$gte: initCurrentMonth.toISOString(),
|
||||
$lt: today.toISOString()
|
||||
} };
|
||||
let filterTotalPLastMonth = { "status": "provisioned", "created": {
|
||||
$gte: initLastMonth.toISOString(),
|
||||
$lt: todayLastMonth.toISOString()
|
||||
} };
|
||||
|
||||
//Counts
|
||||
let totalActiveP = await db.provision.count(filterActiveP);
|
||||
let totalCPRunning = await db.provision.count(filterPRunning);
|
||||
|
||||
let totalPCurrentmonth = await db.provision.count(filterTotalPCurrentMonth);
|
||||
let totalPLastmonth = await db.provision.count(filterTotalPLastMonth);
|
||||
|
||||
let totalUsers = await db.user.count({});
|
||||
let totalAuthUsers = await db.user.count({
|
||||
"lastLogin": {$gte: moment().add(-12, "hours").toISOString()}
|
||||
});
|
||||
let totalScenarios = await db.scenario.count({"isDisabled":false, "isAdminOnly": false});
|
||||
|
||||
cachedStats = {
|
||||
provisions: {
|
||||
active: totalActiveP,
|
||||
running: totalCPRunning,
|
||||
totalCurrentMonthPeriod: totalPCurrentmonth,
|
||||
totalLastMonthPerdiod:totalPLastmonth
|
||||
},
|
||||
users: {
|
||||
total: totalUsers,
|
||||
activeNow: totalAuthUsers,
|
||||
active7days: await db.user.count({
|
||||
"lastLogin": {$gte: moment().add(-7, "days").toISOString()}
|
||||
}),
|
||||
active30days: await db.user.count({
|
||||
"lastLogin": {$gte: moment().add(-30, "days").toISOString()}
|
||||
})
|
||||
},
|
||||
scenarios: {
|
||||
total: totalScenarios
|
||||
}
|
||||
};
|
||||
cachedTime = now;
|
||||
|
||||
return res.json(cachedStats);
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
function appendResult(list, output ){
|
||||
|
||||
list.forEach(v=>{
|
||||
if ( v.tags && v.tags["QMI_user"] ) {
|
||||
if (v.location) {
|
||||
if ( !output.locations[v.location] ) {
|
||||
output.locations[v.location] = 0;
|
||||
}
|
||||
output.locations[v.location] += 1;
|
||||
}
|
||||
|
||||
if (v.storageProfile && v.storageProfile.osDisk && v.storageProfile.osDisk.osType) {
|
||||
if ( !output.types[v.storageProfile.osDisk.osType] ) {
|
||||
output.types[v.storageProfile.osDisk.osType] = 0;
|
||||
}
|
||||
output.types[v.storageProfile.osDisk.osType] += 1;
|
||||
}
|
||||
|
||||
output.out.push(v);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /stats/vms:
|
||||
* get:
|
||||
* description: List azure vms
|
||||
* summary: List azure vms
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Stats
|
||||
* 404:
|
||||
* description: Not found
|
||||
*
|
||||
*/
|
||||
router.get('/vms', async (req, res, next) => {
|
||||
|
||||
try {
|
||||
|
||||
var now = new Date().getTime();
|
||||
var nowMinus5mins = now - CACHED_PERIOD*60*1000;
|
||||
|
||||
if ( (!req.query.disablecache || req.query.disablecache === 'no') && cachedVms && cachedTimeVms && cachedTimeVms > nowMinus5mins ) {
|
||||
console.log("APIStats# VMs: return cached value");
|
||||
cachedTimeVms = now;
|
||||
return res.json(cachedVms);
|
||||
|
||||
} else {
|
||||
console.log("APIStats# VMs: new value");
|
||||
var output = {
|
||||
out : [],
|
||||
locations: {
|
||||
"eastus": 0,
|
||||
"westeurope": 0,
|
||||
"southeastasia": 0
|
||||
},
|
||||
types: {
|
||||
"Windows": 0,
|
||||
"Linux": 0
|
||||
}
|
||||
}
|
||||
|
||||
var result = await azurecli.getAllVms();
|
||||
output = appendResult(result, output);
|
||||
|
||||
if ( result.nextLink ) {
|
||||
console.log("There is a second page");
|
||||
|
||||
result = await azurecli.getAllVmsNext(result.nextLink);
|
||||
output = appendResult(result, output);
|
||||
|
||||
if ( result.nextLink ) {
|
||||
console.log("There is a third page");
|
||||
|
||||
result = await azurecli.getAllVmsNext(result.nextLink);
|
||||
output = appendResult(result, output);
|
||||
|
||||
if ( result.nextLink ) {
|
||||
console.log("There is a forth page");
|
||||
|
||||
result = await azurecli.getAllVmsNext(result.nextLink);
|
||||
output = appendResult(result, output);
|
||||
|
||||
if ( result.nextLink ) {
|
||||
console.log("There is a fifth page");
|
||||
|
||||
result = await azurecli.getAllVmsNext(result.nextLink);
|
||||
output = appendResult(result, output);
|
||||
|
||||
if ( result.nextLink ) {
|
||||
console.log("There is a sixth page");
|
||||
|
||||
result = await azurecli.getAllVmsNext(result.nextLink);
|
||||
output = appendResult(result, output);
|
||||
|
||||
if ( result.nextLink ) {
|
||||
console.log("There is a seventh page");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cachedTimeVms = now;
|
||||
cachedVms = {
|
||||
total: output.out.length,
|
||||
locations: output.locations,
|
||||
types: output.types
|
||||
};
|
||||
|
||||
return res.json(cachedVms);
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
446
server/routes/api-training.js
Normal file
446
server/routes/api-training.js
Normal file
@@ -0,0 +1,446 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('qmi-cloud-common/mongo');
|
||||
const passport = require('../passport-okta');
|
||||
const cloudshare = require('../training/cloudshare');
|
||||
const qa = require('../training/automations');
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/sessions:
|
||||
* get:
|
||||
* description: Get all training sessions (admin)
|
||||
* summary: Get all training sessions (admin)
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingTemplate
|
||||
*/
|
||||
router.get('/sessions', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
|
||||
const result = await db.trainingSession.get(filter);
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/templates:
|
||||
* get:
|
||||
* description: Get all training templates
|
||||
* summary: Get all training templates
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingTemplate
|
||||
*/
|
||||
router.get('/templates', passport.ensureAuthenticated, async (req, res, next) => {
|
||||
try {
|
||||
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
|
||||
const result = await db.trainingTemplate.get(filter);
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/session/{id}:
|
||||
* get:
|
||||
* description: Get training session by ID
|
||||
* summary: Get training session by ID
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingTemplate
|
||||
*/
|
||||
router.get('/session/:id', async (req, res, next) => {
|
||||
try {
|
||||
const result = await db.trainingSession.get({"_id":req.params.id}, 'passwd -user description status created updated studentEmailFilter');
|
||||
if (!result || !result.results || result.results.length === 0 ){
|
||||
return res.status(404).json({"msg": "Not found"});
|
||||
}
|
||||
return res.json(result.results[0]);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/session/{id}:
|
||||
* post:
|
||||
* description: Post new student for session by ID
|
||||
* summary: Post new student for session by ID
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingTemplate
|
||||
*/
|
||||
router.post('/session/:id', async (req, res, next) => {
|
||||
try {
|
||||
const session = await db.trainingSession.getById(req.params.id);
|
||||
if (!session){
|
||||
return res.status(404).json({"msg": "Not found"});
|
||||
}
|
||||
|
||||
let student = await db.trainingStudent.getOne({"email": req.body.email, "session": session._id});
|
||||
let isNew = false;
|
||||
if ( !student ) {
|
||||
let data = req.body;
|
||||
data.session = session._id;
|
||||
student = await db.trainingStudent.add(data);
|
||||
isNew = true;
|
||||
}
|
||||
|
||||
let result1, result2;
|
||||
if ( session.template.cloudshare && session.cloudshareClass ) {
|
||||
result1 = await cloudshare.addStudentToClass(session, student.email);
|
||||
}
|
||||
if ( session.template.needQcsAutomation && session.qaUrl && session.qaToken ) {
|
||||
result2 = await qa.runQlikAutomation(session, student.email);
|
||||
}
|
||||
|
||||
if (result1 && result1.error || result2 && result2.error) {
|
||||
return res.status(500).json({"msg": "Something went wrong when registering studeent", err: result1.error? result1.error : result2.error});
|
||||
} else {
|
||||
student = await db.trainingStudent.update(student._id, {"status": "sent", "created": new Date()});
|
||||
if ( isNew ) {
|
||||
db.trainingSession.update({ _id: session._id }, { $inc: { studentsCount: 1 } });
|
||||
}
|
||||
return res.json(student);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/session/{id}:
|
||||
* delete:
|
||||
* description: Delete session by ID
|
||||
* summary: Delete session by ID
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* parameters:
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingTemplate
|
||||
*/
|
||||
router.delete('/session/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const result = await db.trainingSession.update(req.params.id, {status: "terminated"});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/templates:
|
||||
* post:
|
||||
* description: Create new training template
|
||||
* summary: Create new training template
|
||||
* tags:
|
||||
* - admin
|
||||
* produces:
|
||||
* - application/json
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* description:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingTemplate
|
||||
*/
|
||||
router.post('/templates', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
|
||||
try {
|
||||
let data = req.body;
|
||||
const result = await db.trainingTemplate.add(data);
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/{userId}/sessions:
|
||||
* post:
|
||||
* description: Add new training session
|
||||
* summary: Add new training session
|
||||
* parameters:
|
||||
* - name: userId
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingSession
|
||||
*/
|
||||
router.post('/:userId/sessions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
|
||||
let data = req.body;
|
||||
data.user = userId;
|
||||
let session = await db.trainingSession.add(data);
|
||||
session = await db.trainingSession.getById(session._id);
|
||||
|
||||
let mainAutomation = await qa.getAutomation(session, 'main');
|
||||
if (!mainAutomation) {
|
||||
let result = await qa.createAutomations(session);
|
||||
mainAutomation = result.main;
|
||||
}
|
||||
session = await db.trainingSession.update(session._id, {qaToken: mainAutomation.executionToken, qaUrl: `https://${session.qcsTenantHost}/api/v1/automations/${mainAutomation.id}/actions/execute`})
|
||||
|
||||
return res.json(session);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/{userId}/sessions:
|
||||
* get:
|
||||
* description: Get all training session by user
|
||||
* summary: Get all training session by user
|
||||
* parameters:
|
||||
* - name: userId
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingSession
|
||||
*/
|
||||
router.get('/:userId/sessions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
|
||||
const result = await db.trainingSession.get({user: userId, status: {"$ne": "terminated"}});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/{userId}/sessions/{id}:
|
||||
* get:
|
||||
* description: Get training session by Id for a user
|
||||
* summary: Get training session by Id for a user
|
||||
* parameters:
|
||||
* - name: userId
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingSession
|
||||
*/
|
||||
router.get('/:userId/sessions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
|
||||
const id = req.params.id;
|
||||
const result = await db.trainingSession.getOne({user: userId, _id: id});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/{userId}/sessions/{id}/automation:
|
||||
* get:
|
||||
* description: Test if automation exists in tenant
|
||||
* summary: Test if automation exists in tenant
|
||||
* parameters:
|
||||
* - name: userId
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingSession
|
||||
*/
|
||||
router.get('/:userId/sessions/:id/automation', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
let session = await db.trainingSession.getById(id);
|
||||
|
||||
let automation = await qa.getAutomation(session);
|
||||
if (automation) {
|
||||
session = await db.trainingSession.update(session._id, {qaToken: automation.executionToken, qaUrl: `https://${session.qcsTenantHost}/api/v1/automations/${automation.id}/actions/execute`})
|
||||
}
|
||||
return res.json(session);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/session/{id}/students:
|
||||
* get:
|
||||
* description: Get studetns for a session
|
||||
* summary: Get studetns for a session
|
||||
* parameters:
|
||||
* - name: userId
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingSession
|
||||
*/
|
||||
router.get('/session/:id/students', passport.ensureAuthenticated, async (req, res, next) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
const result = await db.trainingStudent.get({session: id});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/{userId}/sessions/{id}:
|
||||
* put:
|
||||
* description: Update training session
|
||||
* summary: Update training session
|
||||
* parameters:
|
||||
* - name: userId
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingSession
|
||||
*/
|
||||
router.put('/:userId/sessions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const result = await db.trainingSession.update(req.params.id, req.body);
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /training/{userId}/sessions/{id}:
|
||||
* put:
|
||||
* description: Update training session
|
||||
* summary: Update training session
|
||||
* parameters:
|
||||
* - name: userId
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* - name: id
|
||||
* in: path
|
||||
* type: string
|
||||
* required: true
|
||||
* produces:
|
||||
* - application/json
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TrainingSession
|
||||
*/
|
||||
router.delete('/:userId/sessions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const result = await db.trainingSession.update(req.params.id, {status: "terminated"});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
File diff suppressed because it is too large
Load Diff
269
server/routes/qsProxy.js
Normal file
269
server/routes/qsProxy.js
Normal file
@@ -0,0 +1,269 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
const jsonwebtoken = require("jsonwebtoken");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const cookie = require("cookie");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const axios = require("axios");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const ws = require("ws");
|
||||
const WebSocketServer = ws.WebSocketServer;
|
||||
const WebSocket = ws.WebSocket;
|
||||
const passport = require('../passport-okta');
|
||||
|
||||
|
||||
// This is the frontend application uri used for responding to requests.
|
||||
//const frontendUri = "https://outstanding-desert-gatsby.glitch.me";
|
||||
|
||||
|
||||
const privKey = fs.readFileSync(path.resolve(__dirname, '..', 'certs', 'privateKey.pem'));
|
||||
const TENANT_DOMAIN = process.env["TENANT_DOMAIN"] || "innovation.us.qlikcloud.com";
|
||||
const JWT_KEYID = process.env["JWT_KEYID"] || "8889be0d-6cf0-44eb-9fef-24bbc83712c5";
|
||||
const JWT_ISSUER = process.env["JWT_ISSUER"] || TENANT_DOMAIN;
|
||||
const JWT_USER_GROUPS = ["AnonJWTGroup"];
|
||||
|
||||
console.log("QSProxy# - TENANT_DOMAIN", TENANT_DOMAIN);
|
||||
|
||||
const auth = {
|
||||
generateToken: function (user) {
|
||||
// kid and issuer have to match with the IDP config and the audience has to be qlik.api/jwt-login-session
|
||||
|
||||
const signingOptions = {
|
||||
keyid: JWT_KEYID,
|
||||
algorithm: "RS256",
|
||||
issuer: JWT_ISSUER,
|
||||
expiresIn: "60m",
|
||||
notBefore: "0s",
|
||||
audience: "qlik.api/login/jwt-session",
|
||||
};
|
||||
|
||||
// These are the claims that will be accepted and mapped anything else will be ignored. sub, subtype and name are mandatory.
|
||||
const uuid = uuidv4();
|
||||
userEmail = user.mail;
|
||||
const payload = {
|
||||
jti: uuid,
|
||||
sub: `az/${user.sub}`,
|
||||
subType: "user",
|
||||
name: user.displayName,
|
||||
email: userEmail,
|
||||
email_verified: true,
|
||||
groups: JWT_USER_GROUPS,
|
||||
};
|
||||
|
||||
const token = jsonwebtoken.sign(payload, privKey, signingOptions);
|
||||
return token;
|
||||
},
|
||||
getQlikSessionCookie: async function (token) {
|
||||
|
||||
try {
|
||||
const resp = await axios(`https://${TENANT_DOMAIN}/login/jwt-session`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (resp.status === 200) {
|
||||
console.log("QSProxy# - login/jwt-session", resp.headers['set-cookie'])
|
||||
return resp.headers['set-cookie']
|
||||
.map((e) => {
|
||||
return e.split(";")[0];
|
||||
})
|
||||
.join(";");
|
||||
}
|
||||
return "";
|
||||
} catch (e) {
|
||||
console.log("QSProxy# - ERRRR", e);
|
||||
return "";
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
const getStoreData = function (sidParsed) {
|
||||
const store = passport.sessionStore;
|
||||
return new Promise((resolve) => {
|
||||
store.get(sidParsed, (err, session) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
return resolve(session);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
router.get("/*.js", async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// fetch resource from qlik using a redirect instead of proxy
|
||||
// This endpoint is necessary when your web application uses the capability API.
|
||||
router.get("/resources/*", async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
router.get('/assets/*', async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Issues the necessary pre-flight request to make sure the browser
|
||||
// knows how to work with the web application.
|
||||
router.options("/*", async (req, res) => {
|
||||
setCors(res);
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
function setCors(res) {
|
||||
//res.set("Access-Control-Allow-Origin", "http://localhost:3000");
|
||||
//res.set("Access-Control-Allow-Methods", "GET, OPTIONS");
|
||||
//res.set("Access-Control-Allow-Headers", "Content-Type, x-proxy-session-id");
|
||||
//res.set("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
|
||||
async function newSession(req){
|
||||
console.log("QSProxy# - New cookie request for user", req.user.mail);
|
||||
const jwtToken = auth.generateToken(req.user);
|
||||
const qlikSession = await auth.getQlikSessionCookie(jwtToken);
|
||||
return encodeURIComponent(qlikSession);
|
||||
}
|
||||
|
||||
// Intercepts a request to one of Qlik's REST APIs and proxies the request to
|
||||
// Qlik Cloud.
|
||||
router.get("/api/v1/*", passport.ensureAuthenticated, async (req, res) => {
|
||||
|
||||
setCors(res);
|
||||
|
||||
var session = req.session;
|
||||
|
||||
var reqHeaders = {};
|
||||
try {
|
||||
if (session && session.id && session.qlikSession) {
|
||||
console.log("QSProxy# - COOKIE FROM session", session.qlikSession);
|
||||
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
|
||||
} else {
|
||||
|
||||
const newS = await newSession(req);
|
||||
session.qlikSession = newS;
|
||||
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("QSProxy# - qlikSession", reqHeaders.cookie);
|
||||
const { status, data } = await axios(`https://${TENANT_DOMAIN}${req.path}`, {
|
||||
headers: reqHeaders,
|
||||
});
|
||||
res.status(status).end(data);
|
||||
|
||||
} catch (e2) {
|
||||
console.log("QSProxy# Error: QlikSession expired, requesting a new one.");
|
||||
let newS = await newSession(req);
|
||||
session.qlikSession = newS;
|
||||
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
|
||||
let { status, data } = await axios(`https://${TENANT_DOMAIN}${req.path}`, {
|
||||
headers: reqHeaders,
|
||||
});
|
||||
res.status(status).end(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch (e) {
|
||||
res.status(500).end("Error obtaining qlik session");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
router.get('/qlik-embed-iframe/*', async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
function init (server) {
|
||||
|
||||
// Websocket section for intercepting websocket requests from the
|
||||
// frontend application. When the front end application communicates
|
||||
// communicates with the backend using websockets, this set of
|
||||
// functions will be invoked.
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
wss.on("connection", async function connection(ws, req) {
|
||||
let isOpened = false;
|
||||
// WebSockets do not have access to session information.
|
||||
// To get the session you need to parse the 1st-party cookie.
|
||||
// This will give you access to the Qlik Cloud cookie in order
|
||||
// to proxy requests.
|
||||
const cookieString = req.headers.cookie;
|
||||
|
||||
let qlikCookie = "";
|
||||
if (cookieString) {
|
||||
const cookieParsed = cookie.parse(cookieString);
|
||||
const appCookie = cookieParsed["connect.sid"];
|
||||
if (appCookie) {
|
||||
const sidParsed = cookieParser.signedCookie(appCookie, "secret");
|
||||
var session = await getStoreData(sidParsed);
|
||||
|
||||
qlikCookie = decodeURIComponent(session.qlikSession);
|
||||
}
|
||||
}
|
||||
|
||||
const appId = req.url.match("/app/(.*)\\?")[1];
|
||||
|
||||
if (!qlikCookie){
|
||||
console.log("QSProxy# - Error in Websocket: NO qlikCookie!");
|
||||
return;
|
||||
}
|
||||
|
||||
const matchCookie = qlikCookie.match("_csrfToken=(.*);");
|
||||
|
||||
if (!matchCookie) {
|
||||
console.log("QSProxy# - Error in Websocket: cant find _csrfToken= in qlikCookie");
|
||||
return;
|
||||
}
|
||||
|
||||
const csrfToken = matchCookie[1];
|
||||
|
||||
var wsConnUrl = `wss://${TENANT_DOMAIN}/app/${appId}?qlik-csrf-token=${csrfToken}`;
|
||||
const qlikWebSocket = new WebSocket(
|
||||
wsConnUrl,
|
||||
{
|
||||
headers: {
|
||||
cookie: qlikCookie,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
qlikWebSocket.on("error", console.error);
|
||||
const openPromise = new Promise((resolve) => {
|
||||
qlikWebSocket.on("open", function open() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
ws.on("message", async function message(data) {
|
||||
if (!isOpened) {
|
||||
await openPromise;
|
||||
isOpened = true;
|
||||
}
|
||||
|
||||
qlikWebSocket.send(data.toString());
|
||||
});
|
||||
|
||||
qlikWebSocket.on("message", function message(data) {
|
||||
ws.send(data.toString());
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports.router = router;
|
||||
module.exports.init = init;
|
||||
|
||||
128
server/server.js
128
server/server.js
@@ -2,7 +2,7 @@ const url = require("url");
|
||||
const express = require("express");
|
||||
|
||||
import Arena from 'bull-arena';
|
||||
import { TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from 'qmi-cloud-common/queues';
|
||||
import { TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
|
||||
|
||||
const app = express();
|
||||
const routesApiScenarios = require('./routes/api-scenarios');
|
||||
@@ -11,8 +11,10 @@ const routesApiProvisions = require('./routes/api-provisions');
|
||||
const routesApiDestroyProvisions = require('./routes/api-destroyprovisions');
|
||||
const routesApiNotifications = require('./routes/api-notifications');
|
||||
const routesApiDivvy = require('./routes/api-divvy');
|
||||
const routesApiDeployOpts = require('./routes/api-deployopts')
|
||||
const routesApiApikeys = require('./routes/api-apikeys')
|
||||
const routesApiDeployOpts = require('./routes/api-deployopts');
|
||||
const routesApiApikeys = require('./routes/api-apikeys');
|
||||
const routesApiStats = require('./routes/api-stats');
|
||||
const routesApiTraining = require('./routes/api-training');
|
||||
const swaggerUi = require('swagger-ui-express');
|
||||
const swaggerJsdoc = require('swagger-jsdoc');
|
||||
const cookieParser = require('cookie-parser');
|
||||
@@ -21,7 +23,16 @@ const https = require('https');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const passport = require('./passport');
|
||||
const passport = require('./passport-okta');
|
||||
|
||||
const qsProxy = require("./routes/qsProxy");
|
||||
|
||||
const BACKEND_LOGS_URL = process.env.BACKEND_LOGS_URL;
|
||||
const QMI_MONGO_URL = process.env.QMI_MONGO_URL;
|
||||
|
||||
const IS_SECURE = process.env.CERT_PFX_PASSWORD && process.env.CERT_PFX_FILENAME;
|
||||
|
||||
|
||||
|
||||
function _getRedisConfig(redisUrl) {
|
||||
const redisConfig = url.parse(redisUrl);
|
||||
@@ -51,6 +62,16 @@ app.use('/arena', Arena(
|
||||
name: TF_DESTROY_QUEUE,
|
||||
hostId: 'Worker',
|
||||
redis: _getRedisConfig(process.env.REDIS_URL)
|
||||
},
|
||||
{
|
||||
name: STOP_CONTAINER_QUEUE,
|
||||
hostId: 'Worker',
|
||||
redis: _getRedisConfig(process.env.REDIS_URL)
|
||||
},
|
||||
{
|
||||
name: SYNAPSE_QUEUE,
|
||||
hostId: 'Worker',
|
||||
redis: _getRedisConfig(process.env.REDIS_URL)
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -75,7 +96,7 @@ app.use(bodyParser.json())
|
||||
|
||||
app.use('/',express.static(__dirname + '/../dist/qmi-cloud'));
|
||||
|
||||
passport.init(app);
|
||||
passport.init(app, IS_SECURE? true : false);
|
||||
|
||||
app.use("/api/v1/scenarios", routesApiScenarios);
|
||||
app.use("/api/v1/users", routesApiUsers);
|
||||
@@ -85,26 +106,46 @@ app.use("/api/v1/notifications", routesApiNotifications);
|
||||
app.use("/api/v1/divvy", routesApiDivvy);
|
||||
app.use("/api/v1/deployopts", routesApiDeployOpts);
|
||||
app.use("/api/v1/apikeys", routesApiApikeys);
|
||||
app.use("/api/v1/stats", routesApiStats);
|
||||
app.use("/api/v1/training", routesApiTraining);
|
||||
app.use("/qcsproxy", qsProxy.router);
|
||||
|
||||
function _isAllowedPath(path){
|
||||
const allowedPaths = [ '/api-docs', '/arena', '/costexport', '/backendlogs', '/photos/user/', '/qmimongo' ];
|
||||
let isAllowed = false;
|
||||
for (let i=0; i<allowedPaths.length; i++) {
|
||||
if ( path.startsWith( allowedPaths[i]) ) {
|
||||
isAllowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isAllowed;
|
||||
}
|
||||
|
||||
/* Checking allowedPaths */
|
||||
app.get('/*',(req, res, next) =>{
|
||||
if (req.originalUrl.indexOf("/api-docs") !== -1 || req.originalUrl.indexOf("/arena") !== -1 || req.originalUrl.indexOf("/costexport") !== -1 || req.originalUrl.indexOf("/backendlogs") !== -1) {
|
||||
if ( _isAllowedPath(req.originalUrl) ) {
|
||||
return next();
|
||||
} else if (req.originalUrl.indexOf("oauth-callback.html") !== -1) {
|
||||
res.sendFile(path.join(__dirname,'/../dist/qmi-cloud/oauth-callback.html'));
|
||||
} else {
|
||||
res.sendFile(path.join(__dirname,'/../dist/qmi-cloud/index.html'));
|
||||
}
|
||||
});
|
||||
/* -----------------------*/
|
||||
|
||||
app.get('/login', passport.ensureAuthenticatedDoLogin, function(req, res) {
|
||||
res.redirect("/");
|
||||
});
|
||||
|
||||
app.get('/logout', function(req, res) {
|
||||
res.redirect("/");
|
||||
});
|
||||
|
||||
app.get('/backendlogs', function (req, res) {
|
||||
res.redirect(process.env.BACKEND_LOGS_URL);
|
||||
})
|
||||
res.redirect(BACKEND_LOGS_URL);
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.get('/qmimongo/*', function (req, res) {
|
||||
let path = req.path.split("/qmimongo")[1];
|
||||
console.log("QMI-Mongo# Redirect: " + `${QMI_MONGO_URL}${path}`);
|
||||
res.redirect(`${QMI_MONGO_URL}${path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
|
||||
const options = {
|
||||
@@ -116,8 +157,8 @@ const options = {
|
||||
version: '1.0.0',
|
||||
description: 'REST API for QMI Cloud solutions',
|
||||
contact: {
|
||||
"name": "Qlik GEAR",
|
||||
"email": "DL-Enterprise-ArchitectsGEAR@qlik.com"
|
||||
"name": "Qlik Presales - Innovation & Excellence Team",
|
||||
"email": "DL-PresalesGlobalInnovation@qlik.com"
|
||||
}
|
||||
},
|
||||
servers: [{
|
||||
@@ -131,25 +172,7 @@ const options = {
|
||||
name: "apiKey",
|
||||
in: "query"
|
||||
}
|
||||
},
|
||||
/*schemas: {
|
||||
"user": {
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"upn": {
|
||||
"type": "string"
|
||||
},
|
||||
"oid": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
},
|
||||
security: [{
|
||||
ApiKeyAuth: []
|
||||
@@ -167,21 +190,35 @@ app.use('/costexport*', passport.ensureAuthenticatedAndAdmin, function(req, res)
|
||||
res.status(404).send("Not found");
|
||||
} else {
|
||||
res.header("Content-Type",'application/json');
|
||||
|
||||
res.sendFile(path.resolve(__dirname, '..', 'costexport', req.query.file ));
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
app.use('/photos/user/:oid', passport.ensureAuthenticated, function(req, res){
|
||||
if ( !req.params.oid ) {
|
||||
res.status(404).send("Not found");
|
||||
} else {
|
||||
var pic = path.resolve(__dirname, '..', 'photos', `${req.params.oid}.jpg` );
|
||||
if (fs.existsSync(pic)){
|
||||
res.sendFile(pic);
|
||||
} else {
|
||||
res.status(404).send();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
const specs = swaggerJsdoc(options);
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
|
||||
|
||||
/**
|
||||
* Create necessary folders
|
||||
*/
|
||||
console.log("--- Create necessary folders");
|
||||
var dirs = ['/logs', '/logs/provision', '/logs/destroy'];
|
||||
|
||||
var dirs = ['/logs', '/logs/provision', '/logs/destroy', '/costexports', '/photos'];
|
||||
dirs.forEach(d => {
|
||||
if (!fs.existsSync(d)){
|
||||
console.log(`--- Creating folder '${d}' since it does not exist`);
|
||||
fs.mkdirSync(d);
|
||||
}
|
||||
});
|
||||
@@ -190,19 +227,26 @@ dirs.forEach(d => {
|
||||
/**
|
||||
* Start App
|
||||
*/
|
||||
app.listen(3000, () => {
|
||||
|
||||
|
||||
const server = app.listen(3000, () => {
|
||||
console.log(`Server listening on port 3000`)
|
||||
});
|
||||
qsProxy.init(server);
|
||||
|
||||
if ( process.env.CERT_PFX_PASSWORD && process.env.CERT_PFX_FILENAME) {
|
||||
if ( IS_SECURE ) {
|
||||
var optionsHttps = {
|
||||
pfx: fs.readFileSync(path.resolve(__dirname, 'certs', process.env.CERT_PFX_FILENAME)),
|
||||
passphrase: process.env.CERT_PFX_PASSWORD
|
||||
};
|
||||
|
||||
https.createServer(optionsHttps, app).listen(3100, function(){
|
||||
const httpsServer = https.createServer(optionsHttps, app).listen(3100, function(){
|
||||
console.log(`Secure server listening on port 3100`);
|
||||
});
|
||||
|
||||
qsProxy.init(httpsServer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
129
server/training/automations.js
Normal file
129
server/training/automations.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const axios = require("axios");
|
||||
const AUTOMATION_NAME_PREFIX = "QMI-Training";
|
||||
|
||||
async function asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
}
|
||||
}
|
||||
|
||||
const _newAutomation = async function(host, apikey, index, workspace) {
|
||||
let workspaceData = require(`./workspaces/${index}-${workspace}.json`);
|
||||
let name = `${AUTOMATION_NAME_PREFIX}-${index}-${workspace}`;
|
||||
|
||||
let result = await axios({
|
||||
method: "post",
|
||||
url: `https://${host}/api/v1/automations/`,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${apikey}`,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
data: {
|
||||
"id":"create",
|
||||
"name": name,
|
||||
"description": workspace === 'main'? "Main automation entry point for QMI training session" : ""
|
||||
},
|
||||
});
|
||||
|
||||
let data = result.data;
|
||||
console.log("createAutomation #1", data);
|
||||
|
||||
let result2 = await axios({
|
||||
method: "put",
|
||||
url: `https://${host}/api/v1/automations/${data.id}`,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${apikey}`,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
data: {
|
||||
"workspace": workspaceData
|
||||
},
|
||||
});
|
||||
|
||||
console.log("createAutomation #2", result2.data);
|
||||
return result2.data;
|
||||
|
||||
|
||||
};
|
||||
|
||||
async function runQlikAutomation(session, email) {
|
||||
|
||||
let automationUrl = session.qaUrl;
|
||||
let automationToken = session.qaToken;
|
||||
|
||||
try {
|
||||
var result = await axios({
|
||||
method: "post",
|
||||
url: automationUrl,
|
||||
headers: {
|
||||
"X-Execution-Token": automationToken,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
data: {
|
||||
email: email,
|
||||
apiKey: session.qcsApiKey,
|
||||
sharedSpace: session.qcsSharedSpace? session.qcsSharedSpace : null,
|
||||
dataSpace: session.qcsDataSpace? session.qcsDataSpace : null
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Executed Qlik Automation '${automationUrl}' for user '${email}'`);
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
return {error: error};
|
||||
}
|
||||
}
|
||||
|
||||
async function getAutomation(session, workspace) {
|
||||
const name = `${AUTOMATION_NAME_PREFIX}-${session.template.index}-${workspace}`;
|
||||
console.log(`# GetAutomation: find "${name}"`);
|
||||
try {
|
||||
var result = await axios({
|
||||
method: "get",
|
||||
url: `https://${session.qcsTenantHost}/api/v1/automations?filter=name eq "${name}"`,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${session.qcsApiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
if ( result.data && result.data.length ) {
|
||||
console.log("# GetAutomation", result.data[0]);
|
||||
return result.data[0];
|
||||
} else {
|
||||
console.log("# GetAutomation", 'Not found');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
return {error: error};
|
||||
}
|
||||
}
|
||||
|
||||
async function createAutomations(session) {
|
||||
|
||||
|
||||
try {
|
||||
|
||||
let result = {};
|
||||
console.log('asyncForEach', session.template.needQcsAutomation);
|
||||
await asyncForEach(session.template.needQcsAutomation, async function( workspace ) {
|
||||
console.log("EXECUTING: ", workspace);
|
||||
result[workspace] = await _newAutomation(session.qcsTenantHost, session.qcsApiKey, session.template.index, workspace);
|
||||
});
|
||||
console.log("Final results automations", result);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
return {error: error};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.runQlikAutomation = runQlikAutomation;
|
||||
module.exports.createAutomations = createAutomations;
|
||||
module.exports.getAutomation = getAutomation;
|
||||
128
server/training/cloudshare.js
Normal file
128
server/training/cloudshare.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const BASEURL = "https://use.cloudshare.com/api/v3/class";
|
||||
const axios = require("axios");
|
||||
const sha1 = require("js-sha1");
|
||||
|
||||
const API_KEY = process.env.CLOUDSHARE_API_KEY;
|
||||
const API_ID = process.env.CLOUDSHARE_API_ID;
|
||||
|
||||
function randomString(length) {
|
||||
var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
var result = "";
|
||||
for (var i = length; i > 0; --i)
|
||||
result += chars[Math.floor(Math.random() * chars.length)];
|
||||
return result;
|
||||
}
|
||||
|
||||
function getRequestAuth(url) {
|
||||
var timestamp = Math.floor(Date.now() / 1000);
|
||||
var token = randomString(10);
|
||||
var hmac = `${API_KEY}${url}${timestamp}${token}`;
|
||||
var hmacSHA1 = sha1(hmac);
|
||||
|
||||
return `cs_sha1 userapiid:${API_ID};timestamp:${timestamp};token:${token};hmac:${hmacSHA1}`;
|
||||
|
||||
};
|
||||
|
||||
async function _getAllStudentsInClass(classId) {
|
||||
var url = `${BASEURL}/${classId}/students?isFull=false`;
|
||||
var auth = getRequestAuth(url);
|
||||
|
||||
try {
|
||||
var result = await axios({
|
||||
method: "GET",
|
||||
url: url,
|
||||
headers: {
|
||||
Accpet: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: auth,
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
return {error: error};
|
||||
}
|
||||
};
|
||||
|
||||
async function _sendInvitationToUser(classId, userId) {
|
||||
var url = `${BASEURL}/actions/sendinvitations?isMultiple=true`;
|
||||
var auth = getRequestAuth(url);
|
||||
|
||||
try {
|
||||
var result = await axios({
|
||||
method: "POST",
|
||||
url: url,
|
||||
headers: {
|
||||
Accpet: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: auth,
|
||||
},
|
||||
data: {
|
||||
classId: classId,
|
||||
studentIds: [userId],
|
||||
},
|
||||
});
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
return {error: error};
|
||||
}
|
||||
}
|
||||
|
||||
async function _addUser(classId, email) {
|
||||
var url = `${BASEURL}/${classId}/Students`;
|
||||
var auth = getRequestAuth(url);
|
||||
|
||||
var firstName = email.split("@")[0];
|
||||
var lastName = email.split("@")[1];
|
||||
|
||||
try {
|
||||
var result = await axios({
|
||||
method: "POST",
|
||||
url: url,
|
||||
headers: {
|
||||
Accpet: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: auth,
|
||||
},
|
||||
data: {
|
||||
email: email,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
properties: {},
|
||||
},
|
||||
});
|
||||
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
return {error: error};
|
||||
}
|
||||
}
|
||||
|
||||
var addStudentToClass = async function (session, email) {
|
||||
|
||||
const classId = session.cloudshareClass;
|
||||
|
||||
console.log(`Executed 'Add Student' to class '${classId}' for user '${email}'`);
|
||||
|
||||
const students = await _getAllStudentsInClass(classId);
|
||||
|
||||
let student = students.find(function (user) {
|
||||
return user.email === email;
|
||||
});
|
||||
|
||||
if (student) {
|
||||
console.log(`Student already in class (${classId}), sending invitation: ${student.email}` );
|
||||
return await _sendInvitationToUser(classId, student.id);
|
||||
} else {
|
||||
console.log(`Adding student to class (${classId}): ${email}`);
|
||||
|
||||
student = await _addUser(classId, email);
|
||||
console.log(`Student Added to class (${classId}): ${email}`);
|
||||
console.log(`Student:`, student);
|
||||
|
||||
return await _sendInvitationToUser(classId, student.studentId);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.addStudentToClass = addStudentToClass;
|
||||
1
server/training/workspaces/automl-main.json
Normal file
1
server/training/workspaces/automl-main.json
Normal file
File diff suppressed because one or more lines are too long
1
server/training/workspaces/automl-spaceaccess.json
Normal file
1
server/training/workspaces/automl-spaceaccess.json
Normal file
File diff suppressed because one or more lines are too long
1
server/training/workspaces/qcdi-main.json
Normal file
1
server/training/workspaces/qcdi-main.json
Normal file
File diff suppressed because one or more lines are too long
1
server/training/workspaces/qcdi-userroles.json
Normal file
1
server/training/workspaces/qcdi-userroles.json
Normal file
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
<p style="margin-top: 70px;text-align: right;">
|
||||
<a class="lui-text-info" href="/backendlogs" target="blank"><i class="fas fa-long-arrow-alt-right"></i> Backend Logs (internal)</a>
|
||||
<a class="lui-text-info" href="/backendlogs" target="_blank"><i class="fas fa-long-arrow-alt-right"></i> Backend Logs (internal)</a>
|
||||
</p>
|
||||
|
||||
<ul class="nav nav-pills nav-fill">
|
||||
@@ -10,34 +10,29 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div *ngIf="tab === 'Scenarios'">
|
||||
<!--<h1>Scenarios</h1>-->
|
||||
<div *ngIf="tab === 'scenarios'">
|
||||
<table-scenarios></table-scenarios>
|
||||
</div>
|
||||
<div *ngIf="tab === 'Scenario Deploy Opts'">
|
||||
<!--<h1>Scenarios</h1>-->
|
||||
<div *ngIf="tab === 'scenario-deploy-opts'">
|
||||
<table-subscriptions></table-subscriptions>
|
||||
</div>
|
||||
<div *ngIf="tab === 'Provisions'">
|
||||
<!--<h1>Provisions</h1>-->
|
||||
<div *ngIf="tab === 'provisions'">
|
||||
<table-provisions></table-provisions>
|
||||
</div>
|
||||
<div *ngIf="tab === 'Users'">
|
||||
<!--<h1>Users</h1>-->
|
||||
<div *ngIf="tab === 'users'">
|
||||
<table-users></table-users>
|
||||
</div>
|
||||
<div *ngIf="tab === 'Notifications'">
|
||||
<!--<h1>Scenarios</h1>-->
|
||||
<div *ngIf="tab === 'notifications'">
|
||||
<table-notifications></table-notifications>
|
||||
</div>
|
||||
<div *ngIf="tab === 'API keys'">
|
||||
<!--<h1>Scenarios</h1>-->
|
||||
<div *ngIf="tab === 'api-keys'">
|
||||
<table-apikeys></table-apikeys>
|
||||
</div>
|
||||
|
||||
<div *ngIf="tab === 'VM Types'">
|
||||
<!--<h1>Scenarios</h1>-->
|
||||
<div *ngIf="tab === 'vm-types'">
|
||||
<table-vmtypes></table-vmtypes>
|
||||
</div>
|
||||
|
||||
<qmi-alert></qmi-alert>
|
||||
<div *ngIf="tab === 'sessions'">
|
||||
<table-sessions></table-sessions>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin',
|
||||
templateUrl: './admin.component.html',
|
||||
styleUrls: ['./admin.component.scss']
|
||||
})
|
||||
export class AdminComponent implements OnInit {
|
||||
export class AdminComponent implements OnInit, OnDestroy {
|
||||
|
||||
sections = ['Provisions', 'Scenarios', 'Scenario Deploy Opts', 'Users', 'Notifications','API keys', 'VM Types'];
|
||||
sections = ['provisions', 'scenarios', 'scenario-deploy-opts', 'users', 'notifications','sessions', 'api-keys', 'vm-types'];
|
||||
tab : string = 'Provisions';
|
||||
private sub: any;
|
||||
|
||||
constructor() { }
|
||||
constructor( private route: ActivatedRoute, private router: Router ) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.sub = this.route.params.subscribe(params => {
|
||||
console.log("Params", params);
|
||||
this.tab = params['tab'] || 'provisions'; // (+) converts string 'id' to a number
|
||||
});
|
||||
}
|
||||
|
||||
tabSelect($event, tab) {
|
||||
$event.preventDefault();
|
||||
this.router.navigate(['/admin', tab]);
|
||||
/*$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
this.tab = tab;
|
||||
this.tab = tab;*/
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,17 +6,33 @@ import { StatsComponent } from './stats/stats.component';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { AuthGuard } from './services/auth.guard';
|
||||
import { FaqComponent } from './faq/faq.component';
|
||||
import { UserDashboardComponent } from './user/user-dashboard.component';
|
||||
import { ScenariosSectionComponent } from './scenarios/scenarios-section.component';
|
||||
import { ProvisionsSharedComponent } from './provisions/provisions-shared.component';
|
||||
import { CostComponent } from './cost/cost.component';
|
||||
import { ProvComponent } from './provisions/prov.component';
|
||||
import { TrainingComponent } from './training/training.component';
|
||||
import { SessionFormComponent } from './sessionform/sessionform.component';
|
||||
import { FeatureGuard } from './services/feature.guard';
|
||||
import { ErrorComponent } from './home/error.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'home', component: HomeComponent},
|
||||
{ path: 'faq', component: FaqComponent},
|
||||
{ path: 'error', component: ErrorComponent},
|
||||
{ path: 'scenarios', component: ScenariosSectionComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'provision/:id', component: ProvComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'provisions', component: ProvisionsComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'sharedprovision', component: ProvisionsSharedComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'cost-analysis', component: CostComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'admin/:tab', component: AdminComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'stats', component: StatsComponent, canActivate: [AuthGuard]},
|
||||
{ path: '',
|
||||
redirectTo: '/home',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{ path: 'training', component: TrainingComponent, canActivate: [AuthGuard, FeatureGuard]},
|
||||
{ path: 'training/session/:id/public', component: SessionFormComponent},
|
||||
{ path: 'user/:id', component: UserDashboardComponent, canActivate: [AuthGuard]},
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full'},
|
||||
{ path: '**', redirectTo: '/home' }
|
||||
];
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user