clever-tools 2.9.1 → 2.10.0-beta.2

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 CHANGED
@@ -2,7 +2,13 @@
2
2
 
3
3
  ## Unrelease (????-??-??)
4
4
 
5
- ...
5
+ ## 2.10.0-beta.2 (2022-09-13)
6
+
7
+ * NetworkGroups fix wrong function call.
8
+
9
+ ## 2.10.0-beta.0 (2022-09-06)
10
+
11
+ * Add NetworkGroups commands `clever ng --help`
6
12
 
7
13
  ## 2.9.1 (2022-02-28)
8
14
 
package/bin/clever.js CHANGED
@@ -18,6 +18,7 @@ const updateNotifier = require('update-notifier');
18
18
  const git = require('../src/models/git.js');
19
19
  const Parsers = require('../src/parsers.js');
20
20
  const handleCommandPromise = require('../src/command-promise-handler.js');
21
+ const Formatter = require('../src/models/format-string.js');
21
22
 
22
23
  // Exit cleanly if the program we pipe to exits abruptly
23
24
  process.stdout.on('error', (error) => {
@@ -69,6 +70,7 @@ const ApplicationConfiguration = lazyRequire('../src/models/application_configur
69
70
  const Drain = lazyRequire('../src/models/drain.js');
70
71
  const Notification = lazyRequire('../src/models/notification.js');
71
72
  const Organisation = lazyRequire('../src/models/organisation.js');
73
+ const NetworkGroup = lazyRequire('../src/models/networkgroup.js');
72
74
 
73
75
  function run () {
74
76
 
@@ -113,6 +115,11 @@ function run () {
113
115
  },
114
116
  }),
115
117
  configurationValue: cliparse.argument('configuration-value', { description: 'The new value of the configuration' }),
118
+ ngId: cliparse.argument('ng-id', { description: 'The Network Group id' }),
119
+ ngIdOrLabel: cliparse.argument('ng', {
120
+ description: 'Network Group ID or label',
121
+ parser: Parsers.ngIdOrLabel,
122
+ }),
116
123
  };
117
124
 
118
125
  // OPTIONS
@@ -132,7 +139,9 @@ function run () {
132
139
  aliases: ['f'],
133
140
  description: 'Display access logs continuously (ignores before/until, after/since)',
134
141
  }),
135
- importAsJson: cliparse.flag('json', { description: 'Import variables as JSON (an array of { "name": "THE_NAME", "value": "the value" } objects)' }),
142
+ importAsJson: cliparse.flag('json', {
143
+ description: 'Import variables as JSON (an array of { "name": "THE_NAME", "value": "the value" } objects)',
144
+ }),
136
145
  addonId: cliparse.option('addon', { metavar: 'addon_id', description: 'Addon ID' }),
