clever-tools 2.11.0 → 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,6 +1,25 @@
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)
4
23
 
5
24
  * skip preorder step on addon creation
6
25
  * add `clever addon env` command
package/bin/clever.js CHANGED
@@ -506,6 +506,7 @@ function run () {
506
506
  description: 'A WireGuard® private key',
507
507
  }),
508
508
  jsonFormat: cliparse.flag('json', { aliases: ['j'], description: 'Show result in JSON format' }),
509
+ humanJsonOutputFormat: getOutputFormatOption(),
509
510
  tag: cliparse.option('tag', {
510
511
  required: true,
511
512
  metavar: 'tag',
@@ -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],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "2.11.0",
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
  }
@@ -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,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 };
@@ -26,6 +26,7 @@ async function loadOAuthConf () {
26
26
  Logger.debug('Load configuration from environment variables');
27
27
  if (process.env.CLEVER_TOKEN != null && process.env.CLEVER_SECRET != null) {
28
28
  return {
29
+ source: 'environment variables',
29
30
  token: process.env.CLEVER_TOKEN,
30
31
  secret: process.env.CLEVER_SECRET,
31
32
  };
@@ -33,11 +34,18 @@ async function loadOAuthConf () {
33
34
  Logger.debug('Load configuration from ' + conf.CONFIGURATION_FILE);
34
35
  try {
35
36
  const rawFile = await fs.readFile(conf.CONFIGURATION_FILE);
36
- return JSON.parse(rawFile);
37
+ const { token, secret } = JSON.parse(rawFile);
38
+ return {
39
+ source: 'configuration file',
40
+ token,
41
+ secret,
42
+ };
37
43
  }
38
44
  catch (error) {
39
45
  Logger.info(`Cannot load configuration from ${conf.CONFIGURATION_FILE}\n${error.message}`);
40
- return {};
46
+ return {
47
+ source: 'none',
48
+ };
41
49
  }
42
50
  }
43
51
 
@@ -78,7 +78,7 @@ const peersTableColumnLengths = [
78
78
  36, /* label length */
79
79
  ];
80
80
  const formatPeersTable = formatNgTable(peersTableColumnLengths);
81
- function formatPeersLine(peer) {
81
+ function formatPeersLine (peer) {
82
82
  const ip = (peer.endpoint.type === 'ServerEndpoint') ? peer.endpoint.ng_term.host : peer.endpoint.ng_ip;
83
83
  return formatPeersTable([
84
84
  [
@@ -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) {