clever-tools 2.10.1 → 3.0.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/CHANGELOG.md CHANGED
@@ -1,11 +1,34 @@
1
1
  # clever-tools changelog
2
2
 
3
- ## Unrelease (????-??-??)
3
+ ## Unreleased (????-??-??)
4
+
5
+ ...
6
+
7
+ ## 3.0.0 (2023-10-19)
8
+
9
+ ### ⚠ BREAKING CHANGES
10
+
11
+ * Move from Node.js v12.22.8 to v18.5.0
12
+
13
+ ### Features
14
+
15
+ * add config file and auth source to `clever diag`
16
+ * add shell to `clever diag`
17
+ * improve `clever diag`, display (color and details) for oAuth token and user ID
18
+ * `clever deploy`: use new logs API (faster, longer, order)
19
+ * `clever restart`: use new logs API (faster, longer, order)
20
+ * `clever logs`: use new logs API (faster, longer, order), only for applications for now
21
+
22
+ ## 2.11.0 (2023-07-25)
23
+
24
+ * skip preorder step on addon creation
25
+ * add `clever addon env` command
26
+ * improve implicit ID params (owner and add-on ID / real ID) for `clever database`
4
27
 
5
28
  ## 2.10.1 (2023-02-20)
6
29
 
7
30
  * `clever database` command
8
- * Improve output when trying
31
+ * Improve output when trying
9
32
  * `clever database backups` command
10
33
  * Improve output when there are no backups yet
11
34
  * Fix command with personal orga
@@ -13,8 +36,8 @@
13
36
 
14
37
  ## 2.10.0 (2023-02-16)
15
38
 
16
- * add `clever database backups DATABASE-ID` command to list backups of a database
17
- * add `clever database backups download DATABASE-ID BACKUP-IP` command to download a backup
39
+ * add `clever database backups DATABASE-ID` command to list backups of a database
40
+ * add `clever database backups download DATABASE-ID BACKUP-IP` command to download a backup
18
41
  * add `clever curl` so we can prefix any `curl` command with `clever` and benefit from the local oAuth v1 auth
19
42
 
20
43
  ## 2.10.0-beta.2 (2022-09-13)
@@ -55,7 +78,7 @@
55
78
 
56
79
  * Fix linux-release-info usage in `clever diag`
57
80
 
58
- ## 2.7.0 (2020-08-20)
81
+ ## 2.7.0 (2020-08-20)
59
82
 
60
83
  * Improve `clever deploy` perfs in several cases (partial push, force push...)
61
84
  * NOTE: Pushing a brand new repo is still slow in some situations
@@ -64,8 +87,8 @@
64
87
  * `clever domain favourite` just displays the favourite domain (no prefix)
65
88
  * `clever domain favourite set example.com` sets the favourite domain to `example.com`
66
89
  * `clever domain favourite unset` unsets the favourite domain for this app
67
- * Add a message while waiting for deploy to start `clever deploy` and `clever restart`
68
- * Add shallow detection with appropriate error message for `clever deploy`
90
+ * Add a message while waiting for deploy to start `clever deploy` and `clever restart`
91
+ * Add shallow detection with appropriate error message for `clever deploy`
69
92
  * Fix `clever status` typo in output (Clément Delafargue)
70
93
  * Fix `clever env` error message when JSON input is incorrect (Jean Baptiste Noblot)
71
94
  * Update dependencies
@@ -86,7 +109,7 @@ This is the one with the latest isomorphic-git!!!
86
109
  * Add `clever config` command to configure application options
87
110
  * Existing supported options: `name`, `description`, `zero-downtime`, `sticky-sessions` and `cancel-on-push`
88
111
  * New supported option: `force-https`
89
- * Add `--follow` to `clever deploy` and `clever restart` so you can continue to follow logs after the deployment is finished
112
+ * Add `--follow` to `clever deploy` and `clever restart` so you can continue to follow logs after the deployment is finished
90
113
  * Fix #318 where `clever deploy` and `clever restart` would sometimes never exit
91
114
  * We changed the way we detect when a deployment is finished
92
115
  * Fix #304 where `clever logs` would run endlessly even when using `--before/--until`
@@ -94,7 +117,7 @@ This is the one with the latest isomorphic-git!!!
94
117
  ### Internals
95
118
 
96
119
  * We completely removed our dependency to Bacon.js, goodbye old friend 😘
97
- * `@clevercloud/client` was updated to `6.0.0` so we can use the new event based API for logs and events
120
+ * `@clevercloud/client` was updated to `6.0.0` so we can use the new event based API for logs and events
98
121
 
99
122
  ## 2.4.0 (2020-04-16)
100
123
 
@@ -249,7 +272,7 @@ These release were only created for some tests on our CI pipeline.
249
272
 
250
273
  - Log error trace in --verbose mode
251
274
  - Update deps (rename opn => open)
252
- - Fix logs with `clever deploy` (update @clevercloud/client to 2.0.0-beta.1)
275
+ - Fix logs with `clever deploy` (update @clevercloud/client to 2.0.0-beta.1)
253
276
  - Fix typo "connexion" => "connection" in logs and README
254
277
 
255
278
  ## 1.5.0-beta.5 (2019-08-01)
@@ -279,7 +302,7 @@ These release were only created for some tests on our CI pipeline.
279
302
  - Ease testing for preprod with comments in config
280
303
  - Update @clevercloud/client to 2.0.0-beta.0
281
304
 
282
- ## 1.5.0-beta.4 (2019-07-25)
305
+ ## 1.5.0-beta.4 (2019-07-25)
283
306
 
284
307
  - Fix --add-export option for `clever env`
285
308
 
@@ -381,7 +404,7 @@ These release were only created for some tests on our CI pipeline.
381
404
 
