clever-tools 3.7.0 → 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 -1
- package/bin/clever.js +99 -47
- package/package.json +47 -47
- package/src/command-options.js +19 -0
- package/src/commands/accesslogs.js +114 -34
- package/src/commands/activity.js +64 -12
- package/src/commands/addon.js +152 -57
- 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/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/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
|
@@ -1,53 +1,133 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { getWarp10AccessLogsToken } = require('@clevercloud/client/cjs/api/v2/warp-10.js');
|
|
5
|
-
const { ONE_HOUR_MICROS, ONE_SECOND_MICROS, toMicroTimestamp } = require('@clevercloud/client/cjs/utils/date.js');
|
|
6
|
-
|
|
7
|
-
const Addon = require('../models/addon.js');
|
|
8
|
-
const AppConfig = require('../models/app_configuration.js');
|
|
3
|
+
const Application = require('../models/application.js');
|
|
9
4
|
const Logger = require('../logger.js');
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
5
|
+
const { getHostAndTokens } = require('../models/send-to-api.js');
|
|
6
|
+
const { ApplicationAccessLogStream } = require('@clevercloud/client/cjs/streams/access-logs.js');
|
|
7
|
+
const { JsonArray } = require('../models/json-array.js');
|
|
8
|
+
const colors = require('colors/safe');
|
|
9
|
+
const formatTable = require('../format-table.js');
|
|
10
|
+
const { truncateWithEllipsis } = require('../models/utils.js');
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
// 2000 logs per 100ms maximum
|
|
13
|
+
const THROTTLE_ELEMENTS = 2000;
|
|
14
|
+
const THROTTLE_PER_IN_MILLISECONDS = 100;
|
|
15
|
+
const CITY_MAX_LENGTH = 20;
|
|
14
16
|
|
|
15
17
|
async function accessLogs (params) {
|
|
16
|
-
const { alias, format, before, after, addon: addonId, follow } = params.options;
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
// TODO: drop when add-ons are supported in API
|
|
20
|
+
if (params.options.addon) {
|
|
21
|
+
throw new Error('Access Logs are not available for add-ons yet');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { apiHost, tokens } = await getHostAndTokens();
|
|
25
|
+
const { alias, app: appIdOrName, format, before: until, after: since } = params.options;
|
|
26
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
27
|
+
|
|
28
|
+
const stream = new ApplicationAccessLogStream({
|
|
29
|
+
apiHost,
|
|
30
|
+
tokens,
|
|
31
|
+
ownerId,
|
|
32
|
+
appId,
|
|
33
|
+
since,
|
|
34
|
+
until,
|
|
35
|
+
throttleElements: THROTTLE_ELEMENTS,
|
|
36
|
+
throttlePerInMilliseconds: THROTTLE_PER_IN_MILLISECONDS,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (format === 'human') {
|
|
40
|
+
Logger.warn(colors.yellow('/!\\ Access Logs feature is in Alpha testing phase'));
|
|
41
|
+
}
|
|
22
42
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
43
|
+
if (format === 'json' && (!until)) {
|
|
44
|
+
throw new Error('JSON format only works with a limiting parameter such as `before`');
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
: getAccessLogsFromWarp10InBatches({ appId, realAddonId, from, to, warpToken }, sendToWarp10);
|
|
47
|
+
// used for 'json' format
|
|
48
|
+
const jsonArray = new JsonArray();
|
|
30
49
|
|
|
31
|
-
|
|
50
|
+
stream
|
|
51
|
+
.on('open', (event) => {
|
|
52
|
+
Logger.debug(colors.blue(`Logs stream (open) ${JSON.stringify({ appId })}`));
|
|
53
|
+
if (format === 'json') {
|
|
54
|
+
jsonArray.open();
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
.on('error', (event) => {
|
|
58
|
+
Logger.debug(colors.red(`Logs stream (error) ${event.error.message}`));
|
|
59
|
+
})
|
|
60
|
+
.onLog((log) => {
|
|
61
|
+
switch (format) {
|
|
62
|
+
case 'json':
|
|
63
|
+
jsonArray.push(log);
|
|
64
|
+
break;
|
|
65
|
+
case 'json-stream':
|
|
66
|
+
Logger.printJson(log);
|
|
67
|
+
break;
|
|
68
|
+
case 'human':
|
|
69
|
+
default:
|
|
70
|
+
// when the connection is cut too early, or for TCP redirections, we don't have HTTP section
|
|
71
|
+
if (log.http == null) {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
32
74
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
75
|
+
Logger.println(formatHuman(log));
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
36
79
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
80
|
+
// Properly close the stream
|
|
81
|
+
process.once('SIGINT', (signal) => stream.close(signal));
|
|
82
|
+
|
|
83
|
+
const closeReason = await stream.start();
|
|
84
|
+
|
|
85
|
+
if (format === 'json') {
|
|
86
|
+
jsonArray.close();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
Logger.debug(`stream closed: ${closeReason?.type}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatHuman (log) {
|
|
93
|
+
const { date, http, source } = log;
|
|
94
|
+
const country = source.countryCode ?? '(unknown)';
|
|
95
|
+
const hasSourceCity = source.city ?? '';
|
|
96
|
+
|
|
97
|
+
return row([[
|
|
98
|
+
colors.grey(date.toISOString(date)),
|
|
99
|
+
source.ip,
|
|
100
|
+
`${country}${hasSourceCity ? '/' + truncateWithEllipsis(CITY_MAX_LENGTH, source.city) : ''}`,
|
|
101
|
+
colorStatusCode(http.response.statusCode),
|
|
102
|
+
http.request.method.toString().padEnd(4, ' ') + ' ' + http.request.path,
|
|
103
|
+
]]);
|
|
40
104
|
}
|
|
41
105
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
106
|
+
const row = formatTable([
|
|
107
|
+
'2024-06-24T08:05:43.880Z',
|
|
108
|
+
'255.255.255.255',
|
|
109
|
+
// country / city
|
|
110
|
+
2 + 1 + CITY_MAX_LENGTH,
|
|
111
|
+
'XXX',
|
|
112
|
+
// longest method name
|
|
113
|
+
'OPTIONS',
|
|
114
|
+
// path
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
function colorStatusCode (code) {
|
|
118
|
+
if (code >= 500) {
|
|
119
|
+
return colors.red(code);
|
|
120
|
+
}
|
|
121
|
+
if (code >= 400) {
|
|
122
|
+
return colors.yellow(code);
|
|
123
|
+
}
|
|
124
|
+
if (code >= 300) {
|
|
125
|
+
return colors.blue(code);
|
|
126
|
+
}
|
|
127
|
+
if (code >= 200) {
|
|
128
|
+
return colors.green(code);
|
|
49
129
|
}
|
|
50
|
-
return
|
|
130
|
+
return code;
|
|
51
131
|
}
|
|
52
132
|
|
|
53
133
|
module.exports = { accessLogs };
|
package/src/commands/activity.js
CHANGED
|
@@ -4,12 +4,12 @@ const colors = require('colors/safe');
|
|
|
4
4
|
const moment = require('moment');
|
|
5
5
|
|
|
6
6
|
const Activity = require('../models/activity.js');
|
|
7
|
-
const AppConfig = require('../models/app_configuration.js');
|
|
8
7
|
const formatTable = require('../format-table');
|
|
9
8
|
const Logger = require('../logger.js');
|
|
10
9
|
const { Deferred } = require('../models/utils.js');
|
|
11
10
|
const { EventsStream } = require('@clevercloud/client/cjs/streams/events.node.js');
|
|
12
11
|
const { getHostAndTokens } = require('../models/send-to-api.js');
|
|
12
|
+
const Application = require('../models/application.js');
|
|
13
13
|
|
|
14
14
|
function getColoredState (state, isLast) {
|
|
15
15
|
if (state === 'OK') {
|
|
@@ -39,6 +39,17 @@ const formatActivityTable = formatTable([
|
|
|
39
39
|
0,
|
|
40
40
|
]);
|
|
41
41
|
|
|
42
|
+
function convertEventToJson (event) {
|
|
43
|
+
return {
|
|
44
|
+
uuid: event.uuid,
|
|
45
|
+
date: event.date,
|
|
46
|
+
state: event.state,
|
|
47
|
+
action: event.action,
|
|
48
|
+
commit: event.commit,
|
|
49
|
+
cause: event.cause,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
function formatActivityLine (event) {
|
|
43
54
|
return formatActivityTable([
|
|
44
55
|
[
|
|
@@ -50,7 +61,7 @@ function formatActivityLine (event) {
|
|
|
50
61
|
event.cause,
|
|
51
62
|
],
|
|
52
63
|
]);
|
|
53
|
-
}
|
|
64
|
+
}
|
|
54
65
|
|
|
55
66
|
function isTemporaryEvent (ev) {
|
|
56
67
|
if (ev == null) {
|
|
@@ -67,28 +78,65 @@ function clearPreviousLine () {
|
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
function handleEvent (previousEvent, event) {
|
|
81
|
+
function handleEvent (previousEvent, event, handler) {
|
|
71
82
|
if (isTemporaryEvent(previousEvent)) {
|
|
72
|
-
|
|
83
|
+
handler.pop();
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
Logger.println(activityLine);
|
|
86
|
+
handler.print(event);
|
|
77
87
|
|
|
78
88
|
return event;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
function onEvent (previousEvent, newEvent) {
|
|
91
|
+
function onEvent (previousEvent, newEvent, handler) {
|
|
82
92
|
const { event, date, data: { uuid, state, action, commit, cause } } = newEvent;
|
|
83
93
|
if (event !== 'DEPLOYMENT_ACTION_BEGIN' && event !== 'DEPLOYMENT_ACTION_END') {
|
|
84
94
|
return previousEvent;
|
|
85
95
|
}
|
|
86
|
-
return handleEvent(previousEvent, { date, uuid, state, action, commit, cause, isLast: true });
|
|
96
|
+
return handleEvent(previousEvent, { date, uuid, state, action, commit, cause, isLast: true }, handler);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getEventHandler (format, follow) {
|
|
100
|
+
switch (format) {
|
|
101
|
+
case 'json': {
|
|
102
|
+
if (follow) {
|
|
103
|
+
throw new Error('The `follow` option and "json" format are not compatible. Use "json-stream" format instead.');
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const buf = [];
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
print: (event) => buf.push(convertEventToJson(event)),
|
|
110
|
+
pop: () => buf.pop(),
|
|
111
|
+
end: () => {
|
|
112
|
+
Logger.printJson(buf);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
case 'json-stream': {
|
|
118
|
+
return {
|
|
119
|
+
print: (event) => {
|
|
120
|
+
Logger.println(JSON.stringify(convertEventToJson(event)));
|
|
121
|
+
},
|
|
122
|
+
pop: clearPreviousLine,
|
|
123
|
+
end: () => {},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
case 'human':
|
|
127
|
+
default: {
|
|
128
|
+
return {
|
|
129
|
+
print: (event) => Logger.println(formatActivityLine(event)),
|
|
130
|
+
pop: clearPreviousLine,
|
|
131
|
+
end: () => {},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
87
135
|
}
|
|
88
136
|
|
|
89
137
|
async function activity (params) {
|
|
90
|
-
const { alias, 'show-all': showAll, follow } = params.options;
|
|
91
|
-
const { ownerId, appId } = await
|
|
138
|
+
const { alias, app: appIdOrName, 'show-all': showAll, follow, format } = params.options;
|
|
139
|
+
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
92
140
|
const events = await Activity.list(ownerId, appId, showAll);
|
|
93
141
|
const reversedArrayWithIndex = events
|
|
94
142
|
.reverse()
|
|
@@ -96,9 +144,13 @@ async function activity (params) {
|
|
|
96
144
|
const isLast = index === all.length - 1;
|
|
97
145
|
return ({ ...event, isLast });
|
|
98
146
|
});
|
|
99
|
-
|
|
147
|
+
|
|
148
|
+
const handler = getEventHandler(format, follow);
|
|
149
|
+
|
|
150
|
+
let lastEvent = reversedArrayWithIndex.reduce((previousEvent, newEvent) => handleEvent(previousEvent, newEvent, handler), {});
|
|
100
151
|
|
|
101
152
|
if (!follow) {
|
|
153
|
+
handler.end();
|
|
102
154
|
return lastEvent;
|
|
103
155
|
}
|
|
104
156
|
|
|
@@ -110,7 +162,7 @@ async function activity (params) {
|
|
|
110
162
|
eventsStream
|
|
111
163
|
.on('open', () => Logger.debug('WS for events (open) ' + JSON.stringify({ appId })))
|
|
112
164
|
.on('event', (event) => {
|
|
113
|
-
lastEvent = onEvent(lastEvent, event);
|
|
165
|
+
lastEvent = onEvent(lastEvent, event, handler);
|
|
114
166
|
return lastEvent;
|
|
115
167
|
})
|
|
116
168
|
.on('ping', () => Logger.debug('WS for events (ping)'))
|
package/src/commands/addon.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const _ = require('lodash');
|
|
4
3
|
const colors = require('colors/safe');
|
|
5
4
|
|
|
6
5
|
const Addon = require('../models/addon.js');
|
|
@@ -101,6 +100,45 @@ async function create (params) {
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
function displayAddon (format, addon, providerName, message) {
|
|
103
|
+
|
|
104
|
+
const WIP_PROVIDERS = {
|
|
105
|
+
keycloak: {
|
|
106
|
+
statut: 'alpha',
|
|
107
|
+
postCreateInstructions: [
|
|
108
|
+
'Learn more about Keycloak on Clever Cloud: https://developers.clever-cloud.com/doc/addons/keycloak/',
|
|
109
|
+
].join('\n'),
|
|
110
|
+
},
|
|
111
|
+
kv: {
|
|
112
|
+
status: 'alpha',
|
|
113
|
+
postCreateInstructions: [
|
|
114
|
+
colors.yellow('You can easily use Materia KV with \'redis-cli\', with such commands:'),
|
|
115
|
+
colors.blue(`source <(clever addon env ${addon.id} -F shell)`),
|
|
116
|
+
colors.blue('redis-cli -h $KV_HOST -p $KV_PORT --tls'),
|
|
117
|
+
'Learn more about Materia KV on Clever Cloud: https://developers.clever-cloud.com/doc/addons/materia-kv/',
|
|
118
|
+
].join('\n'),
|
|
119
|
+
},
|
|
120
|
+
'addon-pulsar': {
|
|
121
|
+
status: 'beta',
|
|
122
|
+
postCreateInstructions: [
|
|
123
|
+
'Learn more about Pulsar on Clever Cloud: https://developers.clever-cloud.com/doc/addons/pulsar/',
|
|
124
|
+
].join('\n'),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
let providerNameToShow = '';
|
|
129
|
+
let statusMessage = '';
|
|
130
|
+
if (providerName in WIP_PROVIDERS) {
|
|
131
|
+
|
|
132
|
+
providerNameToShow = providerName === 'kv'
|
|
133
|
+
? 'Materia KV'
|
|
134
|
+
: providerName;
|
|
135
|
+
|
|
136
|
+
statusMessage = `The ${providerNameToShow} provider is in ${WIP_PROVIDERS[providerName].status} testing phase`;
|
|
137
|
+
statusMessage += WIP_PROVIDERS[providerName].status === 'alpha'
|
|
138
|
+
? '. Don\'t store sensitive or production grade data.'
|
|
139
|
+
: '';
|
|
140
|
+
}
|
|
141
|
+
|
|
104
142
|
switch (format) {
|
|
105
143
|
|
|
106
144
|
case 'json': {
|
|
@@ -109,8 +147,9 @@ function displayAddon (format, addon, providerName, message) {
|
|
|
109
147
|
realId: addon.realId,
|
|
110
148
|
name: addon.name,
|
|
111
149
|
};
|
|
112
|
-
|
|
113
|
-
|
|
150
|
+
|
|
151
|
+
Logger.printJson((WIP_PROVIDERS[providerName] != null)
|
|
152
|
+
? { ...jsonAddon, availability: WIP_PROVIDERS[providerName].status, message: statusMessage }
|
|
114
153
|
: jsonAddon);
|
|
115
154
|
break;
|
|
116
155
|
}
|
|
@@ -123,15 +162,12 @@ function displayAddon (format, addon, providerName, message) {
|
|
|
123
162
|
`Real ID: ${addon.realId}`,
|
|
124
163
|
`Name: ${addon.name}`,
|
|
125
164
|
].join('\n'));
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
colors.blue('redis-cli -h $KV_HOST -p $KV_PORT --tls'),
|
|
133
|
-
].join('\n');
|
|
134
|
-
Logger.println(materiaMessage);
|
|
165
|
+
|
|
166
|
+
if (providerName in WIP_PROVIDERS) {
|
|
167
|
+
|
|
168
|
+
Logger.println();
|
|
169
|
+
Logger.println(colors.yellow(`/!\\ ${statusMessage}`));
|
|
170
|
+
Logger.println(WIP_PROVIDERS[providerName].postCreateInstructions);
|
|
135
171
|
}
|
|
136
172
|
}
|
|
137
173
|
}
|
|
@@ -156,64 +192,123 @@ async function rename (params) {
|
|
|
156
192
|
Logger.println(`Addon ${addon.addon_id || addon.addon_name} successfully renamed to ${newName}`);
|
|
157
193
|
}
|
|
158
194
|
|
|
159
|
-
async function listProviders () {
|
|
195
|
+
async function listProviders (params) {
|
|
196
|
+
const { format } = params.options;
|
|
160
197
|
|
|
161
198
|
const providers = await Addon.listProviders();
|
|
162
199
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
200
|
+
switch (format) {
|
|
201
|
+
case 'json': {
|
|
202
|
+
const formattedProviders = providers.map((provider) => ({
|
|
203
|
+
id: provider.id,
|
|
204
|
+
name: provider.name,
|
|
205
|
+
shortDesc: provider.shortDesc,
|
|
206
|
+
regions: provider.regions,
|
|
207
|
+
plans: provider.plans.map((plan) => ({
|
|
208
|
+
id: plan.id,
|
|
209
|
+
name: plan.name,
|
|
210
|
+
slug: plan.slug,
|
|
211
|
+
})),
|
|
212
|
+
}));
|
|
213
|
+
Logger.printJson(formattedProviders);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 'human':
|
|
217
|
+
default: {
|
|
218
|
+
const formattedProviders = providers.map((provider) => {
|
|
219
|
+
return [
|
|
220
|
+
colors.bold(provider.id),
|
|
221
|
+
provider.name,
|
|
222
|
+
provider.shortDesc || '',
|
|
223
|
+
];
|
|
224
|
+
});
|
|
225
|
+
Logger.println(formatTable(formattedProviders));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
171
228
|
}
|
|
172
229
|
|
|
173
230
|
async function showProvider (params) {
|
|
174
231
|
const [providerName] = params.args;
|
|
232
|
+
const { format } = params.options;
|
|
175
233
|
|
|
176
234
|
const provider = await Addon.getProvider(providerName);
|
|
177
235
|
const providerInfos = await Addon.getProviderInfos(providerName);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Logger.println(` Options for version ${version}:`);
|
|
210
|
-
features.forEach(({ name, enabled }) => {
|
|
211
|
-
Logger.println(` ${name}: default=${enabled}`);
|
|
236
|
+
|
|
237
|
+
const formattedPlans = [...provider.plans]
|
|
238
|
+
.sort((a, b) => a.price - b.price)
|
|
239
|
+
.map((plan) => {
|
|
240
|
+
const formattedFeatures = plan.features
|
|
241
|
+
.map((feature) => ({
|
|
242
|
+
name: feature.name,
|
|
243
|
+
value: feature.value,
|
|
244
|
+
}))
|
|
245
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
246
|
+
|
|
247
|
+
const formattedPlan = {
|
|
248
|
+
id: plan.id,
|
|
249
|
+
name: plan.name,
|
|
250
|
+
slug: plan.slug,
|
|
251
|
+
features: formattedFeatures,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (providerInfos != null) {
|
|
255
|
+
const planType = plan.features.find(({ name }) => name.toLowerCase() === 'type');
|
|
256
|
+
if (planType != null && planType.value.toLowerCase() === 'dedicated') {
|
|
257
|
+
const planVersions = Object.keys(providerInfos.dedicated);
|
|
258
|
+
formattedPlan.versions = planVersions.map((version) => {
|
|
259
|
+
return {
|
|
260
|
+
version,
|
|
261
|
+
isDefault: version === providerInfos.defaultDedicatedVersion,
|
|
262
|
+
options: providerInfos.dedicated[version].features.map((feature) => ({
|
|
263
|
+
name: feature.name,
|
|
264
|
+
enabledByDefault: feature.enabled,
|
|
265
|
+
})),
|
|
266
|
+
};
|
|
212
267
|
});
|
|
213
|
-
}
|
|
268
|
+
}
|
|
214
269
|
}
|
|
270
|
+
|
|
271
|
+
return formattedPlan;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const formattedProvider = {
|
|
275
|
+
id: provider.id,
|
|
276
|
+
name: provider.name,
|
|
277
|
+
shortDesc: provider.shortDesc,
|
|
278
|
+
regions: provider.regions,
|
|
279
|
+
plans: formattedPlans,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
switch (format) {
|
|
283
|
+
case 'json': {
|
|
284
|
+
Logger.printJson(formattedProvider);
|
|
285
|
+
break;
|
|
215
286
|
}
|
|
216
|
-
|
|
287
|
+
case 'human':
|
|
288
|
+
default: {
|
|
289
|
+
Logger.println(colors.bold(formattedProvider.id));
|
|
290
|
+
Logger.println(`${formattedProvider.name}: ${formattedProvider.shortDesc}`);
|
|
291
|
+
Logger.println();
|
|
292
|
+
Logger.println(`Available regions: ${formattedProvider.regions.join(', ')}`);
|
|
293
|
+
Logger.println();
|
|
294
|
+
Logger.println('Available plans');
|
|
295
|
+
|
|
296
|
+
formattedProvider.plans.forEach((plan) => {
|
|
297
|
+
Logger.println(`Plan ${colors.bold(plan.slug)}`);
|
|
298
|
+
plan.features.forEach(({ name, value }) => Logger.println(` ${name}: ${value}`));
|
|
299
|
+
|
|
300
|
+
if (plan.versions != null) {
|
|
301
|
+
Logger.println(` Available versions: ${plan.versions.map(({ version, isDefault }) => isDefault ? `${version} (default)` : version).join(', ')}`);
|
|
302
|
+
plan.versions.forEach(({ version, options }) => {
|
|
303
|
+
Logger.println(` Options for version ${version}:`);
|
|
304
|
+
options.forEach(({ name, enabledByDefault }) => {
|
|
305
|
+
Logger.println(` ${name}: default=${enabledByDefault}`);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
217
312
|
}
|
|
218
313
|
|
|
219
314
|
async function env (params) {
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
const colors = require('colors/safe');
|
|
4
4
|
|
|
5
5
|
const AppConfig = require('../models/app_configuration.js');
|
|
6
|
+
const Application = require('../models/application.js');
|
|
7
|
+
const Organisation = require('../models/organisation.js');
|
|
6
8
|
const Logger = require('../logger.js');
|
|
9
|
+
const formatTable = require('../format-table.js');
|
|
10
|
+
const { truncateWithEllipsis } = require('../models/utils.js');
|
|
7
11
|
|
|
8
12
|
async function list (params) {
|
|
9
13
|
const { 'only-aliases': onlyAliases, json } = params.options;
|
|
@@ -43,4 +47,82 @@ function formatApps (apps, onlyAliases, json) {
|
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
async function listAll (params) {
|
|
51
|
+
const { org: orgaIdOrName, format } = params.options;
|
|
52
|
+
|
|
53
|
+
const linkedApps = await AppConfig.loadApplicationConf().then((conf) => conf.apps);
|
|
54
|
+
|
|
55
|
+
const ownerId = (orgaIdOrName != null && orgaIdOrName.orga_name !== '')
|
|
56
|
+
? await Organisation.getId(orgaIdOrName)
|
|
57
|
+
: null;
|
|
58
|
+
|
|
59
|
+
const allAppsPerOrg = await Application.getAllApps(ownerId);
|
|
60
|
+
|
|
61
|
+
// For each app, we try to find a corresponding local alias in the configuration file
|
|
62
|
+
const allAppsWithAliasPerOrg = allAppsPerOrg.map((org) => {
|
|
63
|
+
const applicationsWithAlias = org.applications.map((app) => {
|
|
64
|
+
const foundAlias = linkedApps.find((a) => a.app_id === app.app_id)?.alias;
|
|
65
|
+
return { ...app, alias: foundAlias ?? '' };
|
|
66
|
+
});
|
|
67
|
+
return { ...org, applications: applicationsWithAlias };
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
switch (format) {
|
|
71
|
+
case 'json': {
|
|
72
|
+
Logger.printJson(allAppsWithAliasPerOrg);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'human':
|
|
76
|
+
default: {
|
|
77
|
+
|
|
78
|
+
const headers = ['APPLICATION ID', 'NAME', 'TYPE', 'ZONE', 'LOCAL ALIAS'];
|
|
79
|
+
const appNameWidth = 42;
|
|
80
|
+
|
|
81
|
+
// We calculate the maximum width of each column to format the table
|
|
82
|
+
const allApps = allAppsWithAliasPerOrg.flatMap((org) => org.applications);
|
|
83
|
+
const columnWidths = [
|
|
84
|
+
getPropertyMaxWidth(allApps, 'app_id'),
|
|
85
|
+
appNameWidth,
|
|
86
|
+
getPropertyMaxWidth(allApps, 'type'),
|
|
87
|
+
getPropertyMaxWidth(allApps, 'zone'),
|
|
88
|
+
getPropertyMaxWidth(allApps, 'alias'),
|
|
89
|
+
];
|
|
90
|
+
const formatTableWithColumnWidth = formatTable(columnWidths);
|
|
91
|
+
|
|
92
|
+
allAppsWithAliasPerOrg.forEach((org) => {
|
|
93
|
+
|
|
94
|
+
Logger.println();
|
|
95
|
+
|
|
96
|
+
const applicationsPlural = org.applications.length !== 1 ? 'applications' : 'application';
|
|
97
|
+
const punctuation = org.applications.length > 0 ? ':' : '';
|
|
98
|
+
|
|
99
|
+
Logger.println(colors.blue(`• Organization '${org.name}' (${org.id}) with ${org.applications.length} ${applicationsPlural}${punctuation}`));
|
|
100
|
+
|
|
101
|
+
if (org.applications.length > 0) {
|
|
102
|
+
Logger.println();
|
|
103
|
+
Logger.println(formatTableWithColumnWidth([
|
|
104
|
+
headers,
|
|
105
|
+
...org.applications.map((app) => [
|
|
106
|
+
app.app_id,
|
|
107
|
+
truncateWithEllipsis(appNameWidth, app.name),
|
|
108
|
+
app.type,
|
|
109
|
+
app.zone,
|
|
110
|
+
app.alias,
|
|
111
|
+
]),
|
|
112
|
+
]));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getPropertyMaxWidth (array, propertyName) {
|
|
122
|
+
return Math.max(...array.map((o) => o[propertyName].length));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
list,
|
|
127
|
+
listAll,
|
|
128
|
+
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const Application = require('../models/application.js');
|
|
4
4
|
const Logger = require('../logger.js');
|
|
5
5
|
const { getAllDeployments, cancelDeployment } = require('@clevercloud/client/cjs/api/v2/application.js');
|
|
6
6
|
const { sendToApi } = require('../models/send-to-api.js');
|
|
7
7
|
|
|
8
8
|
async function cancelDeploy (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
|
const deployments = await getAllDeployments({ id: ownerId, appId, limit: 1 }).then(sendToApi);
|
|
13
13
|
|