clever-tools 3.11.0 → 3.13.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.
- package/bin/clever.js +416 -3
- package/package.json +4 -3
- package/src/clever-client/auth-bridge.js +61 -0
- package/src/clever-client/ng.js +18 -0
- package/src/clever-client/operators.js +121 -0
- package/src/commands/addon.js +34 -28
- package/src/commands/cancel-deploy.js +3 -4
- package/src/commands/config.js +16 -20
- package/src/commands/console.js +4 -10
- package/src/commands/create.js +76 -9
- package/src/commands/curl.js +17 -17
- package/src/commands/database.js +9 -9
- package/src/commands/delete.js +11 -10
- package/src/commands/deploy.js +35 -21
- package/src/commands/domain.js +13 -16
- package/src/commands/emails.js +174 -0
- package/src/commands/env.js +15 -8
- package/src/commands/keycloak.js +138 -0
- package/src/commands/kv.js +1 -1
- package/src/commands/link.js +7 -3
- package/src/commands/makeDefault.js +2 -1
- package/src/commands/matomo.js +85 -0
- package/src/commands/metabase.js +112 -0
- package/src/commands/ng.js +202 -0
- package/src/commands/open.js +2 -5
- package/src/commands/otoroshi.js +138 -0
- package/src/commands/profile.js +11 -10
- package/src/commands/published-config.js +7 -7
- package/src/commands/restart.js +1 -1
- package/src/commands/ssh-keys.js +159 -0
- package/src/commands/stop.js +3 -3
- package/src/commands/tcp-redirs.js +8 -8
- package/src/commands/tokens.js +146 -0
- package/src/commands/unlink.js +2 -1
- package/src/experimental-features.js +47 -2
- package/src/lib/ng-print.js +195 -0
- package/src/lib/operator-commands.js +281 -0
- package/src/lib/prompts.js +30 -0
- package/src/lib/slugify.js +10 -0
- package/src/logger.js +3 -0
- package/src/models/activity.js +2 -3
- package/src/models/addon.js +9 -6
- package/src/models/app_configuration.js +12 -9
- package/src/models/application.js +44 -22
- package/src/models/application_configuration.js +72 -72
- package/src/models/configuration.js +1 -0
- package/src/models/git.js +29 -1
- package/src/models/ids-resolver.js +37 -1
- package/src/models/interact.js +0 -24
- package/src/models/log-v4.js +13 -4
- package/src/models/namespaces.js +3 -2
- package/src/models/ng-resources.js +270 -0
- package/src/models/ng.js +276 -0
- package/src/models/operator.js +48 -0
- package/src/models/send-to-api.js +18 -0
- package/src/models/utils.js +21 -0
- package/src/parsers.js +71 -3
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import dedent from 'dedent';
|
|
4
|
+
|
|
5
|
+
import * as Operator from '../models/operator.js';
|
|
6
|
+
|
|
7
|
+
import { Logger } from '../logger.js';
|
|
8
|
+
import { sendToApi } from '../models/send-to-api.js';
|
|
9
|
+
import {
|
|
10
|
+
ngDisableOperator,
|
|
11
|
+
ngEnableOperator,
|
|
12
|
+
rebootOperator,
|
|
13
|
+
rebuildOperator,
|
|
14
|
+
versionCheck,
|
|
15
|
+
versionUpdate,
|
|
16
|
+
} from '../clever-client/operators.js';
|
|
17
|
+
import { findAddonsByAddonProvider } from '../models/ids-resolver.js';
|
|
18
|
+
import { openBrowser } from '../models/utils.js';
|
|
19
|
+
import { confirm, selectAnswer } from './prompts.js';
|
|
20
|
+
import { conf } from '../models/configuration.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check the version of an operator
|
|
24
|
+
* @param {string} provider The operator's provider
|
|
25
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
26
|
+
* @param {string} params.options.format The output format
|
|
27
|
+
* @returns {Promise<void>}
|
|
28
|
+
*/
|
|
29
|
+
export async function operatorCheckVersion (provider, addonIdOrName, format) {
|
|
30
|
+
|
|
31
|
+
const realId = await Operator.getSingleRealId(addonIdOrName);
|
|
32
|
+
const name = getDisplayName(addonIdOrName);
|
|
33
|
+
const versions = await versionCheck({ provider, realId }).then(sendToApi);
|
|
34
|
+
|
|
35
|
+
switch (format) {
|
|
36
|
+
case 'json':
|
|
37
|
+
Logger.printJson(versions);
|
|
38
|
+
break;
|
|
39
|
+
case 'human':
|
|
40
|
+
default:
|
|
41
|
+
if (!versions.needUpdate || (provider === 'metabase' && versions.installed === 'community-latest')) {
|
|
42
|
+
Logger.printSuccess(`${colors.green(name)} is up-to-date (${colors.green(versions.installed)})`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
Logger.println(dedent`
|
|
46
|
+
🔄 ${colors.red(name)} is outdated
|
|
47
|
+
• Installed version: ${colors.red(versions.installed)}
|
|
48
|
+
• Latest version: ${colors.green(versions.latest)}
|
|
49
|
+
`);
|
|
50
|
+
Logger.println();
|
|
51
|
+
|
|
52
|
+
await confirm(
|
|
53
|
+
`Do you want to update it to ${colors.green(versions.latest)} now?`,
|
|
54
|
+
'No confirmation, aborting version update',
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
await versionUpdate({ provider, realId }, { targetVersion: versions.latest }).then(sendToApi);
|
|
58
|
+
Logger.printSuccess(`${colors.green(name)} is up-to-date and being rebuilt…`);
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Update the version of an operator
|
|
66
|
+
* @param {string} provider The operator's provider
|
|
67
|
+
* @param {string} askedVersion The version to update to
|
|
68
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
69
|
+
* @returns {Promise<void>}
|
|
70
|
+
*/
|
|
71
|
+
export async function operatorUpdateVersion (provider, askedVersion, addonIdOrName) {
|
|
72
|
+
const realId = await Operator.getSingleRealId(addonIdOrName);
|
|
73
|
+
const name = getDisplayName(addonIdOrName);
|
|
74
|
+
|
|
75
|
+
const versions = await versionCheck({ provider, realId }).then(sendToApi);
|
|
76
|
+
|
|
77
|
+
const targetVersion = askedVersion ?? await selectAnswer(
|
|
78
|
+
`Which version do you want to update ${colors.blue(name)} to, current is ${colors.blue(versions.installed)}?`,
|
|
79
|
+
versions.available.reverse(),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (!versions.available.includes(targetVersion)) {
|
|
83
|
+
throw new Error(`Version ${colors.red(targetVersion)} is not available`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (versions.installed === targetVersion) {
|
|
87
|
+
Logger.printSuccess(`${colors.green(name)} is already at version ${colors.green(targetVersion)}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await versionUpdate({ provider, realId }, { targetVersion }).then(sendToApi);
|
|
92
|
+
Logger.printSuccess(`${colors.green(name)} updated to ${colors.green(targetVersion)} and being rebuilt…`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Unlink an operator from a Network Group
|
|
97
|
+
* @param {string} provider The operator's provider
|
|
98
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
* @throws {Error} If the Network Group feature is already disabled
|
|
101
|
+
*/
|
|
102
|
+
export async function operatorNgDisable (provider, addonIdOrName) {
|
|
103
|
+
const name = getDisplayName(addonIdOrName);
|
|
104
|
+
const operator = await Operator.getDetails(provider, addonIdOrName);
|
|
105
|
+
if (!operator.features.networkGroup?.id) {
|
|
106
|
+
throw new Error(`Network Group is already disabled on ${colors.red(name)}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await ngDisableOperator({ provider, realId: operator.resourceId }).then(sendToApi);
|
|
110
|
+
Logger.println(`Disabling Network Group on ${colors.blue(name)}…`);
|
|
111
|
+
|
|
112
|
+
await operatorPrint(provider, addonIdOrName);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Link an operator to a Network Group
|
|
117
|
+
* @param {string} provider The operator's provider
|
|
118
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
* @throws {Error} If the Network Group feature is already enabled
|
|
121
|
+
*/
|
|
122
|
+
export async function operatorNgEnable (provider, addonIdOrName) {
|
|
123
|
+
const name = getDisplayName(addonIdOrName);
|
|
124
|
+
const operator = await Operator.getDetails(provider, addonIdOrName);
|
|
125
|
+
|
|
126
|
+
if (operator.features.networkGroup?.id) {
|
|
127
|
+
throw new Error(`Network Group is already enabled on ${colors.red(name)}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await ngEnableOperator({ provider, realId: operator.resourceId }).then(sendToApi);
|
|
131
|
+
Logger.println(`Enabling Network Group on ${colors.blue(name)}…`);
|
|
132
|
+
|
|
133
|
+
await operatorPrint(provider, addonIdOrName);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* List all operators for a given provider
|
|
138
|
+
* @param {string} provider The operator's provider
|
|
139
|
+
* @param {string} format The output format
|
|
140
|
+
* @returns {Promise<void>}
|
|
141
|
+
*/
|
|
142
|
+
export async function operatorList (provider, format) {
|
|
143
|
+
const deployed = await findAddonsByAddonProvider(provider);
|
|
144
|
+
const providerName = _.capitalize(provider.replace('addon-', ''));
|
|
145
|
+
const operatorsPerOwner = _.groupBy(deployed, 'ownerId');
|
|
146
|
+
|
|
147
|
+
switch (format) {
|
|
148
|
+
case 'json':
|
|
149
|
+
Logger.printJson(operatorsPerOwner);
|
|
150
|
+
break;
|
|
151
|
+
case 'human':
|
|
152
|
+
default:
|
|
153
|
+
|
|
154
|
+
if (deployed.length === 0) {
|
|
155
|
+
Logger.println(`🔎 No ${providerName} found, create one with ${colors.blue(`clever addon create ${providerName.toLocaleLowerCase()}`)} command`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Logger.println(`🔎 Found ${deployed.length} ${providerName} operator${deployed.length > 1 ? 's' : ''}:`);
|
|
160
|
+
Logger.println();
|
|
161
|
+
|
|
162
|
+
Object.values(operatorsPerOwner).forEach((operators) => {
|
|
163
|
+
Logger.println(`• ${colors.bold(`${(operators[0].ownerId)} (${(operators[0].ownerName)})`)}`);
|
|
164
|
+
operators.forEach((operator) => {
|
|
165
|
+
Logger.println(` • ${operator.name} ${colors.grey(`(${operator.realId})`)}`);
|
|
166
|
+
});
|
|
167
|
+
Logger.println();
|
|
168
|
+
});
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Open an operator dashboard in the Clever Cloud Console in the browser
|
|
175
|
+
* @param {string} provider The operator's provider
|
|
176
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
179
|
+
export async function operatorOpen (provider, addonIdOrName) {
|
|
180
|
+
const operator = await Operator.getDetails(provider, addonIdOrName);
|
|
181
|
+
await openBrowser(`${conf.GOTO_URL}/${operator.addonId}`, `🌐 Opening ${colors.blue(operator.addonId)} in the browser…`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Open the Logs section of an operator application in the Clever Cloud Console
|
|
186
|
+
* @param {string} provider The operator's provider
|
|
187
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
188
|
+
* @returns {Promise<void>}
|
|
189
|
+
*/
|
|
190
|
+
export async function operatorOpenLogs (provider, addonIdOrName) {
|
|
191
|
+
const operator = await Operator.getDetails(provider, addonIdOrName);
|
|
192
|
+
await openBrowser(
|
|
193
|
+
`/organisations/${operator.ownerId}/applications/${operator.resources.entrypoint}/logs`,
|
|
194
|
+
`🌐 Opening ${colors.blue(operator.addonId)} logs in the Clever Cloud Console…`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Open an operator Web UI in the browser
|
|
200
|
+
* @param {string} provider The operator's provider
|
|
201
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
202
|
+
* @returns {Promise<void>}
|
|
203
|
+
*/
|
|
204
|
+
export async function operatorOpenWebUi (provider, addonIdOrName) {
|
|
205
|
+
const operator = await Operator.getDetails(provider, addonIdOrName);
|
|
206
|
+
await openBrowser(operator.accessUrl, `🌐 Opening ${colors.blue(operator.addonId)} Management interface in the browser…`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Reboot an operator
|
|
211
|
+
* @param {object} params The command's parameters
|
|
212
|
+
* @param {string} provider The operator's provider
|
|
213
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
214
|
+
* @returns {Promise<void>}
|
|
215
|
+
*/
|
|
216
|
+
export async function operatorReboot (provider, addonIdOrName) {
|
|
217
|
+
const name = getDisplayName(addonIdOrName);
|
|
218
|
+
const realId = await Operator.getSingleRealId(addonIdOrName);
|
|
219
|
+
await rebootOperator({ provider, realId }).then(sendToApi);
|
|
220
|
+
Logger.println(`🔄 Restarting ${colors.blue(name)}…`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Rebuild an operator
|
|
225
|
+
* @param {object} params The command's parameters
|
|
226
|
+
* @param {string} provider The operator's provider
|
|
227
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
228
|
+
* @returns {Promise<void>}
|
|
229
|
+
*/
|
|
230
|
+
export async function operatorRebuild (provider, addonIdOrName) {
|
|
231
|
+
const name = getDisplayName(addonIdOrName);
|
|
232
|
+
const realId = await Operator.getSingleRealId(addonIdOrName);
|
|
233
|
+
await rebuildOperator({ provider, realId }).then(sendToApi);
|
|
234
|
+
Logger.println(`🔄 Rebuilding ${colors.blue(name)}…`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Print the details of an operator
|
|
239
|
+
* @param {string} provider The operator's provider
|
|
240
|
+
* @param {{ addon_name?: string, operator_id?: string, addon_id?: string }} addonIdOrName The operator's name or ID
|
|
241
|
+
* @param {string} format The output format
|
|
242
|
+
* @returns {void}
|
|
243
|
+
*/
|
|
244
|
+
export async function operatorPrint (provider, addonIdOrName, format = 'human') {
|
|
245
|
+
|
|
246
|
+
const operator = await Operator.getDetails(provider, addonIdOrName);
|
|
247
|
+
|
|
248
|
+
const dataToPrint = {
|
|
249
|
+
Name: operator.name,
|
|
250
|
+
ID: operator.resourceId,
|
|
251
|
+
Owner: operator.ownerId,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
dataToPrint.Version = provider === 'matomo'
|
|
255
|
+
? `${operator.version} (PHP ${operator.phpVersion})`
|
|
256
|
+
: `${operator.version} (Java ${operator.javaVersion})`;
|
|
257
|
+
|
|
258
|
+
dataToPrint['Access URL'] = operator.accessUrl;
|
|
259
|
+
|
|
260
|
+
if (provider === 'otoroshi') {
|
|
261
|
+
dataToPrint['API URL'] = operator.api.url;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (['otoroshi', 'keycloak'].includes(provider)) {
|
|
265
|
+
dataToPrint['Network Group'] = operator.features.networkGroup?.id ?? false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
switch (format) {
|
|
269
|
+
case 'json':
|
|
270
|
+
Logger.printJson(operator);
|
|
271
|
+
break;
|
|
272
|
+
case 'human':
|
|
273
|
+
default:
|
|
274
|
+
console.table(dataToPrint);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getDisplayName (addonIdOrName) {
|
|
280
|
+
return addonIdOrName.addon_name ?? addonIdOrName.operator_id ?? addonIdOrName.addon_id;
|
|
281
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { confirm as confirmPrompt, input, password, select } from '@inquirer/prompts';
|
|
2
|
+
|
|
3
|
+
export function promptSecret (message) {
|
|
4
|
+
return password({ message, mask: true }).catch(exitOnPromptError);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function confirm (message, rejectionMessage) {
|
|
8
|
+
const answer = await confirmPrompt({ message }).catch(exitOnPromptError);
|
|
9
|
+
if (!answer) {
|
|
10
|
+
throw new Error(rejectionMessage);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function confirmAnswer (message, rejectionMessage, expectedAnswer) {
|
|
15
|
+
const answer = await input({ message }).catch(exitOnPromptError);
|
|
16
|
+
if (answer !== expectedAnswer) {
|
|
17
|
+
throw new Error(rejectionMessage);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function selectAnswer (message, choices) {
|
|
22
|
+
return select({ message, choices }).catch(exitOnPromptError);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function exitOnPromptError (error) {
|
|
26
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import slugifyRaw from 'slugify';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts a string to a slug using strict mode
|
|
5
|
+
* @param {string} string - The string to be converted to a slug
|
|
6
|
+
* @returns {string} The slugified string
|
|
7
|
+
*/
|
|
8
|
+
export function slugify (string) {
|
|
9
|
+
return slugifyRaw(string, { strict: true });
|
|
10
|
+
}
|
package/src/logger.js
CHANGED
|
@@ -55,6 +55,9 @@ export const Logger = _(['debug', 'info', 'warn', 'error'])
|
|
|
55
55
|
// No decoration for Logger.println
|
|
56
56
|
Logger.println = console.log;
|
|
57
57
|
|
|
58
|
+
// Logger for success with a green check before the message
|
|
59
|
+
Logger.printSuccess = (message) => console.log(`${colors.bold.green('✓')} ${message}`);
|
|
60
|
+
|
|
58
61
|
// No decoration for Logger.println
|
|
59
62
|
Logger.printJson = (obj) => {
|
|
60
63
|
console.log(JSON.stringify(obj, null, 2));
|
package/src/models/activity.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import * as application from '@clevercloud/client/esm/api/v2/application.js';
|
|
2
|
-
|
|
3
1
|
import { sendToApi } from './send-to-api.js';
|
|
2
|
+
import { getAllDeployments } from '@clevercloud/client/esm/api/v2/application.js';
|
|
4
3
|
|
|
5
4
|
export function list (ownerId, appId, showAll) {
|
|
6
5
|
const limit = showAll ? null : 10;
|
|
7
|
-
return
|
|
6
|
+
return getAllDeployments({ id: ownerId, appId, limit }).then(sendToApi);
|
|
8
7
|
};
|
package/src/models/addon.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as application from '@clevercloud/client/esm/api/v2/application.js';
|
|
2
1
|
import cliparse from 'cliparse';
|
|
3
2
|
|
|
4
3
|
import { get as getAddon, getAll as getAllAddons, getAllEnvVars, remove as removeAddon, create as createAddon, update as updateAddon } from '@clevercloud/client/esm/api/v2/addon.js';
|
|
@@ -6,10 +5,11 @@ import { getAllAddonProviders } from '@clevercloud/client/esm/api/v2/product.js'
|
|
|
6
5
|
import { getSummary } from '@clevercloud/client/esm/api/v2/user.js';
|
|
7
6
|
import { getAddonProvider } from '@clevercloud/client/esm/api/v4/addon-providers.js';
|
|
8
7
|
|
|
9
|
-
import
|
|
8
|
+
import { confirm } from '../lib/prompts.js';
|
|
10
9
|
import { Logger } from '../logger.js';
|
|
11
10
|
import { sendToApi } from '../models/send-to-api.js';
|
|
12
11
|
import { resolveOwnerId } from './ids-resolver.js';
|
|
12
|
+
import { getAllLinkedAddons, linkAddon, unlinkAddon } from '@clevercloud/client/esm/api/v2/application.js';
|
|
13
13
|
|
|
14
14
|
export function listProviders () {
|
|
15
15
|
return getAllAddonProviders({}).then(sendToApi);
|
|
@@ -42,7 +42,7 @@ export async function list (ownerId, appId, showAll) {
|
|
|
42
42
|
return allAddons;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const myAddons = await
|
|
45
|
+
const myAddons = await getAllLinkedAddons({ id: ownerId, appId }).then(sendToApi);
|
|
46
46
|
|
|
47
47
|
if (showAll == null) {
|
|
48
48
|
return myAddons;
|
|
@@ -191,19 +191,22 @@ async function getId (ownerId, addon) {
|
|
|
191
191
|
|
|
192
192
|
export async function link (ownerId, appId, addon) {
|
|
193
193
|
const addonId = await getId(ownerId, addon);
|
|
194
|
-
return
|
|
194
|
+
return linkAddon({ id: ownerId, appId }, JSON.stringify(addonId)).then(sendToApi);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
export async function unlink (ownerId, appId, addon) {
|
|
198
198
|
const addonId = await getId(ownerId, addon);
|
|
199
|
-
return
|
|
199
|
+
return unlinkAddon({ id: ownerId, appId, addonId }).then(sendToApi);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
export async function deleteAddon (ownerId, addonIdOrName, skipConfirmation) {
|
|
203
203
|
const addonId = await getId(ownerId, addonIdOrName);
|
|
204
204
|
|
|
205
205
|
if (!skipConfirmation) {
|
|
206
|
-
await
|
|
206
|
+
await confirm(
|
|
207
|
+
'Deleting the add-on can\'t be undone, are you sure?',
|
|
208
|
+
'No confirmation, aborting add-on deletion',
|
|
209
|
+
);
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
return removeAddon({ id: ownerId, addonId }).then(sendToApi);
|
|
@@ -2,7 +2,7 @@ import { promises as fs } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
import _ from 'lodash';
|
|
5
|
-
import slugify from 'slugify';
|
|
5
|
+
import { slugify } from '../lib/slugify.js';
|
|
6
6
|
|
|
7
7
|
import { Logger } from '../logger.js';
|
|
8
8
|
import * as User from './user.js';
|
|
@@ -54,19 +54,22 @@ export async function addLinkedApplication (appData, alias, ignoreParentConfig)
|
|
|
54
54
|
|
|
55
55
|
export async function removeLinkedApplication ({ appId, alias }) {
|
|
56
56
|
const currentConfig = await loadApplicationConf();
|
|
57
|
+
const appToUnlink = currentConfig.apps.find((a) => a.app_id === appId || a.alias === alias);
|
|
58
|
+
if (appToUnlink == null) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
57
61
|
const newConfig = {
|
|
58
62
|
...currentConfig,
|
|
59
|
-
apps: currentConfig.apps.filter((
|
|
60
|
-
return appEntry.app_id !== appId && appEntry.alias !== alias;
|
|
61
|
-
}),
|
|
63
|
+
apps: currentConfig.apps.filter((a) => a !== appToUnlink),
|
|
62
64
|
};
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const isDefault = currentConfig.default === appToUnlink.app_id;
|
|
67
|
+
if (isDefault) {
|
|
68
|
+
delete newConfig.default;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
await persistConfig(newConfig);
|
|
72
|
+
return true;
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
export function findApp (config, alias) {
|
|
@@ -134,7 +137,7 @@ async function getAppDetailsForId (appId) {
|
|
|
134
137
|
}
|
|
135
138
|
if (secondAppById != null) {
|
|
136
139
|
throw new Error(`There are several applications matching id '${appId}'.`
|
|
137
|
-
|
|
140
|
+
+ 'This should not happen, your `.clever.json` should be fixed.');
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
return appById;
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
addDependency,
|
|
4
|
+
create as createApplication,
|
|
5
|
+
get as getApplication,
|
|
6
|
+
getAll as getAllApplications,
|
|
7
|
+
getAllDependencies,
|
|
8
|
+
redeploy as redeployApplication,
|
|
9
|
+
remove as removeApplication,
|
|
10
|
+
removeDependency,
|
|
11
|
+
update as updateApplication,
|
|
12
|
+
} from '@clevercloud/client/esm/api/v2/application.js';
|
|
3
13
|
import cliparse from 'cliparse';
|
|
4
|
-
import * as product from '@clevercloud/client/esm/api/v2/product.js';
|
|
5
14
|
import { getSummary } from '@clevercloud/client/esm/api/v2/user.js';
|
|
15
|
+
import colors from 'colors/safe.js';
|
|
6
16
|
|
|
7
17
|
import * as AppConfiguration from './app_configuration.js';
|
|
8
|
-
import
|
|
18
|
+
import { confirmAnswer } from '../lib/prompts.js';
|
|
9
19
|
import { Logger } from '../logger.js';
|
|
10
20
|
import * as Organisation from './organisation.js';
|
|
11
21
|
import * as User from './user.js';
|
|
12
22
|
|
|
13
23
|
import { sendToApi } from '../models/send-to-api.js';
|
|
14
24
|
import { resolveOwnerId } from './ids-resolver.js';
|
|
25
|
+
import { getAvailableInstances } from '@clevercloud/client/esm/api/v2/product.js';
|
|
15
26
|
|
|
16
27
|
export function listAvailableTypes () {
|
|
17
|
-
return cliparse.autocomplete.words(['docker', 'elixir', 'go', 'gradle', 'haskell', 'jar', 'maven', 'meteor', 'node', 'php', 'play1', 'play2', 'python', 'ruby', 'rust', 'sbt', 'static-apache', 'war']);
|
|
28
|
+
return cliparse.autocomplete.words(['docker', 'elixir', 'frankenphp', 'go', 'gradle', 'haskell', 'jar', 'linux', 'maven', 'meteor', 'node', 'php', 'play1', 'play2', 'python', 'ruby', 'rust', 'sbt', 'static', 'static-apache', 'v', 'war']);
|
|
18
29
|
};
|
|
19
30
|
|
|
20
|
-
export const AVAILABLE_ZONES = ['par', 'grahds', 'rbx', 'rbxhds', 'scw', 'mtl', 'sgp', 'syd', 'wsw'];
|
|
31
|
+
export const AVAILABLE_ZONES = ['par', 'parhds', 'grahds', 'rbx', 'rbxhds', 'scw', 'ldn', 'mtl', 'sgp', 'syd', 'wsw'];
|
|
21
32
|
|
|
22
33
|
export function listAvailableZones () {
|
|
23
34
|
return cliparse.autocomplete.words(AVAILABLE_ZONES);
|
|
@@ -42,7 +53,7 @@ async function getId (ownerId, dependency) {
|
|
|
42
53
|
async function getInstanceType (type) {
|
|
43
54
|
|
|
44
55
|
// TODO: We should be able to use it without {}
|
|
45
|
-
const types = await
|
|
56
|
+
const types = await getAvailableInstances({}).then(sendToApi);
|
|
46
57
|
|
|
47
58
|
const enabledTypes = types.filter((t) => t.enabled);
|
|
48
59
|
const matchingVariants = enabledTypes.filter((t) => t.variant != null && t.variant.slug === type);
|
|
@@ -83,21 +94,21 @@ export async function create (name, typeName, region, orgaIdOrName, github, isTa
|
|
|
83
94
|
newApp.oauthApp = github;
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
return
|
|
97
|
+
return createApplication({ id: ownerId }, newApp).then(sendToApi);
|
|
87
98
|
};
|
|
88
99
|
|
|
89
100
|
export async function deleteApp (app, skipConfirmation) {
|
|
90
101
|
Logger.debug('Deleting app: ' + app.name + ' (' + app.id + ')');
|
|
91
102
|
|
|
92
103
|
if (!skipConfirmation) {
|
|
93
|
-
await
|
|
94
|
-
`Deleting
|
|
104
|
+
await confirmAnswer(
|
|
105
|
+
`Deleting an application can't be undone, please type ${colors.green(app.name)} to confirm:`,
|
|
95
106
|
'No confirmation, aborting application deletion',
|
|
96
|
-
|
|
107
|
+
app.name,
|
|
97
108
|
);
|
|
98
109
|
}
|
|
99
110
|
|
|
100
|
-
return
|
|
111
|
+
return removeApplication({ id: app.ownerId, appId: app.id }).then(sendToApi);
|
|
101
112
|
};
|
|
102
113
|
|
|
103
114
|
export async function getAllApps (ownerId) {
|
|
@@ -123,7 +134,7 @@ export async function getAllApps (ownerId) {
|
|
|
123
134
|
};
|
|
124
135
|
|
|
125
136
|
async function getApplicationsForOwner (ownerId) {
|
|
126
|
-
const rawApplications = await
|
|
137
|
+
const rawApplications = await getAllApplications({ id: ownerId }).then(sendToApi);
|
|
127
138
|
return rawApplications.map((app) => {
|
|
128
139
|
return {
|
|
129
140
|
app_id: app.id,
|
|
@@ -150,13 +161,24 @@ function getApplicationByName (apps, name) {
|
|
|
150
161
|
};
|
|
151
162
|
|
|
152
163
|
async function getByName (ownerId, name) {
|
|
153
|
-
const apps = await
|
|
164
|
+
const apps = await getAllApplications({ id: ownerId }).then(sendToApi);
|
|
154
165
|
return getApplicationByName(apps, name);
|
|
155
166
|
};
|
|
156
167
|
|
|
168
|
+
function addInstanceLifetime (app) {
|
|
169
|
+
// Patch to help config commands
|
|
170
|
+
app.instanceLifetime = app.instance.lifetime;
|
|
171
|
+
return app;
|
|
172
|
+
}
|
|
173
|
+
|
|
157
174
|
export function get (ownerId, appId) {
|
|
158
175
|
Logger.debug(`Get information for the app: ${appId}`);
|
|
159
|
-
return
|
|
176
|
+
return getApplication({ id: ownerId, appId }).then(sendToApi).then(addInstanceLifetime);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export function updateOptions (ownerId, appId, options) {
|
|
180
|
+
Logger.debug(`Update app: ${appId}`);
|
|
181
|
+
return updateApplication({ id: ownerId, appId }, options).then(sendToApi).then(addInstanceLifetime);
|
|
160
182
|
};
|
|
161
183
|
|
|
162
184
|
function getFromSelf (appId) {
|
|
@@ -164,7 +186,7 @@ function getFromSelf (appId) {
|
|
|
164
186
|
// /self differs from /organisations only for this one:
|
|
165
187
|
// it fallbacks to the organisations of which the user
|
|
166
188
|
// is a member, if it doesn't belong to Personal Space.
|
|
167
|
-
return
|
|
189
|
+
return getApplication({ appId }).then(sendToApi);
|
|
168
190
|
};
|
|
169
191
|
|
|
170
192
|
/**
|
|
@@ -245,7 +267,7 @@ export function unlinkRepo (alias) {
|
|
|
245
267
|
export function redeploy (ownerId, appId, commit, withoutCache) {
|
|
246
268
|
Logger.debug(`Redeploying the app: ${appId}`);
|
|
247
269
|
const useCache = (withoutCache) ? 'no' : null;
|
|
248
|
-
return
|
|
270
|
+
return redeployApplication({ id: ownerId, appId, commit, useCache }).then(sendToApi);
|
|
249
271
|
};
|
|
250
272
|
|
|
251
273
|
export function mergeScalabilityParameters (scalabilityParameters, instance) {
|
|
@@ -283,7 +305,7 @@ export function mergeScalabilityParameters (scalabilityParameters, instance) {
|
|
|
283
305
|
export async function setScalability (appId, ownerId, scalabilityParameters, buildFlavor) {
|
|
284
306
|
Logger.info('Scaling the app: ' + appId);
|
|
285
307
|
|
|
286
|
-
const app = await
|
|
308
|
+
const app = await getApplication({ id: ownerId, appId }).then(sendToApi);
|
|
287
309
|
const instance = _.cloneDeep(app.instance);
|
|
288
310
|
|
|
289
311
|
instance.minFlavor = instance.minFlavor.name;
|
|
@@ -301,17 +323,17 @@ export async function setScalability (appId, ownerId, scalabilityParameters, bui
|
|
|
301
323
|
}
|
|
302
324
|
}
|
|
303
325
|
|
|
304
|
-
return
|
|
326
|
+
return updateApplication({ id: ownerId, appId }, newConfig).then(sendToApi);
|
|
305
327
|
};
|
|
306
328
|
|
|
307
329
|
export async function listDependencies (ownerId, appId, showAll) {
|
|
308
|
-
const applicationDeps = await
|
|
330
|
+
const applicationDeps = await getAllDependencies({ id: ownerId, appId }).then(sendToApi);
|
|
309
331
|
|
|
310
332
|
if (!showAll) {
|
|
311
333
|
return applicationDeps.map((app) => ({ ...app, isLinked: true }));
|
|
312
334
|
}
|
|
313
335
|
|
|
314
|
-
const allApps = await
|
|
336
|
+
const allApps = await getAllApplications({ id: ownerId }).then(sendToApi);
|
|
315
337
|
|
|
316
338
|
const applicationDepsIds = applicationDeps.map((app) => app.id);
|
|
317
339
|
return allApps.map((app) => {
|
|
@@ -322,10 +344,10 @@ export async function listDependencies (ownerId, appId, showAll) {
|
|
|
322
344
|
|
|
323
345
|
export async function link (ownerId, appId, dependency) {
|
|
324
346
|
const dependencyId = await getId(ownerId, dependency);
|
|
325
|
-
return
|
|
347
|
+
return addDependency({ id: ownerId, appId, dependencyId }).then(sendToApi);
|
|
326
348
|
};
|
|
327
349
|
|
|
328
350
|
export async function unlink (ownerId, appId, dependency) {
|
|
329
351
|
const dependencyId = await getId(ownerId, dependency);
|
|
330
|
-
return
|
|
352
|
+
return removeDependency({ id: ownerId, appId, dependencyId }).then(sendToApi);
|
|
331
353
|
};
|