382
405
  - Replace [nodegit](https://github.com/nodegit/nodegit) with [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git)
383
406
  - Add ESLint config with a big refactoring to go along
384
- - Always use the npm `colors` package in safe mode: no global `String` pollution
407
+ - Always use the npm `colors` package in safe mode: no global `String` pollution
385
408
  - Build, package and publish with a multibranch pipeline project on Jenkins
386
409
 
387
410
  ### Packaging & distribution
package/README.md CHANGED
@@ -39,7 +39,7 @@ If you are using a GNU/Linux distribution that uses `.deb` packages like Debian
39
39
 
40
40
  ```sh
41
41
  curl -fsSL https://clever-tools.clever-cloud.com/gpg/cc-nexus-deb.public.gpg.key | gpg --dearmor -o /usr/share/keyrings/cc-nexus-deb.gpg
42
- echo "deb [signed-by=/usr/share/keyrings/cc-nexus-deb.gpg] https://nexus.clever-cloud.com/repository/deb stable main" | tee -a /etc/apt/sources.list
42
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/cc-nexus-deb.gpg] https://nexus.clever-cloud.com/repository/deb stable main" | tee -a /etc/apt/sources.list
43
43
  apt-get update
44
44
  apt-get install clever-tools
45
45
  ```
@@ -51,7 +51,7 @@ NOTES:
51
51
  * If you want access to the beta channel, you can use this in your `sources.list`:
52
52
 
53
53
  ```sh
54
- echo "deb [signed-by=/usr/share/keyrings/cc-nexus-deb.gpg] https://nexus.clever-cloud.com/repository/deb-beta beta main" | tee -a /etc/apt/sources.list
54
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/cc-nexus-deb.gpg] https://nexus.clever-cloud.com/repository/deb-beta beta main" | tee -a /etc/apt/sources.list
55
55
  ```
56
56
 
57
57
  #### CentOS/Fedora (.rpm)
@@ -252,7 +252,7 @@ Where `type` is one of:
252
252
  - `php`: for PHP applications
253
253
  - `play1`: for Play1 applications
254
254
  - `play2`: for Play2 applications
255
- - `python`: for python27 applications
255
+ - `python`: for python27 and python3 applications
256
256
  - `ruby`: for ruby applications
257
257
  - `rust`: for rust applications
258
258
  - `sbt`: for applications launched with SBT
@@ -334,9 +334,12 @@ ElasticSearch drains use the Elastic bulk API. To match this endpoint, specify `
334
334
 
335
335
  Datadog has two zones, EU and COM. An account on one zone is not available on the other, make sure to target the good EU or COM intake endpoint.
336
336
 
337
- To create a [Datadog](https://docs.datadoghq.com/api/?lang=python#send-logs-over-http) drain, you just need to use:
337
+ To create a [Datadog](https://docs.datadoghq.com/api/?lang=python#send-logs-over-http) drain, you just need to use one of the following command depending on your zone:
338
338
 
339
339
  ```sh
340
+ # EU
341
+ clever drain create DatadogHTTP "https://http-intake.logs.datadoghq.eu/v1/input/<API_KEY>?ddsource=clevercloud&service=<SERVICE>&host=<HOST>"
342
+ # US
340
343
  clever drain create DatadogHTTP "https://http-intake.logs.datadoghq.com/v1/input/<API_KEY>?ddsource=clevercloud&service=<SERVICE>&host=<HOST>"
341
344
  ```
342
345
 
package/bin/clever.js CHANGED
@@ -13,12 +13,15 @@ if (process.argv.includes('--autocomplete-index')) {
13
13
  }
14
14
 
15
15
  const cliparse = require('cliparse');
16
+ const cliparseCommands = require('cliparse/src/command.js');
16
17
  const updateNotifier = require('update-notifier');
18
+ const _sortBy = require('lodash/sortBy.js');
17
19
 
18
20
  const git = require('../src/models/git.js');
19
21
  const Parsers = require('../src/parsers.js');
20
22
  const handleCommandPromise = require('../src/command-promise-handler.js');
21
23
  const Formatter = require('../src/models/format-string.js');
24
+ const { getOutputFormatOption } = require('../src/get-output-format-option.js');
22
25
 
23
26
  // Exit cleanly if the program we pipe to exits abruptly
24
27
  process.stdout.on('error', (error) => {
@@ -63,7 +66,6 @@ function lazyRequire (modulePath) {
63
66
  };
64
67
  }
65
68
 
66
- const AccessLogs = lazyRequire('../src/models/accesslogs.js');
67
69
  const Addon = lazyRequire('../src/models/addon.js');
68
70
  const Application = lazyRequire('../src/models/application.js');
69
71
  const ApplicationConfiguration = lazyRequire('../src/models/application_configuration.js');
@@ -89,7 +91,7 @@ function run () {
89
91
  }),
90
92
  appNameCreation: cliparse.argument('app-name', { description: 'Application name' }),
91
93
  backupId: cliparse.argument('backup-id', { description: 'A Database backup ID (format: UUID)' }),
92
- databaseId: cliparse.argument('database-id', { description: 'A database ID (format: postgresql_UUID, mysql_UUID, ...)' }),
94
+ databaseId: cliparse.argument('database-id', { description: 'Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...)' }),
93
95
  drainId: cliparse.argument('drain-id', { description: 'Drain ID' }),
94
96
  drainType: cliparse.argument('drain-type', {
95
97
  description: 'Drain type',
@@ -127,16 +129,8 @@ function run () {
127
129
  // OPTIONS
128
130
  const opts = {
129
131
  sourceableEnvVarsList: cliparse.flag('add-export', { description: 'Display sourceable env variables setting' }),
130
- accesslogsFormat: cliparse.option('format', {
131
- aliases: ['F'],
132
- metavar: 'format',
133
- parser: Parsers.accessLogsFormat,
134
- default: 'simple',
135
- description: 'Output format (one of simple, extended, clf or json)',
136
- complete () {
137
- return cliparse.autocomplete.words(AccessLogs('listAvailableFormats')());
138
- },
139
- }),
132
+ accesslogsFormat: getOutputFormatOption(['simple', 'extended', 'clf']),
133
+ addonEnvFormat: getOutputFormatOption(['shell']),
140
134
  accesslogsFollow: cliparse.flag('follow', {
141
135
  aliases: ['f'],
142
136
  description: 'Display access logs continuously (ignores before/until, after/since)',
@@ -512,6 +506,7 @@ function run () {
512
506
  description: 'A WireGuard® private key',
513
507
  }),
514
508
  jsonFormat: cliparse.flag('json', { aliases: ['j'], description: 'Show result in JSON format' }),
509
+ humanJsonOutputFormat: getOutputFormatOption(),
515
510
  tag: cliparse.option('tag', {
516
511
  required: true,
517
512
  metavar: 'tag',
@@ -581,10 +576,16 @@ function run () {
581
576
  description: 'List available addon providers',
582
577
  commands: [addonShowProviderCommand],
583
578
  }, addon('listProviders'));
579
+ const addonEnvCommand = cliparse.command('env', {
580
+ description: 'List the environment variables for an add-on',
581
+ options: [opts.addonEnvFormat],
582
+ args: [opts.addonId],
583
+ }, addon('env'));
584
+
584
585
  const addonCommands = cliparse.command('addon', {
585
586
  description: 'Manage addons',
586
587
  options: [opts.orgaIdOrName],
587
- commands: [addonCreateCommand, addonDeleteCommand, addonRenameCommand, addonProvidersCommand],
588
+ commands: [addonCreateCommand, addonDeleteCommand, addonRenameCommand, addonProvidersCommand, addonEnvCommand],
588
589
  }, addon('list'));
589
590
 
590
591
  // APPLICATIONS COMMAND
@@ -626,7 +627,7 @@ function run () {
626
627
  const appCreateCommand = cliparse.command('create', {
627
628
  description: 'Create a Clever Cloud application',
628
629
  args: [args.appNameCreation],
629
- options: [opts.instanceType, opts.orgaIdOrName, opts.aliasCreation, opts.region, opts.github],
630
+ options: [opts.instanceType, opts.orgaIdOrName, opts.aliasCreation, opts.region, opts.github, opts.humanJsonOutputFormat],
630
631
  }, create('create'));
631
632
 
632
633
  // DELETE COMMAND
@@ -828,11 +829,13 @@ function run () {
828
829
  });
829
830
  // peer category - end
830
831
 
832
+ // eslint-disable-next-line no-unused-vars
831
833
  const networkGroupsCommand = cliparse.command('networkgroups', {
832
834
  description: 'List Network Group commands',
833
835
  options: [opts.orgaIdOrName, opts.alias],
834
836
  commands: [networkGroupsListCommand, networkGroupsCreateCommand, networkGroupsDeleteCommand, networkGroupsMembersCategoryCommand, networkGroupsPeersCategoryCommand],
835
837
  });
838
+ // eslint-disable-next-line no-unused-vars
836
839
  const ngCommand = cliparse.command('ng', {
837
840
  description: `Alias for ${Formatter.formatCommand('clever networkgroups')}`,
838
841
  options: [opts.orgaIdOrName, opts.alias],
@@ -1011,7 +1014,7 @@ function run () {
1011
1014
  const downloadBackupCommand = cliparse.command('download', {
1012
1015
  description: 'Download a database backup',
1013
1016
  args: [args.databaseId, args.backupId],
1014
- options: [opts.orgaIdOrName, opts.output],
1017
+ options: [opts.output],
1015
1018
  }, database('downloadBackups'));
1016
1019
  const backupsCommand = cliparse.command('backups', {
1017
1020
  description: 'List available database backups',
@@ -1030,51 +1033,55 @@ function run () {
1030
1033
  console.info('clever database backups download');
1031
1034
  });
1032
1035
 
1036
+ const commands = _sortBy([
1037
+ accesslogsCommand,
1038
+ activityCommand,
1039
+ addonCommands,
1040
+ appCreateCommand,
1041
+ applicationsCommand,
1042
+ appLinkCommand,
1043
+ appUnlinkCommand,
1044
+ cancelDeployCommand,
1045
+ configCommands,
1046
+ databaseCommand,
1047
+ deleteCommand,
1048
+ deployCommand,
1049
+ diagCommand,
1050
+ domainCommands,
1051
+ drainCommands,
1052
+ emailNotificationsCommand,
1053
+ envCommands,
1054
+ cliparseCommands.helpCommand,
1055
+ loginCommand,
1056
+ logoutCommand,
1057
+ logsCommand,
1058
+ makeDefaultCommand,
1059
+ // Not ready for stable release yet
1060
+ // networkGroupsCommand,
1061
+ // ngCommand,
1062
+ openCommand,
1063
+ consoleCommand,
1064
+ profileCommand,
1065
+ publishedConfigCommands,
1066
+ restartCommand,
1067
+ scaleCommand,
1068
+ serviceCommands,
1069
+ sshCommand,
1070
+ statusCommand,
1071
+ stopCommand,
1072
+ tcpRedirsCommands,
1073
+ versionCommand,
1074
+ webhooksCommand,
1075
+ ], 'name');
1076
+
1033
1077
  // CLI PARSER
1034
1078
  const cliParser = cliparse.cli({
1035
1079
  name: 'clever',
1036
1080
  description: 'CLI tool to manage Clever Cloud data and products',
1037
1081
  version: pkg.version,
1038
1082
  options: [opts.verbose, opts.noUpdateNotifier],
1039
- commands: [
1040
- accesslogsCommand,
1041
- activityCommand,
1042
- addonCommands,
1043
- appCreateCommand,
1044
- applicationsCommand,
1045
- appLinkCommand,
1046
- appUnlinkCommand,
1047
- cancelDeployCommand,
1048
- configCommands,
1049
- databaseCommand,
1050
- deleteCommand,
1051
- deployCommand,
1052
- diagCommand,
1053
- domainCommands,
1054
- drainCommands,
1055
- emailNotificationsCommand,
1056
- envCommands,
1057
- loginCommand,
1058
- logoutCommand,
1059
- logsCommand,
1060
- makeDefaultCommand,
1061
- // Not ready for stable release yet
1062
- // networkGroupsCommand,
1063
- // ngCommand,
1064
- openCommand,
1065
- consoleCommand,
1066
- profileCommand,
1067
- publishedConfigCommands,
1068
- restartCommand,
1069
- scaleCommand,
1070
- serviceCommands,
1071
- sshCommand,
1072
- statusCommand,
1073
- stopCommand,
1074
- tcpRedirsCommands,
1075
- versionCommand,
1076
- webhooksCommand,
1077
- ],
1083
+ helpCommand: false,
1084
+ commands,
1078
1085
  });
1079
1086
 
1080
1087
  // Make sure argv[0] is always "node"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "2.10.1",
3
+ "version": "3.0.0",
4
4
  "description": "Command Line Interface for Clever Cloud.",
5
5
  "main": "bin/clever.js",
6
6
  "keywords": [
@@ -9,9 +9,9 @@
9
9
  "clever cloud"
10
10
  ],
11
11
  "engines": {
12
- "node": ">=12"
12
+ "node": ">=18"
13
13
  },
14
- "pkg-node-version": "12",
14
+ "pkg-node-version": "18",
15
15
  "author": "Clever Cloud <ci@clever-cloud.com>",
16
16
  "license": "Apache-2.0",
17
17
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "scripts/*.sh"
26
26
  ],
27
27
  "dependencies": {
28
- "@clevercloud/client": "7.9.0",
28
+ "@clevercloud/client": "^8.0.1",
29
29
  "clf-date": "^0.2.0",
30
30
  "cliparse": "^0.3.3",
31
31
  "colors": "1.4.0",
@@ -64,7 +64,7 @@
64
64
  "grunt-http": "^2.3.3",
65
65
  "grunt-mocha-test": "^0.13.3",
66
66
  "mocha": "^8.4.0",
67
- "pkg": "^5.2.1",
67
+ "pkg": "^5.8.1",
68
68
  "semver": "^7.3.5"
69
69
  },
70
70
  "scripts": {
@@ -87,7 +87,6 @@
87
87
  ]
88
88
  },
89
89
  "volta": {
90
- "node": "12.22.8",
91
- "npm": "6.14.15"
90
+ "node": "18.5.0"
92
91
  }
93
92
  }
@@ -9,7 +9,11 @@ const formatTable = require('../format-table')();
9
9
  const Logger = require('../logger.js');
10
10
  const Organisation = require('../models/organisation.js');
11
11
  const User = require('../models/user.js');
12
- const { parseAddonOptions } = require('../models/addon.js');
12
+ const { parseAddonOptions, findOwnerId } = require('../models/addon.js');
13
+ const { getAllEnvVars } = require('@clevercloud/client/cjs/api/v2/addon.js');
14
+ const { sendToApi } = require('../models/send-to-api.js');
15
+ const { toNameEqualsValueString } = require('@clevercloud/client/cjs/utils/env-vars.js');
16
+ const { resolveAddonId } = require('../models/ids-resolver.js');
13
17
 
14
18
  async function list (params) {
15
19
  const { org: orgaIdOrName } = params.options;
@@ -57,7 +61,16 @@ async function create (params) {
57
61
  Logger.println(`Addon ${name} (id: ${newAddon.id}) successfully created and linked to the application`);
58
62
  }
59
63
  else {
60
- const newAddon = await Addon.create({ ownerId, name, providerName, planName, region, skipConfirmation, version, addonOptions });
64
+ const newAddon = await Addon.create({
65
+ ownerId,
66
+ name,
67
+ providerName,
68
+ planName,
69
+ region,
70
+ skipConfirmation,
71
+ version,
72
+ addonOptions,
73
+ });
61
74
  Logger.println(`Addon ${name} (id: ${newAddon.id}) successfully created`);
62
75
  }
63
76
  }
@@ -142,6 +155,36 @@ async function showProvider (params) {
142
155
  });
143
156
  }
144
157
 
158
+ async function env (params) {
159
+
160
+ const { org, format } = params.options;
161
+ const [addonIdOrRealId] = params.args;
162
+
163
+ const addonId = await resolveAddonId(addonIdOrRealId);
164
+ const ownerId = await findOwnerId(org, addonId);
165
+
166
+ const envFromAddon = await getAllEnvVars({ id: ownerId, addonId }).then(sendToApi);
167
+
168
+ switch (format) {
169
+
170
+ case 'json': {
171
+ const envFromAddonJson = Object.fromEntries(
172
+ envFromAddon.map(({ name, value }) => [name, value]),
173
+ );
174
+ Logger.println(JSON.stringify(envFromAddonJson, null, 2));
175
+ break;
176
+ }
177
+
178
+ case 'shell':
179
+ Logger.println(toNameEqualsValueString(envFromAddon, { addExports: true }));
180
+ break;
181
+
182
+ case 'human':
183
+ default:
184
+ Logger.println(toNameEqualsValueString(envFromAddon, { addExports: false }));
185
+ }
186
+ }
187
+
145
188
  module.exports = {
146
189
  list,
147
190
  create,
@@ -149,4 +192,5 @@ module.exports = {
149
192
  rename,
150
193
  listProviders,
151
194
  showProvider,
195
+ env,
152
196
  };
@@ -3,17 +3,28 @@
3
3
  const Application = require('../models/application.js');
4
4
  const AppConfig = require('../models/app_configuration.js');
5
5
  const Logger = require('../logger.js');
6
+ const { toNameEqualsValueString } = require('@clevercloud/client/cjs/utils/env-vars.js');
6
7
 
7
8
  async function create (params) {
8
9
  const { type: typeName } = params.options;
9
10
  const [name] = params.args;
10
- const { org: orgaIdOrName, alias, region, github: githubOwnerRepo } = params.options;
11
+ const { org: orgaIdOrName, alias, region, github: githubOwnerRepo, format } = params.options;
11
12
  const github = getGithubDetails(githubOwnerRepo);
12
13
 
13
14
  const app = await Application.create(name, typeName, region, orgaIdOrName, github);
14
15
  await AppConfig.addLinkedApplication(app, alias);
15
16
 
16
- Logger.println('Your application has been successfully created!');
17
+ switch (format) {
18
+
19
+ case 'json': {
20
+ console.log(JSON.stringify(app, null, 2));
21
+ break;
22
+ }
23
+
24
+ case 'human':
25
+ default:
26
+ Logger.println('Your application has been successfully created!');
27
+ }
17
28
  };
18
29
 
19
30
  function getGithubDetails (githubOwnerRepo) {
@@ -3,7 +3,7 @@
3
3
  const { parseCurlCommand } = require('curlconverter/util.js');
4
4
  const { spawn } = require('child_process');
5
5
  const { loadOAuthConf, conf } = require('../models/configuration.js');
6
- const { addOauthHeader } = require('@clevercloud/client/cjs/oauth.node.js');
6
+ const { addOauthHeader } = require('@clevercloud/client/cjs/oauth.js');
7
7
 
8
8
  async function loadTokens () {
9
9
  const tokens = await loadOAuthConf();
@@ -5,20 +5,19 @@ const { getBackups } = require('@clevercloud/client/cjs/api/v2/backups.js');
5
5
  const { println } = require('../logger.js');
6
6
  const formatTable = require('../format-table')();
7
7
  const superagent = require('superagent');
8
- const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
9
8
  const fs = require('fs');
9
+ const { findOwnerId } = require('../models/addon.js');
10
+ const { resolveRealId } = require('../models/ids-resolver.js');
10
11
 
11
12
  async function listBackups (params) {
12
13
 
13
- const [databaseId] = params.args;
14
14
  const { org } = params.options;
15
+ const [addonIdOrRealId] = params.args;
15
16
 
16
- const ownerId = await resolveOwnerId(org, databaseId);
17
- if (ownerId == null) {
18
- throw new Error('organisation ID is mandatory');
19
- }
17
+ const addonId = await resolveRealId(addonIdOrRealId);
18
+ const ownerId = await findOwnerId(org, addonId);
20
19
 
21
- const backups = await getBackups({ ownerId, ref: databaseId }).then(sendToApi);
20
+ const backups = await getBackups({ ownerId, ref: addonId }).then(sendToApi);
22
21
 
23
22
  if (backups.length === 0) {
24
23
  println('There are no backups yet');
@@ -47,15 +46,13 @@ async function listBackups (params) {
47
46
 
48
47
  async function downloadBackups (params) {
49
48
 
50
- const [databaseId, backupId] = params.args;
51
49
  const { org, output } = params.options;
50
+ const [addonIdOrRealId, backupId] = params.args;
52
51
 
53
- const ownerId = await resolveOwnerId(org, databaseId);
54
- if (ownerId == null) {
55
- throw new Error('organisation ID is mandatory');
56
- }
52
+ const addonId = await resolveRealId(addonIdOrRealId);
53
+ const ownerId = await findOwnerId(org, addonId);
57
54
 
58
- const backups = await getBackups({ ownerId, ref: databaseId }).then(sendToApi);
55
+ const backups = await getBackups({ ownerId, ref: addonId }).then(sendToApi);
59
56
  const backup = backups.find((backup) => backup.backup_id === backupId);
60
57
 
61
58
  if (backup == null) {
@@ -74,33 +71,4 @@ async function downloadBackups (params) {
74
71
  process.stdout.write(res.body);
75
72
  }
76
73
 
77
- /**
78
- * Try to get an ownerId from the API
79
- * @param {*} org cliparse param's option for orga
80
- * @param {String} databaseId the resource which belong to the owner we are looking for
81
- * @returns the owner ID (or null if cannot be found)
82
- */
83
- async function resolveOwnerId (org, databaseId) {
84
-
85
- if (org != null && org.orga_name != null) {
86
- return org.orga_name;
87
- }
88
-
89
- const summary = await getSummary().then(sendToApi);
90
-
91
- const userHasAddon = summary.user.addons.some((addon) => addon.realId === databaseId);
92
- if (userHasAddon) {
93
- return summary.user.id;
94
- }
95
-
96
- for (const orga of summary.organisations) {
97
- const orgaHasAddon = orga.addons.some((addon) => addon.realId === databaseId);
98
- if (orgaHasAddon) {
99
- return orga.id;
100
- }
101
- }
102
-
103
- return null;
104
- }
105
-
106
74
  module.exports = { listBackups, downloadBackups };
@@ -5,7 +5,7 @@ const colors = require('colors/safe');
5
5
  const AppConfig = require('../models/app_configuration.js');
6
6
  const Application = require('../models/application.js');
7
7
  const git = require('../models/git.js');
8
- const Log = require('../models/log.js');
8
+ const Log = require('../models/log-v4.js');
9
9
  const Logger = require('../logger.js');
10
10
  const { getAllDeployments } = require('@clevercloud/client/cjs/api/v2/application.js');
11
11
  const { sendToApi } = require('../models/send-to-api.js');
@@ -8,12 +8,12 @@ const colors = require('colors/safe');
8
8
  const Logger = require('../logger.js');
9
9
  const pkg = require('../../package.json');
10
10
  const User = require('../models/user.js');
11
- const { loadOAuthConf } = require('../models/configuration.js');
11
+ const { conf, loadOAuthConf } = require('../models/configuration.js');
12
12
 
13
13
  async function diag () {
14
14
 
15
- const userId = await User.getCurrentId().catch(() => 'Not connected');
16
- const oauthToken = await loadOAuthConf().then((o) => o.token).catch(() => 'Not connected');
15
+ const userId = await User.getCurrentId().catch(() => null);
16
+ const authDetails = await loadOAuthConf();
17
17
 
18
18
  Logger.println('clever-tools ' + colors.green(pkg.version));
19
19
  Logger.println('Node.js ' + colors.green(process.version));
@@ -28,12 +28,30 @@ async function diag () {
28
28
  Logger.println('Linux ' + colors.green(linuxInfos));
29
29
  }
30
30
 
31
+ Logger.println('Shell ' + colors.green(process.env.SHELL));
32
+
31
33
  const isPackaged = (process.pkg != null);
32
34
  Logger.println('Packaged ' + colors.green(isPackaged));
33
35
  Logger.println('Exec path ' + colors.green(process.execPath));
34
-
35
- Logger.println('User id ' + colors.green(userId || 'Not connected'));
36
- Logger.println('oAuth token ' + colors.green(oauthToken));
36
+ Logger.println('Config file ' + colors.green(conf.CONFIGURATION_FILE));
37
+ Logger.println('Auth source ' + colors.green(authDetails.source));
38
+
39
+ const oauthToken = (authDetails.token != null)
40
+ ? colors.green(authDetails.token)
41
+ : colors.red('(none)');
42
+ Logger.println('oAuth token ' + oauthToken);
43
+
44
+ if (authDetails.token != null) {
45
+ if (userId != null) {
46
+ Logger.println('User ID ' + colors.green(userId));
47
+ }
48
+ else {
49
+ Logger.println('User ID ' + colors.red('Authentication failed'));
50
+ }
51
+ }
52
+ else {
53
+ Logger.println('User ID ' + colors.red('Not connected'));
54
+ }
37
55
  }
38
56
 
39
57
  module.exports = { diag };
@@ -1,20 +1,31 @@
1
1
  'use strict';
2
2
 
3
3
  const AppConfig = require('../models/app_configuration.js');
4
- const Log = require('../models/log.js');
4
+ const LogV2 = require('../models/log.js');
5
+ const Log = require('../models/log-v4.js');
5
6
  const Logger = require('../logger.js');
7
+ const { Deferred } = require('../models/utils.js');
6
8
 
7
9
  async function appLogs (params) {
8
10
  const { alias, after: since, before: until, search, 'deployment-id': deploymentId } = params.options;
9
- const { addon: addonId } = params.options;
10
11
 
11
12
  // ignore --search ""
12
13
  const filter = (search !== '') ? search : null;
13
- const appAddonId = addonId || await AppConfig.getAppDetails({ alias }).then(({ appId }) => appId);
14
+
15
+ const { appId, ownerId } = await AppConfig.getAppDetails({ alias });
14
16
 
15
17
  Logger.println('Waiting for application logs…');
16
18
 
17
- return Log.displayLogs({ appAddonId, since, until, filter, deploymentId });
19
+ // TODO: drop when addons are migrated to the v4 API
20
+ if (params.addon) {
21
+ const { addon: addonId } = params.options;
22
+ const appAddonId = addonId || await AppConfig.getAppDetails({ alias }).then(({ appId }) => appId);
23
+ return LogV2.displayLogs({ appAddonId, since, until, filter, deploymentId });
24
+ }
25
+
26
+ const deferred = new Deferred();
27
+ await Log.displayLogs({ ownerId, appId, since, until, filter, deploymentId, deferred });
28
+ return deferred.promise;
18
29
  }
19
30
 
20
31
  module.exports = { appLogs };
@@ -5,7 +5,7 @@ const colors = require('colors/safe');
5
5
  const AppConfig = require('../models/app_configuration.js');
6
6
  const Application = require('../models/application.js');
7
7
  const git = require('../models/git.js');
8
- const Log = require('../models/log.js');
8
+ const Log = require('../models/log-v4.js');
9
9
  const Logger = require('../logger.js');
10
10
 
11
11
  // Once the API call to redeploy() has been triggerred successfully,
@@ -24,9 +24,19 @@ async function restart (params) {
24
24
  Logger.println(`Restarting ${appName} on commit ${colors.green(commitId)}${cacheSuffix}`);
25
25
  }
26
26
 
27
+ // This should be handled by the API when a deployment ID is set but we'll do this for now
28
+ const redeployDate = new Date();
29
+
27
30
  const redeploy = await Application.redeploy(ownerId, appId, fullCommitId, withoutCache);
28
31
 
29
- return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, deploymentId: redeploy.deploymentId, quiet, follow });
32
+ return Log.watchDeploymentAndDisplayLogs({
33
+ ownerId,
34
+ appId,
35
+ deploymentId: redeploy.deploymentId,
36
+ quiet,
37
+ follow,
38
+ redeployDate,
39
+ });
30
40
  }
31
41
 
32
42
  module.exports = { restart };
@@ -0,0 +1,23 @@
1
+ const cliparse = require('cliparse');
2
+
3
+ function getOutputFormatOption (formats = []) {
4
+ const availableFormats = ['human', 'json', ...formats];
5
+ return cliparse.option('format', {
6
+ aliases: ['F'],
7
+ metavar: 'format',
8
+ parser: (format) => {
9
+ return availableFormats.includes(format)
10
+ ? cliparse.parsers.success(format)
11
+ : cliparse.parsers.error('The output format must be one of ' + availableFormats.join(', '));
12
+ },
13
+ default: 'human',
14
+ description: `Output format (${availableFormats.join(', ')})`,
15
+ complete () {
16
+ return cliparse.autocomplete.words(availableFormats);
17
+ },
18
+ });
19
+ }
20
+
21
+ module.exports = {
22
+ getOutputFormatOption,
23
+ };
@@ -2,12 +2,10 @@
2
2
 
3
3
  const clfDate = require('clf-date');
4
4
 
5
- function listAvailableFormats () {
6
- return ['simple', 'extended', 'clf', 'json'];
7
- }
8
-
9
5
  function getFormatter (format, isAddon) {
10
6
  switch (format.toLowerCase()) {
7
+ // "simple" is the legacy default, "human" is the new one
8
+ case 'human':
11
9
  case 'simple':
12
10
  return isAddon ? formatSimpleAddon : formatSimple;
13
11
  case 'extended':
@@ -53,4 +51,4 @@ function formatCLFAddon (l) {
53
51
  return `${l.ipS} - - [${clfDate(new Date(l.t))}] "- - -" - ${l.bOut}`;
54
52
  }
55
53
 
56
- module.exports = { listAvailableFormats, getFormatter };
54
+ module.exports = { getFormatter };
@@ -2,8 +2,13 @@
2
2
 
3
3
  const application = require('@clevercloud/client/cjs/api/v2/application.js');
4
4
  const autocomplete = require('cliparse').autocomplete;
5
- const colors = require('colors/safe');
6
- const { get: getAddon, getAll: getAllAddons, remove: removeAddon, create: createAddon, preorder: preorderAddon, update: updateAddon } = require('@clevercloud/client/cjs/api/v2/addon.js');
5
+ const {
6
+ get: getAddon,
7
+ getAll: getAllAddons,
8
+ remove: removeAddon,
9
+ create: createAddon,
10
+ update: updateAddon,
11
+ } = require('@clevercloud/client/cjs/api/v2/addon.js');
7
12
  const { getAllAddonProviders } = require('@clevercloud/client/cjs/api/v2/product.js');
8
13
  const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
9
14
  const { getAddonProvider } = require('@clevercloud/client/cjs/api/v4/addon-providers.js');
@@ -11,6 +16,7 @@ const { getAddonProvider } = require('@clevercloud/client/cjs/api/v4/addon-provi
11
16
  const Interact = require('./interact.js');
12
17
  const Logger = require('../logger.js');
13
18
  const { sendToApi } = require('../models/send-to-api.js');
19
+ const { resolveOwnerId } = require('./ids-resolver.js');
14
20
 
15
21
  function listProviders () {
16
22
  return getAllAddonProviders({}).then(sendToApi);
@@ -167,19 +173,6 @@ async function create ({ ownerId, name, providerName, planName, region, skipConf
167
173
  options: createOptions,
168
174
  };
169
175
 
170
- const result = await preorderAddon({ id: ownerId }, addonToCreate).then(sendToApi);
171
-
172
- if (result.totalTTC > 0 && !skipConfirmation) {
173
- result.lines.forEach(({ description, VAT, price }) => Logger.println(`${description}\tVAT: ${VAT}%\tPrice: ${price}€`));
174
- Logger.println(`Total (without taxes): ${result.totalHT}€`);
175
- Logger.println(colors.bold(`Total (with taxes): ${result.totalTTC}€`));
176
-
177
- await Interact.confirm(
178
- `You're about to pay ${result.totalTTC}€, confirm? (yes or no) `,
179
- 'No confirmation, aborting addon creation',
180
- );
181
- }
182
-
183
176
  return createAddon({ id: ownerId }, addonToCreate).then(sendToApi);
