clever-tools 3.6.1 → 3.8.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/README.md +1 -7
- package/bin/clever.js +99 -47
- package/package.json +48 -50
- package/src/command-options.js +22 -3
- package/src/commands/accesslogs.js +114 -34
- package/src/commands/activity.js +70 -16
- package/src/commands/addon.js +153 -75
- package/src/commands/applications.js +83 -1
- package/src/commands/cancel-deploy.js +3 -3
- package/src/commands/config.js +6 -7
- package/src/commands/console.js +16 -4
- package/src/commands/curl.js +13 -16
- package/src/commands/delete.js +16 -6
- package/src/commands/deploy.js +9 -6
- package/src/commands/diag.js +68 -28
- package/src/commands/domain.js +13 -13
- package/src/commands/drain.js +42 -27
- package/src/commands/env.js +53 -24
- package/src/commands/logs.js +3 -3
- package/src/commands/notify-email.js +33 -18
- package/src/commands/open.js +3 -3
- package/src/commands/profile.js +29 -9
- package/src/commands/published-config.js +26 -15
- package/src/commands/restart.js +12 -5
- package/src/commands/scale.js +2 -3
- package/src/commands/service.js +42 -15
- package/src/commands/ssh.js +3 -3
- package/src/commands/status.js +72 -46
- package/src/commands/stop.js +3 -3
- package/src/commands/tcp-redirs.js +54 -18
- package/src/commands/webhooks.js +28 -13
- package/src/logger.js +9 -6
- package/src/models/addon.js +21 -12
- package/src/models/app_configuration.js +11 -3
- package/src/models/application.js +106 -7
- package/src/models/exit-strategy-option.js +24 -0
- package/src/models/json-array.js +28 -0
- package/src/models/log-v4.js +9 -29
- package/src/models/log.js +9 -2
- package/src/models/namespaces.js +20 -0
- package/src/models/organisation.js +0 -20
- package/src/models/utils.js +8 -1
- package/src/models/accesslogs.js +0 -54
- package/vendors/README_VENDORS.md +0 -18
- package/vendors/curlconverter-parse.js +0 -3709
package/src/commands/status.js
CHANGED
|
@@ -3,22 +3,49 @@
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const colors = require('colors/safe');
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const Application = require('../models/application.js');
|
|
7
7
|
const Logger = require('../logger.js');
|
|
8
8
|
|
|
9
9
|
const { get: getApplication, getAllInstances } = require('@clevercloud/client/cjs/api/v2/application.js');
|
|
10
10
|
const { sendToApi } = require('../models/send-to-api.js');
|
|
11
11
|
|
|
12
|
-
function
|
|
13
|
-
|
|
12
|
+
async function status (params) {
|
|
13
|
+
const { alias, app: appIdOrName, format } = params.options;
|
|
14
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
15
|
+
|
|
16
|
+
const instances = await getAllInstances({ id: ownerId, appId }).then(sendToApi);
|
|
17
|
+
const app = await getApplication({ id: ownerId, appId }).then(sendToApi);
|
|
18
|
+
|
|
19
|
+
const status = computeStatus(instances, app);
|
|
20
|
+
|
|
21
|
+
switch (format) {
|
|
22
|
+
case 'json': {
|
|
23
|
+
Logger.printJson(status);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case 'human':
|
|
27
|
+
default: {
|
|
28
|
+
const statusMessage = status.status === 'running'
|
|
29
|
+
? `${colors.bold.green('running')} ${displayInstances(status.instances, status.commit)}`
|
|
30
|
+
: colors.bold.red('stopped');
|
|
31
|
+
|
|
32
|
+
Logger.println(`${status.name}: ${statusMessage}`);
|
|
33
|
+
Logger.println(`Executed as: ${colors.bold(status.lifetime)}`);
|
|
34
|
+
if (status.deploymentInProgress) {
|
|
35
|
+
Logger.println(`Deployment in progress ${displayInstances(status.deploymentInProgress.instances, status.deploymentInProgress.commit)}`);
|
|
36
|
+
}
|
|
37
|
+
Logger.println();
|
|
38
|
+
Logger.println('Scalability:');
|
|
39
|
+
Logger.println(` Auto scalability: ${status.scalability.enabled ? colors.green('enabled') : colors.red('disabled')}`);
|
|
40
|
+
Logger.println(` Scalers: ${colors.bold(formatScalability(status.scalability.horizontal))}`);
|
|
41
|
+
Logger.println(` Sizes: ${colors.bold(formatScalability(status.scalability.vertical))}`);
|
|
42
|
+
Logger.println(` Dedicated build: ${status.separateBuild ? colors.bold(status.buildFlavor) : colors.red('disabled')}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
14
45
|
}
|
|
15
46
|
|
|
16
|
-
function
|
|
17
|
-
return
|
|
18
|
-
.groupBy((i) => i.flavor.name)
|
|
19
|
-
.map((instances, flavorName) => `${instances.length}*${flavorName}`)
|
|
20
|
-
.value()
|
|
21
|
-
.join(', ');
|
|
47
|
+
function displayInstances (instances, commit) {
|
|
48
|
+
return `(${instances.map((instance) => `${instance.count}*${instance.flavor}`)}, Commit: ${commit || 'N/A'})`;
|
|
22
49
|
}
|
|
23
50
|
|
|
24
51
|
function computeStatus (instances, app) {
|
|
@@ -30,50 +57,49 @@ function computeStatus (instances, app) {
|
|
|
30
57
|
const isDeploying = !_.isEmpty(deployingInstances);
|
|
31
58
|
const deployingCommit = _(deployingInstances).map('commit').head();
|
|
32
59
|
|
|
33
|
-
const statusMessage = isUp
|
|
34
|
-
? `${colors.bold.green('running')} ${displayGroupInfo(upInstances, upCommit)}`
|
|
35
|
-
: colors.bold.red('stopped');
|
|
36
|
-
|
|
37
|
-
const statusLine = `${app.name}: ${statusMessage}`;
|
|
38
|
-
const taskLine = `Executed as: ${colors.bold(app.instance.lifetime)}`;
|
|
39
|
-
const deploymentLine = isDeploying
|
|
40
|
-
? `Deployment in progress ${displayGroupInfo(deployingInstances, deployingCommit)}`
|
|
41
|
-
: '';
|
|
42
|
-
|
|
43
|
-
return [statusLine, taskLine, deploymentLine].join('\n');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function displayScalability (app) {
|
|
47
|
-
|
|
48
60
|
const { minFlavor, maxFlavor, minInstances, maxInstances } = app.instance;
|
|
49
61
|
|
|
50
|
-
const
|
|
51
|
-
? minFlavor.name
|
|
52
|
-
: `${minFlavor.name} to ${maxFlavor.name}`;
|
|
53
|
-
|
|
54
|
-
const horizontal = (minInstances === maxInstances)
|
|
55
|
-
? minInstances
|
|
56
|
-
: `${minInstances} to ${maxInstances}`;
|
|
57
|
-
|
|
58
|
-
const enabled = (minFlavor.name !== maxFlavor.name)
|
|
62
|
+
const scalabilityEnabled = (minFlavor.name !== maxFlavor.name)
|
|
59
63
|
|| (minInstances !== maxInstances);
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const status = {
|
|
66
|
+
id: app.id,
|
|
67
|
+
name: app.name,
|
|
68
|
+
lifetime: app.instance.lifetime,
|
|
69
|
+
status: isUp ? 'running' : 'stopped',
|
|
70
|
+
commit: upCommit,
|
|
71
|
+
instances: groupInstances(upInstances),
|
|
72
|
+
scalability: {
|
|
73
|
+
enabled: scalabilityEnabled,
|
|
74
|
+
vertical: { min: minFlavor.name, max: maxFlavor.name },
|
|
75
|
+
horizontal: { min: minInstances, max: maxInstances },
|
|
76
|
+
},
|
|
77
|
+
separateBuild: app.separateBuild,
|
|
78
|
+
buildFlavor: app.buildFlavor.name,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (isDeploying) {
|
|
82
|
+
status.deploymentInProgress = {
|
|
83
|
+
commit: deployingCommit,
|
|
84
|
+
instances: groupInstances(deployingInstances),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return status;
|
|
66
89
|
}
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const instances = await getAllInstances({ id: ownerId, appId }).then(sendToApi);
|
|
73
|
-
const app = await getApplication({ id: ownerId, appId }).then(sendToApi);
|
|
91
|
+
function formatScalability ({ min, max }) {
|
|
92
|
+
return (min === max) ? min : `${min} to ${max}`;
|
|
93
|
+
}
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
function groupInstances (instances) {
|
|
96
|
+
return _(instances)
|
|
97
|
+
.groupBy((i) => i.flavor.name)
|
|
98
|
+
.map((instances, flavorName) => ({
|
|
99
|
+
flavor: flavorName,
|
|
100
|
+
count: instances.length,
|
|
101
|
+
}))
|
|
102
|
+
.value();
|
|
77
103
|
}
|
|
78
104
|
|
|
79
105
|
module.exports = { status };
|
package/src/commands/stop.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const Application = require('../models/application.js');
|
|
4
4
|
const application = require('@clevercloud/client/cjs/api/v2/application.js');
|
|
5
5
|
const Logger = require('../logger.js');
|
|
6
6
|
const { sendToApi } = require('../models/send-to-api.js');
|
|
7
7
|
|
|
8
8
|
async function stop (params) {
|
|
9
|
-
const { alias } = params.options;
|
|
10
|
-
const { ownerId, appId } = await
|
|
9
|
+
const { alias, app: appIdOrName } = params.options;
|
|
10
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
11
11
|
|
|
12
12
|
await application.undeploy({ id: ownerId, appId }).then(sendToApi);
|
|
13
13
|
Logger.println('App successfully stopped!');
|
|
@@ -2,32 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
const colors = require('colors/safe');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const Organisation = require('../models/organisation.js');
|
|
5
|
+
const Namespaces = require('../models/namespaces.js');
|
|
7
6
|
const { sendToApi } = require('../models/send-to-api.js');
|
|
8
7
|
const Interact = require('../models/interact.js');
|
|
9
8
|
const Logger = require('../logger.js');
|
|
10
9
|
const application = require('@clevercloud/client/cjs/api/v2/application.js');
|
|
10
|
+
const Application = require('../models/application.js');
|
|
11
11
|
|
|
12
12
|
async function listNamespaces (params) {
|
|
13
|
-
const
|
|
13
|
+
const { alias, app: appIdOrName, format } = params.options;
|
|
14
|
+
const { ownerId } = await Application.resolveId(appIdOrName, alias);
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const namespaces = await Namespaces.getNamespaces(ownerId);
|
|
17
|
+
|
|
18
|
+
namespaces.sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
19
|
+
|
|
20
|
+
switch (format) {
|
|
21
|
+
case 'json': {
|
|
22
|
+
Logger.printJson(namespaces);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
case 'human':
|
|
26
|
+
default: {
|
|
27
|
+
Logger.println('Available namespaces:');
|
|
28
|
+
namespaces.forEach(({ namespace }) => {
|
|
29
|
+
switch (namespace) {
|
|
30
|
+
case 'cleverapps':
|
|
31
|
+
Logger.println(`- ${namespace}: for redirections used with 'cleverapps.io' domain`);
|
|
32
|
+
break;
|
|
33
|
+
case 'default':
|
|
34
|
+
Logger.println(`- ${namespace}: for redirections used with custom domains`);
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
Logger.println(`- ${namespace}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
17
44
|
|
|
18
45
|
async function list (params) {
|
|
19
|
-
const { alias } = params.options;
|
|
20
|
-
const { ownerId, appId } = await
|
|
46
|
+
const { alias, app: appIdOrName, format } = params.options;
|
|
47
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
21
48
|
|
|
22
49
|
const redirs = await application.getTcpRedirs({ id: ownerId, appId }).then(sendToApi);
|
|
23
50
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
51
|
+
switch (format) {
|
|
52
|
+
case 'json': {
|
|
53
|
+
Logger.printJson(redirs);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'human':
|
|
57
|
+
default: {
|
|
58
|
+
if (redirs.length === 0) {
|
|
59
|
+
Logger.println('No active TCP redirection for this application');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
Logger.println('Enabled TCP redirections:');
|
|
63
|
+
for (const { namespace, port } of redirs) {
|
|
64
|
+
Logger.println(port + ' on ' + namespace);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
31
67
|
}
|
|
32
68
|
}
|
|
33
69
|
}
|
|
@@ -46,8 +82,8 @@ async function acceptPayment (result, skipConfirmation) {
|
|
|
46
82
|
}
|
|
47
83
|
|
|
48
84
|
async function add (params) {
|
|
49
|
-
const { alias, namespace, yes: skipConfirmation } = params.options;
|
|
50
|
-
const { ownerId, appId } = await
|
|
85
|
+
const { alias, app: appIdOrName, namespace, yes: skipConfirmation } = params.options;
|
|
86
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
51
87
|
|
|
52
88
|
const { port } = await application.addTcpRedir({ id: ownerId, appId }, { namespace }).then(sendToApi).catch((error) => {
|
|
53
89
|
if (error.status === 402) {
|
|
@@ -65,8 +101,8 @@ async function add (params) {
|
|
|
65
101
|
|
|
66
102
|
async function remove (params) {
|
|
67
103
|
const [port] = params.args;
|
|
68
|
-
const { alias, namespace } = params.options;
|
|
69
|
-
const { ownerId, appId } = await
|
|
104
|
+
const { alias, app: appIdOrName, namespace } = params.options;
|
|
105
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
70
106
|
|
|
71
107
|
await application.removeTcpRedir({ id: ownerId, appId, sourcePort: port, namespace }).then(sendToApi);
|
|
72
108
|
|
package/src/commands/webhooks.js
CHANGED
|
@@ -8,29 +8,44 @@ const { getOwnerAndApp, getOrgaIdOrUserId } = require('../models/notification.js
|
|
|
8
8
|
const { getWebhooks, createWebhook, deleteWebhook } = require('@clevercloud/client/cjs/api/v2/notification.js');
|
|
9
9
|
const { sendToApi } = require('../models/send-to-api.js');
|
|
10
10
|
|
|
11
|
-
function displayWebhook (hook) {
|
|
12
|
-
Logger.println((hook.name && colors.bold(hook.name)) || hook.id);
|
|
13
|
-
Logger.println(` id: ${hook.id}`);
|
|
14
|
-
Logger.println(` services: ${(hook.scope && hook.scope.join(', ')) || hook.ownerId}`);
|
|
15
|
-
Logger.println(` events: ${(hook.events && hook.events.join(', ')) || colors.bold('ALL')}`);
|
|
16
|
-
Logger.println(' hooks:');
|
|
17
|
-
hook.urls.forEach((url) => Logger.println(` ${url.url} (${url.format})`));
|
|
18
|
-
Logger.println();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
11
|
async function list (params) {
|
|
22
|
-
const { org, 'list-all': listAll } = params.options;
|
|
12
|
+
const { org, 'list-all': listAll, format } = params.options;
|
|
23
13
|
|
|
24
14
|
// TODO: fix alias option
|
|
25
15
|
const { ownerId, appId } = await getOwnerAndApp(null, org, !listAll);
|
|
26
16
|
const hooks = await getWebhooks({ ownerId }).then(sendToApi);
|
|
27
17
|
|
|
28
|
-
hooks
|
|
18
|
+
const formattedHooks = hooks
|
|
29
19
|
.filter((hook) => {
|
|
30
20
|
const emptyScope = !hook.scope || hook.scope.length === 0;
|
|
31
21
|
return !appId || emptyScope || hook.scope.includes(appId);
|
|
32
22
|
})
|
|
33
|
-
.
|
|
23
|
+
.map((hook) => ({
|
|
24
|
+
id: hook.id,
|
|
25
|
+
name: hook.name,
|
|
26
|
+
ownerId: hook.ownerId,
|
|
27
|
+
services: hook.scope ?? [hook.ownerId],
|
|
28
|
+
events: hook.events ?? ['ALL'],
|
|
29
|
+
urls: hook.urls,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
switch (format) {
|
|
33
|
+
case 'json': {
|
|
34
|
+
Logger.printJson(formattedHooks);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case 'human':
|
|
38
|
+
default: {
|
|
39
|
+
formattedHooks.forEach((hook) => {
|
|
40
|
+
Logger.println(hook.name ? colors.bold(hook.name) : hook.id);
|
|
41
|
+
Logger.println(` id: ${hook.id}`);
|
|
42
|
+
Logger.println(` services: ${hook.services.join(', ')}`);
|
|
43
|
+
Logger.println(` events: ${hook.events.join(', ')}`);
|
|
44
|
+
Logger.println(' hooks:');
|
|
45
|
+
hook.urls.forEach((url) => Logger.println(` ${url.url} (${url.format})`));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
async function add (params) {
|
package/src/logger.js
CHANGED
|
@@ -28,20 +28,24 @@ function formatLines (prefixLength, lines) {
|
|
|
28
28
|
.join('\n');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function consoleErrorWithoutColor (line) {
|
|
32
|
+
process.stderr.write(line + '\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
const Logger = _(['debug', 'info', 'warn', 'error'])
|
|
32
36
|
.map((severity) => {
|
|
33
37
|
if (process.env.CLEVER_QUIET || (!process.env.CLEVER_VERBOSE && (severity === 'debug' || severity === 'info'))) {
|
|
34
38
|
return [severity, _.noop];
|
|
35
39
|
}
|
|
36
|
-
const consoleFn = (severity === 'error') ?
|
|
40
|
+
const consoleFn = (severity === 'error') ? consoleErrorWithoutColor : console.log;
|
|
37
41
|
const { prefix, prefixLength } = getPrefix(severity);
|
|
38
42
|
return [severity, (err) => {
|
|
39
43
|
const message = _.get(err, 'message', err);
|
|
40
44
|
const formattedMsg = formatLines(prefixLength, processApiError(message));
|
|
41
45
|
if (process.env.CLEVER_VERBOSE && severity === 'error') {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
consoleErrorWithoutColor('[STACKTRACE]');
|
|
47
|
+
consoleErrorWithoutColor(err);
|
|
48
|
+
consoleErrorWithoutColor('[/STACKTRACE]');
|
|
45
49
|
}
|
|
46
50
|
return consoleFn(`${prefix}${formattedMsg}`);
|
|
47
51
|
}];
|
|
@@ -57,8 +61,7 @@ Logger.printJson = (obj) => {
|
|
|
57
61
|
console.log(JSON.stringify(obj, null, 2));
|
|
58
62
|
};
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
Logger.printErrorLine = console.error;
|
|
64
|
+
Logger.printErrorLine = consoleErrorWithoutColor;
|
|
62
65
|
|
|
63
66
|
// Only exported for testing, shouldn't be used directly
|
|
64
67
|
Logger.processApiError = processApiError;
|
package/src/models/addon.js
CHANGED
|
@@ -26,7 +26,7 @@ async function getProvider (providerName) {
|
|
|
26
26
|
const providers = await listProviders();
|
|
27
27
|
const provider = providers.find((p) => p.id === providerName);
|
|
28
28
|
if (provider == null) {
|
|
29
|
-
throw new Error(
|
|
29
|
+
throw new Error(`Invalid provider name. Available providers: ${providers.map((p) => p.id).join(', ')}`);
|
|
30
30
|
}
|
|
31
31
|
return provider;
|
|
32
32
|
}
|
|
@@ -136,22 +136,17 @@ function validateAddonVersionAndOptions (region, version, addonOptions, provider
|
|
|
136
136
|
async function create ({ ownerId, name, providerName, planName, region, skipConfirmation, version, addonOptions }) {
|
|
137
137
|
|
|
138
138
|
// TODO: We should be able to use it without {}
|
|
139
|
-
const
|
|
139
|
+
const provider = await getProvider(providerName);
|
|
140
140
|
|
|
141
|
-
const provider = providers.find((p) => p.id === providerName);
|
|
142
|
-
if (provider == null) {
|
|
143
|
-
throw new Error('invalid provider name');
|
|
144
|
-
}
|
|
145
141
|
if (!provider.regions.includes(region)) {
|
|
146
|
-
throw new Error(`
|
|
142
|
+
throw new Error(`Invalid region name. Available regions: ${provider.regions.join(', ')}`);
|
|
147
143
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (plan == null) {
|
|
151
|
-
const availablePlans = provider.plans.map((p) => p.slug);
|
|
152
|
-
throw new Error(`invalid plan name. Available plans: ${availablePlans.join(', ')}`);
|
|
144
|
+
if (provider.plans.length === 0) {
|
|
145
|
+
throw new Error(`No plans available for provider ${providerName}`);
|
|
153
146
|
}
|
|
154
147
|
|
|
148
|
+
const plan = getPlan(planName, provider.plans);
|
|
149
|
+
|
|
155
150
|
const providerInfos = await getProviderInfos(provider.id);
|
|
156
151
|
const planType = plan.features.find(({ name }) => name.toLowerCase() === 'type');
|
|
157
152
|
|
|
@@ -289,6 +284,20 @@ function parseAddonOptions (options) {
|
|
|
289
284
|
}, {});
|
|
290
285
|
}
|
|
291
286
|
|
|
287
|
+
function getPlan (planName, plans) {
|
|
288
|
+
// if no plan specified, pick the cheapest one
|
|
289
|
+
if (planName == null || planName === '') {
|
|
290
|
+
return plans.sort((p1, p2) => p1.price - p2.price)[0];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const plan = plans.find((p) => p.slug.toLowerCase() === planName.toLowerCase());
|
|
294
|
+
if (plan == null) {
|
|
295
|
+
const availablePlans = plans.map((p) => p.slug);
|
|
296
|
+
throw new Error(`Invalid plan name. Available plans: ${availablePlans.join(', ')}`);
|
|
297
|
+
}
|
|
298
|
+
return plan;
|
|
299
|
+
}
|
|
300
|
+
|
|
292
301
|
module.exports = {
|
|
293
302
|
completePlan,
|
|
294
303
|
completeRegion,
|
|
@@ -54,13 +54,21 @@ async function addLinkedApplication (appData, alias, ignoreParentConfig) {
|
|
|
54
54
|
return persistConfig(currentConfig);
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
async function removeLinkedApplication (alias) {
|
|
57
|
+
async function removeLinkedApplication ({ appId, alias }) {
|
|
58
58
|
const currentConfig = await loadApplicationConf();
|
|
59
59
|
const newConfig = {
|
|
60
60
|
...currentConfig,
|
|
61
|
-
apps: currentConfig.apps.filter((appEntry) =>
|
|
61
|
+
apps: currentConfig.apps.filter((appEntry) => {
|
|
62
|
+
return appEntry.app_id !== appId && appEntry.alias !== alias;
|
|
63
|
+
}),
|
|
62
64
|
};
|
|
63
|
-
|
|
65
|
+
|
|
66
|
+
if (currentConfig.apps.length !== newConfig.apps.length) {
|
|
67
|
+
await persistConfig(newConfig);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return false;
|
|
64
72
|
};
|
|
65
73
|
|
|
66
74
|
function findApp (config, alias) {
|
|
@@ -4,6 +4,7 @@ const _ = require('lodash');
|
|
|
4
4
|
const application = require('@clevercloud/client/cjs/api/v2/application.js');
|
|
5
5
|
const autocomplete = require('cliparse').autocomplete;
|
|
6
6
|
const product = require('@clevercloud/client/cjs/api/v2/product.js');
|
|
7
|
+
const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
|
|
7
8
|
|
|
8
9
|
const AppConfiguration = require('./app_configuration.js');
|
|
9
10
|
const Interact = require('./interact.js');
|
|
@@ -12,6 +13,8 @@ const Organisation = require('./organisation.js');
|
|
|
12
13
|
const User = require('./user.js');
|
|
13
14
|
|
|
14
15
|
const { sendToApi } = require('../models/send-to-api.js');
|
|
16
|
+
const AppConfig = require('./app_configuration.js');
|
|
17
|
+
const { resolveOwnerId } = require('./ids-resolver.js');
|
|
15
18
|
|
|
16
19
|
function listAvailableTypes () {
|
|
17
20
|
return autocomplete.words(['docker', 'elixir', 'go', 'gradle', 'haskell', 'jar', 'maven', 'meteor', 'node', 'php', 'play1', 'play2', 'python', 'ruby', 'rust', 'sbt', 'static-apache', 'war']);
|
|
@@ -86,20 +89,58 @@ async function create (name, typeName, region, orgaIdOrName, github, isTask, env
|
|
|
86
89
|
return application.create({ id: ownerId }, newApp).then(sendToApi);
|
|
87
90
|
};
|
|
88
91
|
|
|
89
|
-
async function deleteApp (
|
|
90
|
-
Logger.debug('Deleting app: ' +
|
|
92
|
+
async function deleteApp (app, skipConfirmation) {
|
|
93
|
+
Logger.debug('Deleting app: ' + app.name + ' (' + app.id + ')');
|
|
91
94
|
|
|
92
95
|
if (!skipConfirmation) {
|
|
93
96
|
await Interact.confirm(
|
|
94
|
-
`Deleting the application ${
|
|
97
|
+
`Deleting the application ${app.name} can't be undone, please type '${app.name}' to confirm: `,
|
|
95
98
|
'No confirmation, aborting application deletion',
|
|
96
|
-
[
|
|
99
|
+
[app.name],
|
|
97
100
|
);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
return application.remove({ id:
|
|
103
|
+
return application.remove({ id: app.ownerId, appId: app.id }).then(sendToApi);
|
|
101
104
|
};
|
|
102
105
|
|
|
106
|
+
async function getAllApps (ownerId) {
|
|
107
|
+
|
|
108
|
+
const summary = await getSummary().then(sendToApi);
|
|
109
|
+
|
|
110
|
+
const orgaWithApps = await Promise.all(
|
|
111
|
+
summary.organisations
|
|
112
|
+
// If owner ID is present, only keep the matching org
|
|
113
|
+
.filter((org) => ownerId == null || org.id === ownerId)
|
|
114
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
115
|
+
.map(async (org) => {
|
|
116
|
+
const applications = await getApplicationsForOwner(org.id);
|
|
117
|
+
return {
|
|
118
|
+
id: org.id,
|
|
119
|
+
name: org.name,
|
|
120
|
+
applications: applications.sort((a, b) => a.name.localeCompare(b.name)),
|
|
121
|
+
};
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return orgaWithApps;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
async function getApplicationsForOwner (ownerId) {
|
|
129
|
+
const rawApplications = await application.getAll({ id: ownerId }).then(sendToApi);
|
|
130
|
+
return rawApplications.map((app) => {
|
|
131
|
+
return {
|
|
132
|
+
app_id: app.id,
|
|
133
|
+
org_id: ownerId,
|
|
134
|
+
name: app.name,
|
|
135
|
+
zone: app.zone,
|
|
136
|
+
type: app.instance.variant.slug,
|
|
137
|
+
createdAt: new Date(app.creationDate).toISOString(),
|
|
138
|
+
deploy_url: app.deployment.httpUrl,
|
|
139
|
+
git_ssh_url: app.deployment.url,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
103
144
|
function getApplicationByName (apps, name) {
|
|
104
145
|
const filteredApps = apps.filter((app) => app.name === name);
|
|
105
146
|
if (filteredApps.length === 1) {
|
|
@@ -129,6 +170,62 @@ function getFromSelf (appId) {
|
|
|
129
170
|
return application.get({ appId }).then(sendToApi);
|
|
130
171
|
};
|
|
131
172
|
|
|
173
|
+
/**
|
|
174
|
+
* @param {{app_id: string}|{app_name: string}} appIdOrName
|
|
175
|
+
* @param {string} alias
|
|
176
|
+
* @return {Promise<{appId: string, ownerId: string}>}
|
|
177
|
+
*/
|
|
178
|
+
async function resolveId (appIdOrName, alias) {
|
|
179
|
+
if (appIdOrName != null && alias != null) {
|
|
180
|
+
throw new Error('Only one of the `--app` or `--alias` options can be set at a time');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// -- resolve by linked app
|
|
184
|
+
|
|
185
|
+
if (appIdOrName == null) {
|
|
186
|
+
const appDetails = await AppConfig.getAppDetails({ alias });
|
|
187
|
+
return { appId: appDetails.appId, ownerId: appDetails.ownerId };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// -- resolve by app id
|
|
191
|
+
|
|
192
|
+
if (appIdOrName.app_id != null) {
|
|
193
|
+
const ownerId = await resolveOwnerId(appIdOrName.app_id);
|
|
194
|
+
if (ownerId != null) {
|
|
195
|
+
return {
|
|
196
|
+
appId: appIdOrName.app_id,
|
|
197
|
+
ownerId,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw new Error('Application not found');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// -- resolve by app name
|
|
205
|
+
|
|
206
|
+
const summary = await getSummary({}).then(sendToApi);
|
|
207
|
+
|
|
208
|
+
const candidates = [summary.user, ...summary.organisations]
|
|
209
|
+
.flatMap((owner) => owner.applications.map((app) => ({ app, owner })))
|
|
210
|
+
.filter((candidate) => candidate.app.name === appIdOrName.app_name);
|
|
211
|
+
|
|
212
|
+
if (candidates.length === 0) {
|
|
213
|
+
throw new Error('Application not found');
|
|
214
|
+
}
|
|
215
|
+
if (candidates.length === 1) {
|
|
216
|
+
return {
|
|
217
|
+
appId: candidates[0].app.id,
|
|
218
|
+
ownerId: candidates[0].owner.id,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
Logger.printErrorLine(`The name '${appIdOrName.app_name}' refers to multiple applications:`);
|
|
223
|
+
candidates.forEach((candidate) => {
|
|
224
|
+
Logger.printErrorLine(`- ${candidate.owner.name}: ${candidate.app.id} (${candidate.app.variantSlug})`);
|
|
225
|
+
});
|
|
226
|
+
throw new Error('Ambiguous application name, use the `--app` option with one of the IDs above');
|
|
227
|
+
}
|
|
228
|
+
|
|
132
229
|
async function linkRepo (app, orgaIdOrName, alias, ignoreParentConfig) {
|
|
133
230
|
Logger.debug(`Linking current repository to the app: ${app.app_id || app.app_name}`);
|
|
134
231
|
|
|
@@ -145,7 +242,7 @@ async function linkRepo (app, orgaIdOrName, alias, ignoreParentConfig) {
|
|
|
145
242
|
|
|
146
243
|
function unlinkRepo (alias) {
|
|
147
244
|
Logger.debug(`Unlinking current repository from the app: ${alias}`);
|
|
148
|
-
return AppConfiguration.removeLinkedApplication(alias);
|
|
245
|
+
return AppConfiguration.removeLinkedApplication({ alias });
|
|
149
246
|
};
|
|
150
247
|
|
|
151
248
|
function redeploy (ownerId, appId, commit, withoutCache) {
|
|
@@ -214,7 +311,7 @@ async function listDependencies (ownerId, appId, showAll) {
|
|
|
214
311
|
const applicationDeps = await application.getAllDependencies({ id: ownerId, appId }).then(sendToApi);
|
|
215
312
|
|
|
216
313
|
if (!showAll) {
|
|
217
|
-
return applicationDeps;
|
|
314
|
+
return applicationDeps.map((app) => ({ ...app, isLinked: true }));
|
|
218
315
|
}
|
|
219
316
|
|
|
220
317
|
const allApps = await application.getAll({ id: ownerId }).then(sendToApi);
|
|
@@ -237,9 +334,11 @@ async function unlink (ownerId, appId, dependency) {
|
|
|
237
334
|
};
|
|
238
335
|
|
|
239
336
|
module.exports = {
|
|
337
|
+
resolveId,
|
|
240
338
|
create,
|
|
241
339
|
deleteApp,
|
|
242
340
|
get,
|
|
341
|
+
getAllApps,
|
|
243
342
|
link,
|
|
244
343
|
linkRepo,
|
|
245
344
|
listAvailableAliases,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const colors = require('colors/safe');
|
|
4
|
+
const Logger = require('../logger.js');
|
|
5
|
+
|
|
6
|
+
function get (follow, exitOnDeploy) {
|
|
7
|
+
if (follow) {
|
|
8
|
+
if (exitOnDeploy === 'deploy-start') {
|
|
9
|
+
throw new Error('The `follow` and `exit-on` set to "deploy-start" options are not compatible');
|
|
10
|
+
}
|
|
11
|
+
Logger.println(colors.yellow('The `follow` option is deprecated and will be removed in an upcoming major, use --exit-on set to "never" instead'));
|
|
12
|
+
return 'never';
|
|
13
|
+
}
|
|
14
|
+
return exitOnDeploy;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// plotQuietWarning: If in quiet mode and exitStrategy set to never plot a warning to indicate that the command will end
|
|
18
|
+
function plotQuietWarning (exitStrategy, quiet) {
|
|
19
|
+
if (exitStrategy === 'never' && quiet) {
|
|
20
|
+
Logger.println(colors.bold.yellow('The "never" exit-on strategy is not compatible with the "quiet" mode, it will exit once the deployment ends'));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { get, plotQuietWarning };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to print a real JSON array with starting `[` and ending `]`
|
|
3
|
+
*/
|
|
4
|
+
class JsonArray {
|
|
5
|
+
constructor () {
|
|
6
|
+
this._isFirst = true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
open () {
|
|
10
|
+
process.stdout.write('[\n');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
push (log) {
|
|
14
|
+
if (this._isFirst) {
|
|
15
|
+
this._isFirst = false;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
process.stdout.write(',\n');
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write(` ${JSON.stringify(log)}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
close () {
|
|
24
|
+
process.stdout.write('\n]');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { JsonArray };
|