clever-tools 2.9.0 → 2.10.0-beta.1
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/CHANGELOG.md +10 -0
- package/README.md +24 -20
- package/bin/clever.js +248 -3
- package/package.json +7 -6
- package/src/commands/applications.js +24 -15
- package/src/commands/console.js +1 -1
- package/src/commands/login.js +1 -1
- package/src/commands/networkgroups/commands.js +7 -0
- package/src/commands/networkgroups/index.js +67 -0
- package/src/commands/networkgroups/members.js +80 -0
- package/src/commands/networkgroups/peers.js +83 -0
- package/src/commands/open.js +1 -1
- package/src/models/app_configuration.js +38 -0
- package/src/models/format-ng-table.js +115 -0
- package/src/models/format-string.js +44 -0
- package/src/models/networkgroup.js +62 -0
- package/src/models/wireguard-conf.js +95 -0
- package/src/models/wireguard.js +75 -0
- package/src/parsers.js +76 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ngApi = require('@clevercloud/client/cjs/api/v4/network-group.js');
|
|
4
|
+
|
|
5
|
+
const { sendToApi } = require('../../models/send-to-api.js');
|
|
6
|
+
|
|
7
|
+
const Logger = require('../../logger.js');
|
|
8
|
+
const NetworkGroup = require('../../models/networkgroup.js');
|
|
9
|
+
const Formatter = require('../../models/format-string.js');
|
|
10
|
+
const TableFormatter = require('../../models/format-ng-table.js');
|
|
11
|
+
|
|
12
|
+
async function listMembers(params) {
|
|
13
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, 'natural-name': naturalName, json } = params.options;
|
|
14
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
15
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
|
|
16
|
+
|
|
17
|
+
Logger.info(`Listing members from Network Group '${networkGroupId}'`);
|
|
18
|
+
const result = await ngApi.listNetworkGroupMembers({ ownerId, networkGroupId }).then(sendToApi);
|
|
19
|
+
|
|
20
|
+
if (json) {
|
|
21
|
+
Logger.println(JSON.stringify(result, null, 2));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
if (result.length === 0) {
|
|
25
|
+
Logger.println(`No member found. You can add one with ${Formatter.formatCommand('clever networkgroups members add')}.`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
await TableFormatter.printMembersTableHeader(naturalName);
|
|
29
|
+
for (const ng of result) {
|
|
30
|
+
Logger.println(await TableFormatter.formatMembersLine(ng, naturalName));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function getMember(params) {
|
|
37
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, 'member-id': memberId, 'natural-name': naturalName, json } = params.options;
|
|
38
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
39
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);;
|
|
40
|
+
|
|
41
|
+
Logger.info(`Getting details for member ${Formatter.formatString(memberId)} in Network Group ${Formatter.formatString(networkGroupId)}`);
|
|
42
|
+
const result = await ngApi.getNetworkGroupMember({ ownerId, networkGroupId, memberId: memberId }).then(sendToApi);
|
|
43
|
+
|
|
44
|
+
if (json) {
|
|
45
|
+
Logger.println(JSON.stringify(result, null, 2));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await TableFormatter.printMembersTableHeader(naturalName);
|
|
49
|
+
Logger.println(await TableFormatter.formatMembersLine(result, naturalName));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function addMember(params) {
|
|
54
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, 'member-id': memberId, type, 'domain-name': domainName, label } = params.options;
|
|
55
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
56
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
|
|
57
|
+
|
|
58
|
+
const body = { id: memberId, label, domain_name: domainName, type };
|
|
59
|
+
Logger.debug('Sending body: ' + JSON.stringify(body, null, 2));
|
|
60
|
+
await ngApi.createNetworkGroupMember({ ownerId, networkGroupId }, body).then(sendToApi);
|
|
61
|
+
|
|
62
|
+
Logger.println(`Successfully added member ${Formatter.formatString(memberId)} to Network Group ${Formatter.formatString(networkGroupId)}.`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function removeMember(params) {
|
|
66
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, 'member-id': memberId } = params.options;
|
|
67
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
68
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
|
|
69
|
+
|
|
70
|
+
await ngApi.deleteNetworkGroupMember({ ownerId, networkGroupId, memberId }).then(sendToApi);
|
|
71
|
+
|
|
72
|
+
Logger.println(`Successfully removed member ${Formatter.formatString(memberId)} from Network Group ${Formatter.formatString(networkGroupId)}.`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
listMembers,
|
|
77
|
+
getMember,
|
|
78
|
+
addMember,
|
|
79
|
+
removeMember,
|
|
80
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ngApi = require('@clevercloud/client/cjs/api/v4/network-group.js');
|
|
4
|
+
|
|
5
|
+
const { sendToApi } = require('../../models/send-to-api.js');
|
|
6
|
+
|
|
7
|
+
const Logger = require('../../logger.js');
|
|
8
|
+
const NetworkGroup = require('../../models/networkgroup.js');
|
|
9
|
+
const Formatter = require('../../models/format-string.js');
|
|
10
|
+
const TableFormatter = require('../../models/format-ng-table.js');
|
|
11
|
+
|
|
12
|
+
async function listPeers(params) {
|
|
13
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, json } = params.options;
|
|
14
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
15
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
|
|
16
|
+
|
|
17
|
+
Logger.info(`Listing peers from Network Group ${Formatter.formatString(networkGroupId)}`);
|
|
18
|
+
const result = await ngApi.listNetworkGroupPeers({ ownerId, networkGroupId }).then(sendToApi);
|
|
19
|
+
|
|
20
|
+
if (json) {
|
|
21
|
+
Logger.println(JSON.stringify(result, null, 2));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
if (result.length === 0) {
|
|
25
|
+
Logger.println(`No peer found. You can add an external one with ${Formatter.formatCommand('clever networkgroups peers add-external')}.`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
TableFormatter.printPeersTableHeader();
|
|
29
|
+
result.forEach((peer) => {
|
|
30
|
+
Logger.println(TableFormatter.formatPeersLine(peer));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function getPeer(params) {
|
|
37
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, 'peer-id': peerId, json } = params.options;
|
|
38
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
39
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
|
|
40
|
+
|
|
41
|
+
Logger.info(`Getting details for peer ${Formatter.formatString(peerId)} in Network Group ${Formatter.formatString(networkGroupId)}`);
|
|
42
|
+
const peer = await ngApi.getNetworkGroupPeer({ ownerId, networkGroupId, peerId }).then(sendToApi);
|
|
43
|
+
|
|
44
|
+
if (json) {
|
|
45
|
+
Logger.println(JSON.stringify(peer, null, 2));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
TableFormatter.printPeersTableHeader();
|
|
49
|
+
Logger.println(TableFormatter.formatPeersLine(peer));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function addExternalPeer(params) {
|
|
54
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, role, 'public-key': publicKey, label, parent, ip, port } = params.options;
|
|
55
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
56
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
|
|
57
|
+
|
|
58
|
+
const body = { peer_role: role, public_key: publicKey, label, parent_member: parent, ip, port };
|
|
59
|
+
Logger.info(`Adding external peer to Network Group ${Formatter.formatString(networkGroupId)}`);
|
|
60
|
+
Logger.debug('Sending body: ' + JSON.stringify(body, null, 2));
|
|
61
|
+
const { id: peerId } = await ngApi.createNetworkGroupExternalPeer({ ownerId, networkGroupId }, body).then(sendToApi);
|
|
62
|
+
|
|
63
|
+
Logger.println(`External peer ${Formatter.formatString(peerId)} must have been added to Network Group ${Formatter.formatString(networkGroupId)}.`);
|
|
64
|
+
return peerId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function removeExternalPeer(params) {
|
|
68
|
+
const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel, 'peer-id': peerId } = params.options;
|
|
69
|
+
const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
|
|
70
|
+
const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
|
|
71
|
+
|
|
72
|
+
Logger.info(`Removing external peer ${Formatter.formatString(peerId)} from Network Group ${Formatter.formatString(networkGroupId)}`);
|
|
73
|
+
await ngApi.deleteNetworkGroupExternalPeer({ ownerId, networkGroupId, peerId }).then(sendToApi);
|
|
74
|
+
|
|
75
|
+
Logger.println(`External peer ${Formatter.formatString(peerId)} must have been removed from Network Group ${Formatter.formatString(networkGroupId)}.`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
listPeers,
|
|
80
|
+
getPeer,
|
|
81
|
+
addExternalPeer,
|
|
82
|
+
removeExternalPeer,
|
|
83
|
+
};
|
package/src/commands/open.js
CHANGED
|
@@ -79,6 +79,14 @@ function findApp (config, alias) {
|
|
|
79
79
|
return appByAlias;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
return findDefaultApp(config);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function findDefaultApp (config) {
|
|
86
|
+
if (_.isEmpty(config.apps)) {
|
|
87
|
+
throw new Error('There are no applications linked. You can add one with `clever link`');
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
if (config.default != null) {
|
|
83
91
|
const defaultApp = _.find(config.apps, { app_id: config.default });
|
|
84
92
|
if (defaultApp == null) {
|
|
@@ -95,6 +103,25 @@ function findApp (config, alias) {
|
|
|
95
103
|
throw new Error(`Several applications are linked. You can specify one with the "--alias" option. Run "clever applications" to list linked applications. Available aliases: ${aliases}`);
|
|
96
104
|
}
|
|
97
105
|
|
|
106
|
+
async function getAppDetailsForId (appId) {
|
|
107
|
+
const config = await loadApplicationConf();
|
|
108
|
+
|
|
109
|
+
if (_.isEmpty(config.apps)) {
|
|
110
|
+
throw new Error('There are no applications linked. You can add one with `clever link`');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const [appById, secondAppById] = _.filter(config.apps, { app_id: appId });
|
|
114
|
+
if (appById == null) {
|
|
115
|
+
throw new Error(`There are no applications matching id '${appId}'`);
|
|
116
|
+
}
|
|
117
|
+
if (secondAppById != null) {
|
|
118
|
+
throw new Error(`There are several applications matching id '${appId}'.`
|
|
119
|
+
+ 'This should not happen, your `.clever.json` should be fixed.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return appById;
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
async function getAppDetails ({ alias }) {
|
|
99
126
|
const config = await loadApplicationConf();
|
|
100
127
|
const app = findApp(config, alias);
|
|
@@ -110,6 +137,16 @@ async function getAppDetails ({ alias }) {
|
|
|
110
137
|
};
|
|
111
138
|
};
|
|
112
139
|
|
|
140
|
+
async function getMostNaturalName (appId) {
|
|
141
|
+
try {
|
|
142
|
+
const details = await getAppDetailsForId(appId);
|
|
143
|
+
return details.alias || details.name || appId;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return appId;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
function persistConfig (modifiedConfig) {
|
|
114
151
|
const jsonContents = JSON.stringify(modifiedConfig, null, 2);
|
|
115
152
|
return fs.writeFile(conf.APP_CONFIGURATION_FILE, jsonContents);
|
|
@@ -128,5 +165,6 @@ module.exports = {
|
|
|
128
165
|
removeLinkedApplication,
|
|
129
166
|
findApp,
|
|
130
167
|
getAppDetails,
|
|
168
|
+
getMostNaturalName,
|
|
131
169
|
setDefault,
|
|
132
170
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const formatNgTable = require('../format-table.js');
|
|
4
|
+
const colors = require('colors/safe');
|
|
5
|
+
|
|
6
|
+
const AppConfig = require('./app_configuration.js');
|
|
7
|
+
const Logger = require('../logger.js');
|
|
8
|
+
const Formatter = require('./format-string.js');
|
|
9
|
+
|
|
10
|
+
function printSeparator (columnLengths) {
|
|
11
|
+
Logger.println('─'.repeat(columnLengths.reduce((a, b) => a + b + 2)));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// We use examples of maximum width text to have a clean display
|
|
15
|
+
const networkGroupsTableColumnLengths = [
|
|
16
|
+
39, /* id length */
|
|
17
|
+
48, /* label length */
|
|
18
|
+
7, /* members length */
|
|
19
|
+
5, /* peers length */
|
|
20
|
+
48, /* description */
|
|
21
|
+
];
|
|
22
|
+
const formatNetworkGroupsTable = formatNgTable(networkGroupsTableColumnLengths);
|
|
23
|
+
function formatNetworkGroupsLine (ng) {
|
|
24
|
+
return formatNetworkGroupsTable([
|
|
25
|
+
[
|
|
26
|
+
Formatter.formatId(ng.id),
|
|
27
|
+
Formatter.formatString(ng.label, false),
|
|
28
|
+
Formatter.formatNumber(ng.members.length),
|
|
29
|
+
Formatter.formatNumber(ng.peers.length),
|
|
30
|
+
Formatter.formatString(ng.description || ' ', false),
|
|
31
|
+
],
|
|
32
|
+
]);
|
|
33
|
+
};
|
|
34
|
+
function printNetworkGroupsTableHeader () {
|
|
35
|
+
Logger.println(colors.bold(formatNetworkGroupsTable([
|
|
36
|
+
['Network Group ID', 'Label', 'Members', 'Peers', 'Description'],
|
|
37
|
+
])));
|
|
38
|
+
printSeparator(networkGroupsTableColumnLengths);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const membersTableColumnLengths = [
|
|
42
|
+
48, /* id length */
|
|
43
|
+
12, /* type length */
|
|
44
|
+
48, /* label length */
|
|
45
|
+
24, /* domain-name length */
|
|
46
|
+
];
|
|
47
|
+
const formatMembersTable = formatNgTable(membersTableColumnLengths);
|
|
48
|
+
async function formatMembersLine (member, showAliases = false) {
|
|
49
|
+
return formatMembersTable([
|
|
50
|
+
[
|
|
51
|
+
showAliases
|
|
52
|
+
? Formatter.formatString(await AppConfig.getMostNaturalName(member.id), false)
|
|
53
|
+
: Formatter.formatId(member.id),
|
|
54
|
+
Formatter.formatString(member.type, false),
|
|
55
|
+
Formatter.formatString(member.label, false),
|
|
56
|
+
Formatter.formatString(member.domain_name || ' ', false),
|
|
57
|
+
],
|
|
58
|
+
]);
|
|
59
|
+
};
|
|
60
|
+
async function printMembersTableHeader (naturalName = false) {
|
|
61
|
+
Logger.println(colors.bold(formatMembersTable([
|
|
62
|
+
[
|
|
63
|
+
naturalName ? 'Member' : 'Member ID',
|
|
64
|
+
'Member Type',
|
|
65
|
+
'Label',
|
|
66
|
+
'Domain Name',
|
|
67
|
+
],
|
|
68
|
+
])));
|
|
69
|
+
printSeparator(membersTableColumnLengths);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const peersTableColumnLengths = [
|
|
73
|
+
45, /* id length */
|
|
74
|
+
12, /* type length */
|
|
75
|
+
14, /* endpoint type length */
|
|
76
|
+
48, /* label length */
|
|
77
|
+
24, /* hostname */
|
|
78
|
+
15, /* ip */
|
|
79
|
+
];
|
|
80
|
+
const formatPeersTable = formatNgTable(peersTableColumnLengths);
|
|
81
|
+
function formatPeersLine (peer) {
|
|
82
|
+
const ip = (peer.endpoint.type === 'ServerEndpoint') ? peer.endpoint.ng_term.ip : peer.endpoint.ng_ip;
|
|
83
|
+
return formatPeersTable([
|
|
84
|
+
[
|
|
85
|
+
Formatter.formatId(peer.id),
|
|
86
|
+
Formatter.formatString(peer.type, false),
|
|
87
|
+
Formatter.formatString(peer.endpoint.type, false),
|
|
88
|
+
Formatter.formatString(peer.label, false),
|
|
89
|
+
Formatter.formatString(peer.hostname, false),
|
|
90
|
+
Formatter.formatIp(ip),
|
|
91
|
+
],
|
|
92
|
+
]);
|
|
93
|
+
};
|
|
94
|
+
function printPeersTableHeader () {
|
|
95
|
+
Logger.println(colors.bold(formatPeersTable([
|
|
96
|
+
[
|
|
97
|
+
'Peer ID',
|
|
98
|
+
'Peer Type',
|
|
99
|
+
'Endpoint Type',
|
|
100
|
+
'Label',
|
|
101
|
+
'Hostname',
|
|
102
|
+
'IP Address',
|
|
103
|
+
],
|
|
104
|
+
])));
|
|
105
|
+
printSeparator(peersTableColumnLengths);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
formatNetworkGroupsLine,
|
|
110
|
+
printNetworkGroupsTableHeader,
|
|
111
|
+
formatMembersLine,
|
|
112
|
+
printMembersTableHeader,
|
|
113
|
+
formatPeersLine,
|
|
114
|
+
printPeersTableHeader,
|
|
115
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const colors = require('colors/safe');
|
|
4
|
+
|
|
5
|
+
function formatId (id) {
|
|
6
|
+
return colors.dim(id);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatString (str, decorated = true) {
|
|
10
|
+
const string = decorated ? `'${str}'` : str;
|
|
11
|
+
return colors.green(string);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatNumber (number) {
|
|
15
|
+
return colors.yellow(number);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatIp (ip) {
|
|
19
|
+
return colors.cyan(ip);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatUrl (url, decorated = true) {
|
|
23
|
+
const string = decorated ? `<${url}>` : url;
|
|
24
|
+
return colors.cyan(string);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatCommand (command, decorated = true) {
|
|
28
|
+
const string = decorated ? `\`${command}\`` : command;
|
|
29
|
+
return colors.magenta(string);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatCode (code, decorated = true) {
|
|
33
|
+
return formatCommand(code, decorated);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
formatId,
|
|
38
|
+
formatString,
|
|
39
|
+
formatNumber,
|
|
40
|
+
formatIp,
|
|
41
|
+
formatUrl,
|
|
42
|
+
formatCommand,
|
|
43
|
+
formatCode,
|
|
44
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const autocomplete = require('cliparse').autocomplete;
|
|
4
|
+
const Organisation = require('../models/organisation.js');
|
|
5
|
+
const User = require('../models/user.js');
|
|
6
|
+
const AppConfig = require('./app_configuration.js');
|
|
7
|
+
const ngApi = require('@clevercloud/client/cjs/api/v4/network-group.js');
|
|
8
|
+
const { sendToApi } = require('./send-to-api.js');
|
|
9
|
+
|
|
10
|
+
async function getOwnerId(orgaIdOrName, alias) {
|
|
11
|
+
if (orgaIdOrName == null) {
|
|
12
|
+
try {
|
|
13
|
+
return (await AppConfig.getAppDetails({alias})).ownerId;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return (await User.getCurrentId())
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
return (await Organisation.getId(orgaIdOrName));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function getId(ownerId, ngIdOrLabel) {
|
|
23
|
+
if (ngIdOrLabel == null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (ngIdOrLabel.ng_id != null) {
|
|
28
|
+
return ngIdOrLabel.ng_id;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return getByLabel(ownerId, ngIdOrLabel.ng_label)
|
|
32
|
+
.then((ng) => ng.id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function getByLabel(owner_id, label) {
|
|
36
|
+
const networkGroups = await ngApi.get({ owner_id }).then(sendToApi);
|
|
37
|
+
const filteredNgs = networkGroups.filter((ng) => ng.label === label);
|
|
38
|
+
|
|
39
|
+
if (filteredNgs.length === 0) {
|
|
40
|
+
throw new Error('Network Group not found');
|
|
41
|
+
}
|
|
42
|
+
if (filteredNgs.length > 1) {
|
|
43
|
+
throw new Error('Ambiguous Network Group label');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return filteredNgs[0];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function listAvailablePeerRoles() {
|
|
50
|
+
return autocomplete.words(['client', 'server']);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function listAvailableMemberTypes() {
|
|
54
|
+
return autocomplete.words(['application', 'addon', 'external']);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
getOwnerId,
|
|
59
|
+
getId,
|
|
60
|
+
listAvailablePeerRoles,
|
|
61
|
+
listAvailableMemberTypes,
|
|
62
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { promises: fs, existsSync } = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const Logger = require('../logger.js');
|
|
8
|
+
const Formatter = require('./format-string.js');
|
|
9
|
+
|
|
10
|
+
function getWgConfFolder () {
|
|
11
|
+
// TODO: See if we can use runtime dirs
|
|
12
|
+
return path.join(os.tmpdir(), 'com.clever-cloud.networkgroups');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function createWgConfFolderIfNeeded () {
|
|
16
|
+
const confFolder = getWgConfFolder();
|
|
17
|
+
if (!existsSync(confFolder)) {
|
|
18
|
+
await fs.mkdir(confFolder);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getWgConfInformation (ngId) {
|
|
23
|
+
const confName = `wgcc${ngId.slice(-8)}`;
|
|
24
|
+
const confPath = path.join(getWgConfFolder(), `${confName}.conf`);
|
|
25
|
+
|
|
26
|
+
return { confName, confPath };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getPeerIdPath (confName) {
|
|
30
|
+
return path.join(getWgConfFolder(), `${confName}.id`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function storePeerId (peerId, confName) {
|
|
34
|
+
const filePath = getPeerIdPath(confName);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await fs.writeFile(filePath, peerId, { mode: 0o600, flag: 'wx' });
|
|
38
|
+
Logger.info(`Saved peer ID file to ${Formatter.formatUrl(filePath)}`);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`Error saving peer ID: ${error}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getPeerId (ngId) {
|
|
46
|
+
const { confName } = getWgConfInformation(ngId);
|
|
47
|
+
const filePath = getPeerIdPath(confName);
|
|
48
|
+
if (existsSync(filePath)) {
|
|
49
|
+
Logger.debug(`Reading peer ID from ${Formatter.formatUrl(filePath)}`);
|
|
50
|
+
return (await fs.readFile(filePath, { encoding: 'utf-8' })).trim();
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
Logger.debug(`No file found at ${Formatter.formatUrl(filePath)}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function deletePeerIdFile (ngId) {
|
|
59
|
+
const { confName } = getWgConfInformation(ngId);
|
|
60
|
+
const filePath = getPeerIdPath(confName);
|
|
61
|
+
// We need `force: true` to avoid errors if file doesn't exist
|
|
62
|
+
await fs.rm(filePath, { force: true });
|
|
63
|
+
Logger.info(`Deleted peer ID from ${Formatter.formatUrl(filePath)}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function getInterfaceName (confName) {
|
|
67
|
+
// This file is created by WireGuard®, hence the file path (`/var/run/…`)
|
|
68
|
+
// TODO: Handle Windows (not yet supported by `wg-quick` anyway)
|
|
69
|
+
const interfaceNameFile = path.join('/var', 'run', 'wireguard', `${confName}.name`);
|
|
70
|
+
|
|
71
|
+
Logger.debug(`Reading WireGuard® interface name in ${Formatter.formatUrl(interfaceNameFile)}…`);
|
|
72
|
+
const interfaceName = (await fs.readFile(interfaceNameFile, { encoding: 'utf-8' })).trim();
|
|
73
|
+
Logger.debug(`Found WireGuard® interface name ${Formatter.formatString(interfaceName)} for ${Formatter.formatString(confName)}`);
|
|
74
|
+
return interfaceName;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function confWithoutPlaceholders (conf, { privateKey }) {
|
|
78
|
+
conf = conf.replace('<%PrivateKey%>', privateKey);
|
|
79
|
+
|
|
80
|
+
// TODO: This just removes leading and trailing new lines in the configuration file
|
|
81
|
+
// It should be better formatted on the API's side
|
|
82
|
+
conf = conf.trim();
|
|
83
|
+
|
|
84
|
+
return conf;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
createWgConfFolderIfNeeded,
|
|
89
|
+
getWgConfInformation,
|
|
90
|
+
storePeerId,
|
|
91
|
+
getPeerId,
|
|
92
|
+
deletePeerIdFile,
|
|
93
|
+
getInterfaceName,
|
|
94
|
+
confWithoutPlaceholders,
|
|
95
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync, spawnSync } = require('child_process');
|
|
4
|
+
const Logger = require('../logger.js');
|
|
5
|
+
|
|
6
|
+
function privateKey () {
|
|
7
|
+
return execSync('wg genkey', { encoding: 'utf-8' }).trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function publicKey (privateKey) {
|
|
11
|
+
return execSync(`echo '${privateKey}' | wg pubkey`, { encoding: 'utf-8' }).trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function up (confPath) {
|
|
15
|
+
// We must use `spawn` with `detached: true` instead of `exec`
|
|
16
|
+
// because `wg-quick up` starts a `wireguard-go` used by `wg-quick down`
|
|
17
|
+
const { stdout, stderr } = spawnSync('wg-quick', ['up', confPath], { detached: true, encoding: 'utf-8' });
|
|
18
|
+
if (stdout.length > 0) {
|
|
19
|
+
Logger.debug(stdout.trim());
|
|
20
|
+
}
|
|
21
|
+
if (stderr.length > 0) {
|
|
22
|
+
Logger.debug(stderr.trim());
|
|
23
|
+
}
|
|
24
|
+
Logger.println('Activated WireGuard® tunnel');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function update (confPath, interfaceName) {
|
|
28
|
+
try {
|
|
29
|
+
// Update WireGuard® configuration
|
|
30
|
+
execSync(`wg-quick strip ${confPath} | wg syncconf ${interfaceName} /dev/stdin`);
|
|
31
|
+
Logger.info('Updated WireGuard® tunnel configuration');
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`Error updating WireGuard® tunnel configuration: ${error}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function down (confPath) {
|
|
39
|
+
const { stdout, stderr } = spawnSync('wg-quick', ['down', confPath], { encoding: 'utf-8' });
|
|
40
|
+
if (stdout.length > 0) {
|
|
41
|
+
Logger.debug(stdout.trim());
|
|
42
|
+
}
|
|
43
|
+
if (stderr.length > 0) {
|
|
44
|
+
Logger.debug(stderr.trim());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check that `wg` and `wg-quick` are installed
|
|
50
|
+
*/
|
|
51
|
+
function checkAvailable () {
|
|
52
|
+
try {
|
|
53
|
+
// The redirect to `/dev/null` ensures that your program does not produce the output of these commands.
|
|
54
|
+
execSync('which wg > /dev/null 2>&1');
|
|
55
|
+
execSync('which wg-quick > /dev/null 2>&1');
|
|
56
|
+
|
|
57
|
+
// TODO: Handle Windows
|
|
58
|
+
// - Those checks won't work on Windows, and wg-quick doesn't exist anyway.
|
|
59
|
+
// - We need to wait for a Windows version of wg-quick to support the rest of the operations
|
|
60
|
+
// - Or we could use vanilla wg on Windows, and wg-quick on other OSs
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
privateKey,
|
|
70
|
+
publicKey,
|
|
71
|
+
up,
|
|
72
|
+
update,
|
|
73
|
+
down,
|
|
74
|
+
checkAvailable,
|
|
75
|
+
};
|