184
177
  }
185
178
 
@@ -255,6 +248,20 @@ async function findById (addonId) {
255
248
  throw new Error(`Could not find add-on with ID: ${addonId}`);
256
249
  }
257
250
 
251
+ async function findOwnerId (org, addonId) {
252
+
253
+ if (org != null && org.orga_id != null) {
254
+ return org.orga_id;
255
+ }
256
+
257
+ const ownerId = await resolveOwnerId(addonId);
258
+ if (ownerId != null) {
259
+ return ownerId;
260
+ }
261
+
262
+ throw new Error(`Add-on ${addonId} does not exist`);
263
+ }
264
+
258
265
  function parseAddonOptions (options) {
259
266
  if (options == null) {
260
267
  return {};
@@ -288,6 +295,7 @@ module.exports = {
288
295
  create,
289
296
  delete: deleteAddon,
290
297
  findById,
298
+ findOwnerId,
291
299
  getProvider,
292
300
  getProviderInfos,
293
301
  link,
@@ -10,63 +10,42 @@ const xdg = require('xdg');
10
10
  const Logger = require('../logger.js');
11
11
  const env = commonEnv(Logger);
12
12
 
13
- function getConfigDir () {
14
- if (process.platform === 'win32') {
15
- return path.resolve(process.env.APPDATA, 'clever-cloud');
16
- }
17
- else {
18
- return xdg.basedir.configPath('clever-cloud');
19
- }
20
- }
13
+ const CONFIG_FILES = {
14
+ MAIN: 'clever-tools.json',
15
+ IDS_CACHE: 'ids-cache.json',
16
+ };
21
17
 
22
- function getConfigPath () {
23
- const configDir = getConfigDir();
24
- return path.resolve(configDir, 'clever-tools.json');
25
- }
26
-
27
- async function isFile (path) {
28
- try {
29
- const pathStat = await fs.stat(path);
30
- return pathStat.isFile();
31
- }
32
- catch (e) {
33
- return false;
34
- }
35
- }
36
-
37
- async function maybeMigrateFromLegacyConfigurationPath () {
38
- // This used to be a file
39
- const configDir = getConfigDir();
40
- const legacyConfig = await isFile(configDir);
41
- // If it is still a file, we replace it with a dir and move it inside
42
- if (legacyConfig) {
43
- const tmpConfigFile = `${configDir}.tmp`;
44
- const configFile = getConfigPath();
45
-
46
- // Rename so that we can create the directory
47
- await fs.rename(configDir, tmpConfigFile);
48
- await mkdirp(configDir, { mode: 0o700 });
49
- await fs.rename(tmpConfigFile, configFile);
50
- }
18
+ function getConfigPath (configFile) {
19
+ const configDir = (process.platform === 'win32')
20
+ ? path.resolve(process.env.APPDATA, 'clever-cloud')
21
+ : xdg.basedir.configPath('clever-cloud');
22
+ return path.resolve(configDir, configFile);
51
23
  }
52
24
 
53
25
  async function loadOAuthConf () {
54
26
  Logger.debug('Load configuration from environment variables');
55
27
  if (process.env.CLEVER_TOKEN != null && process.env.CLEVER_SECRET != null) {
56
28
  return {
29
+ source: 'environment variables',
57
30
  token: process.env.CLEVER_TOKEN,
58
31
  secret: process.env.CLEVER_SECRET,
59
32
  };
60
33
  }
61
34
  Logger.debug('Load configuration from ' + conf.CONFIGURATION_FILE);
62
- await maybeMigrateFromLegacyConfigurationPath();
63
35
  try {
64
36
  const rawFile = await fs.readFile(conf.CONFIGURATION_FILE);
65
- return JSON.parse(rawFile);
37
+ const { token, secret } = JSON.parse(rawFile);
38
+ return {
39
+ source: 'configuration file',
40
+ token,
41
+ secret,
42
+ };
66
43
  }
67
44
  catch (error) {
68
45
  Logger.info(`Cannot load configuration from ${conf.CONFIGURATION_FILE}\n${error.message}`);
69
- return {};
46
+ return {
47
+ source: 'none',
48
+ };
70
49
  }
71
50
  }
72
51
 
@@ -82,6 +61,32 @@ async function writeOAuthConf (oauthData) {
82
61
  }
83
62
  }
84
63
 
64
+ async function loadIdsCache () {
65
+ const cachePath = getConfigPath(CONFIG_FILES.IDS_CACHE);
66
+ try {
67
+ const rawFile = await fs.readFile(cachePath);
68
+ return JSON.parse(rawFile);
69
+ }
70
+ catch (error) {
71
+ Logger.info(`Cannot load IDs cache from ${cachePath}\n${error.message}`);
72
+ return {
73
+ owners: {},
74
+ addons: {},
75
+ };
76
+ }
77
+ }
78
+
79
+ async function writeIdsCache (ids) {
80
+ const cachePath = getConfigPath(CONFIG_FILES.IDS_CACHE);
81
+ const idsJson = JSON.stringify(ids);
82
+ try {
83
+ await fs.writeFile(cachePath, idsJson);
84
+ }
85
+ catch (error) {
86
+ throw new Error(`Cannot write IDs cache to ${cachePath}\n${error.message}`);
87
+ }
88
+ }
89
+
85
90
  const conf = env.getOrElseAll({
86
91
  API_HOST: 'https://api.clever-cloud.com',
87
92
  // API_HOST: 'https://ccapi-preprod.cleverapps.io',
@@ -94,7 +99,7 @@ const conf = env.getOrElseAll({
94
99
  OAUTH_CONSUMER_SECRET: 'MgVMqTr6fWlf2M0tkC2MXOnhfqBWDT',
95
100
  SSH_GATEWAY: 'ssh@sshgateway-clevercloud-customers.services.clever-cloud.com',
96
101
 
97
- CONFIGURATION_FILE: getConfigPath(),
102
+ CONFIGURATION_FILE: getConfigPath(CONFIG_FILES.MAIN),
98
103
  CONSOLE_TOKEN_URL: 'https://console.clever-cloud.com/cli-oauth',
99
104
  // CONSOLE_TOKEN_URL: 'https://next-console.cleverapps.io/cli-oauth',
100
105
 
@@ -102,4 +107,4 @@ const conf = env.getOrElseAll({
102
107
  APP_CONFIGURATION_FILE: path.resolve('.', '.clever.json'),
103
108
  });
104
109
 
105
- module.exports = { conf, loadOAuthConf, writeOAuthConf };
110
+ module.exports = { conf, loadOAuthConf, writeOAuthConf, loadIdsCache, writeIdsCache };
@@ -42,7 +42,7 @@ const membersTableColumnLengths = [
42
42
  48, /* id length */
43
43
  12, /* type length */
44
44
  48, /* label length */
45
- 24, /* domain-name length */
45
+ 110, /* domain-name length */
46
46
  ];
47
47
  const formatMembersTable = formatNgTable(membersTableColumnLengths);
48
48
  async function formatMembersLine (member, showAliases = false) {
@@ -71,23 +71,23 @@ async function printMembersTableHeader (naturalName = false) {
71
71
 
72
72
  const peersTableColumnLengths = [
73
73
  45, /* id length */
74
+ 15, /* ip */
75
+ 36, /* hostname */
74
76
  12, /* type length */
75
77
  14, /* endpoint type length */
76
- 48, /* label length */
77
- 24, /* hostname */
78
- 15, /* ip */
78
+ 36, /* label length */
79
79
  ];
80
80
  const formatPeersTable = formatNgTable(peersTableColumnLengths);
81
81
  function formatPeersLine (peer) {
82
- const ip = (peer.endpoint.type === 'ServerEndpoint') ? peer.endpoint.ng_term.ip : peer.endpoint.ng_ip;
82
+ const ip = (peer.endpoint.type === 'ServerEndpoint') ? peer.endpoint.ng_term.host : peer.endpoint.ng_ip;
83
83
  return formatPeersTable([
84
84
  [
85
85
  Formatter.formatId(peer.id),
86
+ Formatter.formatIp(ip),
87
+ Formatter.formatString(peer.hostname, false),
86
88
  Formatter.formatString(peer.type, false),
87
89
  Formatter.formatString(peer.endpoint.type, false),
88
90
  Formatter.formatString(peer.label, false),
89
- Formatter.formatString(peer.hostname, false),
90
- Formatter.formatIp(ip),
91
91
  ],
92
92
  ]);
93
93
  };
@@ -95,11 +95,11 @@ function printPeersTableHeader () {
95
95
  Logger.println(colors.bold(formatPeersTable([
96
96
  [
97
97
  'Peer ID',
98
+ 'IP Address',
99
+ 'Hostname',
98
100
  'Peer Type',
99
101
  'Endpoint Type',
100
102
  'Label',
101
- 'Hostname',
102
- 'IP Address',
103
103
  ],
104
104
  ])));
105
105
  printSeparator(peersTableColumnLengths);
@@ -0,0 +1,110 @@
1
+ const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
2
+ const { sendToApi } = require('./send-to-api.js');
3
+ const { loadIdsCache, writeIdsCache } = require('./configuration.js');
4
+
5
+ /*
6
+ This system uses a simplified representation of the summary to expose IDs links:
7
+
8
+ * app ID => owner ID
9
+ * add-on ID => owner ID
10
+ * real add-on ID => owner ID
11
+ * add-on ID => real add-on ID
12
+ * real add-on ID => add-on ID
13
+
14
+ {
15
+ owners: {
16
+ [appid]: [ownerId],
17
+ [addonId]: [ownerId],
18
+ [realId]: [ownerId],
19
+ },
20
+ addons: {
21
+ [addonId]: { realId: [realId], addonId: [addonId] },
22
+ [realId]: { realId: [realId], addonId: [addonId] },
23
+ },
24
+ }
25
+ */
26
+
27
+ async function resolveOwnerId (id) {
28
+ return getIdFromCacheOrSummary((ids) => ids.owners[id]);
29
+ }
30
+
31
+ async function resolveAddonId (id) {
32
+
33
+ const addonId = await getIdFromCacheOrSummary((ids) => {
34
+ return (ids.addons[id] != null) ? ids.addons[id].addonId : null;
35
+ });
36
+
37
+ if (addonId != null) {
38
+ return addonId;
39
+ }
40
+
41
+ throw new Error(`Add-on ${id} does not exist`);
42
+ }
43
+
44
+ async function resolveRealId (id) {
45
+
46
+ const realId = await getIdFromCacheOrSummary((ids) => {
47
+ return (ids.addons[id] != null) ? ids.addons[id].realId : null;
48
+ });
49
+
50
+ if (realId != null) {
51
+ return realId;
52
+ }
53
+
54
+ throw new Error(`Add-on ${id} does not exist foo`);
55
+ }
56
+
57
+ async function getIdFromCacheOrSummary (callback) {
58
+
59
+ const idsFromCache = await loadIdsCache();
60
+ const idFromCache = callback(idsFromCache);
61
+ if (idFromCache != null) {
62
+ return idFromCache;
63
+ }
64
+
65
+ const idsFromSummary = await getIdsFromSummary();
66
+ await writeIdsCache(idsFromSummary);
67
+
68
+ const idFromSummary = callback(idsFromSummary);
69
+ if (idFromSummary != null) {
70
+ return idFromSummary;
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ async function getIdsFromSummary () {
77
+
78
+ const ids = {
79
+ owners: {},
80
+ addons: {},
81
+ };
82
+
83
+ const summary = await getSummary().then(sendToApi);
84
+
85
+ const owners = [
86
+ summary.user,
87
+ ...summary.organisations,
88
+ ];
89
+
90
+ for (const owner of owners) {
91
+ for (const app of owner.applications) {
92
+ ids.owners[app.id] = owner.id;
93
+ }
94
+ for (const addon of owner.addons) {
95
+ ids.owners[addon.id] = owner.id;
96
+ ids.owners[addon.realId] = owner.id;
97
+ const addonIds = { addonId: addon.id, realId: addon.realId };
98
+ ids.addons[addon.id] = addonIds;
99
+ ids.addons[addon.realId] = addonIds;
100
+ }
101
+ }
102
+
103
+ return ids;
104
+ }
105
+
106
+ module.exports = {
107
+ resolveOwnerId,
108
+ resolveAddonId,
109
+ resolveRealId,
110
+ };
@@ -0,0 +1,136 @@
1
+ const { getHostAndTokens } = require('./send-to-api.js');
2
+ const colors = require('colors/safe');
3
+ const { Deferred } = require('./utils.js');
4
+ const Logger = require('../logger.js');
5
+ const { waitForDeploymentEnd, waitForDeploymentStart } = require('./deployments.js');
6
+ const { ApplicationLogStream } = require('@clevercloud/client/cjs/streams/application-logs.js');
7
+
8
+ // 2000 logs per 100ms maximum
9
+ const THROTTLE_ELEMENTS = 2000;
10
+ const THROTTLE_PER_IN_MILLISECONDS = 100;
11
+
12
+ async function displayLogs (params) {
13
+
14
+ const deferred = params.deferred || new Deferred();
15
+ const { apiHost, tokens } = await getHostAndTokens();
16
+ const { ownerId, appId, filter, since, until, deploymentId } = params;
17
+
18
+ const logStream = new ApplicationLogStream({
19
+ apiHost,
20
+ tokens,
21
+ ownerId,
22
+ appId,
23
+ since,
24
+ until,
25
+ deploymentId,
26
+ filter,
27
+ throttleElements: THROTTLE_ELEMENTS,
28
+ throttlePerInMilliseconds: THROTTLE_PER_IN_MILLISECONDS,
29
+ });
30
+
31
+ // Properly close the stream
32
+ process.once('SIGINT', (signal) => logStream.close(signal));
33
+
34
+ logStream
35
+ .on('open', (event) => {
36
+ Logger.debug(`stream opened! ${JSON.stringify({ appId, filter, deploymentId })}`);
37
+ })
38
+ .on('error', (event) => {
39
+ Logger.error(`an error occured: ${event.detail}`);
40
+ })
41
+ .onLog((log) => {
42
+ Logger.println(formatLogLine(log));
43
+ });
44
+
45
+ // start() is blocking until end of stream
46
+ logStream.start()
47
+ .then((reason) => deferred.resolve())
48
+ .catch((error) => deferred.reject(error));
49
+
50
+ return logStream;
51
+ }
52
+
53
+ async function watchDeploymentAndDisplayLogs (options) {
54
+
55
+ const {
56
+ ownerId,
57
+ appId,
58
+ deploymentId,
59
+ commitId,
60
+ knownDeployments,
61
+ quiet,
62
+ follow,
63
+ redeployDate,
64
+ } = options;
65
+
66
+ Logger.println('Waiting for deployment to start…');
67
+ const deployment = await waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments });
68
+ Logger.println(colors.bold.blue(`Deployment started (${deployment.uuid})`));
69
+
70
+ const deferred = new Deferred();
71
+ let logsStream;
72
+
73
+ if (!quiet) {
74
+ // About the deferred…
75
+ // If displayLogs() throws an error,
76
+ // the async function we're in (watchDeploymentAndDisplayLogs) will stop here and the error will be passed to the parent.
77
+ // displayLogs() defines callback listeners so if it catches error in those callbacks,
78
+ // it has no proper way to bubble up the error here.
79
+ // Using the deferred enables this.
80
+ logsStream = await displayLogs({ ownerId, appId, deploymentId: deployment.uuid, since: redeployDate, deferred });
81
+ }
82
+
83
+ Logger.println('Waiting for application logs…');
84
+
85
+ // Wait for deployment end (or an error thrown by logs with the deferred)
86
+ const deploymentEnded = await Promise.race([
87
+ waitForDeploymentEnd({ ownerId, appId, deploymentId: deployment.uuid }),
88
+ deferred.promise,
89
+ ]);
90
+
91
+ if (!quiet && !follow) {
92
+ logsStream.close(quiet ? 'quiet' : 'follow');
93
+ }
94
+
95
+ if (deploymentEnded.state === 'OK') {
96
+ Logger.println(colors.bold.green('Deployment successful'));
97
+ }
98
+ else if (deploymentEnded.state === 'CANCELLED') {
99
+ throw new Error('Deployment was cancelled. Please check the activity');
100
+ }
101
+ else {
102
+ throw new Error('Deployment failed. Please check the logs');
103
+ }
104
+ }
105
+
106
+ function formatLogLine (log) {
107
+ const { date, message } = log;
108
+ if (isDeploymentSuccessMessage(log)) {
109
+ return `${date.toISOString()}: ${colors.bold.green(message)}`;
110
+ }
111
+ else if (isDeploymentFailedMessage(log)) {
112
+ return `${date.toISOString()}: ${colors.bold.red(message)}`;
113
+ }
114
+ else if (isBuildSucessMessage(log)) {
115
+ return `${date.toISOString()}: ${colors.bold.blue(message)}`;
116
+ }
117
+ return `${date.toISOString()}: ${message}`;
118
+ }
119
+
120
+ function isCleverMessage (log) {
121
+ return log.service !== 'bas-deploy.service';
122
+ };
123
+
124
+ function isDeploymentSuccessMessage (log) {
125
+ return isCleverMessage(log) && log.message.toLowerCase().startsWith('successfully deployed in');
126
+ };
127
+
128
+ function isDeploymentFailedMessage (log) {
129
+ return isCleverMessage(log) && log.message.toLowerCase().startsWith('deploy failed in');
130
+ };
131
+
132
+ function isBuildSucessMessage (log) {
133
+ return isCleverMessage(log) && log.message.toLowerCase().startsWith('build succeeded in');
134
+ };
135
+
136
+ module.exports = { displayLogs, watchDeploymentAndDisplayLogs };
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const Logger = require('../logger.js');
4
- const { addOauthHeader } = require('@clevercloud/client/cjs/oauth.node.js');
4
+ const { addOauthHeader } = require('@clevercloud/client/cjs/oauth.js');
5
5
  const { conf, loadOAuthConf } = require('../models/configuration.js');
6
6
  const { execWarpscript } = require('@clevercloud/client/cjs/request-warp10.superagent.js');
7
7
  const { prefixUrl } = require('@clevercloud/client/cjs/prefix-url.js');
8
- const { request } = require('@clevercloud/client/cjs/request.superagent.js');
8
+ const { request } = require('@clevercloud/client/cjs/request.fetch.js');
9
9
 
10
10
  async function loadTokens () {
11
11
  const tokens = await loadOAuthConf();
package/src/parsers.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  const cliparse = require('cliparse');
4
4
 
5
- const AccessLogs = require('./models/accesslogs.js');
6
5
  const Application = require('./models/application.js');
7
6
 
8
7
  function flavor (flavor) {
@@ -79,14 +78,6 @@ function commaSeparated (string) {
79
78
  return cliparse.parsers.success(string.split(','));
80
79
  }
81
80
 
82
- function accessLogsFormat (format) {
83
- const availableFormats = AccessLogs.listAvailableFormats();
84
- if (availableFormats.includes(format)) {
85
- return cliparse.parsers.success(format);
86
- }
87
- return cliparse.parsers.error('The format must be one of ' + availableFormats.join(', '));
88
- }
89
-
90
81
  function integer (string) {
91
82
  const integer = parseInt(string);
92
83
  if (isNaN(integer)) {
@@ -162,7 +153,6 @@ module.exports = {
162
153
  addonIdOrName,
163
154
  ngIdOrLabel,
164
155
  commaSeparated,
165
- accessLogsFormat,
166
156
  integer,
167
157
  tag,
168
158
  tags,