@rockcarver/frodo-lib 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.eslintrc +32 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +30 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. package/.github/README.md +121 -0
  5. package/.github/workflows/pipeline.yml +287 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +512 -0
  8. package/CODE_OF_CONDUCT.md +128 -0
  9. package/LICENSE +21 -0
  10. package/README.md +8 -0
  11. package/docs/CONTRIBUTE.md +96 -0
  12. package/docs/PIPELINE.md +169 -0
  13. package/docs/images/npm_versioning_guidelines.png +0 -0
  14. package/docs/images/release_pipeline.png +0 -0
  15. package/jsconfig.json +6 -0
  16. package/package.json +95 -0
  17. package/resources/sampleEntitiesFile.json +8 -0
  18. package/resources/sampleEnvFile.env +2 -0
  19. package/src/api/AuthenticateApi.js +33 -0
  20. package/src/api/BaseApi.js +242 -0
  21. package/src/api/CirclesOfTrustApi.js +87 -0
  22. package/src/api/EmailTemplateApi.js +37 -0
  23. package/src/api/IdmConfigApi.js +88 -0
  24. package/src/api/LogApi.js +45 -0
  25. package/src/api/ManagedObjectApi.js +62 -0
  26. package/src/api/OAuth2ClientApi.js +69 -0
  27. package/src/api/OAuth2OIDCApi.js +73 -0
  28. package/src/api/OAuth2ProviderApi.js +32 -0
  29. package/src/api/RealmApi.js +99 -0
  30. package/src/api/Saml2Api.js +176 -0
  31. package/src/api/ScriptApi.js +84 -0
  32. package/src/api/SecretsApi.js +151 -0
  33. package/src/api/ServerInfoApi.js +41 -0
  34. package/src/api/SocialIdentityProvidersApi.js +114 -0
  35. package/src/api/StartupApi.js +45 -0
  36. package/src/api/ThemeApi.js +181 -0
  37. package/src/api/TreeApi.js +207 -0
  38. package/src/api/VariablesApi.js +104 -0
  39. package/src/api/utils/ApiUtils.js +77 -0
  40. package/src/api/utils/ApiUtils.test.js +96 -0
  41. package/src/api/utils/Base64.js +62 -0
  42. package/src/index.js +32 -0
  43. package/src/index.test.js +13 -0
  44. package/src/ops/AdminOps.js +901 -0
  45. package/src/ops/AuthenticateOps.js +342 -0
  46. package/src/ops/CirclesOfTrustOps.js +350 -0
  47. package/src/ops/ConnectionProfileOps.js +254 -0
  48. package/src/ops/EmailTemplateOps.js +326 -0
  49. package/src/ops/IdmOps.js +227 -0
  50. package/src/ops/IdpOps.js +342 -0
  51. package/src/ops/JourneyOps.js +2026 -0
  52. package/src/ops/LogOps.js +357 -0
  53. package/src/ops/ManagedObjectOps.js +34 -0
  54. package/src/ops/OAuth2ClientOps.js +151 -0
  55. package/src/ops/OrganizationOps.js +85 -0
  56. package/src/ops/RealmOps.js +139 -0
  57. package/src/ops/SamlOps.js +541 -0
  58. package/src/ops/ScriptOps.js +211 -0
  59. package/src/ops/SecretsOps.js +288 -0
  60. package/src/ops/StartupOps.js +114 -0
  61. package/src/ops/ThemeOps.js +379 -0
  62. package/src/ops/VariablesOps.js +185 -0
  63. package/src/ops/templates/OAuth2ClientTemplate.json +270 -0
  64. package/src/ops/templates/OrgModelUserAttributesTemplate.json +149 -0
  65. package/src/ops/templates/cloud/GenericExtensionAttributesTemplate.json +392 -0
  66. package/src/ops/templates/cloud/managed.json +4119 -0
  67. package/src/ops/utils/Console.js +434 -0
  68. package/src/ops/utils/DataProtection.js +92 -0
  69. package/src/ops/utils/DataProtection.test.js +28 -0
  70. package/src/ops/utils/ExportImportUtils.js +146 -0
  71. package/src/ops/utils/ExportImportUtils.test.js +119 -0
  72. package/src/ops/utils/OpsUtils.js +76 -0
  73. package/src/ops/utils/Wordwrap.js +11 -0
  74. package/src/storage/SessionStorage.js +45 -0
  75. package/src/storage/StaticStorage.js +15 -0
  76. package/test/e2e/journey/baseline/ForgottenUsername.journey.json +216 -0
  77. package/test/e2e/journey/baseline/Login.journey.json +205 -0
  78. package/test/e2e/journey/baseline/PasswordGrant.journey.json +139 -0
  79. package/test/e2e/journey/baseline/ProgressiveProfile.journey.json +198 -0
  80. package/test/e2e/journey/baseline/Registration.journey.json +249 -0
  81. package/test/e2e/journey/baseline/ResetPassword.journey.json +268 -0
  82. package/test/e2e/journey/baseline/UpdatePassword.journey.json +323 -0
  83. package/test/e2e/journey/baseline/allAlphaJourneys.journeys.json +1520 -0
  84. package/test/e2e/journey/delete/ForgottenUsername.journey.json +216 -0
  85. package/test/e2e/journey/delete/Login.journey.json +205 -0
  86. package/test/e2e/journey/delete/PasswordGrant.journey.json +139 -0
  87. package/test/e2e/journey/delete/ProgressiveProfile.journey.json +198 -0
  88. package/test/e2e/journey/delete/Registration.journey.json +249 -0
  89. package/test/e2e/journey/delete/ResetPassword.journey.json +268 -0
  90. package/test/e2e/journey/delete/UpdatePassword.journey.json +323 -0
  91. package/test/e2e/journey/delete/deleteMe.journey.json +230 -0
  92. package/test/e2e/journey/list/Disabled.journey.json +43 -0
  93. package/test/e2e/journey/list/ForgottenUsername.journey.json +216 -0
  94. package/test/e2e/journey/list/Login.journey.json +205 -0
  95. package/test/e2e/journey/list/PasswordGrant.journey.json +139 -0
  96. package/test/e2e/journey/list/ProgressiveProfile.journey.json +198 -0
  97. package/test/e2e/journey/list/Registration.journey.json +249 -0
  98. package/test/e2e/journey/list/ResetPassword.journey.json +268 -0
  99. package/test/e2e/journey/list/UpdatePassword.journey.json +323 -0
  100. package/test/e2e/setup.js +107 -0
  101. package/test/e2e/theme/baseline/Contrast.theme.json +95 -0
  102. package/test/e2e/theme/baseline/Highlander.theme.json +95 -0
  103. package/test/e2e/theme/baseline/Robroy.theme.json +95 -0
  104. package/test/e2e/theme/baseline/Starter-Theme.theme.json +94 -0
  105. package/test/e2e/theme/baseline/Zardoz.theme.json +95 -0
  106. package/test/e2e/theme/import/Contrast.theme.json +95 -0
  107. package/test/e2e/theme/import/Highlander.theme.json +95 -0
  108. package/test/e2e/theme/import/Robroy.theme.json +95 -0
  109. package/test/e2e/theme/import/Starter-Theme.theme.json +94 -0
  110. package/test/e2e/theme/import/Zardoz.default.theme.json +95 -0
  111. package/test/fs_tmp/.gitkeep +2 -0
  112. package/test/global/setup.js +65 -0