137
146
  after: cliparse.option('after', {
138
147
  metavar: 'after',
@@ -151,6 +160,10 @@ function run () {
151
160
  description: 'Short name for the application',
152
161
  complete: Application('listAvailableAliases'),
153
162
  }),
163
+ naturalName: cliparse.flag('natural-name', {
164
+ aliases: ['n'],
165
+ description: 'Show application names or aliases if possible',
166
+ }),
154
167
  before: cliparse.option('before', {
155
168
  metavar: 'before',
156
169
  aliases: ['until'],
@@ -270,7 +283,7 @@ function run () {
270
283
  onlyAliases: cliparse.flag('only-aliases', { description: 'List only application aliases' }),
271
284
  onlyApps: cliparse.flag('only-apps', { description: 'Only show app dependencies' }),
272
285
  orgaIdOrName: cliparse.option('org', {
273
- aliases: ['o'],
286
+ aliases: ['o', 'owner'],
274
287
  description: 'Organisation ID (or name, if unambiguous)',
275
288
  parser: Parsers.orgaIdOrName,
276
289
  }),
@@ -366,6 +379,158 @@ function run () {
366
379
  aliases: ['y'],
367
380
  description: 'Skip confirmation even if the TCP redirection is not free',
368
381
  }),
382
+ ngLabel: cliparse.option('label', {
383
+ required: true,
384
+ metavar: 'ng_label',
385
+ description: 'Network Group label, also used for dns context',
386
+ }),
387
+ ngIdOrLabel: cliparse.option('ng', {
388
+ required: true,
389
+ metavar: 'ng',
390
+ description: 'Network Group ID or label',
391
+ parser: Parsers.ngIdOrLabel,
392
+ // complete: NetworkGroup('xxx'),
393
+ }),
394
+ ngDescription: cliparse.option('description', {
395
+ required: true,
396
+ metavar: 'ng_description',
397
+ description: 'Network Group description',
398
+ }),
399
+ ngMemberId: cliparse.option('member-id', {
400
+ aliases: ['m'],
401
+ required: true,
402
+ metavar: 'member_id',
403
+ description: `The member ID: an app ID (i.e. ${Formatter.formatCode('app_xxx')}), add-on ID (i.e. ${Formatter.formatCode('addon_xxx')}) or external node category ID`,
404
+ // complete: NetworkGroup('xxx'),
405
+ }),
406
+ ngMemberDomainName: cliparse.option('domain-name', {
407
+ required: true,
408
+ metavar: 'domain_name',
409
+ description: `Member name used in the ${Formatter.formatUrl('<memberName>.m.<ngID>.ng.clever-cloud.com', false)} domain name alias`,
410
+ }),
411
+ ngPeerId: cliparse.option('peer-id', {
412
+ required: true,
413
+ metavar: 'peer_id',
414
+ description: 'The peer ID',
415
+ // complete: NetworkGroup('xxx'),
416
+ }),
417
+ ngPeerRole: cliparse.option('role', {
418
+ required: true,
419
+ metavar: 'peer_role',
420
+ description: `The peer role, (${Formatter.formatString('client')} or ${Formatter.formatString('server')})`,
421
+ parser: Parsers.ngPeerRole,
422
+ complete: NetworkGroup('listAvailablePeerRoles'),
423
+ }),
424
+ // FIXME: Add "internal" member type
425
+ ngMemberType: cliparse.option('type', {
426
+ required: true,
427
+ metavar: 'member_type',
428
+ description: `The member type (${Formatter.formatString('application')}, ${Formatter.formatString('addon')} or ${Formatter.formatString('external')})`,
429
+ parser: Parsers.ngMemberType,
430
+ complete: NetworkGroup('listAvailableMemberTypes'),
431
+ }),
432
+ ngMemberLabel: cliparse.option('label', {
433
+ required: true,
434
+ metavar: 'member_label',
435
+ description: 'Network Group member label',
436
+ }),
437
+ ngNodeCategoryId: cliparse.option('node-category-id', {
438
+ required: true,
439
+ aliases: ['c'],
440
+ metavar: 'node_category_id',
441
+ description: 'The external node category ID',
442
+ // complete: NetworkGroup('xxx'),
443
+ }),
444
+ ngPeerLabel: cliparse.option('label', {
445
+ required: true,
446
+ metavar: 'peer_label',
447
+ description: 'Network Group peer label',
448
+ }),
449
+ ngPeerParentMemberId: cliparse.option('parent', {
450
+ required: true,
451
+ metavar: 'member_id',
452
+ description: 'Network Group peer category ID (parent member ID)',
453
+ // complete: NetworkGroup('xxx'),
454
+ }),
455
+ optNgIdOrLabel: cliparse.option('ng', {
456
+ required: false,
457
+ metavar: 'ng',
458
+ description: 'Network Group ID or label',
459
+ parser: Parsers.ngIdOrLabel,
460
+ // complete: NetworkGroup('xxx'),
461
+ }),
462
+ optNgMemberLabel: cliparse.option('label', {
463
+ required: false,
464
+ metavar: 'member_label',
465
+ description: 'The member label',
466
+ }),
467
+ optNgNodeCategoryId: cliparse.option('node-category-id', {
468
+ required: false,
469
+ aliases: ['c'],
470
+ metavar: 'node_category_id',
471
+ description: 'The external node category ID',
472
+ // complete: NetworkGroup('xxx'),
473
+ }),
474
+ optNgPeerId: cliparse.option('peer-id', {
475
+ required: false,
476
+ metavar: 'peer_id',
477
+ description: 'The peer ID',
478
+ // complete: NetworkGroup('xxx'),
479
+ }),
480
+ optNgPeerRole: cliparse.option('role', {
481
+ required: false,
482
+ default: 'client',
483
+ metavar: 'peer_role',
484
+ description: `The peer role, (${Formatter.formatString('client')} or ${Formatter.formatString('server')})`,
485
+ parser: Parsers.ngPeerRole,
486
+ complete: NetworkGroup('listAvailablePeerRoles'),
487
+ }),
488
+ optNgSearchAppId: cliparse.option('app-id', {
489
+ required: false,
490
+ metavar: 'app_id',
491
+ description: 'The app id to search',
492
+ // complete: NetworkGroup('xxx'),
493
+ }),
494
+ wgPublicKey: cliparse.option('public-key', {
495
+ required: true,
496
+ metavar: 'public_key',
497
+ description: 'A WireGuard® public key',
498
+ }),
499
+ optWgPrivateKey: cliparse.option('private-key', {
500
+ required: false,
501
+ metavar: 'private_key',
502
+ description: 'A WireGuard® private key',
503
+ }),
504
+ jsonFormat: cliparse.flag('json', { aliases: ['j'], description: 'Show result in JSON format' }),
505
+ tag: cliparse.option('tag', {
506
+ required: true,
507
+ metavar: 'tag',
508
+ description: 'A tag',
509
+ parser: Parsers.tag,
510
+ }),
511
+ tags: cliparse.option('tags', {
512
+ required: true,
513
+ metavar: 'tags',
514
+ description: 'List of tags separated by a comma',
515
+ parser: Parsers.tags,
516
+ }),
517
+ optTags: cliparse.option('tags', {
518
+ metavar: 'tags',
519
+ description: 'List of tags separated by a comma',
520
+ parser: Parsers.tags,
521
+ }),
522
+ optIpAddress: cliparse.option('ip', {
523
+ required: false,
524
+ metavar: 'ip_address',
525
+ description: 'An IP address',
526
+ parser: Parsers.ipAddress,
527
+ }),
528
+ optPortNumber: cliparse.option('port', {
529
+ required: false,
530
+ metavar: 'port_number',
531
+ description: 'A port number',
532
+ parser: Parsers.portNumber,
533
+ }),
369
534
  };
370
535
 
371
536
  // ACCESSLOGS COMMAND
@@ -416,7 +581,7 @@ function run () {
416
581
  const applications = lazyRequirePromiseModule('../src/commands/applications.js');
417
582
  const applicationsCommand = cliparse.command('applications', {
418
583
  description: 'List linked applications',
419
- options: [opts.onlyAliases],
584
+ options: [opts.onlyAliases, opts.jsonFormat],
420
585
  }, applications('list'));
421
586
 
422
587
  // CANCEL DEPLOY COMMAND
@@ -586,6 +751,84 @@ function run () {
586
751
  args: [args.alias],
587
752
  }, makeDefault('makeDefault'));
588
753
 
754
+ // NETWORK GROUPS COMMANDS
755
+ const networkgroups = lazyRequirePromiseModule('../src/commands/networkgroups/commands.js');
756
+
757
+ // network group category - start
758
+ const networkGroupsListCommand = cliparse.command('list', {
759
+ description: 'List Network Groups with their labels',
760
+ options: [opts.jsonFormat],
761
+ }, networkgroups('listNetworkGroups'));
762
+ const networkGroupsCreateCommand = cliparse.command('create', {
763
+ description: 'Create a Network Group',
764
+ options: [opts.ngLabel, opts.ngDescription, opts.optTags, opts.jsonFormat],
765
+ }, networkgroups('createNg'));
766
+ const networkGroupsDeleteCommand = cliparse.command('delete', {
767
+ description: 'Delete a Network Group',
768
+ options: [opts.ngIdOrLabel],
769
+ }, networkgroups('deleteNg'));
770
+ // network group category - end
771
+
772
+ // member category - start
773
+ const networkGroupsMemberListCommand = cliparse.command('list', {
774
+ description: 'List members of a Network Group',
775
+ // Add option opts.optNgSearchAppId ?
776
+ options: [opts.ngIdOrLabel, opts.naturalName, opts.jsonFormat],
777
+ }, networkgroups('listMembers'));
778
+ const networkGroupsMemberGetCommand = cliparse.command('get', {
779
+ description: 'Get a Network Group member details',
780
+ options: [opts.ngIdOrLabel, opts.ngMemberId, opts.naturalName, opts.jsonFormat],
781
+ }, networkgroups('getMember'));
782
+ const networkGroupsMemberAddCommand = cliparse.command('add', {
783
+ description: 'Add an app or addon as a Network Group member',
784
+ options: [opts.ngIdOrLabel, opts.ngMemberId, opts.ngMemberType, opts.ngMemberDomainName, opts.optNgMemberLabel],
785
+ }, networkgroups('addMember'));
786
+ const networkGroupsMemberRemoveCommand = cliparse.command('remove', {
787
+ description: 'Remove an app or addon from a Network Group',
788
+ options: [opts.ngIdOrLabel, opts.ngMemberId],
789
+ }, networkgroups('removeMember'));
790
+
791
+ const networkGroupsMembersCategoryCommand = cliparse.command('members', {
792
+ description: 'List commands for interacting with Network Group members',
793
+ commands: [networkGroupsMemberListCommand, networkGroupsMemberGetCommand, networkGroupsMemberAddCommand, networkGroupsMemberRemoveCommand],
794
+ });
795
+ // member category - end
796
+
797
+ // peer category - start
798
+ const networkGroupsPeerListCommand = cliparse.command('list', {
799
+ description: 'List peers of a Network Group',
800
+ options: [opts.ngIdOrLabel, opts.jsonFormat],
801
+ }, networkgroups('listPeers'));
802
+ const networkGroupsPeerGetCommand = cliparse.command('get', {
803
+ description: 'Get a Network Group peer details',
804
+ options: [opts.ngIdOrLabel, opts.ngPeerId, opts.jsonFormat],
805
+ }, networkgroups('getPeer'));
806
+ const networkGroupsPeerAddCommand = cliparse.command('add-external', {
807
+ description: 'Add an external node as a Network Group peer',
808
+ options: [opts.ngIdOrLabel, opts.ngPeerRole, opts.wgPublicKey, opts.ngPeerLabel, opts.ngPeerParentMemberId],
809
+ }, networkgroups('addExternalPeer'));
810
+ const networkGroupsPeerRemoveExternalCommand = cliparse.command('remove-external', {
811
+ description: 'Remove an external node from a Network Group',
812
+ options: [opts.ngIdOrLabel, opts.ngPeerId],
813
+ }, networkgroups('removeExternalPeer'));
814
+
815
+ const networkGroupsPeersCategoryCommand = cliparse.command('peers', {
816
+ description: 'List commands for interacting with Network Group peers',
817
+ commands: [networkGroupsPeerListCommand, networkGroupsPeerGetCommand, networkGroupsPeerAddCommand, networkGroupsPeerRemoveExternalCommand],
818
+ });
819
+ // peer category - end
820
+
821
+ const networkGroupsCommand = cliparse.command('networkgroups', {
822
+ description: 'List Network Group commands',
823
+ options: [opts.orgaIdOrName, opts.alias],
824
+ commands: [networkGroupsListCommand, networkGroupsCreateCommand, networkGroupsDeleteCommand, networkGroupsMembersCategoryCommand, networkGroupsPeersCategoryCommand],
825
+ });
826
+ const ngCommand = cliparse.command('ng', {
827
+ description: `Alias for ${Formatter.formatCommand('clever networkgroups')}`,
828
+ options: [opts.orgaIdOrName, opts.alias],
829
+ commands: [networkGroupsListCommand, networkGroupsCreateCommand, networkGroupsDeleteCommand, networkGroupsMembersCategoryCommand, networkGroupsPeersCategoryCommand],
830
+ });
831
+
589
832
  // NOTIFY-EMAIL COMMAND
590
833
  const notifyEmail = lazyRequirePromiseModule('../src/commands/notify-email.js');
591
834
  const addEmailNotificationCommand = cliparse.command('add', {
@@ -780,6 +1023,8 @@ function run () {
780
1023
  logoutCommand,
781
1024
  logsCommand,
782
1025
  makeDefaultCommand,
1026
+ networkGroupsCommand,
1027
+ ngCommand,
783
1028
  openCommand,
784
1029
  consoleCommand,
785
1030
  profileCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "2.9.1",
3
+ "version": "2.10.0-beta.2",
4
4
  "description": "Command Line Interface for Clever Cloud.",
5
5
  "main": "bin/clever.js",
6
6
  "keywords": [
@@ -15,9 +15,9 @@
15
15
  "author": "Clever Cloud <ci@clever-cloud.com>",
16
16
  "license": "Apache-2.0",
17
17
  "bin": {
18
- "clever": "./bin/clever.js",
19
- "install-clever-completion": "./scripts/install-autocomplete.sh",
20
- "uninstall-clever-completion": "./scripts/uninstall-autocomplete.sh"
18
+ "clever": "bin/clever.js",
19
+ "install-clever-completion": "scripts/install-autocomplete.sh",
20
+ "uninstall-clever-completion": "scripts/uninstall-autocomplete.sh"
21
21
  },
22
22
  "files": [
23
23
  "bin",
@@ -25,7 +25,7 @@
25
25
  "scripts/*.sh"
26
26
  ],
27
27
  "dependencies": {
28
- "@clevercloud/client": "^7.4.0",
28
+ "@clevercloud/client": "7.9.0",
29
29
  "clf-date": "^0.2.0",
30
30
  "cliparse": "^0.3.3",
31
31
  "colors": "1.4.0",
@@ -42,6 +42,7 @@
42
42
  "superagent": "^6.1.0",
43
43
  "text-table": "^0.2.0",
44
44
  "update-notifier": "^5.1.0",
45
+ "uuid": "^8.3.2",
45
46
  "ws": "^7.4.6",
46
47
  "xdg": "^0.1.1"
47
48
  },
@@ -6,29 +6,38 @@ const AppConfig = require('../models/app_configuration.js');
6
6
  const Logger = require('../logger.js');
7
7
 
8
8
  async function list (params) {
9
- const { 'only-aliases': onlyAliases } = params.options;
9
+ const { 'only-aliases': onlyAliases, json } = params.options;
10
10
 
11
11
  const { apps } = await AppConfig.loadApplicationConf();
12
12
 
13
- const formattedApps = formatApps(apps, onlyAliases);
13
+ const formattedApps = formatApps(apps, onlyAliases, json);
14
14
  Logger.println(formattedApps);
15
15
  };
16
16
 
17
- function formatApps (apps, onlyAliases) {
17
+ function formatApps (apps, onlyAliases, json) {
18
18
 
19
- if (onlyAliases) {
20
- return apps.map((a) => a.alis).join('\n');
19
+ if (json) {
20
+ if (onlyAliases) {
21
+ apps = apps.map((a) => a.alias);
22
+ }
23
+ return JSON.stringify(apps, null, 2);
24
+ }
25
+ else {
26
+ if (onlyAliases) {
27
+ return apps.map((a) => a.alias).join('\n');
28
+ }
29
+ else {
30
+ return apps
31
+ .map((app) =>
32
+ [
33
+ `Application ${app.name}`,
34
+ ` alias: ${colors.bold(app.alias)}`,
35
+ ` id: ${app.app_id}`,
36
+ ` deployment url: ${app.deploy_url}`].join('\n'),
37
+ )
38
+ .join('\n\n');
39
+ }
21
40
  }
22
-
23
- return apps
24
- .map((app) => {
25
- return [
26
- `Application ${app.name}`,
27
- ` alias: ${colors.bold(app.alias)}`,
28
- ` id: ${app.app_id}`,
29
- ` deployment url: ${app.deploy_url}`].join('\n');
30
- })
31
- .join('\n\n');
32
41
  }
33
42
 
34
43
  module.exports = { list };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ ...require('./index.js'),
5
+ ...require('./members.js'),
6
+ ...require('./peers.js'),
7
+ };
@@ -0,0 +1,67 @@
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 listNetworkGroups(params) {
13
+ const { org: orgaIdOrName, alias, json } = params.options;
14
+ const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
15
+
16
+ Logger.info(`Listing Network Groups from owner ${Formatter.formatString(ownerId)}`);
17
+ const result = await ngApi.listNetworkGroups({ ownerId }).then(sendToApi);
18
+
19
+ if (json) {
20
+ Logger.println(JSON.stringify(result, null, 2));
21
+ }
22
+ else {
23
+ if (result.length === 0) {
24
+ Logger.println(`No Network Group found for ${ownerId}. You can create one with ${Formatter.formatCommand('clever networkgroups create')}.`);
25
+ }
26
+ else {
27
+ TableFormatter.printNetworkGroupsTableHeader();
28
+ result
29
+ .map((ng) => TableFormatter.formatNetworkGroupsLine(ng))
30
+ .forEach((ng) => Logger.println(ng));
31
+ }
32
+ }
33
+ }
34
+
35
+ async function createNg(params) {
36
+ const { org: orgaIdOrName, alias, label, description, tags, json } = params.options;
37
+ const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
38
+
39
+ Logger.info(`Creating Network Group from owner ${Formatter.formatString(ownerId)}`);
40
+ const body = { ownerId: ownerId, label, description, tags };
41
+ Logger.debug('Sending body: ' + JSON.stringify(body, null, 2));
42
+ const result = await ngApi.createNetworkGroup({ ownerId }, body).then(sendToApi);
43
+
44
+ if (json) {
45
+ Logger.println(JSON.stringify(result, null, 2));
46
+ }
47
+ else {
48
+ Logger.println(`Network Group ${Formatter.formatString(label)} creation will be performed asynchronously.`);
49
+ }
50
+ }
51
+
52
+ async function deleteNg(params) {
53
+ const { org: orgaIdOrName, alias, ng: networkGroupIdOrLabel } = params.options;
54
+ const ownerId = await NetworkGroup.getOwnerId(orgaIdOrName, alias);
55
+ const networkGroupId = await NetworkGroup.getId(ownerId, networkGroupIdOrLabel);
56
+
57
+ Logger.info(`Deleting Network Group ${Formatter.formatString(networkGroupId)} from owner ${Formatter.formatString(ownerId)}`);
58
+ await ngApi.deleteNetworkGroup({ ownerId, networkGroupId }).then(sendToApi);
59
+
60
+ Logger.println(`Network Group ${Formatter.formatString(networkGroupId)} deletion will be performed asynchronously.`);
61
+ }
62
+
63
+ module.exports = {
64
+ listNetworkGroups,
65
+ createNg,
66
+ deleteNg,
67
+ };
@@ -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
+ };
@@ -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.listNetworkGroups({ 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
+ };
package/src/parsers.js CHANGED
@@ -48,7 +48,7 @@ function appIdOrName (string) {
48
48
  return cliparse.parsers.success({ app_name: string });
49
49
  }
50
50
 
51
- const orgaIdRegex = /^orga_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
51
+ const orgaIdRegex = /^(user_|orga_)[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
52
52
 
53
53
  function orgaIdOrName (string) {
54
54
  if (string.match(orgaIdRegex)) {
@@ -66,6 +66,15 @@ function addonIdOrName (string) {
66
66
  return cliparse.parsers.success({ addon_name: string });
67
67
  }
68
68
 
69
+ const ngIdRegex = /^ng_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
70
+
71
+ function ngIdOrLabel (string) {
72
+ if (string.match(ngIdRegex)) {
73
+ return cliparse.parsers.success({ ng_id: string });
74
+ }
75
+ return cliparse.parsers.success({ ng_label: string });
76
+ }
77
+
69
78
  function commaSeparated (string) {
70
79
  return cliparse.parsers.success(string.split(','));
71
80
  }
@@ -86,6 +95,63 @@ function integer (string) {
86
95
  return cliparse.parsers.success(integer);
87
96
  }
88
97
 
98
+ // /^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/i;
99
+ const tagRegex = /^[^,\s]+$/;
100
+
101
+ function tag (string) {
102
+ if (string.match(tagRegex)) {
103
+ return cliparse.parsers.success(string);
104
+ }
105
+ return cliparse.parsers.error(`Invalid tag '${string}'. Should match ${tagRegex}`);
106
+ }
107
+
108
+ function tags (string) {
109
+ if (String(string).length === 0) {
110
+ return cliparse.parsers.success([]);
111
+ }
112
+ const tags = String(string).split(',');
113
+ for (const current of tags) {
114
+ if (tag(current).error) {
115
+ return cliparse.parsers.error(`Invalid tag '${current}'. Should match \`${tagRegex}\``);
116
+ }
117
+ }
118
+ return cliparse.parsers.success(tags);
119
+ }
120
+
121
+ function ngMemberType (string) {
122
+ const possible = ['application', 'addon', 'external'];
123
+ if (possible.includes(string)) {
124
+ return cliparse.parsers.success(string);
125
+ }
126
+ return cliparse.parsers.error(`Invalid member type '${string}'. Should be in ${JSON.stringify(possible)}`);
127
+ }
128
+
129
+ function ngPeerRole (string) {
130
+ const possible = ['client', 'server'];
131
+ if (possible.includes(string)) {
132
+ return cliparse.parsers.success(string);
133
+ }
134
+ return cliparse.parsers.error(`Invalid peer role '${string}'. Should be in ${JSON.stringify(possible)}`);
135
+ }
136
+
137
+ const ipAddressRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])$/;
138
+
139
+ function ipAddress (string) {
140
+ if (string.match(ipAddressRegex)) {
141
+ return cliparse.parsers.success(string);
142
+ }
143
+ return cliparse.parsers.error(`Invalid IP address '${string}'. Should match ${ipAddressRegex}`);
144
+ }
145
+
146
+ const portNumberRegex = /^\d{1,5}$/;
147
+
148
+ function portNumber (number) {
149
+ if (String(number).match(portNumberRegex)) {
150
+ return cliparse.parsers.success(number);
151
+ }
152
+ return cliparse.parsers.error(`Invalid port number '${number}'. Should match ${portNumberRegex}`);
153
+ }
154
+
89
155
  module.exports = {
90
156
  buildFlavor,
91
157
  flavor,
@@ -94,7 +160,16 @@ module.exports = {
94
160
  appIdOrName,
95
161
  orgaIdOrName,
96
162
  addonIdOrName,
163
+ ngIdOrLabel,
97
164
  commaSeparated,
98
165
  accessLogsFormat,
99
166
  integer,
167
+ tag,
168
+ tags,
169
+ ngMemberType,
170
+ ngPeerRole,
171
+ ipAddressRegex,
172
+ ipAddress,
173
+ portNumberRegex,
174
+ portNumber,
100
175
  };