clever-tools 2.7.2 → 2.9.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,5 +1,23 @@
1
1
  # clever-tools changelog
2
2
 
3
+ ## 2.9.0 (2022-01-12)
4
+
5
+ * Add NewRelic support in `clever drains`
6
+
7
+ ## 2.8.1 (2022-01-10)
8
+
9
+ * Fix `colors` to `1.4.0`
10
+
11
+ ## 2.8.0 (2021-02-09)
12
+
13
+ * Add options support for `clever addon create` (options like encryption-at-rest)
14
+ * Display available versions in `clever addon providers show <provider>`
15
+ * Display available options in `clever addon providers show <provider>`
16
+ * Fix `clever deploy` which was displaying logs from runtime instances
17
+ * Fix `clever accesslogs` so it doesn't fail when source object is undefined
18
+ * Prevent race conditions on parallel CI configs with `clever deploy` and git add remote
19
+ * Format `.clever.json` (Mickael Chanrion)
20
+
3
21
  ## 2.7.2 (2020-10-09)
4
22
 
5
23
  * addon: fix create relying on default region
package/README.md CHANGED
@@ -26,6 +26,14 @@ npm install -g clever-tools@beta
26
26
 
27
27
  #### Debian/Ubuntu (.deb)
28
28
 
29
+ ---
30
+
31
+ ##### Warning
32
+
33
+ Bintray is ending on May 1st 2021, we will migrate hosting as soon as possible. Please refer to the dedicated [issue](https://github.com/CleverCloud/clever-tools/issues/454) to track the migration.
34
+
35
+ ---
36
+
29
37
  If you are using a GNU/Linux distribution that uses `.deb` packages like Debian or Ubuntu, you can run:
30
38
 
31
39
  ```sh
@@ -46,6 +54,14 @@ echo "deb https://dl.bintray.com/clevercloud/deb unstable beta" | tee -a /etc/ap
46
54
 
47
55
  #### CentOS/Fedora (.rpm)
48
56
 
57
+ ---
58
+
59
+ ##### Warning
60
+
61
+ Bintray is ending on May 1st 2021, we will migrate hosting as soon as possible. Please refer to the dedicated [issue](https://github.com/CleverCloud/clever-tools/issues/454) to track the migration.
62
+
63
+ ---
64
+
49
65
  If you are using a GNU/Linux distribution that uses `.rpm` packages like CentOS or Fedora, you can run:
50
66
 
51
67
  ```sh
@@ -131,7 +147,15 @@ NOTES:
131
147
 
132
148
  #### Using chocolatey
133
149
 
134
- If you are using Windows and you have [chocolatey](https://chocolatey.org) installed, you can run:
150
+ ---
151
+
152
+ ##### Warning
153
+
154
+ Bintray is ending on May 1st 2021, we will migrate hosting as soon as possible. Please refer to the dedicated [issue](https://github.com/CleverCloud/clever-tools/issues/454) to track the migration.
155
+
156
+ ---
157
+
158
+ If you are using Windows and you have [chocolatey](https://chocolatey.org) installed, you can run:
135
159
 
136
160
  ```bash
137
161
  choco sources add -n=clevercloud -s='https://api.bintray.com/nuget/clevercloud/nupkg'
@@ -160,6 +184,14 @@ docker pull clevercloud/clever-tools
160
184
  docker run --rm clever-tools <command>
161
185
  ```
162
186
 
187
+ #### Dockerfile
188
+
189
+ In your `Dockerfile` you can copy the clever-tools CLI from the image itself with a simple one liner:
190
+
191
+ ```Dockerfile
192
+ COPY --from=clevercloud/clever-tools /bin/clever /usr/local/bin/clever
193
+ ```
194
+
163
195
  ### Via Nix
164
196
 
165
197
  If you are using Nix, you will find a Nix derivation on Fretlink's Github repository: https://github.com/fretlink/clever-tools-nix
@@ -168,7 +200,7 @@ If you are using Nix, you will find a Nix derivation on Fretlink's Github reposi
168
200
 
169
201
  The clever-tools CLI comes with a comprehensive auto-completion system.
170
202
  Some installation methods like `.deb` packages, `.rpm` packages or brew will try to enable it automatically.
171
- If it does not work, try this for bash:
203
+ If it does not work, try this for bash:
172
204
 
173
205
  ```bash
174
206
  clever --bash-autocomplete-script $(which clever) | sudo tee /usr/share/bash-completion/completions/clever
@@ -288,6 +320,7 @@ Where `DRAIN-TYPE` is one of:
288
320
  - `HTTP`: for TCP syslog endpoint (note that this endpoint has optional username/passwordparameters as HTTP Basic Authentication);
289
321
  - `ElasticSearch`: for ElasticSearch endpoint (note that this endpoint requires username/password parameters as HTTP Basic Authentication);
290
322
  - `DatadogHTTP`: for Datadog endpoint (note that this endpoint needs your Datadog API Key).
323
+ - `NewRelicHTTP`: for NewRelic endpoint (note that this endpoint needs your NewRelic API Key).
291
324
 
292
325
  #### ElasticSearch logs drains
293
326
 
@@ -305,6 +338,16 @@ clever drain create DatadogHTTP "https://http-intake.logs.datadoghq.com/v1/input
305
338
 
306
339
  Please note that the `host` query parameter is not mandatory: in the Datadog pipeline configuration, you can map `@source_host` which is the host provided by Clever Cloud in logs as `host` property.
307
340
 
341
+ #### NewRelic logs drains
342
+
343
+ NewRelic has two zones, EU and US. An account on one zone is not available on the other, make sure to target the good EU or US intake endpoint.
344
+
345
+ To create a [NewRelic](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/) drain, you just need to use:
346
+
347
+ ```sh
348
+ clever drain create NewRelicHTTP "https://log-api.eu.newrelic.com/log/v1" --api-key <API_KEY>
349
+ ```
350
+
308
351
  ### Display help
309
352
 
310
353
  You can display help about each command with `clever help`.
package/bin/clever.js CHANGED
@@ -297,6 +297,14 @@ function run () {
297
297
  description: 'Region to provision the addon in, depends on the provider',
298
298
  complete: Addon('completeRegion'),
299
299
  }),
300
+ addonVersion: cliparse.option('addon-version', {
301
+ metavar: 'addon-version',
302
+ description: 'The version to use for the add-on',
303
+ }),
304
+ addonOptions: cliparse.option('option', {
305
+ metavar: 'option',
306
+ description: 'Option to enable for the add-on. Multiple --option argument can be passed to enable multiple options',
307
+ }),
300
308
  region: cliparse.option('region', {
301
309
  aliases: ['r'],
302
310
  default: 'par',
@@ -379,7 +387,7 @@ function run () {
379
387
  const addonCreateCommand = cliparse.command('create', {
380
388
  description: 'Create an addon',
381
389
  args: [args.addonProvider, args.addonName],
382
- options: [opts.linkAddon, opts.confirmAddonCreation, opts.addonPlan, opts.addonRegion],
390
+ options: [opts.linkAddon, opts.confirmAddonCreation, opts.addonPlan, opts.addonRegion, opts.addonVersion, opts.addonOptions],
383
391
  }, addon('create'));
384
392
  const addonDeleteCommand = cliparse.command('delete', {
385
393
  description: 'Delete an addon',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "2.7.2",
3
+ "version": "2.9.0",
4
4
  "description": "Command Line Interface for Clever Cloud.",
5
5
  "main": "bin/clever.js",
6
6
  "keywords": [
@@ -25,45 +25,45 @@
25
25
  "scripts/*.sh"
26
26
  ],
27
27
  "dependencies": {
28
- "@clevercloud/client": "^6.0.0",
28
+ "@clevercloud/client": "^7.4.0",
29
29
  "clf-date": "^0.2.0",
30
- "cliparse": "^0.3.1",
31
- "colors": "^1.4.0",
30
+ "cliparse": "^0.3.3",
31
+ "colors": "1.4.0",
32
32
  "common-env": "^6.4.0",
33
- "eventsource": "^1.0.7",
34
- "isomorphic-git": "1.7.4",
33
+ "eventsource": "^1.1.0",
34
+ "isomorphic-git": "^1.8.2",
35
35
  "linux-release-info": "^3.0.0",
36
- "lodash": "^4.17.20",
36
+ "lodash": "^4.17.21",
37
37
  "mkdirp": "^1.0.4",
38
- "moment": "^2.27.0",
38
+ "moment": "^2.29.1",
39
39
  "opn": "^6.0.0",
40
- "slugify": "^1.4.5",
41
- "string-length": "^3.1.0",
42
- "superagent": "^6.0.0",
40
+ "slugify": "^1.5.3",
41
+ "string-length": "^4.0.2",
42
+ "superagent": "^6.1.0",
43
43
  "text-table": "^0.2.0",
44
- "update-notifier": "^4.1.1",
45
- "ws": "^7.3.1",
44
+ "update-notifier": "^5.1.0",
45
+ "ws": "^7.4.6",
46
46
  "xdg": "^0.1.1"
47
47
  },
48
48
  "devDependencies": {
49
- "aws-sdk": "^2.735.0",
50
- "chai": "^4.2.0",
51
- "del": "^5.1.0",
52
- "eslint": "^7.7.0",
53
- "eslint-config-standard": "^14.1.1",
54
- "eslint-plugin-import": "^2.22.0",
49
+ "aws-sdk": "^2.919.0",
50
+ "chai": "^4.3.4",
51
+ "del": "^6.0.0",
52
+ "eslint": "^7.27.0",
53
+ "eslint-config-standard": "^16.0.3",
54
+ "eslint-plugin-import": "^2.23.4",
55
55
  "eslint-plugin-node": "^11.1.0",
56
- "eslint-plugin-promise": "^4.2.1",
57
- "eslint-plugin-standard": "^4.0.1",
58
- "fs-extra": "^9.0.1",
59
- "glob": "^7.1.6",
60
- "grunt": "^1.2.1",
61
- "grunt-cli": "^1.3.2",
56
+ "eslint-plugin-promise": "^5.1.0",
57
+ "eslint-plugin-standard": "^5.0.0",
58
+ "fs-extra": "^10.0.0",
59
+ "glob": "^7.1.7",
60
+ "grunt": "^1.4.1",
61
+ "grunt-cli": "^1.4.3",
62
62
  "grunt-http": "^2.3.3",
63
63
  "grunt-mocha-test": "^0.13.3",
64
- "mocha": "^8.1.1",
65
- "pkg": "^4.4.9",
66
- "semver": "^7.3.2"
64
+ "mocha": "^8.4.0",
65
+ "pkg": "^5.2.1",
66
+ "semver": "^7.3.5"
67
67
  },
68
68
  "scripts": {
69
69
  "pretest": "npm run lint",
@@ -83,5 +83,9 @@
83
83
  "scripts": [
84
84
  "src/**/*.js"
85
85
  ]
86
+ },
87
+ "volta": {
88
+ "node": "12.22.8",
89
+ "npm": "6.14.15"
86
90
  }
87
91
  }
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { getAccessLogsFromWarp10InBatches, getContinuousAccessLogsFromWarp10 } = require('@clevercloud/client/cjs/access-logs.js');
4
- const { getWarp10AccessLogsToken } = require('@clevercloud/client/cjs/api/warp-10.js');
4
+ const { getWarp10AccessLogsToken } = require('@clevercloud/client/cjs/api/v2/warp-10.js');
5
5
  const { ONE_HOUR_MICROS, ONE_SECOND_MICROS, toMicroTimestamp } = require('@clevercloud/client/cjs/utils/date.js');
6
6
 
7
7
  const Addon = require('../models/addon.js');
@@ -9,6 +9,7 @@ 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
13
 
13
14
  async function list (params) {
14
15
  const { org: orgaIdOrName } = params.options;
@@ -30,6 +31,8 @@ async function list (params) {
30
31
  async function create (params) {
31
32
  const [providerName, name] = params.args;
32
33
  const { link: linkedAppAlias, plan: planName, region, yes: skipConfirmation, org: orgaIdOrName } = params.options;
34
+ const version = params.options['addon-version'];
35
+ const addonOptions = parseAddonOptions(params.options.option);
33
36
 
34
37
  const ownerId = (orgaIdOrName != null)
35
38
  ? await Organisation.getId(orgaIdOrName)
@@ -47,12 +50,14 @@ async function create (params) {
47
50
  planName,
48
51
  region,
49
52
  skipConfirmation,
53
+ version,
54
+ addonOptions,
50
55
  });
51
56
  await Addon.link(linkedAppData.ownerId, linkedAppData.appId, { addon_id: newAddon.id });
52
57
  Logger.println(`Addon ${name} (id: ${newAddon.id}) successfully created and linked to the application`);
53
58
  }
54
59
  else {
55
- const newAddon = await Addon.create({ ownerId, name, providerName, planName, region, skipConfirmation });
60
+ const newAddon = await Addon.create({ ownerId, name, providerName, planName, region, skipConfirmation, version, addonOptions });
56
61
  Logger.println(`Addon ${name} (id: ${newAddon.id}) successfully created`);
57
62
  }
58
63
  }
@@ -85,7 +90,7 @@ async function listProviders () {
85
90
  return [
86
91
  colors.bold(provider.id),
87
92
  provider.name,
88
- provider.shortDesc,
93
+ provider.shortDesc || '',
89
94
  ];
90
95
  });
91
96
  Logger.println(formatTable(formattedProviders));
@@ -95,6 +100,8 @@ async function showProvider (params) {
95
100
  const [providerName] = params.args;
96
101
 
97
102
  const provider = await Addon.getProvider(providerName);
103
+ const providerInfos = await Addon.getProviderInfos(providerName);
104
+ const providerPlans = provider.plans.sort((a, b) => a.price - b.price);
98
105
 
99
106
  Logger.println(colors.bold(provider.id));
100
107
  Logger.println(`${provider.name}: ${provider.shortDesc}`);
@@ -103,11 +110,35 @@ async function showProvider (params) {
103
110
  Logger.println();
104
111
  Logger.println('Available plans');
105
112
 
106
- provider.plans.forEach((plan) => {
113
+ providerPlans.forEach((plan) => {
107
114
  Logger.println(`Plan ${colors.bold(plan.slug)}`);
108
115
  _(plan.features)
109
116
  .sortBy('name')
110
117
  .forEach(({ name, value }) => Logger.println(` ${name}: ${value}`));
118
+
119
+ if (providerInfos != null) {
120
+ const planType = plan.features.find(({ name }) => name.toLowerCase() === 'type');
121
+ if (planType != null && planType.value.toLowerCase() === 'dedicated') {
122
+ const planVersions = Object.keys(providerInfos.dedicated);
123
+ const versions = planVersions.map((version) => {
124
+ if (version === providerInfos.defaultDedicatedVersion) {
125
+ return `${version} (default)`;
126
+ }
127
+ else {
128
+ return version;
129
+ }
130
+ });
131
+ Logger.println(` Available versions: ${versions.join(', ')}`);
132
+
133
+ planVersions.forEach((version) => {
134
+ const features = providerInfos.dedicated[version].features;
135
+ Logger.println(` Options for version ${version}:`);
136
+ features.forEach(({ name, enabled }) => {
137
+ Logger.println(` ${name}: default=${enabled}`);
138
+ });
139
+ });
140
+ }
141
+ }
111
142
  });
112
143
  }
113
144
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const AppConfig = require('../models/app_configuration.js');
4
4
  const Logger = require('../logger.js');
5
- const { getAllDeployments, cancelDeployment } = require('@clevercloud/client/cjs/api/application.js');
5
+ const { getAllDeployments, cancelDeployment } = require('@clevercloud/client/cjs/api/v2/application.js');
6
6
  const { sendToApi } = require('../models/send-to-api.js');
7
7
 
8
8
  async function cancelDeploy (params) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const application = require('@clevercloud/client/cjs/api/application.js');
3
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
4
4
 
5
5
  const AppConfig = require('../models/app_configuration.js');
6
6
  const Application = require('../models/application.js');
@@ -7,7 +7,7 @@ const Application = require('../models/application.js');
7
7
  const git = require('../models/git.js');
8
8
  const Log = require('../models/log.js');
9
9
  const Logger = require('../logger.js');
10
- const { getAllDeployments } = require('@clevercloud/client/cjs/api/application.js');
10
+ const { getAllDeployments } = require('@clevercloud/client/cjs/api/v2/application.js');
11
11
  const { sendToApi } = require('../models/send-to-api.js');
12
12
 
13
13
  // Once the API call to redeploy() has been triggered successfully,
@@ -9,7 +9,7 @@ const {
9
9
  markFavouriteDomain,
10
10
  unmarkFavouriteDomain,
11
11
  removeDomain,
12
- } = require('@clevercloud/client/cjs/api/application.js');
12
+ } = require('@clevercloud/client/cjs/api/v2/application.js');
13
13
  const { sendToApi } = require('../models/send-to-api.js');
14
14
 
15
15
  function getFavouriteDomain ({ ownerId, appId }) {
@@ -4,7 +4,7 @@ const AppConfig = require('../models/app_configuration.js');
4
4
  const { createDrainBody } = require('../models/drain.js');
5
5
  const Logger = require('../logger.js');
6
6
 
7
- const { getDrains, createDrain, deleteDrain, updateDrainState } = require('@clevercloud/client/cjs/api/log.js');
7
+ const { getDrains, createDrain, deleteDrain, updateDrainState } = require('@clevercloud/client/cjs/api/v2/log.js');
8
8
  const { sendToApi } = require('../models/send-to-api.js');
9
9
 
10
10
  // TODO: This could be useful in other commands
@@ -5,7 +5,7 @@ const Logger = require('../logger.js');
5
5
  const variables = require('../models/variables.js');
6
6
  const { sendToApi } = require('../models/send-to-api.js');
7
7
  const { toNameEqualsValueString, validateName } = require('@clevercloud/client/cjs/utils/env-vars.js');
8
- const application = require('@clevercloud/client/cjs/api/application.js');
8
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
9
9
 
10
10
  async function list (params) {
11
11
  const { alias, 'add-export': addExports } = params.options;
@@ -5,7 +5,7 @@ const colors = require('colors/safe');
5
5
  const Logger = require('../logger.js');
6
6
  const { getOwnerAndApp, getOrgaIdOrUserId } = require('../models/notification.js');
7
7
 
8
- const { getEmailhooks, createEmailhook, deleteEmailhook } = require('@clevercloud/client/cjs/api/notification.js');
8
+ const { getEmailhooks, createEmailhook, deleteEmailhook } = require('@clevercloud/client/cjs/api/v2/notification.js');
9
9
  const { sendToApi } = require('../models/send-to-api.js');
10
10
 
11
11
  function displayEmailhook (hook) {
@@ -55,6 +55,7 @@ function getEmailNotificationTargets (notifTargets) {
55
55
  if (el.toLowerCase() === 'organisation') {
56
56
  return { type: 'organisation' };
57
57
  }
58
+ return null;
58
59
  })
59
60
  .filter((e) => e != null);
60
61
  }
@@ -5,7 +5,7 @@ const Logger = require('../logger.js');
5
5
  const variables = require('../models/variables.js');
6
6
  const { sendToApi } = require('../models/send-to-api.js');
7
7
  const { toNameEqualsValueString, validateName } = require('@clevercloud/client/cjs/utils/env-vars.js');
8
- const application = require('@clevercloud/client/cjs/api/application.js');
8
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
9
9
 
10
10
  async function list (params) {
11
11
  const { alias } = params.options;
@@ -6,7 +6,7 @@ const colors = require('colors/safe');
6
6
  const AppConfig = require('../models/app_configuration.js');
7
7
  const Logger = require('../logger.js');
8
8
 
9
- const { get: getApplication, getAllInstances } = require('@clevercloud/client/cjs/api/application.js');
9
+ const { get: getApplication, getAllInstances } = require('@clevercloud/client/cjs/api/v2/application.js');
10
10
  const { sendToApi } = require('../models/send-to-api.js');
11
11
 
12
12
  function displayGroupInfo (instances, commit) {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const AppConfig = require('../models/app_configuration.js');
4
- const application = require('@clevercloud/client/cjs/api/application.js');
4
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
5
5
  const Logger = require('../logger.js');
6
6
  const { sendToApi } = require('../models/send-to-api.js');
7
7
 
@@ -7,7 +7,7 @@ const Organisation = require('../models/organisation.js');
7
7
  const { sendToApi } = require('../models/send-to-api.js');
8
8
  const Interact = require('../models/interact.js');
9
9
  const Logger = require('../logger.js');
10
- const application = require('@clevercloud/client/cjs/api/application.js');
10
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
11
11
 
12
12
  async function listNamespaces (params) {
13
13
  const namespaces = await Organisation.getNamespaces(params);
@@ -5,7 +5,7 @@ const colors = require('colors/safe');
5
5
  const Logger = require('../logger.js');
6
6
  const { getOwnerAndApp, getOrgaIdOrUserId } = require('../models/notification.js');
7
7
 
8
- const { getWebhooks, createWebhook, deleteWebhook } = require('@clevercloud/client/cjs/api/notification.js');
8
+ const { getWebhooks, createWebhook, deleteWebhook } = require('@clevercloud/client/cjs/api/v2/notification.js');
9
9
  const { sendToApi } = require('../models/send-to-api.js');
10
10
 
11
11
  function displayWebhook (hook) {
@@ -19,12 +19,22 @@ function getFormatter (format, isAddon) {
19
19
  }
20
20
  }
21
21
 
22
+ function formatSource (l) {
23
+ if (l.s != null) {
24
+ const location = l.s.ct ? `${l.s.ct}, ${l.s.co}` : l.s.co;
25
+ return `${l.ipS} - ${location}`;
26
+ }
27
+ else {
28
+ return l.ipS;
29
+ }
30
+ }
31
+
22
32
  function formatSimple (l) {
23
33
  return `${new Date(l.t).toISOString()} ${l.ipS} ${l.vb} ${l.path}`;
24
34
  }
25
35
 
26
36
  function formatExtended (l) {
27
- return `${new Date(l.t).toISOString()} [ ${l.ipS} - ${l.s.ct ? `${l.s.ct}, ${l.s.co}` : `${l.s.co}`} ] ${l.vb} ${l.h} ${l.path} ${l.sC}`;
37
+ return `${new Date(l.t).toISOString()} [ ${formatSource(l)} ] ${l.vb} ${l.h} ${l.path} ${l.sC}`;
28
38
  }
29
39
 
30
40
  function formatCLF (l) {
@@ -36,7 +46,7 @@ function formatSimpleAddon (l) {
36
46
  }
37
47
 
38
48
  function formatExtendedAddon (l) {
39
- return `${new Date(l.t).toISOString()} [ ${l.ipS} - ${l.s.ct ? `${l.s.ct}, ${l.s.co}` : `${l.s.co}`} ] duration(ms): ${l.sDuration}`;
49
+ return `${new Date(l.t).toISOString()} [ ${formatSource(l)} ] duration(ms): ${l.sDuration}`;
40
50
  }
41
51
 
42
52
  function formatCLFAddon (l) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const application = require('@clevercloud/client/cjs/api/application.js');
3
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
4
4
 
5
5
  const { sendToApi } = require('./send-to-api.js');
6
6
 
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const application = require('@clevercloud/client/cjs/api/application.js');
3
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
4
4
  const autocomplete = require('cliparse').autocomplete;
5
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/addon.js');
7
- const { getAllAddonProviders } = require('@clevercloud/client/cjs/api/product.js');
8
- const { getSummary } = require('@clevercloud/client/cjs/api/user.js');
6
+ const { get: getAddon, getAll: getAllAddons, remove: removeAddon, create: createAddon, preorder: preorderAddon, update: updateAddon } = require('@clevercloud/client/cjs/api/v2/addon.js');
7
+ const { getAllAddonProviders } = require('@clevercloud/client/cjs/api/v2/product.js');
8
+ const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
9
+ const { getAddonProvider } = require('@clevercloud/client/cjs/api/v4/addon-providers.js');
9
10
 
10
11
  const Interact = require('./interact.js');
11
12
  const Logger = require('../logger.js');
@@ -24,6 +25,16 @@ async function getProvider (providerName) {
24
25
  return provider;
25
26
  }
26
27
 
28
+ function getProviderInfos (providerName) {
29
+ return getAddonProvider({ providerId: providerName }).then(sendToApi)
30
+ .catch(() => {
31
+ // An error can occur because the add-on api doesn't implement this endpoint yet
32
+ // This is fine, just ignore it
33
+ Logger.debug(`${providerName} doesn't yet implement the provider info endpoint`);
34
+ return Promise.resolve(null);
35
+ });
36
+ }
37
+
27
38
  async function list (ownerId, appId, showAll) {
28
39
  const allAddons = await getAllAddons({ id: ownerId }).then(sendToApi);
29
40
 
@@ -45,7 +56,78 @@ async function list (ownerId, appId, showAll) {
45
56
  });
46
57
  }
