clever-tools 3.10.1 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/clever.js +206 -5
- package/package.json +5 -3
- package/src/clever-client/auth-bridge.js +61 -0
- package/src/clever-client/ng.js +18 -0
- package/src/commands/addon.js +38 -19
- package/src/commands/config.js +3 -3
- package/src/commands/console.js +3 -4
- package/src/commands/curl.js +17 -17
- package/src/commands/database.js +9 -9
- package/src/commands/domain.js +13 -16
- package/src/commands/env.js +15 -8
- package/src/commands/features.js +93 -0
- package/src/commands/kv.js +93 -0
- package/src/commands/link.js +4 -2
- package/src/commands/ng.js +202 -0
- package/src/commands/profile.js +16 -7
- package/src/commands/published-config.js +7 -7
- package/src/commands/stop.js +2 -2
- package/src/commands/tcp-redirs.js +5 -5
- package/src/commands/tokens.js +137 -0
- package/src/experimental-features.js +61 -0
- package/src/lib/ng-print.js +195 -0
- package/src/logger.js +3 -0
- package/src/models/activity.js +2 -3
- package/src/models/addon.js +4 -4
- package/src/models/application.js +26 -16
- package/src/models/configuration.js +51 -15
- package/src/models/ids-resolver.js +38 -1
- package/src/models/namespaces.js +3 -2
- package/src/models/ng-resources.js +270 -0
- package/src/models/ng.js +276 -0
- package/src/models/send-to-api.js +19 -8
- package/src/parsers.js +57 -3
- package/src/prompt-password.js +10 -0
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Discover how to use Clever Tools through [our documentation](docs/).
|
|
|
56
56
|
|
|
57
57
|
## Examples
|
|
58
58
|
|
|
59
|
-
Discover how to deploy many applications on Clever Cloud within [our guides](https://
|
|
59
|
+
Discover how to deploy many applications on Clever Cloud within [our guides](https://www.clever-cloud.com/developers/guides/).
|
|
60
60
|
|
|
61
61
|
## How to send feedback?
|
|
62
62
|
|
package/bin/clever.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import '../src/initial-setup.js';
|
|
5
5
|
|
|
6
6
|
import cliparse from 'cliparse';
|
|
7
|
+
import colors from 'colors/safe.js';
|
|
7
8
|
import cliparseCommands from 'cliparse/src/command.js';
|
|
8
9
|
import _sortBy from 'lodash/sortBy.js';
|
|
9
10
|
|
|
@@ -13,7 +14,9 @@ import * as Parsers from '../src/parsers.js';
|
|
|
13
14
|
import { handleCommandPromise } from '../src/command-promise-handler.js';
|
|
14
15
|
import * as Application from '../src/models/application.js';
|
|
15
16
|
import { AVAILABLE_ZONES } from '../src/models/application.js';
|
|
17
|
+
import { EXPERIMENTAL_FEATURES } from '../src/experimental-features.js';
|
|
16
18
|
import { getExitOnOption, getOutputFormatOption, getSameCommitPolicyOption } from '../src/command-options.js';
|
|
19
|
+
import { getFeatures, conf } from '../src/models/configuration.js';
|
|
17
20
|
|
|
18
21
|
import * as Addon from '../src/models/addon.js';
|
|
19
22
|
import * as ApplicationConfiguration from '../src/models/application_configuration.js';
|
|
@@ -34,11 +37,14 @@ import * as diag from '../src/commands/diag.js';
|
|
|
34
37
|
import * as domain from '../src/commands/domain.js';
|
|
35
38
|
import * as drain from '../src/commands/drain.js';
|
|
36
39
|
import * as env from '../src/commands/env.js';
|
|
40
|
+
import * as features from '../src/commands/features.js';
|
|
41
|
+
import * as kv from '../src/commands/kv.js';
|
|
37
42
|
import * as link from '../src/commands/link.js';
|
|
38
43
|
import * as login from '../src/commands/login.js';
|
|
39
44
|
import * as logout from '../src/commands/logout.js';
|
|
40
45
|
import * as logs from '../src/commands/logs.js';
|
|
41
46
|
import * as makeDefault from '../src/commands/makeDefault.js';
|
|
47
|
+
import * as ng from '../src/commands/ng.js';
|
|
42
48
|
import * as notifyEmail from '../src/commands/notify-email.js';
|
|
43
49
|
import * as open from '../src/commands/open.js';
|
|
44
50
|
import * as consoleModule from '../src/commands/console.js';
|
|
@@ -51,6 +57,7 @@ import * as ssh from '../src/commands/ssh.js';
|
|
|
51
57
|
import * as status from '../src/commands/status.js';
|
|
52
58
|
import * as stop from '../src/commands/stop.js';
|
|
53
59
|
import * as tcpRedirs from '../src/commands/tcp-redirs.js';
|
|
60
|
+
import * as tokens from '../src/commands/tokens.js';
|
|
54
61
|
import * as unlink from '../src/commands/unlink.js';
|
|
55
62
|
import * as version from '../src/commands/version.js';
|
|
56
63
|
import * as webhooks from '../src/commands/webhooks.js';
|
|
@@ -74,10 +81,54 @@ cliparse.command = function (name, options, commandFunction) {
|
|
|
74
81
|
});
|
|
75
82
|
};
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
// Add a yellow color and status tag to the description of an experimental command
|
|
85
|
+
function colorizeExperimentalCommand (command, id) {
|
|
86
|
+
const status = EXPERIMENTAL_FEATURES[id].status;
|
|
87
|
+
command.description = colors.yellow(command.description + ' [' + status.toUpperCase() + ']');
|
|
88
|
+
return command;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function run () {
|
|
78
92
|
|
|
79
93
|
// ARGUMENTS
|
|
80
94
|
const args = {
|
|
95
|
+
kvRawCommand: cliparse.argument('command', { description: 'The raw command to send to the Materia KV or Redis® add-on' }),
|
|
96
|
+
kvIdOrName: cliparse.argument('kv-id', {
|
|
97
|
+
description: 'Add-on/Real ID (or name, if unambiguous) of a Materia KV or Redis® add-on',
|
|
98
|
+
}),
|
|
99
|
+
apiTokenId: cliparse.argument('api-token-id', { description: 'API token ID' }),
|
|
100
|
+
apiTokenName: cliparse.argument('api-token-name', { description: 'API token name' }),
|
|
101
|
+
ngId: cliparse.argument('id', {
|
|
102
|
+
description: 'Network Group ID',
|
|
103
|
+
parser: Parsers.ngResourceType,
|
|
104
|
+
}),
|
|
105
|
+
ngLabel: cliparse.argument('ng-label', {
|
|
106
|
+
description: 'Network Group label',
|
|
107
|
+
parser: Parsers.ngResourceType,
|
|
108
|
+
}),
|
|
109
|
+
ngIdOrLabel: cliparse.argument('ng-id-or-label', {
|
|
110
|
+
description: 'Network Group ID or label',
|
|
111
|
+
parser: Parsers.ngResourceType,
|
|
112
|
+
}),
|
|
113
|
+
ngDescription: cliparse.argument('ng-description', {
|
|
114
|
+
description: 'Network Group description',
|
|
115
|
+
}),
|
|
116
|
+
ngExternalPeerLabel: cliparse.argument('external-peer-label', {
|
|
117
|
+
description: 'External peer label',
|
|
118
|
+
parser: Parsers.ngResourceType,
|
|
119
|
+
}),
|
|
120
|
+
ngExternalIdOrLabel: cliparse.argument('external-peer-id-or-label', {
|
|
121
|
+
description: 'External peer ID or label',
|
|
122
|
+
parser: Parsers.ngResourceType,
|
|
123
|
+
}),
|
|
124
|
+
ngAnyIdOrLabel: cliparse.argument('id-or-label', {
|
|
125
|
+
description: 'ID or Label of a Network group, a member or an (external) peer',
|
|
126
|
+
parser: Parsers.ngResourceType,
|
|
127
|
+
}),
|
|
128
|
+
wgPublicKey: cliparse.argument('public-key', {
|
|
129
|
+
metavar: 'public_key',
|
|
130
|
+
description: 'Wireguard public key of the external peer to link to a Network Group',
|
|
131
|
+
}),
|
|
81
132
|
addonIdOrName: cliparse.argument('addon-id', {
|
|
82
133
|
description: 'Add-on ID (or name, if unambiguous)',
|
|
83
134
|
parser: Parsers.addonIdOrName,
|
|
@@ -102,6 +153,11 @@ function run () {
|
|
|
102
153
|
}),
|
|
103
154
|
drainUrl: cliparse.argument('drain-url', { description: 'Drain URL' }),
|
|
104
155
|
fqdn: cliparse.argument('fqdn', { description: 'Domain name of the application' }),
|
|
156
|
+
features: cliparse.argument('features', {
|
|
157
|
+
description: 'Comma-separated list of experimental features to manage',
|
|
158
|
+
parser: Parsers.commaSeparated,
|
|
159
|
+
}),
|
|
160
|
+
featureId: cliparse.argument('feature', { description: 'Experimental feature to manage' }),
|
|
105
161
|
notificationName: cliparse.argument('name', { description: 'Notification name' }),
|
|
106
162
|
notificationId: cliparse.argument('notification-id', { description: 'Notification ID' }),
|
|
107
163
|
webhookUrl: cliparse.argument('url', { description: 'Webhook URL' }),
|
|
@@ -126,6 +182,36 @@ function run () {
|
|
|
126
182
|
|
|
127
183
|
// OPTIONS
|
|
128
184
|
const opts = {
|
|
185
|
+
apiTokenExpiration: cliparse.option('expiration', {
|
|
186
|
+
aliases: ['e'],
|
|
187
|
+
metavar: 'expiration',
|
|
188
|
+
parser: Parsers.futureDateOrDuration,
|
|
189
|
+
description: 'Duration until API token expiration (e.g.: 1h, 4d, 2w, 6M), default 1y',
|
|
190
|
+
}),
|
|
191
|
+
// Network Groups options
|
|
192
|
+
ngDescription: cliparse.option('description', {
|
|
193
|
+
metavar: 'description',
|
|
194
|
+
description: 'Network Group description',
|
|
195
|
+
}),
|
|
196
|
+
ngMembersIdsToLink: cliparse.option('link', {
|
|
197
|
+
metavar: 'members_ids',
|
|
198
|
+
description: "Comma separated list of members IDs to link to a Network Group ('app_xxx', 'addon_xxx', 'external_xxx')",
|
|
199
|
+
parser: Parsers.commaSeparated,
|
|
200
|
+
}),
|
|
201
|
+
ngMemberLabel: cliparse.option('label', {
|
|
202
|
+
required: false,
|
|
203
|
+
metavar: 'member_label',
|
|
204
|
+
description: 'The member label',
|
|
205
|
+
parser: Parsers.ngResourceType,
|
|
206
|
+
}),
|
|
207
|
+
ngPeerGetConfig: cliparse.flag('config', {
|
|
208
|
+
description: 'Get the Wireguard configuration of an external peer',
|
|
209
|
+
}),
|
|
210
|
+
ngResourceType: cliparse.option('type', {
|
|
211
|
+
metavar: 'type',
|
|
212
|
+
description: 'Type of resource to look for (NetworkGroup, Member, CleverPeer, ExternalPeer)',
|
|
213
|
+
parser: Parsers.ngValidType,
|
|
214
|
+
}),
|
|
129
215
|
sourceableEnvVarsList: cliparse.flag('add-export', { description: 'Display sourceable env variables setting' }),
|
|
130
216
|
logsFormat: getOutputFormatOption(['json-stream']),
|
|
131
217
|
activityFormat: getOutputFormatOption(['json-stream']),
|
|
@@ -666,6 +752,35 @@ function run () {
|
|
|
666
752
|
commands: [envSetCommand, envRemoveCommand, envImportCommand, envImportVarsFromLocalEnvCommand],
|
|
667
753
|
}, env.list);
|
|
668
754
|
|
|
755
|
+
// EXPERIMENTAL FEATURES COMMAND
|
|
756
|
+
const listFeaturesCommand = cliparse.command('list', {
|
|
757
|
+
description: 'List available experimental features',
|
|
758
|
+
options: [opts.humanJsonOutputFormat],
|
|
759
|
+
}, features.list);
|
|
760
|
+
const infoFeaturesCommand = cliparse.command('info', {
|
|
761
|
+
description: 'Display info about an experimental feature',
|
|
762
|
+
args: [args.featureId],
|
|
763
|
+
}, features.info);
|
|
764
|
+
const enableFeatureCommand = cliparse.command('enable', {
|
|
765
|
+
description: 'Enable experimental features',
|
|
766
|
+
args: [args.features],
|
|
767
|
+
}, features.enable);
|
|
768
|
+
const disableFeatureCommand = cliparse.command('disable', {
|
|
769
|
+
description: 'Disable experimental features',
|
|
770
|
+
args: [args.features],
|
|
771
|
+
}, features.disable);
|
|
772
|
+
const featuresCommands = cliparse.command('features', {
|
|
773
|
+
description: 'Manage Clever Tools experimental features',
|
|
774
|
+
commands: [enableFeatureCommand, disableFeatureCommand, listFeaturesCommand, infoFeaturesCommand],
|
|
775
|
+
}, features.list);
|
|
776
|
+
|
|
777
|
+
// KV COMMAND
|
|
778
|
+
const kvRawCommand = cliparse.command('kv', {
|
|
779
|
+
description: 'Send a raw command to a Materia KV or Redis® add-on',
|
|
780
|
+
args: [args.kvIdOrName, args.kvRawCommand],
|
|
781
|
+
options: [opts.orgaIdOrName, opts.humanJsonOutputFormat],
|
|
782
|
+
}, kv.sendRawCommand);
|
|
783
|
+
|
|
669
784
|
// LINK COMMAND
|
|
670
785
|
const appLinkCommand = cliparse.command('link', {
|
|
671
786
|
description: 'Link this repo to an existing application',
|
|
@@ -696,6 +811,56 @@ function run () {
|
|
|
696
811
|
args: [args.alias],
|
|
697
812
|
}, makeDefault.makeDefault);
|
|
698
813
|
|
|
814
|
+
// NETWORK GROUP COMMANDS
|
|
815
|
+
const ngCreateExternalPeerCommand = cliparse.command('external', {
|
|
816
|
+
description: 'Create an external peer in a Network Group',
|
|
817
|
+
args: [args.ngExternalPeerLabel, args.ngIdOrLabel, args.wgPublicKey],
|
|
818
|
+
}, ng.createExternalPeer);
|
|
819
|
+
const ngDeleteExternalPeerCommand = cliparse.command('external', {
|
|
820
|
+
description: 'Delete an external peer from a Network Group',
|
|
821
|
+
args: [args.ngExternalIdOrLabel, args.ngIdOrLabel],
|
|
822
|
+
}, ng.deleteExternalPeer);
|
|
823
|
+
const ngCreateCommand = cliparse.command('create', {
|
|
824
|
+
description: 'Create a Network Group',
|
|
825
|
+
args: [args.ngLabel],
|
|
826
|
+
privateOptions: [opts.ngMembersIdsToLink, opts.ngDescription, opts.optTags],
|
|
827
|
+
commands: [ngCreateExternalPeerCommand],
|
|
828
|
+
}, ng.createNg);
|
|
829
|
+
const ngDeleteCommand = cliparse.command('delete', {
|
|
830
|
+
description: 'Delete a Network Group',
|
|
831
|
+
args: [args.ngIdOrLabel],
|
|
832
|
+
commands: [ngDeleteExternalPeerCommand],
|
|
833
|
+
}, ng.deleteNg);
|
|
834
|
+
const ngLinkCommand = cliparse.command('link', {
|
|
835
|
+
description: 'Link an application or a database add-on by its ID to a Network Group',
|
|
836
|
+
args: [args.ngAnyIdOrLabel, args.ngIdOrLabel],
|
|
837
|
+
}, ng.linkToNg);
|
|
838
|
+
const ngUnlinkCommand = cliparse.command('unlink', {
|
|
839
|
+
description: 'Unlink an application or a database add-on by its ID from a Network Group',
|
|
840
|
+
args: [args.ngAnyIdOrLabel, args.ngIdOrLabel],
|
|
841
|
+
}, ng.unlinkFromNg);
|
|
842
|
+
const ngGetCommand = cliparse.command('get', {
|
|
843
|
+
description: 'Get details about a Network Group, a member or a peer',
|
|
844
|
+
args: [args.ngAnyIdOrLabel],
|
|
845
|
+
options: [opts.ngResourceType, opts.humanJsonOutputFormat],
|
|
846
|
+
}, ng.get);
|
|
847
|
+
const ngGetConfigCommand = cliparse.command('get-config', {
|
|
848
|
+
description: 'Get the Wireguard configuration of a peer in a Network Group',
|
|
849
|
+
args: [args.ngExternalIdOrLabel, args.ngIdOrLabel],
|
|
850
|
+
options: [opts.humanJsonOutputFormat],
|
|
851
|
+
}, ng.getPeerConfig);
|
|
852
|
+
const ngSearchCommand = cliparse.command('search', {
|
|
853
|
+
description: 'Search Network Groups, members or peers and get their details',
|
|
854
|
+
args: [args.ngAnyIdOrLabel],
|
|
855
|
+
options: [opts.ngResourceType, opts.humanJsonOutputFormat],
|
|
856
|
+
}, ng.search);
|
|
857
|
+
const networkGroupsCommand = cliparse.command('ng', {
|
|
858
|
+
description: 'List Network Groups',
|
|
859
|
+
options: [opts.orgaIdOrName],
|
|
860
|
+
privateOptions: [opts.humanJsonOutputFormat],
|
|
861
|
+
commands: [ngCreateCommand, ngDeleteCommand, ngLinkCommand, ngUnlinkCommand, ngGetCommand, ngGetConfigCommand, ngSearchCommand],
|
|
862
|
+
}, ng.listNg);
|
|
863
|
+
|
|
699
864
|
// NOTIFY-EMAIL COMMAND
|
|
700
865
|
const addEmailNotificationCommand = cliparse.command('add', {
|
|
701
866
|
description: 'Add a new email notification',
|
|
@@ -715,7 +880,7 @@ function run () {
|
|
|
715
880
|
|
|
716
881
|
// OPEN COMMAND
|
|
717
882
|
const openCommand = cliparse.command('open', {
|
|
718
|
-
description: 'Open an application in
|
|
883
|
+
description: 'Open an application in the Console',
|
|
719
884
|
options: [opts.alias, opts.appIdOrName],
|
|
720
885
|
}, open.open);
|
|
721
886
|
|
|
@@ -726,9 +891,13 @@ function run () {
|
|
|
726
891
|
}, consoleModule.openConsole);
|
|
727
892
|
|
|
728
893
|
// PROFILE COMMAND
|
|
894
|
+
const profileOpenCommand = cliparse.command('open', {
|
|
895
|
+
description: 'Open your profile in the Console',
|
|
896
|
+
}, profile.openProfile);
|
|
729
897
|
const profileCommand = cliparse.command('profile', {
|
|
730
898
|
description: 'Display the profile of the current user',
|
|
731
899
|
options: [opts.humanJsonOutputFormat],
|
|
900
|
+
commands: [profileOpenCommand],
|
|
732
901
|
}, profile.profile);
|
|
733
902
|
|
|
734
903
|
// PUBLISHED CONFIG COMMANDS
|
|
@@ -826,6 +995,22 @@ function run () {
|
|
|
826
995
|
commands: [tcpRedirsListNamespacesCommand, tcpRedirsAddCommand, tcpRedirsRemoveCommand],
|
|
827
996
|
}, tcpRedirs.list);
|
|
828
997
|
|
|
998
|
+
// TOKENS COMMANDS
|
|
999
|
+
const apiTokenCreateCommand = cliparse.command('create', {
|
|
1000
|
+
description: 'Create an API token',
|
|
1001
|
+
args: [args.apiTokenName],
|
|
1002
|
+
options: [opts.apiTokenExpiration, opts.humanJsonOutputFormat],
|
|
1003
|
+
}, tokens.create);
|
|
1004
|
+
const apiTokenRevokeCommand = cliparse.command('revoke', {
|
|
1005
|
+
description: 'Revoke an API token',
|
|
1006
|
+
args: [args.apiTokenId],
|
|
1007
|
+
}, tokens.revoke);
|
|
1008
|
+
const tokensCommands = cliparse.command('tokens', {
|
|
1009
|
+
description: `Manage API tokens to query Clever Cloud API from ${conf.AUTH_BRIDGE_HOST}`,
|
|
1010
|
+
commands: [apiTokenCreateCommand, apiTokenRevokeCommand],
|
|
1011
|
+
privateOptions: [opts.humanJsonOutputFormat],
|
|
1012
|
+
}, tokens.list);
|
|
1013
|
+
|
|
829
1014
|
// UNLINK COMMAND
|
|
830
1015
|
const appUnlinkCommand = cliparse.command('unlink', {
|
|
831
1016
|
description: 'Unlink this repo from an existing application',
|
|
@@ -881,7 +1066,7 @@ function run () {
|
|
|
881
1066
|
// Patch help command description
|
|
882
1067
|
cliparseCommands.helpCommand.description = 'Display help about the Clever Cloud CLI';
|
|
883
1068
|
|
|
884
|
-
const commands =
|
|
1069
|
+
const commands = [
|
|
885
1070
|
accesslogsCommand,
|
|
886
1071
|
activityCommand,
|
|
887
1072
|
addonCommands,
|
|
@@ -900,6 +1085,7 @@ function run () {
|
|
|
900
1085
|
drainCommands,
|
|
901
1086
|
emailNotificationsCommand,
|
|
902
1087
|
envCommands,
|
|
1088
|
+
featuresCommands,
|
|
903
1089
|
cliparseCommands.helpCommand,
|
|
904
1090
|
loginCommand,
|
|
905
1091
|
logoutCommand,
|
|
@@ -918,7 +1104,22 @@ function run () {
|
|
|
918
1104
|
tcpRedirsCommands,
|
|
919
1105
|
versionCommand,
|
|
920
1106
|
webhooksCommand,
|
|
921
|
-
]
|
|
1107
|
+
];
|
|
1108
|
+
|
|
1109
|
+
// Add experimental features only if they are enabled through the configuration file
|
|
1110
|
+
const featuresFromConf = await getFeatures();
|
|
1111
|
+
|
|
1112
|
+
if (featuresFromConf.kv) {
|
|
1113
|
+
commands.push(colorizeExperimentalCommand(kvRawCommand, 'kv'));
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
if (featuresFromConf.tokens) {
|
|
1117
|
+
commands.push(colorizeExperimentalCommand(tokensCommands, 'tokens'));
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
if (featuresFromConf.ng) {
|
|
1121
|
+
commands.push(colorizeExperimentalCommand(networkGroupsCommand, 'ng'));
|
|
1122
|
+
}
|
|
922
1123
|
|
|
923
1124
|
// CLI PARSER
|
|
924
1125
|
const cliParser = cliparse.cli({
|
|
@@ -927,7 +1128,7 @@ function run () {
|
|
|
927
1128
|
version: getPackageJson().version,
|
|
928
1129
|
options: [opts.color, opts.updateNotifier, opts.verbose],
|
|
929
1130
|
helpCommand: false,
|
|
930
|
-
commands,
|
|
1131
|
+
commands: _sortBy(commands, 'name'),
|
|
931
1132
|
});
|
|
932
1133
|
|
|
933
1134
|
// Make sure argv[0] is always "node"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clever-tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "Command Line Interface for Clever Cloud.",
|
|
5
5
|
"main": "bin/clever.js",
|
|
6
6
|
"type": "module",
|
|
@@ -26,13 +26,15 @@
|
|
|
26
26
|
"scripts/*.sh"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@clevercloud/client": "9.
|
|
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
|
-
"
|
|
35
|
+
"dedent": "1.5.3",
|
|
35
36
|
"eventsource": "1.1.0",
|
|
37
|
+
"ioredis": "5.4.1",
|
|
36
38
|
"iso8601-duration": "2.1.2",
|
|
37
39
|
"isomorphic-git": "1.25.3",
|
|
38
40
|
"linux-release-info": "3.0.0",
|
|
@@ -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
|
+
}
|
package/src/commands/addon.js
CHANGED
|
@@ -11,6 +11,8 @@ import { getAllEnvVars } from '@clevercloud/client/esm/api/v2/addon.js';
|
|
|
11
11
|
import { sendToApi } from '../models/send-to-api.js';
|
|
12
12
|
import { toNameEqualsValueString } from '@clevercloud/client/esm/utils/env-vars.js';
|
|
13
13
|
import { resolveAddonId } from '../models/ids-resolver.js';
|
|
14
|
+
import { conf } from '../models/configuration.js';
|
|
15
|
+
import dedent from 'dedent';
|
|
14
16
|
|
|
15
17
|
const formatTable = initFormatTable();
|
|
16
18
|
|
|
@@ -114,41 +116,51 @@ function displayAddon (format, addon, providerName, message) {
|
|
|
114
116
|
name: 'Metabase',
|
|
115
117
|
urlEnv: 'METABASE_URL',
|
|
116
118
|
},
|
|
119
|
+
otoroshi: {
|
|
120
|
+
name: 'Otoroshi with LLM',
|
|
121
|
+
urlEnv: 'CC_OTOROSHI_URL',
|
|
122
|
+
},
|
|
117
123
|
};
|
|
118
124
|
|
|
119
125
|
const WIP_PROVIDERS = {
|
|
120
126
|
keycloak: {
|
|
121
127
|
status: 'beta',
|
|
122
|
-
postCreateInstructions:
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
postCreateInstructions: dedent`
|
|
129
|
+
Learn more about Keycloak on Clever Cloud: ${conf.DOC_URL}/addons/keycloak/,
|
|
130
|
+
`,
|
|
125
131
|
},
|
|
126
132
|
kv: {
|
|
127
133
|
status: 'alpha',
|
|
128
|
-
postCreateInstructions:
|
|
129
|
-
colors.yellow('You can easily use Materia KV with \'redis-cli\', with such commands:')
|
|
130
|
-
colors.blue(`source <(clever addon env ${addon.id} -F shell)`)
|
|
131
|
-
colors.blue('redis-cli -h $KV_HOST -p $KV_PORT --tls')
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
postCreateInstructions: dedent`
|
|
135
|
+
${colors.yellow('You can easily use Materia KV with \'redis-cli\', with such commands:')}
|
|
136
|
+
${colors.blue(`source <(clever addon env ${addon.id} -F shell)`)}
|
|
137
|
+
${colors.blue('redis-cli -h $KV_HOST -p $KV_PORT --tls')}
|
|
138
|
+
Learn more about Materia KV on Clever Cloud: ${conf.DOC_URL}/addons/materia-kv/
|
|
139
|
+
`,
|
|
134
140
|
},
|
|
135
141
|
'addon-matomo': {
|
|
136
142
|
status: 'beta',
|
|
137
|
-
postCreateInstructions:
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
postCreateInstructions: dedent`
|
|
144
|
+
Learn more about Matomo on Clever Cloud: ${conf.DOC_URL}/addons/matomo/
|
|
145
|
+
`,
|
|
140
146
|
},
|
|
141
147
|
metabase: {
|
|
142
148
|
status: 'beta',
|
|
143
|
-
postCreateInstructions:
|
|
144
|
-
|
|
145
|
-
|
|
149
|
+
postCreateInstructions: dedent`
|
|
150
|
+
Learn more about Metabase on Clever Cloud: ${conf.DOC_URL}/addons/metabase/
|
|
151
|
+
`,
|
|
152
|
+
},
|
|
153
|
+
otoroshi: {
|
|
154
|
+
status: 'beta',
|
|
155
|
+
postCreateInstructions: dedent`
|
|
156
|
+
Learn more about Otoroshi with LLM on Clever Cloud: ${conf.DOC_URL}/addons/otoroshi/
|
|
157
|
+
`,
|
|
146
158
|
},
|
|
147
159
|
'addon-pulsar': {
|
|
148
160
|
status: 'beta',
|
|
149
|
-
postCreateInstructions:
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
postCreateInstructions: dedent`
|
|
162
|
+
Learn more about Pulsar on Clever Cloud: ${conf.DOC_URL}/addons/pulsar/
|
|
163
|
+
`,
|
|
152
164
|
},
|
|
153
165
|
};
|
|
154
166
|
|
|
@@ -200,7 +212,7 @@ function displayAddon (format, addon, providerName, message) {
|
|
|
200
212
|
Logger.println();
|
|
201
213
|
Logger.println(`Your ${provider.name} is starting:`);
|
|
202
214
|
Logger.println(` - Access it: ${urlToShow.startsWith('http') ? urlToShow : `https://${urlToShow}`}`);
|
|
203
|
-
Logger.println(` - Manage it:
|
|
215
|
+
Logger.println(` - Manage it: ${conf.GOTO_URL}/${addon.id}`);
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
if (providerName === 'keycloak') {
|
|
@@ -209,6 +221,13 @@ function displayAddon (format, addon, providerName, message) {
|
|
|
209
221
|
Logger.println(` - Admin user name: ${addon.env.find((e) => e.name === 'CC_KEYCLOAK_ADMIN').value}`);
|
|
210
222
|
Logger.println(` - Temporary password: ${addon.env.find((e) => e.name === 'CC_KEYCLOAK_ADMIN_DEFAULT_PASSWORD').value}`);
|
|
211
223
|
}
|
|
224
|
+
|
|
225
|
+
if (providerName === 'otoroshi') {
|
|
226
|
+
Logger.println();
|
|
227
|
+
Logger.println('An initial account has been created, change the password at first login (Security -> Administrators -> Edit user):');
|
|
228
|
+
Logger.println(` - Admin user name: ${addon.env.find((e) => e.name === 'CC_OTOROSHI_INITIAL_ADMIN_LOGIN').value}`);
|
|
229
|
+
Logger.println(` - Initial password: ${addon.env.find((e) => e.name === 'CC_OTOROSHI_INITIAL_ADMIN_PASSWORD').value}`);
|
|
230
|
+
}
|
|
212
231
|
}
|
|
213
232
|
|
|
214
233
|
if (providerName in WIP_PROVIDERS) {
|
package/src/commands/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { update as updateApplication } from '@clevercloud/client/esm/api/v2/application.js';
|
|
2
2
|
|
|
3
3
|
import * as Application from '../models/application.js';
|
|
4
4
|
import * as ApplicationConfiguration from '../models/application_configuration.js';
|
|
@@ -26,7 +26,7 @@ export async function set (params) {
|
|
|
26
26
|
const config = ApplicationConfiguration.getById(configurationName);
|
|
27
27
|
|
|
28
28
|
if (config != null) {
|
|
29
|
-
const app = await
|
|
29
|
+
const app = await updateApplication({ id: ownerId, appId }, { [config.name]: ApplicationConfiguration.parse(config, configurationValue) }).then(sendToApi);
|
|
30
30
|
|
|
31
31
|
ApplicationConfiguration.printById(app, configurationName);
|
|
32
32
|
}
|
|
@@ -41,7 +41,7 @@ export async function update (params) {
|
|
|
41
41
|
throw new Error('No configuration to update');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const app = await
|
|
44
|
+
const app = await updateApplication({ id: ownerId, appId }, options).then(sendToApi);
|
|
45
45
|
|
|
46
46
|
for (const configName of Object.keys(options)) {
|
|
47
47
|
ApplicationConfiguration.printByName(app, configName);
|
package/src/commands/console.js
CHANGED
|
@@ -2,23 +2,22 @@ import * as Application from '../models/application.js';
|
|
|
2
2
|
import * as AppConfig from '../models/app_configuration.js';
|
|
3
3
|
import { Logger } from '../logger.js';
|
|
4
4
|
import openPage from 'open';
|
|
5
|
+
import { conf } from '../models/configuration.js';
|
|
5
6
|
|
|
6
7
|
export async function openConsole (params) {
|
|
7
8
|
const { alias, app: appIdOrName } = params.options;
|
|
8
9
|
|
|
9
|
-
const baseUrl = 'https://console.clever-cloud.com';
|
|
10
|
-
|
|
11
10
|
const { apps } = await AppConfig.loadApplicationConf();
|
|
12
11
|
// If no app is linked or asked, open the Console without any context
|
|
13
12
|
if (apps.length === 0 && !appIdOrName) {
|
|
14
13
|
Logger.println('Opening the Console in your browser');
|
|
15
|
-
await openPage(
|
|
14
|
+
await openPage(conf.CONSOLE_URL, { wait: false });
|
|
16
15
|
return;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
const { ownerId, appId } = await Application.resolveId(appIdOrName, alias);
|
|
20
19
|
const prefixPath = (ownerId.startsWith('user_')) ? 'users/me' : `organisations/${ownerId}`;
|
|
21
|
-
const url = `${
|
|
20
|
+
const url = `${conf.CONSOLE_URL}/${prefixPath}/applications/${appId}`;
|
|
22
21
|
|
|
23
22
|
Logger.debug(`URL: ${url}`);
|
|
24
23
|
Logger.println(`Opening the Console in your browser for application ${appId}`);
|
package/src/commands/curl.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { conf, loadOAuthConf } from '../models/configuration.js';
|
|
3
3
|
import { addOauthHeader } from '@clevercloud/client/esm/oauth.js';
|
|
4
4
|
import { Logger } from '../logger.js';
|
|
5
5
|
import colors from 'colors/safe.js';
|
|
6
6
|
import curlconverter from 'curlconverter';
|
|
7
|
+
import dedent from 'dedent';
|
|
7
8
|
|
|
8
9
|
async function loadTokens () {
|
|
9
10
|
const tokens = await loadOAuthConf();
|
|
@@ -16,22 +17,21 @@ async function loadTokens () {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function printCleverCurlHelp () {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
${apiDocUrlv4}`);
|
|
20
|
+
Logger.println(dedent`
|
|
21
|
+
Usage: clever curl
|
|
22
|
+
Query Clever Cloud's API using Clever Tools credentials. For example:
|
|
23
|
+
|
|
24
|
+
clever curl ${conf.API_HOST}/v2/self
|
|
25
|
+
clever curl ${conf.API_HOST}/v2/summary
|
|
26
|
+
clever curl ${conf.API_HOST}/v4/products/zones
|
|
27
|
+
clever curl ${conf.API_HOST}/v2/organisations/<ORGANISATION_ID>/applications | jq '.[].id'
|
|
28
|
+
clever curl ${conf.API_HOST}/v4/billing/organisations/<ORGANISATION_ID>/<INVOICE_NUMBER>.pdf > invoice.pdf
|
|
29
|
+
|
|
30
|
+
Our API documentation is available here :
|
|
31
|
+
|
|
32
|
+
${conf.API_DOC_URL}/v2/
|
|
33
|
+
${conf.API_DOC_URL}/v4/
|
|
34
|
+
`);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export async function curl () {
|