clever-tools 3.12.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 +280 -6
- package/package.json +1 -1
- package/src/clever-client/auth-bridge.js +1 -1
- package/src/clever-client/operators.js +121 -0
- package/src/commands/addon.js +12 -7
- 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/delete.js +11 -10
- package/src/commands/deploy.js +35 -21
- package/src/commands/emails.js +174 -0
- package/src/commands/keycloak.js +138 -0
- package/src/commands/link.js +3 -1
- package/src/commands/makeDefault.js +2 -1
- package/src/commands/matomo.js +85 -0
- package/src/commands/metabase.js +112 -0
- package/src/commands/open.js +2 -5
- package/src/commands/otoroshi.js +138 -0
- package/src/commands/profile.js +2 -4
- package/src/commands/restart.js +1 -1
- package/src/commands/ssh-keys.js +159 -0
- package/src/commands/stop.js +1 -1
- package/src/commands/tcp-redirs.js +3 -3
- package/src/commands/tokens.js +16 -7
- package/src/commands/unlink.js +2 -1
- package/src/experimental-features.js +16 -15
- package/src/lib/operator-commands.js +281 -0
- package/src/lib/prompts.js +30 -0
- package/src/lib/slugify.js +10 -0
- package/src/models/addon.js +5 -2
- package/src/models/app_configuration.js +12 -9
- package/src/models/application.js +24 -12
- package/src/models/application_configuration.js +72 -72
- 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/operator.js +48 -0
- package/src/models/utils.js +21 -0
- package/src/parsers.js +14 -0
- package/src/prompt-password.js +0 -10
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
|
-
import { conf } from './models/configuration.js';
|
|
3
2
|
|
|
4
3
|
export const EXPERIMENTAL_FEATURES = {
|
|
5
4
|
kv: {
|
|
@@ -17,19 +16,6 @@ export const EXPERIMENTAL_FEATURES = {
|
|
|
17
16
|
Learn more about Materia KV: https://www.clever-cloud.com/developers/doc/addons/materia-kv/
|
|
18
17
|
`,
|
|
19
18
|
},
|
|
20
|
-
tokens: {
|
|
21
|
-
status: 'beta',
|
|
22
|
-
description: `Manage API tokens to query Clever Cloud API from ${conf.AUTH_BRIDGE_HOST}`,
|
|
23
|
-
instructions: dedent`
|
|
24
|
-
Create, list or revoke API tokens from a single command:
|
|
25
|
-
|
|
26
|
-
clever tokens create myTokenName
|
|
27
|
-
clever tokens --format json
|
|
28
|
-
clever tokens revoke myTokenId
|
|
29
|
-
|
|
30
|
-
Learn more about Clever Cloud API: https://www.clever-cloud.com/developers/api
|
|
31
|
-
`,
|
|
32
|
-
},
|
|
33
19
|
ng: {
|
|
34
20
|
status: 'beta',
|
|
35
21
|
description: 'Manage Network Groups to manage applications, add-ons, external peers through a Wireguard network',
|
|
@@ -55,7 +41,22 @@ export const EXPERIMENTAL_FEATURES = {
|
|
|
55
41
|
- Search Network Groups, members or peers:
|
|
56
42
|
clever ng search myQuery
|
|
57
43
|
|
|
58
|
-
Learn more about Network Groups: https://
|
|
44
|
+
Learn more about Network Groups: https://www.clever-cloud.com/developers/doc/develop/network-groups/
|
|
45
|
+
`,
|
|
46
|
+
},
|
|
47
|
+
operators: {
|
|
48
|
+
status: 'beta',
|
|
49
|
+
description: 'Manage operators and their features such as Keycloak, Matomo, Metabase, Otoroshi',
|
|
50
|
+
instructions: dedent`
|
|
51
|
+
clever keycloak
|
|
52
|
+
clever keycloak get keycloak_xxx
|
|
53
|
+
clever keycloak ng enable myKeycloak
|
|
54
|
+
|
|
55
|
+
clever metabase version check myMetabase
|
|
56
|
+
clever metabase version update myMetabase 0.53
|
|
57
|
+
|
|
58
|
+
clever matomo open myMatomo
|
|
59
|
+
clever otoroshi open logs myOtoroshi
|
|
59
60
|
`,
|
|
60
61
|
},
|
|
61
62
|
};
|
|
@@ -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/models/addon.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getAllAddonProviders } from '@clevercloud/client/esm/api/v2/product.js'
|
|
|
5
5
|
import { getSummary } from '@clevercloud/client/esm/api/v2/user.js';
|
|
6
6
|
import { getAddonProvider } from '@clevercloud/client/esm/api/v4/addon-providers.js';
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import { confirm } from '../lib/prompts.js';
|
|
9
9
|
import { Logger } from '../logger.js';
|
|
10
10
|
import { sendToApi } from '../models/send-to-api.js';
|
|
11
11
|
import { resolveOwnerId } from './ids-resolver.js';
|
|
@@ -203,7 +203,10 @@ 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,20 +1,21 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import {
|
|
3
|
+
addDependency,
|
|
3
4
|
create as createApplication,
|
|
4
|
-
remove as removeApplication,
|
|
5
|
-
getAll as getAllApplications,
|
|
6
5
|
get as getApplication,
|
|
7
|
-
|
|
8
|
-
update as updateApplication,
|
|
6
|
+
getAll as getAllApplications,
|
|
9
7
|
getAllDependencies,
|
|
10
|
-
|
|
8
|
+
redeploy as redeployApplication,
|
|
9
|
+
remove as removeApplication,
|
|
11
10
|
removeDependency,
|
|
11
|
+
update as updateApplication,
|
|
12
12
|
} from '@clevercloud/client/esm/api/v2/application.js';
|
|
13
13
|
import cliparse from 'cliparse';
|
|
14
14
|
import { getSummary } from '@clevercloud/client/esm/api/v2/user.js';
|
|
15
|
+
import colors from 'colors/safe.js';
|
|
15
16
|
|
|
16
17
|
import * as AppConfiguration from './app_configuration.js';
|
|
17
|
-
import
|
|
18
|
+
import { confirmAnswer } from '../lib/prompts.js';
|
|
18
19
|
import { Logger } from '../logger.js';
|
|
19
20
|
import * as Organisation from './organisation.js';
|
|
20
21
|
import * as User from './user.js';
|
|
@@ -24,10 +25,10 @@ import { resolveOwnerId } from './ids-resolver.js';
|
|
|
24
25
|
import { getAvailableInstances } from '@clevercloud/client/esm/api/v2/product.js';
|
|
25
26
|
|
|
26
27
|
export function listAvailableTypes () {
|
|
27
|
-
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']);
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
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'];
|
|
31
32
|
|
|
32
33
|
export function listAvailableZones () {
|
|
33
34
|
return cliparse.autocomplete.words(AVAILABLE_ZONES);
|
|
@@ -100,10 +101,10 @@ export async function deleteApp (app, skipConfirmation) {
|
|
|
100
101
|
Logger.debug('Deleting app: ' + app.name + ' (' + app.id + ')');
|
|
101
102
|
|
|
102
103
|
if (!skipConfirmation) {
|
|
103
|
-
await
|
|
104
|
-
`Deleting
|
|
104
|
+
await confirmAnswer(
|
|
105
|
+
`Deleting an application can't be undone, please type ${colors.green(app.name)} to confirm:`,
|
|
105
106
|
'No confirmation, aborting application deletion',
|
|
106
|
-
|
|
107
|
+
app.name,
|
|
107
108
|
);
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -164,9 +165,20 @@ async function getByName (ownerId, name) {
|
|
|
164
165
|
return getApplicationByName(apps, name);
|
|
165
166
|
};
|
|
166
167
|
|
|
168
|
+
function addInstanceLifetime (app) {
|
|
169
|
+
// Patch to help config commands
|
|
170
|
+
app.instanceLifetime = app.instance.lifetime;
|
|
171
|
+
return app;
|
|
172
|
+
}
|
|
173
|
+
|
|
167
174
|
export function get (ownerId, appId) {
|
|
168
175
|
Logger.debug(`Get information for the app: ${appId}`);
|
|
169
|
-
return getApplication({ id: ownerId, appId }).then(sendToApi);
|
|
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);
|
|
170
182
|
};
|
|
171
183
|
|
|
172
184
|
function getFromSelf (appId) {
|