47
58
 
48
- async function create ({ ownerId, name, providerName, planName, region, skipConfirmation }) {
59
+ function validateAddonVersionAndOptions (region, version, addonOptions, providerInfos, planType) {
60
+ if (providerInfos != null) {
61
+ if (version != null) {
62
+ const type = planType.value.toLowerCase();
63
+ if (type === 'shared') {
64
+ const cluster = providerInfos.clusters.find(({ zone }) => zone === region);
65
+ if (cluster == null) {
66
+ throw new Error(`Can't find cluster for region ${region}`);
67
+ }
68
+ else if (cluster.version !== version) {
69
+ throw new Error(`Invalid version ${version}, selected shared cluster only supports version ${cluster.version}`);
70
+ }
71
+ }
72
+ else if (type === 'dedicated') {
73
+ const availableVersions = Object.keys(providerInfos.dedicated);
74
+ const hasVersion = availableVersions.find((availableVersion) => availableVersion === version);
75
+ if (hasVersion == null) {
76
+ throw new Error(`Invalid version ${addonOptions.version}, available versions are: ${availableVersions.join(', ')}`);
77
+ }
78
+ }
79
+ }
80
+
81
+ const chosenVersion = version != null ? version : providerInfos.defaultDedicatedVersion;
82
+
83
+ // Check the selected options to see if the chosen plan / region offers them
84
+ // If not, abort the creation
85
+ if (Object.keys(addonOptions).length > 0) {
86
+ const type = planType.value.toLowerCase();
87
+ let availableOptions = [];
88
+ if (type === 'shared') {
89
+ const cluster = providerInfos.clusters.find(({ zone }) => zone === region);
90
+ if (cluster == null) {
91
+ throw new Error(`Can't find cluster for region ${region}`);
92
+ }
93
+
94
+ availableOptions = cluster.features;
95
+ }
96
+ else if (type === 'dedicated') {
97
+ availableOptions = providerInfos.dedicated[chosenVersion].features;
98
+ }
99
+
100
+ for (const selectedOption in addonOptions) {
101
+ const isAvailable = availableOptions.find(({ name }) => name === selectedOption);
102
+ if (isAvailable == null) {
103
+ const optionNames = availableOptions.map(({ name }) => name).join(',');
104
+ let availableOptionsError = null;
105
+ if (optionNames.length > 0) {
106
+ availableOptionsError = `Avalailble options are: ${optionNames}.`;
107
+ }
108
+ else {
109
+ availableOptionsError = 'No options are available for this plan.';
110
+ }
111
+
112
+ throw new Error(`Option "${selectedOption}" is not available on this plan. ${availableOptionsError}`);
113
+ }
114
+ }
115
+ }
116
+
117
+ return {
118
+ version: chosenVersion,
119
+ ...addonOptions,
120
+ };
121
+ }
122
+ else {
123
+ if (version != null) {
124
+ throw new Error('You provided a version for an add-on that doesn\'t support choosing the version.');
125
+ }
126
+ return {};
127
+ }
128
+ }
129
+
130
+ async function create ({ ownerId, name, providerName, planName, region, skipConfirmation, version, addonOptions }) {
49
131
 
50
132
  // TODO: We should be able to use it without {}
51
133
  const providers = await listProviders();
@@ -58,13 +140,33 @@ async function create ({ ownerId, name, providerName, planName, region, skipConf
58
140
  throw new Error(`invalid region name. Available regions: ${provider.regions.join(', ')}`);
59
141
  }
60
142
 
61
- const plan = provider.plans.find((p) => p.slug === planName);
143
+ const plan = provider.plans.find((p) => p.slug.toLowerCase() === planName.toLowerCase());
62
144
  if (plan == null) {
63
145
  const availablePlans = provider.plans.map((p) => p.slug);
64
146
  throw new Error(`invalid plan name. Available plans: ${availablePlans.join(', ')}`);
65
147
  }
66
148
 
67
- const addonToCreate = { name, plan: plan.id, providerId: provider.id, region };
149
+ const providerInfos = await getProviderInfos(provider.id);
150
+ const planType = plan.features.find(({ name }) => name.toLowerCase() === 'type');
151
+
152
+ // If we have a providerInfos but we don't have a planType, we won't be able to go further
153
+ // The process should stop here to make sure users don't create something they don't intend to
154
+ // This missing feature should have been added during the add-on's development phase
155
+ // The console has a similar check so I believe we shouldn't hit this
156
+ if (providerInfos != null && planType == null) {
157
+ throw new Error('Internal error. The selected plan misses the TYPE feature. Please contact our support with the command line you used');
158
+ }
159
+
160
+ const createOptions = validateAddonVersionAndOptions(region, version, addonOptions, providerInfos, planType);
161
+
162
+ const addonToCreate = {
163
+ name,
164
+ plan: plan.id,
165
+ providerId: provider.id,
166
+ region,
167
+ options: createOptions,
168
+ };
169
+
68
170
  const result = await preorderAddon({ id: ownerId }, addonToCreate).then(sendToApi);
69
171
 
70
172
  if (result.totalTTC > 0 && !skipConfirmation) {
@@ -153,6 +255,33 @@ async function findById (addonId) {
153
255
  throw new Error(`Could not find add-on with ID: ${addonId}`);
154
256
  }
155
257
 
258
+ function parseAddonOptions (options) {
259
+ if (options == null) {
260
+ return {};
261
+ }
262
+
263
+ return options.split(',').reduce((options, option) => {
264
+ const [key, value] = option.split('=');
265
+ if (value == null) {
266
+ throw new Error("Options are malformed. Usage is '--option name=enabled|disabled|true|false'");
267
+ }
268
+
269
+ let formattedValue = value;
270
+ if (value === 'true' || value === 'enabled') {
271
+ formattedValue = 'true';
272
+ }
273
+ else if (value === 'false' || value === 'disabled') {
274
+ formattedValue = 'false';
275
+ }
276
+ else {
277
+ throw new Error(`Can't parse option value: ${value}. Accepted values are: enabled, disabled, true, false`);
278
+ }
279
+
280
+ options[key] = formattedValue;
281
+ return options;
282
+ }, {});
283
+ }
284
+
156
285
  module.exports = {
157
286
  completePlan,
158
287
  completeRegion,
@@ -160,9 +289,11 @@ module.exports = {
160
289
  delete: deleteAddon,
161
290
  findById,
162
291
  getProvider,
292
+ getProviderInfos,
163
293
  link,
164
294
  list,
165
295
  listProviders,
296
+ parseAddonOptions,
166
297
  rename,
167
298
  unlink,
168
299
  };
@@ -111,7 +111,7 @@ async function getAppDetails ({ alias }) {
111
111
  };
112
112
 
113
113
  function persistConfig (modifiedConfig) {
114
- const jsonContents = JSON.stringify(modifiedConfig);
114
+ const jsonContents = JSON.stringify(modifiedConfig, null, 2);
115
115
  return fs.writeFile(conf.APP_CONFIGURATION_FILE, jsonContents);
116
116
  };
117
117
 
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const _ = require('lodash');
4
- const application = require('@clevercloud/client/cjs/api/application.js');
4
+ const application = require('@clevercloud/client/cjs/api/v2/application.js');
5
5
  const autocomplete = require('cliparse').autocomplete;
6
- const product = require('@clevercloud/client/cjs/api/product.js');
6
+ const product = require('@clevercloud/client/cjs/api/v2/product.js');
7
7
 
8
8
  const AppConfiguration = require('./app_configuration.js');
9
9
  const Interact = require('./interact.js');
@@ -1,11 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('fs');
3
+ const { promises: fs } = require('fs');
4
4
  const path = require('path');
5
- const util = require('util');
6
-
7
- const readFile = util.promisify(fs.readFile);
8
- const writeFile = util.promisify(fs.writeFile);
9
5
 
10
6
  const commonEnv = require('common-env');
11
7
  const mkdirp = require('mkdirp');
@@ -14,7 +10,7 @@ const xdg = require('xdg');
14
10
  const Logger = require('../logger.js');
15
11
  const env = commonEnv(Logger);
16
12
 
17
- function getConfigPath () {
13
+ function getConfigDir () {
18
14
  if (process.platform === 'win32') {
19
15
  return path.resolve(process.env.APPDATA, 'clever-cloud');
20
16
  }
@@ -23,6 +19,37 @@ function getConfigPath () {
23
19
  }
24
20
  }
25
21
 
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
+ }
51
+ }
52
+
26
53
  async function loadOAuthConf () {
27
54
  Logger.debug('Load configuration from environment variables');
28
55
  if (process.env.CLEVER_TOKEN != null && process.env.CLEVER_SECRET != null) {
@@ -32,8 +59,9 @@ async function loadOAuthConf () {
32
59
  };
33
60
  }
34
61
  Logger.debug('Load configuration from ' + conf.CONFIGURATION_FILE);
62
+ await maybeMigrateFromLegacyConfigurationPath();
35
63
  try {
36
- const rawFile = await readFile(conf.CONFIGURATION_FILE);
64
+ const rawFile = await fs.readFile(conf.CONFIGURATION_FILE);
37
65
  return JSON.parse(rawFile);
38
66
  }
39
67
  catch (error) {
@@ -47,7 +75,7 @@ async function writeOAuthConf (oauthData) {
47
75
  const configDir = path.dirname(conf.CONFIGURATION_FILE);
48
76
  try {
49
77
  await mkdirp(configDir, { mode: 0o700 });
50
- await writeFile(conf.CONFIGURATION_FILE, JSON.stringify(oauthData));
78
+ await fs.writeFile(conf.CONFIGURATION_FILE, JSON.stringify(oauthData));
51
79
  }
52
80
  catch (error) {
53
81
  throw new Error(`Cannot write configuration to ${conf.CONFIGURATION_FILE}\n${error.message}`);
@@ -55,8 +83,8 @@ async function writeOAuthConf (oauthData) {
55
83
  }
56
84
 
57
85
  const conf = env.getOrElseAll({
58
- API_HOST: 'https://api.clever-cloud.com/v2',
59
- // API_HOST: 'https://ccapi-preprod.cleverapps.io/v2',
86
+ API_HOST: 'https://api.clever-cloud.com',
87
+ // API_HOST: 'https://ccapi-preprod.cleverapps.io',
60
88
  LOG_WS_URL: 'wss://api.clever-cloud.com/v2/logs/logs-socket/<%- appId %>?since=<%- timestamp %>',
61
89
  LOG_HTTP_URL: 'https://api.clever-cloud.com/v2/logs/<%- appId %>',
62
90
  EVENT_URL: 'wss://api.clever-cloud.com/v2/events/event-socket',
@@ -4,7 +4,7 @@ const { promisify } = require('util');
4
4
  const delay = promisify(setTimeout);
5
5
 
6
6
  const Logger = require('../logger.js');
7
- const { getDeployment, getAllDeployments } = require('@clevercloud/client/cjs/api/application.js');
7
+ const { getDeployment, getAllDeployments } = require('@clevercloud/client/cjs/api/v2/application.js');
8
8
  const { sendToApi } = require('./send-to-api.js');
9
9
 
10
10
  const DEPLOYMENT_POLLING_DELAY = 5000;
@@ -30,6 +30,7 @@ async function waitForDeploymentStart ({ ownerId, appId, deploymentId, commitId,
30
30
  const isNew = knownDeployments.every(({ uuid }) => uuid !== d.uuid);
31
31
  return isNew && d.commit === commitId;
32
32
  }
33
+ return false;
33
34
  });
34
35
  if (deployment != null) {
35
36
  Logger.debug(`Deployment has started (state:${deployment.state})`);
@@ -3,7 +3,7 @@
3
3
  const _ = require('lodash');
4
4
 
5
5
  const Logger = require('../logger.js');
6
- const { getAllDomains, getFavouriteDomain } = require('@clevercloud/client/cjs/api/application.js');
6
+ const { getAllDomains, getFavouriteDomain } = require('@clevercloud/client/cjs/api/v2/application.js');
7
7
  const { sendToApi } = require('../models/send-to-api.js');
8
8
 
9
9
  async function getBest (appId, orgaId) {
@@ -8,12 +8,13 @@ const DRAIN_TYPES = [
8
8
  { id: 'HTTP', credentials: 'OPTIONAL' },
9
9
  { id: 'ElasticSearch', credentials: 'MANDATORY' },
10
10
  { id: 'DatadogHTTP' },
11
+ { id: 'NewRelicHTTP', apiKey: 'MANDATORY' },
11
12
  ];
12
13
 
13
14
  function createDrainBody (appId, drainTargetURL, drainTargetType, drainTargetCredentials, drainTargetConfig) {
14
15
 
15
- if (!authorizeDrainCreation(drainTargetType, drainTargetCredentials)) {
16
- throw new Error("Credentials are: optional for HTTP, mandatory for ElasticSearch and TCPSyslog/UDPSyslog don't need them.");
16
+ if (!authorizeDrainCreation(drainTargetType, drainTargetCredentials, drainTargetConfig)) {
17
+ throw new Error("Credentials are: optional for HTTP, mandatory for ElasticSearch, NewRelicHTTP and TCPSyslog/UDPSyslog don't need them.");
17
18
  }
18
19
 
19
20
  const body = {
@@ -26,20 +27,37 @@ function createDrainBody (appId, drainTargetURL, drainTargetType, drainTargetCre
26
27
  password: drainTargetCredentials.password || '',
27
28
  };
28
29
  }
30
+ if (keyExist(drainTargetConfig)) {
31
+ body.APIKey = drainTargetConfig.apiKey;
32
+ }
29
33
  return body;
30
34
  }
31
35
 
32
- function authorizeDrainCreation (drainTargetType, drainTargetCredentials) {
36
+ function authorizeDrainCreation (drainTargetType, drainTargetCredentials, drainTargetConfig) {
33
37
  if (drainTypeExists(drainTargetType)) {
34
38
  // retrieve creds for drain type ('mandatory', 'optional', undefined)
35
- const status = credentialsStatus(drainTargetType).credentials;
36
- if (status === 'MANDATORY') {
39
+ const credStatus = credentialsStatus(drainTargetType).credentials;
40
+ const keyStatus = credentialsStatus(drainTargetType).apiKey;
41
+
42
+ if (credStatus === 'MANDATORY') {
37
43
  return credentialsExist(drainTargetCredentials);
38
44
  }
39
- if (status === 'OPTIONAL') {
45
+ if (credStatus === 'OPTIONAL') {
46
+ return true;
47
+ }
48
+ if (!credStatus && !keyStatus) {
49
+ return credentialsEmpty(drainTargetCredentials);
50
+ }
51
+
52
+ if (keyStatus === 'MANDATORY') {
53
+ return keyExist(drainTargetConfig);
54
+ }
55
+ if (keyStatus === 'OPTIONAL') {
40
56
  return true;
41
57
  }
42
- return credentialsEmpty(drainTargetCredentials);
58
+ if (!keyStatus) {
59
+ return keyEmpty(drainTargetConfig);
60
+ }
43
61
  }
44
62
  }
45
63
 
@@ -59,6 +77,14 @@ function credentialsEmpty ({ username, password }) {
59
77
  return username == null && password == null;
60
78
  }
61
79
 
80
+ function keyExist ({ apiKey }) {
81
+ return apiKey != null;
82
+ }
83
+
84
+ function keyEmpty ({ apiKey }) {
85
+ return apiKey == null;
86
+ }
87
+
62
88
  function listDrainTypes () {
63
89
  return autocomplete.words(DRAIN_TYPES.map((type) => type.id));
64
90
  }
@@ -1,14 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const { join } = require('path');
4
- const fs = require('fs');
5
- const { promisify } = require('util');
6
-
7
- const fsStat = promisify(fs.stat);
4
+ const { promises: fs } = require('fs');
8
5
 
9
6
  function findPath (dir, name) {
10
7
  const fullPath = join(dir, name);
11
- return fsStat(fullPath)
8
+ return fs.stat(fullPath)
12
9
  .then(() => dir)
13
10
  .catch((e) => {
14
11
  if (e.code === 'ENOENT' && dir !== '/') {
package/src/models/git.js CHANGED
@@ -36,7 +36,8 @@ async function addRemote (remoteName, url) {
36
36
  const allRemotes = await git.listRemotes({ ...repo });
37
37
  const existingRemote = _.find(allRemotes, { remote: safeRemoteName });
38
38
  if (existingRemote == null) {
39
- return git.addRemote({ ...repo, remote: safeRemoteName, url });
39
+ // In some situations, we may end up with race conditions so we force it
40
+ return git.addRemote({ ...repo, remote: safeRemoteName, url, force: true });
40
41
  }
41
42
  }
42
43
 
package/src/models/log.js CHANGED
@@ -5,7 +5,7 @@ const colors = require('colors/safe');
5
5
 
6
6
  const Logger = require('../logger.js');
7
7
  const { Deferred } = require('./utils.js');
8
- const { getOldLogs } = require('@clevercloud/client/cjs/api/log.js');
8
+ const { getOldLogs } = require('@clevercloud/client/cjs/api/v2/log.js');
9
9
  const { LogsStream } = require('@clevercloud/client/cjs/streams/logs.node.js');
10
10
  const { sendToApi, getHostAndTokens } = require('./send-to-api.js');
11
11
  const { waitForDeploymentEnd, waitForDeploymentStart } = require('./deployments.js');
@@ -25,7 +25,8 @@ function isDeploymentFailedMessage (line) {
25
25
  };
26
26
 
27
27
  function isBuildSucessMessage (line) {
28
- return _.startsWith(line._source['@message'].toLowerCase(), 'build succeeded in');
28
+ return isCleverMessage(line)
29
+ && _.startsWith(line._source['@message'].toLowerCase(), 'build succeeded in');
29
30
  };
30
31
 
31
32
  function formatLogLine (line) {
@@ -115,7 +116,7 @@ async function watchDeploymentAndDisplayLogs ({ ownerId, appId, deploymentId, co
115
116
  // displayLiveLogs() defines callback listeners so if it catches error in those callbacks,
116
117
  // it has no proper way to bubble up the error here.
117
118
  // Using the deferred enables this.
118
- logsStream = await displayLiveLogs({ appId, deploymentId }, deferred);
119
+ logsStream = await displayLiveLogs({ appId, deploymentId: deployment.uuid }, deferred);
119
120
  }
120
121
 
121
122
  Logger.println('Waiting for application logs…');
@@ -5,8 +5,8 @@ const autocomplete = require('cliparse').autocomplete;
5
5
 
6
6
  const AppConfig = require('./app_configuration.js');
7
7
 
8
- const organisation = require('@clevercloud/client/cjs/api/organisation.js');
9
- const { getSummary } = require('@clevercloud/client/cjs/api/user.js');
8
+ const organisation = require('@clevercloud/client/cjs/api/v2/organisation.js');
9
+ const { getSummary } = require('@clevercloud/client/cjs/api/v2/user.js');
10
10
  const { sendToApi } = require('../models/send-to-api.js');
11
11
 
12
12
  async function getId (orgaIdOrName) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { get } = require('@clevercloud/client/cjs/api/organisation.js');
3
+ const { get } = require('@clevercloud/client/cjs/api/v2/organisation.js');
4
4
  const { sendToApi } = require('../models/send-to-api.js');
5
5
 
6
6
  function getCurrent () {