clever-tools 3.11.0 → 3.13.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.
Files changed (57) hide show
  1. package/bin/clever.js +416 -3
  2. package/package.json +4 -3
  3. package/src/clever-client/auth-bridge.js +61 -0
  4. package/src/clever-client/ng.js +18 -0
  5. package/src/clever-client/operators.js +121 -0
  6. package/src/commands/addon.js +34 -28
  7. package/src/commands/cancel-deploy.js +3 -4
  8. package/src/commands/config.js +16 -20
  9. package/src/commands/console.js +4 -10
  10. package/src/commands/create.js +76 -9
  11. package/src/commands/curl.js +17 -17
  12. package/src/commands/database.js +9 -9
  13. package/src/commands/delete.js +11 -10
  14. package/src/commands/deploy.js +35 -21
  15. package/src/commands/domain.js +13 -16
  16. package/src/commands/emails.js +174 -0
  17. package/src/commands/env.js +15 -8
  18. package/src/commands/keycloak.js +138 -0
  19. package/src/commands/kv.js +1 -1
  20. package/src/commands/link.js +7 -3
  21. package/src/commands/makeDefault.js +2 -1
  22. package/src/commands/matomo.js +85 -0
  23. package/src/commands/metabase.js +112 -0
  24. package/src/commands/ng.js +202 -0
  25. package/src/commands/open.js +2 -5
  26. package/src/commands/otoroshi.js +138 -0
  27. package/src/commands/profile.js +11 -10
  28. package/src/commands/published-config.js +7 -7
  29. package/src/commands/restart.js +1 -1
  30. package/src/commands/ssh-keys.js +159 -0
  31. package/src/commands/stop.js +3 -3
  32. package/src/commands/tcp-redirs.js +8 -8
  33. package/src/commands/tokens.js +146 -0
  34. package/src/commands/unlink.js +2 -1
  35. package/src/experimental-features.js +47 -2
  36. package/src/lib/ng-print.js +195 -0
  37. package/src/lib/operator-commands.js +281 -0
  38. package/src/lib/prompts.js +30 -0
  39. package/src/lib/slugify.js +10 -0
  40. package/src/logger.js +3 -0
  41. package/src/models/activity.js +2 -3
  42. package/src/models/addon.js +9 -6
  43. package/src/models/app_configuration.js +12 -9
  44. package/src/models/application.js +44 -22
  45. package/src/models/application_configuration.js +72 -72
  46. package/src/models/configuration.js +1 -0
  47. package/src/models/git.js +29 -1
  48. package/src/models/ids-resolver.js +37 -1
  49. package/src/models/interact.js +0 -24
  50. package/src/models/log-v4.js +13 -4
  51. package/src/models/namespaces.js +3 -2
  52. package/src/models/ng-resources.js +270 -0
  53. package/src/models/ng.js +276 -0
  54. package/src/models/operator.js +48 -0
  55. package/src/models/send-to-api.js +18 -0
  56. package/src/models/utils.js +21 -0
  57. package/src/parsers.js +71 -3
package/bin/clever.js CHANGED
@@ -16,7 +16,7 @@ import * as Application from '../src/models/application.js';
16
16
  import { AVAILABLE_ZONES } from '../src/models/application.js';
17
17
  import { EXPERIMENTAL_FEATURES } from '../src/experimental-features.js';
18
18
  import { getExitOnOption, getOutputFormatOption, getSameCommitPolicyOption } from '../src/command-options.js';
19
- import { getFeatures } from '../src/models/configuration.js';
19
+ import { getFeatures, conf } from '../src/models/configuration.js';
20
20
 
21
21
  import * as Addon from '../src/models/addon.js';
22
22
  import * as ApplicationConfiguration from '../src/models/application_configuration.js';
@@ -36,26 +36,34 @@ import * as deploy from '../src/commands/deploy.js';
36
36
  import * as diag from '../src/commands/diag.js';
37
37
  import * as domain from '../src/commands/domain.js';
38
38
  import * as drain from '../src/commands/drain.js';
39
+ import * as emails from '../src/commands/emails.js';
39
40
  import * as env from '../src/commands/env.js';
40
41
  import * as features from '../src/commands/features.js';
42
+ import * as keycloak from '../src/commands/keycloak.js';
41
43
  import * as kv from '../src/commands/kv.js';
42
44
  import * as link from '../src/commands/link.js';
43
45
  import * as login from '../src/commands/login.js';
44
46
  import * as logout from '../src/commands/logout.js';
45
47
  import * as logs from '../src/commands/logs.js';
48
+ import * as matomo from '../src/commands/matomo.js';
49
+ import * as metabase from '../src/commands/metabase.js';
46
50
  import * as makeDefault from '../src/commands/makeDefault.js';
51
+ import * as ng from '../src/commands/ng.js';
47
52
  import * as notifyEmail from '../src/commands/notify-email.js';
48
53
  import * as open from '../src/commands/open.js';
