clever-tools 3.10.1 → 3.12.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 +206 -5
- package/package.json +5 -3
- package/src/clever-client/auth-bridge.js +61 -0
- package/src/clever-client/ng.js +18 -0
- package/src/commands/addon.js +38 -19
- package/src/commands/config.js +3 -3
- package/src/commands/console.js +3 -4
- package/src/commands/curl.js +17 -17
- package/src/commands/database.js +9 -9
- package/src/commands/domain.js +13 -16
- package/src/commands/env.js +15 -8
- package/src/commands/features.js +93 -0
- package/src/commands/kv.js +93 -0
- package/src/commands/link.js +4 -2
- package/src/commands/ng.js +202 -0
- package/src/commands/profile.js +16 -7
- package/src/commands/published-config.js +7 -7
- package/src/commands/stop.js +2 -2
- package/src/commands/tcp-redirs.js +5 -5
- package/src/commands/tokens.js +137 -0
- package/src/experimental-features.js +61 -0
- package/src/lib/ng-print.js +195 -0
- package/src/logger.js +3 -0
- package/src/models/activity.js +2 -3
- package/src/models/addon.js +4 -4
- package/src/models/application.js +26 -16
- package/src/models/configuration.js +51 -15
- package/src/models/ids-resolver.js +38 -1
- 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/send-to-api.js +19 -8
- package/src/parsers.js +57 -3
- package/src/prompt-password.js +10 -0
|
@@ -4,8 +4,8 @@ import * as Namespaces from '../models/namespaces.js';
|
|
|
4
4
|
import { sendToApi } from '../models/send-to-api.js';
|
|
5
5
|
import * as Interact from '../models/interact.js';
|
|
6
6
|
import { Logger } from '../logger.js';
|
|
7
|
-
import * as application from '@clevercloud/client/esm/api/v2/application.js';
|
|
8
7
|
import * as Application from '../models/application.js';
|
|
8
|
+
import { addTcpRedir, getTcpRedirs, removeTcpRedir } from '@clevercloud/client/esm/api/v2/application.js';
|
|
9
9
|
|
|
10
10
|
export async function listNamespaces (params) {
|
|
11
11
|
const { alias, app: appIdOrName, format } = params.options;
|
|
@@ -44,7 +44,7 @@ export async function list (params) {
|
|
|
44
44
|
const { alias, app: appIdOrName, format } = params.options;
|
|
45
45
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
46
46
|
|
|
47
|
-
const redirs = await
|
|
47
|
+
const redirs = await getTcpRedirs({ id: ownerId, appId }).then(sendToApi);
|
|
48
48
|
|
|
49
49
|
switch (format) {
|
|
50
50
|
case 'json': {
|
|
@@ -83,10 +83,10 @@ export async function add (params) {
|
|
|
83
83
|
const { alias, app: appIdOrName, namespace, yes: skipConfirmation } = params.options;
|
|
84
84
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
85
85
|
|
|
86
|
-
const { port } = await
|
|
86
|
+
const { port } = await addTcpRedir({ id: ownerId, appId }, { namespace }).then(sendToApi).catch((error) => {
|
|
87
87
|
if (error.status === 402) {
|
|
88
88
|
return acceptPayment(error.response.body, skipConfirmation).then(() => {
|
|
89
|
-
return
|
|
89
|
+
return addTcpRedir({ id: ownerId, appId, payment: 'accepted' }, { namespace }).then(sendToApi);
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
@@ -102,7 +102,7 @@ export async function remove (params) {
|
|
|
102
102
|
const { alias, app: appIdOrName, namespace } = params.options;
|
|
103
103
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
104
104
|
|
|
105
|
-
await
|
|
105
|
+
await removeTcpRedir({ id: ownerId, appId, sourcePort: port, namespace }).then(sendToApi);
|
|
106
106
|
|
|
107
107
|
Logger.println('Successfully removed tcp redirection.');
|
|
108
108
|
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
import { createApiToken, deleteApiToken, listApiTokens } from '../clever-client/auth-bridge.js';
|
|
4
|
+
import { sendToAuthBridge } from '../models/send-to-api.js';
|
|
5
|
+
import { getCurrent as getCurrentUser } from '../models/user.js';
|
|
6
|
+
import { conf } from '../models/configuration.js';
|
|
7
|
+
import dedent from 'dedent';
|
|
8
|
+
import { promptPassword } from '../prompt-password.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a new API token
|
|
12
|
+
* @param {Object} params - Function parameters
|
|
13
|
+
* @param {[string]} params.args - Command line args
|
|
14
|
+
* @param {Object} params.options - Command line options
|
|
15
|
+
* @param {'json'|'human'} params.options.format - Output format
|
|
16
|
+
*/
|
|
17
|
+
export async function create (params) {
|
|
18
|
+
const [apiTokenName] = params.args;
|
|
19
|
+
const { expiration, format } = params.options;
|
|
20
|
+
|
|
21
|
+
// Expire in 1 year
|
|
22
|
+
const dateObject = new Date();
|
|
23
|
+
dateObject.setFullYear(dateObject.getFullYear() + 1);
|
|
24
|
+
const maxExpirationDate = dateObject;
|
|
25
|
+
|
|
26
|
+
let expirationDate;
|
|
27
|
+
if (expiration != null) {
|
|
28
|
+
if (expiration > maxExpirationDate) {
|
|
29
|
+
throw new Error('You cannot set an expiration date greater than 1 year');
|
|
30
|
+
}
|
|
31
|
+
expirationDate = expiration;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
expirationDate = maxExpirationDate;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const user = await getCurrentUser();
|
|
38
|
+
|
|
39
|
+
const password = await promptPassword('Enter your password:');
|
|
40
|
+
|
|
41
|
+
let mfaCode;
|
|
42
|
+
if (user.preferredMFA === 'TOTP') {
|
|
43
|
+
mfaCode = await promptPassword('Enter your 2FA code:');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const tokenData = {
|
|
47
|
+
email: user.email,
|
|
48
|
+
password,
|
|
49
|
+
mfaCode,
|
|
50
|
+
name: apiTokenName,
|
|
51
|
+
expirationDate: expirationDate.toISOString(),
|
|
52
|
+
};
|
|
53
|
+
const createdToken = await createApiToken(tokenData).then(sendToAuthBridge).catch((error) => {
|
|
54
|
+
const errorCode = error?.cause?.responseBody?.code;
|
|
55
|
+
if (errorCode === 'invalid-credential') {
|
|
56
|
+
throw new Error('Invalid credentials, check your password');
|
|
57
|
+
}
|
|
58
|
+
if (errorCode === 'invalid-mfa-code') {
|
|
59
|
+
throw new Error('Invalid credentials, check your 2FA code');
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
switch (format) {
|
|
65
|
+
case 'json':
|
|
66
|
+
Logger.printJson(createdToken);
|
|
67
|
+
break;
|
|
68
|
+
case 'human':
|
|
69
|
+
default:
|
|
70
|
+
Logger.println(dedent`
|
|
71
|
+
${colors.green('✔')} API token successfully created! Store it securely, you won't able to print it again.
|
|
72
|
+
|
|
73
|
+
- API token ID : ${colors.grey(createdToken.apiTokenId)}
|
|
74
|
+
- API token : ${colors.grey(createdToken.apiToken)}
|
|
75
|
+
- Expiration : ${colors.grey(formatDate(createdToken.expirationDate))}
|
|
76
|
+
|
|
77
|
+
Export this token and use it to make authenticated requests to the Clever Cloud API through the Auth Bridge:
|
|
78
|
+
|
|
79
|
+
export CC_API_TOKEN=${createdToken.apiToken}
|
|
80
|
+
curl -H "Authorization: Bearer $CC_API_TOKEN" ${conf.AUTH_BRIDGE_HOST}/v2/self
|
|
81
|
+
|
|
82
|
+
Then, to revoke this token, run:
|
|
83
|
+
clever tokens revoke ${createdToken.apiTokenId}
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get information about an API token
|
|
90
|
+
* @param {Object} params - Function parameters
|
|
91
|
+
* @param {Object} params.options - Command line options
|
|
92
|
+
* @param {Object} params.options.format - Output format
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
export async function list (params) {
|
|
96
|
+
const { format } = params.options;
|
|
97
|
+
|
|
98
|
+
const tokens = await listApiTokens().then(sendToAuthBridge);
|
|
99
|
+
|
|
100
|
+
if (format === 'json') {
|
|
101
|
+
Logger.printJson(tokens);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (tokens.length === 0) {
|
|
105
|
+
Logger.println(`ℹ️ No API token found, create one with ${colors.blue('clever tokens create')} command`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.table(tokens.map((token) => {
|
|
109
|
+
return {
|
|
110
|
+
'API token ID': token.apiTokenId,
|
|
111
|
+
Name: token.name,
|
|
112
|
+
'Creation IP address': token.ip,
|
|
113
|
+
Creation: formatDate(token.creationDate),
|
|
114
|
+
Expiration: formatDate(token.expirationDate),
|
|
115
|
+
};
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Revoke an API token
|
|
123
|
+
* @param {Object} params - Function parameters
|
|
124
|
+
* @param {string[]} params.args - Command line arguments, token ID to revoke is expected as first argument
|
|
125
|
+
* @returns {Promise<void>}
|
|
126
|
+
*/
|
|
127
|
+
export async function revoke (params) {
|
|
128
|
+
const [apiTokenId] = params.args;
|
|
129
|
+
|
|
130
|
+
await deleteApiToken(apiTokenId).then(sendToAuthBridge);
|
|
131
|
+
|
|
132
|
+
Logger.println(colors.green('✔'), 'API token successfully revoked!');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatDate (dateInput) {
|
|
136
|
+
return new Date(dateInput).toISOString().substring(0, 16).replace('T', ' ');
|
|
137
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import dedent from 'dedent';
|
|
2
|
+
import { conf } from './models/configuration.js';
|
|
3
|
+
|
|
4
|
+
export const EXPERIMENTAL_FEATURES = {
|
|
5
|
+
kv: {
|
|
6
|
+
status: 'alpha',
|
|
7
|
+
description: 'Send commands to databases such as Materia KV or Redis® directly from Clever Tools, without other dependencies',
|
|
8
|
+
instructions: dedent`
|
|
9
|
+
Target any compatible add-on by its name or ID (with an org ID if needed) and send commands to it:
|
|
10
|
+
|
|
11
|
+
clever kv myMateriaKV SET myKey myValue
|
|
12
|
+
clever kv kv_xxxxxxxx GET myKey -F json
|
|
13
|
+
clever kv addon_xxxxx SET myTempKey myTempValue EX 120
|
|
14
|
+
clever kv myMateriaKV -o myOrg TTL myTempKey
|
|
15
|
+
clever kv redis_xxxxx --org org_xxxxx PING
|
|
16
|
+
|
|
17
|
+
Learn more about Materia KV: https://www.clever-cloud.com/developers/doc/addons/materia-kv/
|
|
18
|
+
`,
|
|
19
|
+
},
|
|
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
|
+
ng: {
|
|
34
|
+
status: 'beta',
|
|
35
|
+
description: 'Manage Network Groups to manage applications, add-ons, external peers through a Wireguard network',
|
|
36
|
+
instructions: dedent`
|
|
37
|
+
- Create a Network Group:
|
|
38
|
+
clever ng create myNG
|
|
39
|
+
- Create a Network Group with members (application, database add-on):
|
|
40
|
+
clever ng create myNG --link app_xxx,addon_xxx
|
|
41
|
+
- List Network Groups:
|
|
42
|
+
clever ng
|
|
43
|
+
- Delete a Network Group:
|
|
44
|
+
clever ng delete myNG
|
|
45
|
+
- (Un)Link an application or a database add-on to an existing Network Group:
|
|
46
|
+
clever ng link app_xxx myNG
|
|
47
|
+
clever ng unlink addon_xxx myNG
|
|
48
|
+
- Get the Wireguard configuration of a peer:
|
|
49
|
+
clever ng get-config peerIdOrLabel myNG
|
|
50
|
+
- Get details about a Network Group, a member or a peer:
|
|
51
|
+
clever ng get myNg
|
|
52
|
+
clever ng get app_xxx
|
|
53
|
+
clever ng get peerId
|
|
54
|
+
clever ng get memberLabel
|
|
55
|
+
- Search Network Groups, members or peers:
|
|
56
|
+
clever ng search myQuery
|
|
57
|
+
|
|
58
|
+
Learn more about Network Groups: https://github.com/CleverCloud/clever-tools/blob/master/docs/ng.md
|
|
59
|
+
`,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import colors from 'colors/safe.js';
|
|
2
|
+
import * as networkGroup from '../models/ng.js';
|
|
3
|
+
|
|
4
|
+
import { Logger } from '../logger.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Print a Network Group
|
|
8
|
+
* @param {Object} ng The Network Group to print
|
|
9
|
+
* @param {string} format Output format
|
|
10
|
+
* @param {boolean} full If true, get more details about the Network Group (default: false)
|
|
11
|
+
*/
|
|
12
|
+
function printNg (ng, format, full = false) {
|
|
13
|
+
|
|
14
|
+
switch (format) {
|
|
15
|
+
case 'json': {
|
|
16
|
+
Logger.printJson(ng);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
case 'human':
|
|
20
|
+
default: {
|
|
21
|
+
const ngData = {
|
|
22
|
+
ID: ng.id,
|
|
23
|
+
Label: ng.label,
|
|
24
|
+
Description: ng.description,
|
|
25
|
+
Network: `${ng.networkIp}`,
|
|
26
|
+
'Members/Peers': `${Object.keys(ng.members)?.length}/${Object.keys(ng.peers)?.length}`,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
console.table(ngData);
|
|
30
|
+
|
|
31
|
+
if (full) {
|
|
32
|
+
const members = Object.entries(ng.members)
|
|
33
|
+
.sort((a, b) => a[1].domainName.localeCompare(b[1].domainName))
|
|
34
|
+
.map(([id, member]) => ({
|
|
35
|
+
Domain: member.domainName,
|
|
36
|
+
}));
|
|
37
|
+
if (members.length > 0) {
|
|
38
|
+
Logger.println(colors.bold(' • Members:'));
|
|
39
|
+
console.table(members);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const peers = Object.entries(ng.peers)
|
|
43
|
+
.sort((a, b) => a[1].parentMember.localeCompare(b[1].parentMember))
|
|
44
|
+
.map(([id, peer]) => formatPeer(peer));
|
|
45
|
+
if (peers.length > 0) {
|
|
46
|
+
Logger.println(colors.bold(' • Peers:'));
|
|
47
|
+
console.table(peers);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Print a Network Group member
|
|
56
|
+
* @param {Object} member The Network Group member to print
|
|
57
|
+
* @param {string} format Output format
|
|
58
|
+
*/
|
|
59
|
+
function printMember (member, format) {
|
|
60
|
+
|
|
61
|
+
switch (format) {
|
|
62
|
+
case 'json': {
|
|
63
|
+
Logger.printJson(member);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'human':
|
|
67
|
+
default: {
|
|
68
|
+
console.table({
|
|
69
|
+
Label: member.label,
|
|
70
|
+
Domain: member.domainName,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Print a Network Group peer
|
|
78
|
+
* @param {Object} peer The Network Group peer to print
|
|
79
|
+
* @param {string} format Output format
|
|
80
|
+
* @param {boolean} full If true, get more details about the peer (default: false)
|
|
81
|
+
*/
|
|
82
|
+
function printPeer (peer, format, full = false) {
|
|
83
|
+
switch (format) {
|
|
84
|
+
case 'json': {
|
|
85
|
+
Logger.printJson(peer);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'human':
|
|
89
|
+
default: {
|
|
90
|
+
console.table(formatPeer(peer, full));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Format a peer to print
|
|
97
|
+
* @param {Object} peer
|
|
98
|
+
* @param {boolean} full If true, get more details about the peer (default: false)
|
|
99
|
+
*/
|
|
100
|
+
function formatPeer (peer, full = false) {
|
|
101
|
+
const peerToPrint = {
|
|
102
|
+
'Parent Member': peer.parentMember,
|
|
103
|
+
ID: peer.id,
|
|
104
|
+
Label: peer.label,
|
|
105
|
+
Type: peer.type,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (full) {
|
|
109
|
+
if (peer.endpoint.ngTerm != null) {
|
|
110
|
+
peerToPrint['Host:IP'] = `${peer.endpoint.ngTerm.host}:${peer.endpoint.ngTerm.port}`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
peerToPrint.Host = peer.endpoint.ngIp;
|
|
114
|
+
}
|
|
115
|
+
if (peer.endpoint.publicTerm != null) {
|
|
116
|
+
peerToPrint['Public Term'] = `${peer.endpoint.publicTerm.host}:${peer.endpoint.publicTerm.port}`;
|
|
117
|
+
}
|
|
118
|
+
peerToPrint['Public Key'] = peer.publicKey;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return peerToPrint;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Print the results of a search or get action
|
|
125
|
+
* @param {object} idOrLabel ID or label of the Network Group, a member or a peer
|
|
126
|
+
* @param {object} org Organisation ID or name
|
|
127
|
+
* @param {string} format Output format
|
|
128
|
+
* @param {string} action Action to perform (search or get)
|
|
129
|
+
* @param {string} type Type of item to search (NetworkGroup, Member, Peer)
|
|
130
|
+
*/
|
|
131
|
+
export async function printResults (idOrLabel, org, format, action, type) {
|
|
132
|
+
|
|
133
|
+
const exactMatch = action === 'get';
|
|
134
|
+
const toLookFor = type ?? (action === 'search' ? 'all' : 'single');
|
|
135
|
+
|
|
136
|
+
const found = await networkGroup.searchNgOrResource(idOrLabel, org, toLookFor, exactMatch);
|
|
137
|
+
|
|
138
|
+
if (!found.length) {
|
|
139
|
+
const searchString = idOrLabel.ngId
|
|
140
|
+
?? idOrLabel.memberId
|
|
141
|
+
?? idOrLabel.ngResourceLabel;
|
|
142
|
+
Logger.println(`${colors.blue('!')} No Network Group or resource found for ${colors.blue(searchString)}`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (found.length === 1) {
|
|
147
|
+
switch (found[0].type) {
|
|
148
|
+
case 'NetworkGroup':
|
|
149
|
+
return printNg(found[0], format, true);
|
|
150
|
+
case 'Member':
|
|
151
|
+
return printMember(found[0], format);
|
|
152
|
+
case 'CleverPeer':
|
|
153
|
+
case 'ExternalPeer':
|
|
154
|
+
return printPeer(found[0], format, true);
|
|
155
|
+
default:
|
|
156
|
+
throw new Error(`Unknown item type: ${found[0].type}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (action === 'search') {
|
|
161
|
+
// Group found items by type in a new object
|
|
162
|
+
const grouped = found.reduce((acc, item) => {
|
|
163
|
+
if (!acc[item.type]) {
|
|
164
|
+
acc[item.type] = [];
|
|
165
|
+
}
|
|
166
|
+
acc[item.type].push(item);
|
|
167
|
+
return acc;
|
|
168
|
+
}, {});
|
|
169
|
+
|
|
170
|
+
switch (format) {
|
|
171
|
+
case 'json': {
|
|
172
|
+
Logger.printJson(grouped);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'human':
|
|
176
|
+
default: {
|
|
177
|
+
if (grouped.NetworkGroup) {
|
|
178
|
+
Logger.println(`${colors.bold(` • Found ${grouped.NetworkGroup.length} Network Group(s):`)}`);
|
|
179
|
+
grouped.NetworkGroup?.forEach((item) => printNg(item, format));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (grouped.Member) {
|
|
183
|
+
Logger.println(`${colors.bold(` • Found ${grouped.Member.length} Member(s):`)}`);
|
|
184
|
+
grouped.Member?.forEach((item) => printMember(item, format));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (grouped.ExternalPeer || grouped.CleverPeer) {
|
|
188
|
+
Logger.println(`${colors.bold(` • Found ${grouped.ExternalPeer.length + grouped.CleverPeer.length} Peer(s):`)}`);
|
|
189
|
+
grouped.CleverPeer?.forEach((item) => printPeer(item, format));
|
|
190
|
+
grouped.ExternalPeer?.forEach((item) => printPeer(item, format));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
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';
|
|
@@ -10,6 +9,7 @@ import * as Interact from './interact.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,12 +191,12 @@ 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) {
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
create as createApplication,
|
|
4
|
+
remove as removeApplication,
|
|
5
|
+
getAll as getAllApplications,
|
|
6
|
+
get as getApplication,
|
|
7
|
+
redeploy as redeployApplication,
|
|
8
|
+
update as updateApplication,
|
|
9
|
+
getAllDependencies,
|
|
10
|
+
addDependency,
|
|
11
|
+
removeDependency,
|
|
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';
|
|
6
15
|
|
|
7
16
|
import * as AppConfiguration from './app_configuration.js';
|
|
@@ -12,6 +21,7 @@ import * as User from './user.js';
|
|
|
12
21
|
|
|
13
22
|
import { sendToApi } from '../models/send-to-api.js';
|
|
14
23
|
import { resolveOwnerId } from './ids-resolver.js';
|
|
24
|
+
import { getAvailableInstances } from '@clevercloud/client/esm/api/v2/product.js';
|
|
15
25
|
|
|
16
26
|
export function listAvailableTypes () {
|
|
17
27
|
return cliparse.autocomplete.words(['docker', 'elixir', 'go', 'gradle', 'haskell', 'jar', 'maven', 'meteor', 'node', 'php', 'play1', 'play2', 'python', 'ruby', 'rust', 'sbt', 'static-apache', 'war']);
|
|
@@ -42,7 +52,7 @@ async function getId (ownerId, dependency) {
|
|
|
42
52
|
async function getInstanceType (type) {
|
|
43
53
|
|
|
44
54
|
// TODO: We should be able to use it without {}
|
|
45
|
-
const types = await
|
|
55
|
+
const types = await getAvailableInstances({}).then(sendToApi);
|
|
46
56
|
|
|
47
57
|
const enabledTypes = types.filter((t) => t.enabled);
|
|
48
58
|
const matchingVariants = enabledTypes.filter((t) => t.variant != null && t.variant.slug === type);
|
|
@@ -83,7 +93,7 @@ export async function create (name, typeName, region, orgaIdOrName, github, isTa
|
|
|
83
93
|
newApp.oauthApp = github;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
|
-
return
|
|
96
|
+
return createApplication({ id: ownerId }, newApp).then(sendToApi);
|
|
87
97
|
};
|
|
88
98
|
|
|
89
99
|
export async function deleteApp (app, skipConfirmation) {
|
|
@@ -97,7 +107,7 @@ export async function deleteApp (app, skipConfirmation) {
|
|
|
97
107
|
);
|
|
98
108
|
}
|
|
99
109
|
|
|
100
|
-
return
|
|
110
|
+
return removeApplication({ id: app.ownerId, appId: app.id }).then(sendToApi);
|
|
101
111
|
};
|
|
102
112
|
|
|
103
113
|
export async function getAllApps (ownerId) {
|
|
@@ -123,7 +133,7 @@ export async function getAllApps (ownerId) {
|
|
|
123
133
|
};
|
|
124
134
|
|
|
125
135
|
async function getApplicationsForOwner (ownerId) {
|
|
126
|
-
const rawApplications = await
|
|
136
|
+
const rawApplications = await getAllApplications({ id: ownerId }).then(sendToApi);
|
|
127
137
|
return rawApplications.map((app) => {
|
|
128
138
|
return {
|
|
129
139
|
app_id: app.id,
|
|
@@ -150,13 +160,13 @@ function getApplicationByName (apps, name) {
|
|
|
150
160
|
};
|
|
151
161
|
|
|
152
162
|
async function getByName (ownerId, name) {
|
|
153
|
-
const apps = await
|
|
163
|
+
const apps = await getAllApplications({ id: ownerId }).then(sendToApi);
|
|
154
164
|
return getApplicationByName(apps, name);
|
|
155
165
|
};
|
|
156
166
|
|
|
157
167
|
export function get (ownerId, appId) {
|
|
158
168
|
Logger.debug(`Get information for the app: ${appId}`);
|
|
159
|
-
return
|
|
169
|
+
return getApplication({ id: ownerId, appId }).then(sendToApi);
|
|
160
170
|
};
|
|
161
171
|
|
|
162
172
|
function getFromSelf (appId) {
|
|
@@ -164,7 +174,7 @@ function getFromSelf (appId) {
|
|
|
164
174
|
// /self differs from /organisations only for this one:
|
|
165
175
|
// it fallbacks to the organisations of which the user
|
|
166
176
|
// is a member, if it doesn't belong to Personal Space.
|
|
167
|
-
return
|
|
177
|
+
return getApplication({ appId }).then(sendToApi);
|
|
168
178
|
};
|
|
169
179
|
|
|
170
180
|
/**
|
|
@@ -245,7 +255,7 @@ export function unlinkRepo (alias) {
|
|
|
245
255
|
export function redeploy (ownerId, appId, commit, withoutCache) {
|
|
246
256
|
Logger.debug(`Redeploying the app: ${appId}`);
|
|
247
257
|
const useCache = (withoutCache) ? 'no' : null;
|
|
248
|
-
return
|
|
258
|
+
return redeployApplication({ id: ownerId, appId, commit, useCache }).then(sendToApi);
|
|
249
259
|
};
|
|
250
260
|
|
|
251
261
|
export function mergeScalabilityParameters (scalabilityParameters, instance) {
|
|
@@ -283,7 +293,7 @@ export function mergeScalabilityParameters (scalabilityParameters, instance) {
|
|
|
283
293
|
export async function setScalability (appId, ownerId, scalabilityParameters, buildFlavor) {
|
|
284
294
|
Logger.info('Scaling the app: ' + appId);
|
|
285
295
|
|
|
286
|
-
const app = await
|
|
296
|
+
const app = await getApplication({ id: ownerId, appId }).then(sendToApi);
|
|
287
297
|
const instance = _.cloneDeep(app.instance);
|
|
288
298
|
|
|
289
299
|
instance.minFlavor = instance.minFlavor.name;
|
|
@@ -301,17 +311,17 @@ export async function setScalability (appId, ownerId, scalabilityParameters, bui
|
|
|
301
311
|
}
|
|
302
312
|
}
|
|
303
313
|
|
|
304
|
-
return
|
|
314
|
+
return updateApplication({ id: ownerId, appId }, newConfig).then(sendToApi);
|
|
305
315
|
};
|
|
306
316
|
|
|
307
317
|
export async function listDependencies (ownerId, appId, showAll) {
|
|
308
|
-
const applicationDeps = await
|
|
318
|
+
const applicationDeps = await getAllDependencies({ id: ownerId, appId }).then(sendToApi);
|
|
309
319
|
|
|
310
320
|
if (!showAll) {
|
|
311
321
|
return applicationDeps.map((app) => ({ ...app, isLinked: true }));
|
|
312
322
|
}
|
|
313
323
|
|
|
314
|
-
const allApps = await
|
|
324
|
+
const allApps = await getAllApplications({ id: ownerId }).then(sendToApi);
|
|
315
325
|
|
|
316
326
|
const applicationDepsIds = applicationDeps.map((app) => app.id);
|
|
317
327
|
return allApps.map((app) => {
|
|
@@ -322,10 +332,10 @@ export async function listDependencies (ownerId, appId, showAll) {
|
|
|
322
332
|
|
|
323
333
|
export async function link (ownerId, appId, dependency) {
|
|
324
334
|
const dependencyId = await getId(ownerId, dependency);
|
|
325
|
-
return
|
|
335
|
+
return addDependency({ id: ownerId, appId, dependencyId }).then(sendToApi);
|
|
326
336
|
};
|
|
327
337
|
|
|
328
338
|
export async function unlink (ownerId, appId, dependency) {
|
|
329
339
|
const dependencyId = await getId(ownerId, dependency);
|
|
330
|
-
return
|
|
340
|
+
return removeDependency({ id: ownerId, appId, dependencyId }).then(sendToApi);
|
|
331
341
|
};
|