@@ -0,0 +1,211 @@
1
+ import fs from 'fs';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { applyNameCollisionPolicy } from './utils/OpsUtils.js';
4
+ import {
5
+ createProgressBar,
6
+ createTable,
7
+ printMessage,
8
+ stopProgressBar,
9
+ updateProgressBar,
10
+ } from './utils/Console.js';
11
+ import { getScriptByName, getScripts, putScript } from '../api/ScriptApi.js';
12
+ import wordwrap from './utils/Wordwrap.js';
13
+ import {
14
+ convertBase64TextToArray,
15
+ convertTextArrayToBase64,
16
+ getTypedFilename,
17
+ saveToFile,
18
+ titleCase,
19
+ validateImport,
20
+ } from './utils/ExportImportUtils.js';
21
+ import storage from '../storage/SessionStorage.js';
22
+
23
+ /**
24
+ * List scripts
25
+ */
26
+ export async function listScripts(long = false) {
27
+ try {
28
+ const scripts = (await getScripts()).data.result;
29
+ scripts.sort((a, b) => a.name.localeCompare(b.name));
30
+ if (long) {
31
+ const table = createTable([
32
+ 'Name',
33
+ 'UUID',
34
+ 'Language',
35
+ 'Context',
36
+ 'Description',
37
+ ]);
38
+ const langMap = { JAVASCRIPT: 'JS', GROOVY: 'Groovy' };
39
+ scripts.forEach((script) => {
40
+ table.push([
41
+ wordwrap(script.name, 25, ' '),
42
+ script._id,
43
+ langMap[script.language],
44
+ wordwrap(titleCase(script.context.split('_').join(' ')), 25),
45
+ wordwrap(script.description, 30),
46
+ ]);
47
+ });
48
+ printMessage(table.toString(), 'data');
49
+ } else {
50
+ scripts.forEach((script) => {
51
+ printMessage(`${script.name}`, 'data');
52
+ });
53
+ }
54
+ } catch (error) {
55
+ printMessage(`Error listing scripts - ${error}`, 'error');
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Export script to file
61
+ * @param {String} name script name
62
+ * @param {String} file file name
63
+ */
64
+ export async function exportScriptByName(name, file) {
65
+ let fileName = getTypedFilename(name, 'script');
66
+ if (file) {
67
+ fileName = file;
68
+ }
69
+ const scriptData = (await getScriptByName(name)).data.result;
70
+ if (scriptData.length > 1) {
71
+ printMessage(`Multiple scripts with name ${name} found...`, 'error');
72
+ }
73
+ scriptData.forEach((element) => {
74
+ const scriptTextArray = convertBase64TextToArray(element.script);
75
+ // eslint-disable-next-line no-param-reassign
76
+ element.script = scriptTextArray;
77
+ });
78
+ saveToFile('script', scriptData, '_id', fileName);
79
+ }
80
+
81
+ /**
82
+ * Export all scripts to single file
83
+ * @param {String} file file name
84
+ */
85
+ export async function exportScriptsToFile(file) {
86
+ let fileName = getTypedFilename(
87
+ `all${storage.session.getRealm()}Scripts`,
88
+ 'script'
89
+ );
90
+ if (file) {
91
+ fileName = file;
92
+ }
93
+ const scriptList = (await getScripts()).data.result;
94
+ const allScriptsData = [];
95
+ createProgressBar(scriptList.length, 'Exporting script');
96
+ for (const item of scriptList) {
97
+ updateProgressBar(`Reading script ${item.name}`);
98
+ // eslint-disable-next-line no-await-in-loop
99
+ const scriptData = (await getScriptByName(item.name)).data.result;
100
+ scriptData.forEach((element) => {
101
+ const scriptTextArray = convertBase64TextToArray(element.script);
102
+ // eslint-disable-next-line no-param-reassign
103
+ element.script = scriptTextArray;
104
+ allScriptsData.push(element);
105
+ });
106
+ }
107
+ stopProgressBar('Done');
108
+ saveToFile('script', allScriptsData, '_id', fileName);
109
+ }
110
+
111
+ /**
112
+ * Export all scripts to individual files
113
+ */
114
+ export async function exportScriptsToFiles() {
115
+ const scriptList = (await getScripts()).data.result;
116
+ createProgressBar(scriptList.length, 'Exporting script');
117
+ for (const item of scriptList) {
118
+ updateProgressBar(`Reading script ${item.name}`);
119
+ // eslint-disable-next-line no-await-in-loop
120
+ const scriptData = (await getScriptByName(item.name)).data.result;
121
+ scriptData.forEach((element) => {
122
+ const scriptTextArray = convertBase64TextToArray(element.script);
123
+ // eslint-disable-next-line no-param-reassign
124
+ element.script = scriptTextArray;
125
+ });
126
+ const fileName = getTypedFilename(item.name, 'script');
127
+ saveToFile('script', scriptData, '_id', fileName);
128
+ }
129
+ stopProgressBar('Done');
130
+ }
131
+
132
+ /**
133
+ * Import script
134
+ * @param {String} id script uuid
135
+ * @param {Object} data script object
136
+ * @returns {Object} a status object
137
+ */
138
+ export async function createOrUpdateScript(id, data) {
139
+ try {
140
+ await putScript(id, data);
141
+ return { error: false, name: data.name };
142
+ } catch (e) {
143
+ if (e.response.status === 409) {
144
+ printMessage(
145
+ `createOrUpdateScript WARNING: script with name ${data.name} already exists, using renaming policy... <name> => <name - imported (n)>`,
146
+ 'warn'
147
+ );
148
+ const newName = applyNameCollisionPolicy(data.name);
149
+ // console.log(newName);
150
+ printMessage(`Trying to save script as ${newName}`, 'warn');
151
+ // eslint-disable-next-line no-param-reassign
152
+ data.name = newName;
153
+ await createOrUpdateScript(id, data);
154
+ return { error: false, name: data.name };
155
+ }
156
+ printMessage(
157
+ `createOrUpdateScript ERROR: put script error, script ${id} - ${e.message}`,
158
+ 'error'
159
+ );
160
+ return { error: true, name: data.name };
161
+ }
162
+ }
163
+
164
+ export async function importScriptsFromFile(name, file, reUuid = false) {
165
+ fs.readFile(file, 'utf8', (err, data) => {
166
+ if (err) throw err;
167
+ const scriptData = JSON.parse(data);
168
+ if (validateImport(scriptData.meta)) {
169
+ createProgressBar(Object.keys(scriptData.script).length, '');
170
+ for (const existingId in scriptData.script) {
171
+ if ({}.hasOwnProperty.call(scriptData.script, existingId)) {
172
+ let newId = existingId;
173
+ // console.log(id);
174
+ const encodedScript = convertTextArrayToBase64(
175
+ scriptData.script[existingId].script
176
+ );
177
+ scriptData.script[existingId].script = encodedScript;
178
+ if (reUuid) {
179
+ newId = uuidv4();
180
+ // printMessage(
181
+ // `Re-uuid-ing script ${scriptData.script[existingId].name} ${existingId} => ${newId}...`
182
+ // );
183
+ scriptData.script[existingId]._id = newId;
184
+ }
185
+ if (name) {
186
+ // printMessage(
187
+ // `Renaming script ${scriptData.script[existingId].name} => ${options.script}...`
188
+ // );
189
+ scriptData.script[existingId].name = name;
190
+ }
191
+ updateProgressBar(`Importing ${scriptData.script[existingId].name}`);
192
+ // console.log(scriptData.script[id]);
193
+ createOrUpdateScript(newId, scriptData.script[existingId]).then(
194
+ (result) => {
195
+ if (result == null)
196
+ printMessage(
197
+ `Error importing ${scriptData.script[existingId].name}`,
198
+ 'error'
199
+ );
200
+ }
201
+ );
202
+ if (name) break;
203
+ }
204
+ }
205
+ stopProgressBar('Done');
206
+ // printMessage('Done');
207
+ } else {
208
+ printMessage('Import validation failed...', 'error');
209
+ }
210
+ });
211
+ }
@@ -0,0 +1,288 @@
1
+ import {
2
+ createKeyValueTable,
3
+ createProgressBar,
4
+ createTable,
5
+ failSpinner,
6
+ printMessage,
7
+ showSpinner,
8
+ stopProgressBar,
9
+ succeedSpinner,
10
+ updateProgressBar,
11
+ } from './utils/Console.js';
12
+ import {
13
+ createNewVersionOfSecret,
14
+ deleteSecret,
15
+ deleteVersionOfSecret,
16
+ getSecret,
17
+ getSecrets,
18
+ getSecretVersions,
19
+ putSecret,
20
+ setSecretDescription,
21
+ setStatusOfVersionOfSecret,
22
+ } from '../api/SecretsApi.js';
23
+ import wordwrap from './utils/Wordwrap.js';
24
+ import { resolveUserName } from './ManagedObjectOps.js';
25
+
26
+ /**
27
+ * List secrets
28
+ * @param {boolean} long Long version, all the fields
29
+ */
30
+ export async function listSecrets(long) {
31
+ let secrets = [];
32
+ try {
33
+ secrets = (await getSecrets()).data.result;
34
+ } catch (error) {
35
+ printMessage(`${error.message}`, 'error');
36
+ printMessage(error.response.data, 'error');
37
+ }
38
+ if (long) {
39
+ const table = createTable([
40
+ 'Id'.brightCyan,
41
+ { hAlign: 'right', content: 'Active\nVersion'.brightCyan },
42
+ { hAlign: 'right', content: 'Loaded\nVersion'.brightCyan },
43
+ 'Status'.brightCyan,
44
+ 'Description'.brightCyan,
45
+ 'Modifier'.brightCyan,
46
+ 'Modified'.brightCyan,
47
+ ]);
48
+ secrets.sort((a, b) => a._id.localeCompare(b._id));
49
+ for (const secret of secrets) {
50
+ table.push([
51
+ secret._id,
52
+ { hAlign: 'right', content: secret.activeVersion },
53
+ { hAlign: 'right', content: secret.loadedVersion },
54
+ secret.loaded ? 'loaded'.brightGreen : 'unloaded'.brightRed,
55
+ wordwrap(secret.description, 40),
56
+ // eslint-disable-next-line no-await-in-loop
57
+ await resolveUserName('teammember', secret.lastChangedBy),
58
+ new Date(secret.lastChangeDate).toLocaleString(),
59
+ ]);
60
+ }
61
+ printMessage(table.toString());
62
+ } else {
63
+ secrets.forEach((secret) => {
64
+ printMessage(secret._id);
65
+ });
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Create secret
71
+ * @param {String} id secret id
72
+ * @param {String} value secret value
73
+ * @param {String} description secret description
74
+ * @param {String} encoding secret encoding
75
+ * @param {boolean} useInPlaceholders use secret in placeholders
76
+ */
77
+ export async function createSecret(
78
+ id,
79
+ value,
80
+ description,
81
+ encoding,
82
+ useInPlaceholders
83
+ ) {
84
+ showSpinner(`Creating secret ${id}...`);
85
+ try {
86
+ await putSecret(id, value, description, encoding, useInPlaceholders);
87
+ succeedSpinner(`Created secret ${id}`);
88
+ } catch (error) {
89
+ failSpinner(
90
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
91
+ );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Set description of secret
97
+ * @param {String} secretId secret id
98
+ * @param {String} description secret description
99
+ */
100
+ export async function setDescriptionOfSecret(secretId, description) {
101
+ showSpinner(`Setting description of secret ${secretId}...`);
102
+ try {
103
+ await setSecretDescription(secretId, description);
104
+ succeedSpinner(`Set description of secret ${secretId}`);
105
+ } catch (error) {
106
+ failSpinner(
107
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
108
+ );
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Delete a secret
114
+ * @param {String} secretId secret id
115
+ */
116
+ export async function deleteSecretCmd(secretId) {
117
+ showSpinner(`Deleting secret ${secretId}...`);
118
+ try {
119
+ await deleteSecret(secretId);
120
+ succeedSpinner(`Deleted secret ${secretId}`);
121
+ } catch (error) {
122
+ failSpinner(
123
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
124
+ );
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Delete all secrets
130
+ */
131
+ export async function deleteSecretsCmd() {
132
+ try {
133
+ const secrets = (await getSecrets()).data.result;
134
+ createProgressBar(secrets.length, `Deleting secrets...`);
135
+ for (const secret of secrets) {
136
+ try {
137
+ // eslint-disable-next-line no-await-in-loop
138
+ await deleteSecret(secret._id);
139
+ updateProgressBar(`Deleted secret ${secret._id}`);
140
+ } catch (error) {
141
+ printMessage(
142
+ `Error: ${error.response.data.code} - ${error.response.data.message}`,
143
+ 'error'
144
+ );
145
+ }
146
+ }
147
+ stopProgressBar(`Secrets deleted.`);
148
+ } catch (error) {
149
+ printMessage(
150
+ `Error: ${error.response.data.code} - ${error.response.data.message}`,
151
+ 'error'
152
+ );
153
+ }
154
+ }
155
+
156
+ /**
157
+ * List all the versions of the secret
158
+ * @param {String} secretId secret id
159
+ */
160
+ export async function listSecretVersionsCmd(secretId) {
161
+ let versions = [];
162
+ try {
163
+ versions = (await getSecretVersions(secretId)).data;
164
+ } catch (error) {
165
+ printMessage(`${error.message}`, 'error');
166
+ printMessage(error.response.data, 'error');
167
+ }
168
+ const table = createTable([
169
+ { hAlign: 'right', content: 'Version'.brightCyan },
170
+ 'Status'.brightCyan,
171
+ 'Loaded'.brightCyan,
172
+ 'Created'.brightCyan,
173
+ ]);
174
+ // versions.sort((a, b) => a._id.localeCompare(b._id));
175
+ const statusMap = {
176
+ ENABLED: 'active'.brightGreen,
177
+ DISABLED: 'inactive'.brightRed,
178
+ DESTROYED: 'deleted'.brightRed,
179
+ };
180
+ versions.forEach((version) => {
181
+ table.push([
182
+ { hAlign: 'right', content: version.version },
183
+ statusMap[version.status],
184
+ version.loaded ? 'loaded'.brightGreen : 'unloaded'.brightRed,
185
+ new Date(version.createDate).toLocaleString(),
186
+ ]);
187
+ });
188
+ printMessage(table.toString());
189
+ }
190
+
191
+ /**
192
+ * Describe a secret
193
+ * @param {String} secretId Secret id
194
+ */
195
+ export async function describeSecret(secretId) {
196
+ const secret = (await getSecret(secretId)).data;
197
+ const table = createKeyValueTable();
198
+ table.push(['Name'.brightCyan, secret._id]);
199
+ table.push(['Active Version'.brightCyan, secret.activeVersion]);
200
+ table.push(['Loaded Version'.brightCyan, secret.loadedVersion]);
201
+ table.push([
202
+ 'Status'.brightCyan,
203
+ secret.loaded ? 'loaded'.brightGreen : 'unloaded'.brightRed,
204
+ ]);
205
+ table.push(['Description'.brightCyan, wordwrap(secret.description, 60)]);
206
+ table.push([
207
+ 'Modified'.brightCyan,
208
+ new Date(secret.lastChangeDate).toLocaleString(),
209
+ ]);
210
+ table.push([
211
+ 'Modifier'.brightCyan,
212
+ await resolveUserName('teammember', secret.lastChangedBy),
213
+ ]);
214
+ table.push(['Modifier UUID'.brightCyan, secret.lastChangedBy]);
215
+ table.push(['Encoding'.brightCyan, secret.encoding]);
216
+ table.push(['Use In Placeholders'.brightCyan, secret.useInPlaceholders]);
217
+ printMessage(table.toString());
218
+ printMessage('\nSecret Versions:');
219
+ await listSecretVersionsCmd(secretId);
220
+ }
221
+
222
+ /**
223
+ * Create new version of secret
224
+ * @param {String} secretId secret id
225
+ * @param {String} value secret value
226
+ */
227
+ export async function createNewVersionOfSecretCmd(secretId, value) {
228
+ showSpinner(`Creating new version of secret ${secretId}...`);
229
+ try {
230
+ const version = (await createNewVersionOfSecret(secretId, value)).data;
231
+ succeedSpinner(`Created version ${version.version} of secret ${secretId}`);
232
+ } catch (error) {
233
+ failSpinner(
234
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
235
+ );
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Activate a version of a secret
241
+ * @param {String} secretId secret id
242
+ * @param {Number} version version of secret
243
+ */
244
+ export async function activateVersionOfSecret(secretId, version) {
245
+ showSpinner(`Activating version ${version} of secret ${secretId}...`);
246
+ try {
247
+ await setStatusOfVersionOfSecret(secretId, version, 'ENABLED');
248
+ succeedSpinner(`Activated version ${version} of secret ${secretId}`);
249
+ } catch (error) {
250
+ failSpinner(
251
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
252
+ );
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Deactivate a version of a secret
258
+ * @param {String} secretId secret id
259
+ * @param {Number} version version of secret
260
+ */
261
+ export async function deactivateVersionOfSecret(secretId, version) {
262
+ showSpinner(`Deactivating version ${version} of secret ${secretId}...`);
263
+ try {
264
+ await setStatusOfVersionOfSecret(secretId, version, 'DISABLED');
265
+ succeedSpinner(`Deactivated version ${version} of secret ${secretId}`);
266
+ } catch (error) {
267
+ failSpinner(
268
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
269
+ );
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Delete version of secret
275
+ * @param {String} secretId secret id
276
+ * @param {Number} version version of secret
277
+ */
278
+ export async function deleteVersionOfSecretCmd(secretId, version) {
279
+ showSpinner(`Deleting version ${version} of secret ${secretId}...`);
280
+ try {
281
+ await deleteVersionOfSecret(secretId, version);
282
+ succeedSpinner(`Deleted version ${version} of secret ${secretId}`);
283
+ } catch (error) {
284
+ failSpinner(
285
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
286
+ );
287
+ }
288
+ }
@@ -0,0 +1,114 @@
1
+ import yesno from 'yesno';
2
+ import {
3
+ createTable,
4
+ failSpinner,
5
+ printMessage,
6
+ showSpinner,
7
+ spinSpinner,
8
+ succeedSpinner,
9
+ } from './utils/Console.js';
10
+ import { getSecrets } from '../api/SecretsApi.js';
11
+ import { getStatus, initiateRestart } from '../api/StartupApi.js';
12
+ import { resolveUserName } from './ManagedObjectOps.js';
13
+ import { getVariables } from '../api/VariablesApi.js';
14
+
15
+ export async function checkForUpdates() {
16
+ showSpinner(`Checking for updates to apply...`);
17
+ const updates = createTable([
18
+ 'Type'.brightCyan,
19
+ 'Name'.brightCyan,
20
+ 'Modified'.brightCyan,
21
+ 'Modifier'.brightCyan,
22
+ ]);
23
+ let secrets = [];
24
+ let variables = [];
25
+ try {
26
+ secrets = (await getSecrets()).data.result;
27
+ for (const secret of secrets) {
28
+ if (!secret.loaded) {
29
+ updates.push([
30
+ 'secret',
31
+ secret._id,
32
+ new Date(secret.lastChangeDate).toLocaleString(),
33
+ // eslint-disable-next-line no-await-in-loop
34
+ await resolveUserName('teammember', secret.lastChangedBy),
35
+ ]);
36
+ }
37
+ }
38
+ variables = (await getVariables()).data.result;
39
+ for (const variable of variables) {
40
+ if (!variable.loaded) {
41
+ updates.push([
42
+ 'variable',
43
+ variable._id,
44
+ new Date(variable.lastChangeDate).toLocaleString(),
45
+ // eslint-disable-next-line no-await-in-loop
46
+ await resolveUserName('teammember', variable.lastChangedBy),
47
+ ]);
48
+ }
49
+ }
50
+ } catch (error) {
51
+ failSpinner(
52
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
53
+ );
54
+ }
55
+ if (updates.length > 0) {
56
+ succeedSpinner(`${updates.length} update(s) need to be applied`);
57
+ printMessage(updates.toString(), 'data');
58
+ return true;
59
+ }
60
+ succeedSpinner(`No updates need to be applied`);
61
+ return false;
62
+ }
63
+
64
+ export async function applyUpdates(force, wait, yes) {
65
+ if ((await checkForUpdates()) || force) {
66
+ const ok =
67
+ yes ||
68
+ (await yesno({
69
+ question: `\nChanges may take up to 10 minutes to propagate, during which time you will not be able to make further updates.\n\nApply updates? (y|n):`,
70
+ }));
71
+ if (ok) {
72
+ showSpinner(`Applying updates...`);
73
+ try {
74
+ await initiateRestart();
75
+ if (wait) {
76
+ const timeout = 10 * 60 * 1000;
77
+ const start = new Date().getTime();
78
+ let runtime = 0;
79
+ // eslint-disable-next-line no-await-in-loop
80
+ let status = (await getStatus()).data.restartStatus;
81
+ while (status !== 'ready' && start + timeout > new Date().getTime()) {
82
+ // eslint-disable-next-line no-await-in-loop, no-promise-executor-return
83
+ await new Promise((resolve) => setTimeout(resolve, 5000));
84
+ // eslint-disable-next-line no-await-in-loop
85
+ status = (await getStatus()).data.restartStatus;
86
+ runtime = new Date().getTime() - start;
87
+ spinSpinner(`${status} (${Math.round(runtime / 1000)}s)`);
88
+ }
89
+ if (runtime < timeout) {
90
+ succeedSpinner(
91
+ `Updates applied in ${Math.round(
92
+ runtime / 1000
93
+ )}s with final status: ${status}`
94
+ );
95
+ } else {
96
+ succeedSpinner(
97
+ `Updates timed out after ${Math.round(
98
+ runtime / 1000
99
+ )}s with final status: ${status}`
100
+ );
101
+ }
102
+ } else {
103
+ succeedSpinner(
104
+ `Updates are being applied. Changes may take up to 10 minutes to propagate, during which time you will not be able to make further updates.`
105
+ );
106
+ }
107
+ } catch (error) {
108
+ failSpinner(
109
+ `Error: ${error.response.data.code} - ${error.response.data.message}`
110
+ );
111
+ }
112
+ }
113
+ }
114
+ }