49
54
  import * as consoleModule from '../src/commands/console.js';
55
+ import * as otoroshi from '../src/commands/otoroshi.js';
50
56
  import * as profile from '../src/commands/profile.js';
51
57
  import * as publishedConfig from '../src/commands/published-config.js';
52
58
  import * as restart from '../src/commands/restart.js';
53
59
  import * as scale from '../src/commands/scale.js';
54
60
  import * as service from '../src/commands/service.js';
55
61
  import * as ssh from '../src/commands/ssh.js';
62
+ import * as sshKeys from '../src/commands/ssh-keys.js';
56
63
  import * as status from '../src/commands/status.js';
57
64
  import * as stop from '../src/commands/stop.js';
58
65
  import * as tcpRedirs from '../src/commands/tcp-redirs.js';
66
+ import * as tokens from '../src/commands/tokens.js';
59
67
  import * as unlink from '../src/commands/unlink.js';
60
68
  import * as version from '../src/commands/version.js';
61
69
  import * as webhooks from '../src/commands/webhooks.js';
@@ -94,6 +102,45 @@ async function run () {
94
102
  kvIdOrName: cliparse.argument('kv-id', {
95
103
  description: 'Add-on/Real ID (or name, if unambiguous) of a Materia KV or Redis® add-on',
96
104
  }),
105
+ apiTokenId: cliparse.argument('api-token-id', { description: 'API token ID' }),
106
+ apiTokenName: cliparse.argument('api-token-name', { description: 'API token name' }),
107
+ ngId: cliparse.argument('id', {
108
+ description: 'Network Group ID',
109
+ parser: Parsers.ngResourceType,
110
+ }),
111
+ ngLabel: cliparse.argument('ng-label', {
112
+ description: 'Network Group label',
113
+ parser: Parsers.ngResourceType,
114
+ }),
115
+ ngIdOrLabel: cliparse.argument('ng-id-or-label', {
116
+ description: 'Network Group ID or label',
117
+ parser: Parsers.ngResourceType,
118
+ }),
119
+ ngDescription: cliparse.argument('ng-description', {
120
+ description: 'Network Group description',
121
+ }),
122
+ ngExternalPeerLabel: cliparse.argument('external-peer-label', {
123
+ description: 'External peer label',
124
+ parser: Parsers.ngResourceType,
125
+ }),
126
+ ngExternalIdOrLabel: cliparse.argument('external-peer-id-or-label', {
127
+ description: 'External peer ID or label',
128
+ parser: Parsers.ngResourceType,
129
+ }),
130
+ ngAnyIdOrLabel: cliparse.argument('id-or-label', {
131
+ description: 'ID or Label of a Network group, a member or an (external) peer',
132
+ parser: Parsers.ngResourceType,
133
+ }),
134
+ wgPublicKey: cliparse.argument('public-key', {
135
+ metavar: 'public_key',
136
+ description: 'Wireguard public key of the external peer to link to a Network Group',
137
+ }),
138
+ email: cliparse.argument('email', {
139
+ description: 'Email address',
140
+ parser: Parsers.email,
141
+ }),
142
+ sshKeyName: cliparse.argument('ssh-key-name', { description: 'SSH key name' }),
143
+ sshKeyPath: cliparse.argument('ssh-key-path', { description: 'SSH public key path (.pub)' }),
97
144
  addonIdOrName: cliparse.argument('addon-id', {
98
145
  description: 'Add-on ID (or name, if unambiguous)',
99
146
  parser: Parsers.addonIdOrName,
@@ -137,7 +184,7 @@ async function run () {
137
184
  parser: Parsers.integer,
138
185
  }),
139
186
  configurationName: cliparse.argument('configuration-name', {
140
- description: 'The name of the configuration to manage',
187
+ description: `Configuration to manage: ${ApplicationConfiguration.listAvailableIds(true)}`,
141
188
  complete () {
142
189
  return cliparse.autocomplete.words(ApplicationConfiguration.listAvailableIds());
143
190
  },
@@ -147,6 +194,40 @@ async function run () {
147
194
 
148
195
  // OPTIONS
149
196
  const opts = {
197
+ targetVersion: cliparse.option('target', {
198
+ metavar: 'version',
199
+ description: 'Target version to upgrade to (e.g.: 24, 2.4, 2.4.1)',
200
+ }),
201
+ apiTokenExpiration: cliparse.option('expiration', {
202
+ aliases: ['e'],
203
+ metavar: 'expiration',
204
+ parser: Parsers.futureDateOrDuration,
205
+ description: 'Duration until API token expiration (e.g.: 1h, 4d, 2w, 6M), default 1y',
206
+ }),
207
+ // Network Groups options
208
+ ngDescription: cliparse.option('description', {
209
+ metavar: 'description',
210
+ description: 'Network Group description',
211
+ }),
212
+ ngMembersIdsToLink: cliparse.option('link', {
213
+ metavar: 'members_ids',
214
+ description: "Comma separated list of members IDs to link to a Network Group ('app_xxx', 'addon_xxx', 'external_xxx')",
215
+ parser: Parsers.commaSeparated,
216
+ }),
217
+ ngMemberLabel: cliparse.option('label', {
218
+ required: false,
219
+ metavar: 'member_label',
220
+ description: 'The member label',
221
+ parser: Parsers.ngResourceType,
222
+ }),
223
+ ngPeerGetConfig: cliparse.flag('config', {
224
+ description: 'Get the Wireguard configuration of an external peer',
225
+ }),
226
+ ngResourceType: cliparse.option('type', {
227
+ metavar: 'type',
228
+ description: 'Type of resource to look for (NetworkGroup, Member, CleverPeer, ExternalPeer)',
229
+ parser: Parsers.ngValidType,
230
+ }),
150
231
  sourceableEnvVarsList: cliparse.flag('add-export', { description: 'Display sourceable env variables setting' }),
151
232
  logsFormat: getOutputFormatOption(['json-stream']),
152
233
  activityFormat: getOutputFormatOption(['json-stream']),
@@ -443,10 +524,18 @@ async function run () {
443
524
  aliases: ['y'],
444
525
  description: 'Skip confirmation and delete the application directly',
445
526
  }),
527
+ confirmSshKeyClean: cliparse.flag('yes', {
528
+ aliases: ['y'],
529
+ description: 'Skip confirmation and remove all SSH keys directly',
530
+ }),
446
531
  confirmTcpRedirCreation: cliparse.flag('yes', {
447
532
  aliases: ['y'],
448
533
  description: 'Skip confirmation even if the TCP redirection is not free',
449
534
  }),
535
+ yes: cliparse.flag('yes', {
536
+ aliases: ['y'],
537
+ description: 'Skip confirmation',
538
+ }),
450
539
  jsonFormat: cliparse.flag('json', { aliases: ['j'], description: 'Show result in JSON format' }),
451
540
  humanJsonOutputFormat: getOutputFormatOption(),
452
541
  tag: cliparse.option('tag', {
@@ -567,7 +656,7 @@ async function run () {
567
656
  description: 'Display or edit the configuration of your application',
568
657
  options: [opts.alias, opts.appIdOrName],
569
658
  commands: [configGetCommand, configSetCommand, configUpdateCommand],
570
- }, config.get);
659
+ }, config.list);
571
660
 
572
661
  // CREATE COMMAND
573
662
  const appCreateCommand = cliparse.command('create', {
@@ -663,6 +752,32 @@ async function run () {
663
752
  commands: [drainCreateCommand, drainRemoveCommand, drainEnableCommand, drainDisableCommand],
664
753
  }, drain.list);
665
754
 
755
+ // EMAILS COMMANDS
756
+ const emailsAddCommand = cliparse.command('add', {
757
+ description: 'Add a new secondary email address to the current user',
758
+ args: [args.email],
759
+ }, emails.addSecondary);
760
+ const emailsPrimaryCommand = cliparse.command('primary', {
761
+ description: 'Set the primary email address of the current user',
762
+ args: [args.email],
763
+ }, emails.setPrimary);
764
+ const emailsRemoveCommand = cliparse.command('remove', {
765
+ description: 'Remove a secondary email address from the current user',
766
+ args: [args.email],
767
+ }, emails.removeSecondary);
768
+ const emailsClearCommand = cliparse.command('remove-all', {
769
+ description: 'Remove all secondary email addresses from the current user',
770
+ options: [opts.yes],
771
+ }, emails.removeAllSecondary);
772
+ const emailsOpenConsoleCommand = cliparse.command('open', {
773
+ description: 'Open the email addresses management page in the Console',
774
+ }, emails.openConsole);
775
+ const emailsCommands = cliparse.command('emails', {
776
+ description: 'Manage email addresses of the current user',
777
+ privateOptions: [opts.humanJsonOutputFormat],
778
+ commands: [emailsAddCommand, emailsPrimaryCommand, emailsRemoveCommand, emailsClearCommand, emailsOpenConsoleCommand],
779
+ }, emails.list);
780
+
666
781
  // ENV COMMANDS
667
782
  const envSetCommand = cliparse.command('set', {
668
783
  description: 'Add or update an environment variable named <variable-name> with the value <variable-value>',
@@ -709,6 +824,63 @@ async function run () {
709
824
  commands: [enableFeatureCommand, disableFeatureCommand, listFeaturesCommand, infoFeaturesCommand],
710
825
  }, features.list);
711
826
 
827
+ // KEYCLOAK COMMAND
828
+ const keycloakGetCommand = cliparse.command('get', {
829
+ description: 'Get information about a deployed Keycloak',
830
+ args: [args.addonIdOrName],
831
+ options: [opts.humanJsonOutputFormat],
832
+ }, keycloak.get);
833
+ const keycloakEnableNgCommand = cliparse.command('enable-ng', {
834
+ description: 'Link Keycloak to a Network Group, used for multi-instances secure communication',
835
+ args: [args.addonIdOrName],
836
+ }, keycloak.ngEnable);
837
+ const keycloakDisableNgCommand = cliparse.command('disable-ng', {
838
+ description: 'Unlink Keycloak from its Network Group',
839
+ args: [args.addonIdOrName],
840
+ }, keycloak.ngDisable);
841
+ const keycloakOpenLogsCommand = cliparse.command('logs', {
842
+ description: 'Open the Keycloak application logs in Clever Cloud Console',
843
+ args: [args.addonIdOrName],
844
+ }, keycloak.openLogs);
845
+ const keycloakOpenWebUiCommand = cliparse.command('webui', {
846
+ description: 'Open the Keycloak admin console in your browser',
847
+ args: [args.addonIdOrName],
848
+ }, keycloak.openWebUi);
849
+ const keycloakOpenCommand = cliparse.command('open', {
850
+ description: 'Open the Keycloak dashboard in Clever Cloud Console',
851
+ args: [args.addonIdOrName],
852
+ commands: [keycloakOpenLogsCommand, keycloakOpenWebUiCommand],
853
+ }, keycloak.open);
854
+ const keycloakRestartCommand = cliparse.command('restart', {
855
+ description: 'Restart Keycloak',
856
+ args: [args.addonIdOrName],
857
+ }, keycloak.reboot);
858
+ const keycloakRebuildCommand = cliparse.command('rebuild', {
859
+ description: 'Rebuild Keycloak',
860
+ args: [args.addonIdOrName],
861
+ }, keycloak.rebuild);
862
+ const keycloakVersionCheckCommand = cliparse.command('check', {
863
+ description: 'Check Keycloak deployed version',
864
+ args: [args.addonIdOrName],
865
+ options: [opts.humanJsonOutputFormat],
866
+ }, keycloak.checkVersion);
867
+ const keycloakVersionUpdateCommand = cliparse.command('update', {
868
+ description: 'Update Keycloak deployed version',
869
+ args: [args.addonIdOrName],
870
+ options: [opts.targetVersion],
871
+ }, keycloak.updateVersion);
872
+ const keycloakVersionsCommands = cliparse.command('version', {
873
+ description: 'Check Keycloak deployed version',
874
+ args: [args.addonIdOrName],
875
+ options: [opts.humanJsonOutputFormat],
876
+ commands: [keycloakVersionCheckCommand, keycloakVersionUpdateCommand],
877
+ }, keycloak.checkVersion);
878
+ const keycloakCommand = cliparse.command('keycloak', {
879
+ description: 'Manage Clever Cloud Keycloak services',
880
+ privateOptions: [opts.humanJsonOutputFormat],
881
+ commands: [keycloakGetCommand, keycloakEnableNgCommand, keycloakDisableNgCommand, keycloakOpenCommand, keycloakRestartCommand, keycloakRebuildCommand, keycloakVersionsCommands],
882
+ }, keycloak.list);
883
+
712
884
  // KV COMMAND
713
885
  const kvRawCommand = cliparse.command('kv', {
714
886
  description: 'Send a raw command to a Materia KV or Redis® add-on',
@@ -746,6 +918,138 @@ async function run () {
746
918
  args: [args.alias],
747
919
  }, makeDefault.makeDefault);
748
920
 
921
+ // MATOMO COMMAND
922
+ const matomoGetCommand = cliparse.command('get', {
923
+ description: 'Get information about a deployed Matomo',
924
+ args: [args.addonIdOrName],
925
+ options: [opts.humanJsonOutputFormat],
926
+ }, matomo.get);
927
+ const matomoOpenLogsCommand = cliparse.command('logs', {
928
+ description: 'Open the Matomo application logs in Clever Cloud Console',
929
+ args: [args.addonIdOrName],
930
+ }, matomo.openLogs);
931
+ const matomoOpenWebUiCommand = cliparse.command('webui', {
932
+ description: 'Open the Matomo admin console in your browser',
933
+ args: [args.addonIdOrName],
934
+ }, matomo.openWebUi);
935
+ const matomoOpenCommand = cliparse.command('open', {
936
+ description: 'Open the Matomo dashboard in Clever Cloud Console',
937
+ args: [args.addonIdOrName],
938
+ commands: [matomoOpenLogsCommand, matomoOpenWebUiCommand],
939
+ }, matomo.open);
940
+ const matomoRestartCommand = cliparse.command('restart', {
941
+ description: 'Restart Matomo',
942
+ args: [args.addonIdOrName],
943
+ }, matomo.reboot);
944
+ const matomoRebuildCommand = cliparse.command('rebuild', {
945
+ description: 'Rebuild Matomo',
946
+ args: [args.addonIdOrName],
947
+ }, matomo.rebuild);
948
+ const matomoCommand = cliparse.command('matomo', {
949
+ description: 'Manage Clever Cloud Matomo services',
950
+ privateOptions: [opts.humanJsonOutputFormat],
951
+ commands: [matomoGetCommand, matomoOpenCommand, matomoRestartCommand, matomoRebuildCommand],
952
+ }, matomo.list);
953
+
954
+ // METABASE COMMAND
955
+ const metabaseGetCommand = cliparse.command('get', {
956
+ description: 'Get information about a deployed Metabase',
957
+ args: [args.addonIdOrName],
958
+ options: [opts.humanJsonOutputFormat],
959
+ }, metabase.get);
960
+ const metabaseOpenLogsCommand = cliparse.command('logs', {
961
+ description: 'Open the Metabase application logs in Clever Cloud Console',
962
+ args: [args.addonIdOrName],
963
+ }, metabase.openLogs);
964
+ const metabaseOpenWebUiCommand = cliparse.command('webui', {
965
+ description: 'Open the Metabase admin console in your browser',
966
+ args: [args.addonIdOrName],
967
+ }, metabase.openWebUi);
968
+ const metabaseOpenCommand = cliparse.command('open', {
969
+ description: 'Open the Metabase dashboard in Clever Cloud Console',
970
+ args: [args.addonIdOrName],
971
+ commands: [metabaseOpenLogsCommand, metabaseOpenWebUiCommand],
972
+ }, metabase.open);
973
+ const metabaseRestartCommand = cliparse.command('restart', {
974
+ description: 'Restart Metabase',
975
+ args: [args.addonIdOrName],
976
+ }, metabase.reboot);
977
+ const metabaseRebuildCommand = cliparse.command('rebuild', {
978
+ description: 'Rebuild Metabase',
979
+ args: [args.addonIdOrName],
980
+ }, metabase.rebuild);
981
+ const metabaseVersionCheckCommand = cliparse.command('check', {
982
+ description: 'Check Metabase deployed version',
983
+ args: [args.addonIdOrName],
984
+ options: [opts.humanJsonOutputFormat],
985
+ }, metabase.checkVersion);
986
+ const metabaseVersionUpdateCommand = cliparse.command('update', {
987
+ description: 'Update Metabase deployed version',
988
+ args: [args.addonIdOrName],
989
+ options: [opts.targetVersion],
990
+ }, metabase.updateVersion);
991
+ const metabaseVersionsCommands = cliparse.command('version', {
992
+ description: 'Manage Metabase deployed version',
993
+ args: [args.addonIdOrName],
994
+ options: [opts.humanJsonOutputFormat],
995
+ commands: [metabaseVersionCheckCommand, metabaseVersionUpdateCommand],
996
+ }, metabase.checkVersion);
997
+ const metabaseCommand = cliparse.command('metabase', {
998
+ description: 'Manage Clever Cloud Metabase services',
999
+ privateOptions: [opts.humanJsonOutputFormat],
1000
+ commands: [metabaseGetCommand, metabaseOpenCommand, metabaseRestartCommand, metabaseRebuildCommand, metabaseVersionsCommands],
1001
+ }, metabase.list);
1002
+
1003
+ // NETWORK GROUP COMMANDS
1004
+ const ngCreateExternalPeerCommand = cliparse.command('external', {
1005
+ description: 'Create an external peer in a Network Group',
1006
+ args: [args.ngExternalPeerLabel, args.ngIdOrLabel, args.wgPublicKey],
1007
+ }, ng.createExternalPeer);
1008
+ const ngDeleteExternalPeerCommand = cliparse.command('external', {
1009
+ description: 'Delete an external peer from a Network Group',
1010
+ args: [args.ngExternalIdOrLabel, args.ngIdOrLabel],
1011
+ }, ng.deleteExternalPeer);
1012
+ const ngCreateCommand = cliparse.command('create', {
1013
+ description: 'Create a Network Group',
1014
+ args: [args.ngLabel],
1015
+ privateOptions: [opts.ngMembersIdsToLink, opts.ngDescription, opts.optTags],
1016
+ commands: [ngCreateExternalPeerCommand],
1017
+ }, ng.createNg);
1018
+ const ngDeleteCommand = cliparse.command('delete', {
1019
+ description: 'Delete a Network Group',
1020
+ args: [args.ngIdOrLabel],
1021
+ commands: [ngDeleteExternalPeerCommand],
1022
+ }, ng.deleteNg);
1023
+ const ngLinkCommand = cliparse.command('link', {
1024
+ description: 'Link an application or a database add-on by its ID to a Network Group',
1025
+ args: [args.ngAnyIdOrLabel, args.ngIdOrLabel],
1026
+ }, ng.linkToNg);
1027
+ const ngUnlinkCommand = cliparse.command('unlink', {
1028
+ description: 'Unlink an application or a database add-on by its ID from a Network Group',
1029
+ args: [args.ngAnyIdOrLabel, args.ngIdOrLabel],
1030
+ }, ng.unlinkFromNg);
1031
+ const ngGetCommand = cliparse.command('get', {
1032
+ description: 'Get details about a Network Group, a member or a peer',
1033
+ args: [args.ngAnyIdOrLabel],
1034
+ options: [opts.ngResourceType, opts.humanJsonOutputFormat],
1035
+ }, ng.get);
1036
+ const ngGetConfigCommand = cliparse.command('get-config', {
1037
+ description: 'Get the Wireguard configuration of a peer in a Network Group',
1038
+ args: [args.ngExternalIdOrLabel, args.ngIdOrLabel],
1039
+ options: [opts.humanJsonOutputFormat],
1040
+ }, ng.getPeerConfig);
1041
+ const ngSearchCommand = cliparse.command('search', {
1042
+ description: 'Search Network Groups, members or peers and get their details',
1043
+ args: [args.ngAnyIdOrLabel],
1044
+ options: [opts.ngResourceType, opts.humanJsonOutputFormat],
1045
+ }, ng.search);
1046
+ const networkGroupsCommand = cliparse.command('ng', {
1047
+ description: 'List Network Groups',
1048
+ options: [opts.orgaIdOrName],
1049
+ privateOptions: [opts.humanJsonOutputFormat],
1050
+ commands: [ngCreateCommand, ngDeleteCommand, ngLinkCommand, ngUnlinkCommand, ngGetCommand, ngGetConfigCommand, ngSearchCommand],
1051
+ }, ng.listNg);
1052
+
749
1053
  // NOTIFY-EMAIL COMMAND
750
1054
  const addEmailNotificationCommand = cliparse.command('add', {
751
1055
  description: 'Add a new email notification',
@@ -769,6 +1073,63 @@ async function run () {
769
1073
  options: [opts.alias, opts.appIdOrName],
770
1074
  }, open.open);
771
1075
 
1076
+ // OTOROSHI COMMAND
1077
+ const otoroshiGetCommand = cliparse.command('get', {
1078
+ description: 'Get information about a deployed Otoroshi',
1079
+ args: [args.addonIdOrName],
1080
+ options: [opts.humanJsonOutputFormat],
1081
+ }, otoroshi.get);
1082
+ const otoroshiEnableNgCommand = cliparse.command('enable-ng', {
1083
+ description: 'Link Otoroshi to a Network Group',
1084
+ args: [args.addonIdOrName],
1085
+ }, otoroshi.ngEnable);
1086
+ const otoroshiDisableNgCommand = cliparse.command('disable-ng', {
1087
+ description: 'Unlink Otoroshi from its Network Group',
1088
+ args: [args.addonIdOrName],
1089
+ }, otoroshi.ngDisable);
1090
+ const otoroshiOpenLogsCommand = cliparse.command('logs', {
1091
+ description: 'Open the Otoroshi application logs in Clever Cloud Console',
1092
+ args: [args.addonIdOrName],
1093
+ }, otoroshi.openLogs);
1094
+ const otoroshiOpenWebUiCommand = cliparse.command('webui', {
1095
+ description: 'Open the Otoroshi admin console in your browser',
1096
+ args: [args.addonIdOrName],
1097
+ }, otoroshi.openWebUi);
1098
+ const otoroshiOpenCommand = cliparse.command('open', {
1099
+ description: 'Open the Otoroshi dashboard in Clever Cloud Console',
1100
+ args: [args.addonIdOrName],
1101
+ commands: [otoroshiOpenLogsCommand, otoroshiOpenWebUiCommand],
1102
+ }, otoroshi.open);
1103
+ const otoroshiRestartCommand = cliparse.command('restart', {
1104
+ description: 'Restart Otoroshi',
1105
+ args: [args.addonIdOrName],
1106
+ }, otoroshi.reboot);
1107
+ const otoroshiRebuildCommand = cliparse.command('rebuild', {
1108
+ description: 'Rebuild Otoroshi',
1109
+ args: [args.addonIdOrName],
1110
+ }, otoroshi.rebuild);
1111
+ const otoroshiVersionCheckCommand = cliparse.command('check', {
1112
+ description: 'Check Otoroshi deployed version',
1113
+ args: [args.addonIdOrName],
1114
+ options: [opts.humanJsonOutputFormat],
1115
+ }, otoroshi.checkVersion);
1116
+ const otoroshiVersionUpdateCommand = cliparse.command('update', {
1117
+ description: 'Update Otoroshi deployed version',
1118
+ args: [args.addonIdOrName],
1119
+ options: [opts.targetVersion],
1120
+ }, otoroshi.updateVersion);
1121
+ const otoroshiVersionsCommands = cliparse.command('version', {
1122
+ description: 'Manage Otoroshi deployed version',
1123
+ args: [args.addonIdOrName],
1124
+ options: [opts.humanJsonOutputFormat],
1125
+ commands: [otoroshiVersionCheckCommand, otoroshiVersionUpdateCommand],
1126
+ }, otoroshi.checkVersion);
1127
+ const otoroshiCommand = cliparse.command('otoroshi', {
1128
+ description: 'Manage Clever Cloud Otoroshi services',
1129
+ privateOptions: [opts.humanJsonOutputFormat],
1130
+ commands: [otoroshiGetCommand, otoroshiEnableNgCommand, otoroshiDisableNgCommand, otoroshiOpenCommand, otoroshiRestartCommand, otoroshiRebuildCommand, otoroshiVersionsCommands],
1131
+ }, otoroshi.list);
1132
+
772
1133
  // CONSOLE COMMAND
773
1134
  const consoleCommand = cliparse.command('console', {
774
1135
  description: 'Open an application in the Console',
@@ -847,6 +1208,28 @@ async function run () {
847
1208
  options: [opts.alias, opts.appIdOrName, opts.sshIdentityFile],
848
1209
  }, ssh.ssh);
849
1210
 
1211
+ // SSH KEYS COMMANDS
1212
+ const sshKeysAddCommand = cliparse.command('add', {
1213
+ description: 'Add a new SSH key to the current user',
1214
+ args: [args.sshKeyName, args.sshKeyPath],
1215
+ }, sshKeys.add);
1216
+ const sshKeysRemoveCommand = cliparse.command('remove', {
1217
+ description: 'Remove a SSH key from the current user',
1218
+ args: [args.sshKeyName],
1219
+ }, sshKeys.remove);
1220
+ const sshKeysRemoveAllCommand = cliparse.command('remove-all', {
1221
+ description: 'Remove all SSH keys from the current user',
1222
+ options: [opts.confirmSshKeyClean],
1223
+ }, sshKeys.removeAll);
1224
+ const sshKeysOpenConsoleCommand = cliparse.command('open', {
1225
+ description: 'Open the SSH keys management page in the Console',
1226
+ }, sshKeys.openConsole);
1227
+ const sshKeysCommands = cliparse.command('ssh-keys', {
1228
+ description: 'Manage SSH keys of the current user',
1229
+ privateOptions: [opts.humanJsonOutputFormat],
1230
+ commands: [sshKeysAddCommand, sshKeysRemoveCommand, sshKeysRemoveAllCommand, sshKeysOpenConsoleCommand],
1231
+ }, sshKeys.list);
1232
+
850
1233
  // STATUS COMMAND
851
1234
  const statusCommand = cliparse.command('status', {
852
1235
  description: 'See the status of an application',
@@ -880,6 +1263,22 @@ async function run () {
880
1263
  commands: [tcpRedirsListNamespacesCommand, tcpRedirsAddCommand, tcpRedirsRemoveCommand],
881
1264
  }, tcpRedirs.list);
882
1265
 
1266
+ // TOKENS COMMANDS
1267
+ const apiTokenCreateCommand = cliparse.command('create', {
1268
+ description: 'Create an API token',
1269
+ args: [args.apiTokenName],
1270
+ options: [opts.apiTokenExpiration, opts.humanJsonOutputFormat],
1271
+ }, tokens.create);
1272
+ const apiTokenRevokeCommand = cliparse.command('revoke', {
1273
+ description: 'Revoke an API token',
1274
+ args: [args.apiTokenId],
1275
+ }, tokens.revoke);
1276
+ const tokensCommands = cliparse.command('tokens', {
1277
+ description: `Manage API tokens to query Clever Cloud API from ${conf.AUTH_BRIDGE_HOST}`,
1278
+ commands: [apiTokenCreateCommand, apiTokenRevokeCommand],
1279
+ privateOptions: [opts.humanJsonOutputFormat],
1280
+ }, tokens.list);
1281
+
883
1282
  // UNLINK COMMAND
884
1283
  const appUnlinkCommand = cliparse.command('unlink', {
885
1284
  description: 'Unlink this repo from an existing application',
@@ -953,6 +1352,7 @@ async function run () {
953
1352
  domainCommands,
954
1353
  drainCommands,
955
1354
  emailNotificationsCommand,
1355
+ emailsCommands,
956
1356
  envCommands,
957
1357
  featuresCommands,
958
1358
  cliparseCommands.helpCommand,
@@ -968,9 +1368,11 @@ async function run () {
968
1368
  scaleCommand,
969
1369
  serviceCommands,
970
1370
  sshCommand,
1371
+ sshKeysCommands,
971
1372
  statusCommand,
972
1373
  stopCommand,
973
1374
  tcpRedirsCommands,
1375
+ tokensCommands,
974
1376
  versionCommand,
975
1377
  webhooksCommand,
976
1378
  ];
@@ -978,10 +1380,21 @@ async function run () {
978
1380
  // Add experimental features only if they are enabled through the configuration file
979
1381
  const featuresFromConf = await getFeatures();
980
1382
 
1383
+ if (featuresFromConf.operators) {
1384
+ commands.push(colorizeExperimentalCommand(keycloakCommand, 'operators'));
1385
+ commands.push(colorizeExperimentalCommand(matomoCommand, 'operators'));
1386
+ commands.push(colorizeExperimentalCommand(metabaseCommand, 'operators'));
1387
+ commands.push(colorizeExperimentalCommand(otoroshiCommand, 'operators'));
1388
+ }
1389
+
981
1390
  if (featuresFromConf.kv) {
982
1391
  commands.push(colorizeExperimentalCommand(kvRawCommand, 'kv'));
983
1392
  }
984
1393
 
1394
+ if (featuresFromConf.ng) {
1395
+ commands.push(colorizeExperimentalCommand(networkGroupsCommand, 'ng'));
1396
+ }
1397
+
985
1398
  // CLI PARSER
986
1399
  const cliParser = cliparse.cli({
987
1400
  name: 'clever',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "3.11.0",
3
+ "version": "3.13.0",
4
4
  "description": "Command Line Interface for Clever Cloud.",
5
5
  "main": "bin/clever.js",
6
6
  "type": "module",
@@ -26,12 +26,13 @@
26
26
  "scripts/*.sh"
27
27
  ],
28
28
  "dependencies": {
29
- "@clevercloud/client": "9.1.0",
29
+ "@clevercloud/client": "9.2.0",
30
+ "@inquirer/prompts": "7.3.2",
30
31
  "cliparse": "0.5.0",
31
32
  "colors": "1.4.0",
32
33
  "common-env": "6.4.0",
33
34
  "curlconverter": "3.21.0",
34
- "duration-js": "4.0.0",
35
+ "dedent": "1.5.3",
35
36
  "eventsource": "1.1.0",
36
37
  "ioredis": "5.4.1",
37
38
  "iso8601-duration": "2.1.2",
@@ -0,0 +1,61 @@
1
+ export function createApiToken ({ email, password, mfaCode, name, description = '', expirationDate }) {
2
+ return Promise.resolve({
3
+ method: 'post',
4
+ url: '/api-tokens',
5
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
6
+ body: { email, password, mfaCode, name, description, expirationDate },
7
+ });
8
+ }
9
+
10
+ export function listApiTokens () {
11
+ return Promise.resolve({
12
+ method: 'get',
13
+ url: '/api-tokens',
14
+ headers: { Accept: 'application/json' },
15
+ });
16
+ }
17
+
18
+ export function updateApiToken (apiTokenId, { name, description }) {
19
+ return Promise.resolve({
20
+ method: 'put',
21
+ url: `/api-tokens/${apiTokenId}`,
22
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
23
+ body: { name, description },
24
+ });
25
+ }
26
+
27
+ export function deleteApiToken (apiTokenId) {
28
+ return Promise.resolve({
29
+ method: 'delete',
30
+ url: `/api-tokens/${apiTokenId}`,
31
+ headers: { Accept: 'application/json' },
32
+ });
33
+ }
34
+
35
+ export function addOauthHeaderPlaintext (tokens) {
36
+
37
+ return async function (requestParams) {
38
+
39
+ const authorizationHeader = (
40
+ 'OAuth '
41
+ + [
42
+ `oauth_consumer_key="${tokens.OAUTH_CONSUMER_KEY}"`,
43
+ `oauth_token="${tokens.API_OAUTH_TOKEN}"`,
44
+ // %26 is URL escaped character "&"
45
+ `oauth_signature="${tokens.OAUTH_CONSUMER_SECRET}%26${tokens.API_OAUTH_TOKEN_SECRET}"`,
46
+ // oauth_nonce is not mandatory
47
+ // oauth_signature_method is not mandatory, it defaults to PLAINTEXT
48
+ // oauth_timestamp is not mandatory
49
+ // oauth_version is not mandatory, it defaults to 1.0
50
+ ].join(', ')
51
+ );
52
+
53
+ return {
54
+ ...requestParams,
55
+ headers: {
56
+ authorization: authorizationHeader,
57
+ ...requestParams.headers,
58
+ },
59
+ };
60
+ };
61
+ }
@@ -0,0 +1,18 @@
1
+ // TODO: Move this to the Clever Cloud JS Client
2
+
3
+ /**
4
+ * GET /networkgroups/organisations/{ownerId}/networkgroups/search?query
5
+ * @param {Object} params
6
+ * @param {String} params.ownerId
7
+ * @param {String} params.query
8
+ */
9
+ export function searchNetworkGroupOrResource (params) {
10
+ // no multipath for /self or /organisations/{id}
11
+ return Promise.resolve({
12
+ method: 'get',
13
+ url: `/v4/networkgroups/organisations/${params.ownerId}/networkgroups/search`,
14
+ headers: { Accept: 'application/json' },
15
+ queryParams: { query: params.query },
16
+ // no body
17
+ });
18
+ }