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 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://developers.clever-cloud.com/guides/).
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
- function run () {
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 your browser',
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 = _sortBy([
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
- ], 'name');
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.10.1",
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.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",
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
+ }
@@ -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
- 'Learn more about Keycloak on Clever Cloud: https://developers.clever-cloud.com/doc/addons/keycloak/',
124
- ].join('\n'),
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
- 'Learn more about Materia KV on Clever Cloud: https://developers.clever-cloud.com/doc/addons/materia-kv/',
133
- ].join('\n'),
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
- 'Learn more about Matomo on Clever Cloud: https://developers.clever-cloud.com/doc/addons/matomo/',
139
- ].join('\n'),
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
- 'Learn more about Metabase on Clever Cloud: https://developers.clever-cloud.com/doc/addons/metabase/',
145
- ].join('\n'),
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
- 'Learn more about Pulsar on Clever Cloud: https://developers.clever-cloud.com/doc/addons/pulsar/',
151
- ].join('\n'),
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: https://console.clever-cloud.com/goto/${addon.id}`);
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) {
@@ -1,4 +1,4 @@
1
- import * as application from '@clevercloud/client/esm/api/v2/application.js';
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 application.update({ id: ownerId, appId }, { [config.name]: ApplicationConfiguration.parse(config, configurationValue) }).then(sendToApi);
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 application.update({ id: ownerId, appId }, options).then(sendToApi);
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);
@@ -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(baseUrl, { wait: false });
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 = `${baseUrl}/${prefixPath}/applications/${appId}`;
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}`);
@@ -1,9 +1,10 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { loadOAuthConf, conf } from '../models/configuration.js';
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
- const apiDocUrlv2 = 'https://developers.clever-cloud.com/api/v2/';
20
- const apiDocUrlv4 = 'https://developers.clever-cloud.com/api/v4/';
21
-
22
- Logger.println(`Usage: clever curl
23
- Query Clever Cloud's API using Clever Tools credentials. For example:
24
-
25
- clever curl ${conf.API_HOST}/v2/self
26
- clever curl ${conf.API_HOST}/v2/summary
27
- clever curl ${conf.API_HOST}/v4/products/zones
28
- clever curl ${conf.API_HOST}/v2/organisations/<ORGANISATION_ID>/applications | jq '.[].id'
29
- clever curl ${conf.API_HOST}/v4/billing/organisations/<ORGANISATION_ID>/<INVOICE_NUMBER>.pdf > invoice.pdf
30
-
31
- Our API documentation is available here :
32
-
33
- ${apiDocUrlv2}
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 () {