clever-tools 3.1.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -6
- package/bin/clever.js +15 -5
- package/package.json +3 -3
- package/src/commands/addon.js +47 -21
- package/src/commands/create.js +19 -5
- package/src/commands/deploy.js +21 -4
- package/src/commands/logs.js +16 -7
- package/src/logger.js +5 -0
- package/src/models/application.js +1 -1
- package/src/models/git.js +14 -1
- package/src/models/log-v4.js +64 -6
- package/src/models/send-to-api.js +15 -5
package/README.md
CHANGED
|
@@ -10,11 +10,6 @@ Clever Tools are availables from many channels. The simpler way to install them,
|
|
|
10
10
|
npm install -g clever-tools
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
If you want to install our latest beta release, you can run:
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
npm install -g clever-tools@beta
|
|
17
|
-
```
|
|
18
13
|
We also distribute binaries and packages for multiple systems and tools:
|
|
19
14
|
|
|
20
15
|
* [GNU/Linux](docs/setup-systems.md#gnulinux)
|
|
@@ -93,6 +88,7 @@ Where `type` is one of:
|
|
|
93
88
|
Where region is one of:
|
|
94
89
|
|
|
95
90
|
- `par` (Paris, [Clever Cloud](https://www.clever-cloud.com/infrastructure/))
|
|
91
|
+
- `grahds` (Gravelines, HDS servers, OVHcloud)
|
|
96
92
|
- `rbx` (Roubaix, OVHcloud)
|
|
97
93
|
- `rbxhds` (Roubaix, HDS servers, OVHcloud)
|
|
98
94
|
- `scw` (Paris, [Scaleway DC5](https://www.clever-cloud.com/blog/press/2023/01/17/clever-cloud-and-scaleway-join-forces-to-unveil-a-sovereign-european-paas-offering/))
|
|
@@ -161,5 +157,5 @@ clever deploy
|
|
|
161
157
|
|
|
162
158
|
## Automated releases
|
|
163
159
|
|
|
164
|
-
This project uses
|
|
160
|
+
This project uses GitHub Actions to build binaries, package them and release them automatically on the various repositories.
|
|
165
161
|
If you want to know more or if you need to release a new version, please read [RELEASE.md](./RELEASE.md) carefully.
|
package/bin/clever.js
CHANGED
|
@@ -90,7 +90,10 @@ function run () {
|
|
|
90
90
|
description: 'Application ID (or name, if unambiguous)',
|
|
91
91
|
parser: Parsers.appIdOrName,
|
|
92
92
|
}),
|
|
93
|
-
appNameCreation: cliparse.argument('app-name', {
|
|
93
|
+
appNameCreation: cliparse.argument('app-name', {
|
|
94
|
+
description: 'Application name (optional, current directory name is used if not specified)',
|
|
95
|
+
default: '',
|
|
96
|
+
}),
|
|
94
97
|
backupId: cliparse.argument('backup-id', { description: 'A Database backup ID (format: UUID)' }),
|
|
95
98
|
databaseId: cliparse.argument('database-id', { description: 'Any database ID (format: addon_UUID, postgresql_UUID, mysql_UUID, ...)' }),
|
|
96
99
|
drainId: cliparse.argument('drain-id', { description: 'Drain ID' }),
|
|
@@ -132,6 +135,7 @@ function run () {
|
|
|
132
135
|
sourceableEnvVarsList: cliparse.flag('add-export', { description: 'Display sourceable env variables setting' }),
|
|
133
136
|
accesslogsFormat: getOutputFormatOption(['simple', 'extended', 'clf']),
|
|
134
137
|
addonEnvFormat: getOutputFormatOption(['shell']),
|
|
138
|
+
logsFormat: getOutputFormatOption(['json-stream']),
|
|
135
139
|
accesslogsFollow: cliparse.flag('follow', {
|
|
136
140
|
aliases: ['f'],
|
|
137
141
|
description: 'Display access logs continuously (ignores before/until, after/since)',
|
|
@@ -180,6 +184,12 @@ function run () {
|
|
|
180
184
|
metavar: 'commit id',
|
|
181
185
|
description: 'Restart the application with a specific commit ID',
|
|
182
186
|
}),
|
|
187
|
+
gitTag: cliparse.option('tag', {
|
|
188
|
+
aliases: ['t'],
|
|
189
|
+
default: '',
|
|
190
|
+
metavar: 'tag',
|
|
191
|
+
description: 'Tag to push (none by default)',
|
|
192
|
+
}),
|
|
183
193
|
databaseId: cliparse.option('database-id', {
|
|
184
194
|
metavar: 'database_id',
|
|
185
195
|
description: 'The Database ID (e.g.: postgresql_xxx)',
|
|
@@ -345,7 +355,7 @@ function run () {
|
|
|
345
355
|
parser: Parsers.commaSeparated,
|
|
346
356
|
}),
|
|
347
357
|
showAllActivity: cliparse.flag('show-all', { description: 'Show all activity' }),
|
|
348
|
-
showAll: cliparse.flag('show-all', { description: 'Show all available
|
|
358
|
+
showAll: cliparse.flag('show-all', { description: 'Show all available add-ons and applications' }),
|
|
349
359
|
loginToken: cliparse.option('token', {
|
|
350
360
|
metavar: 'token',
|
|
351
361
|
description: 'Directly give an existing token',
|
|
@@ -559,7 +569,7 @@ function run () {
|
|
|
559
569
|
const addonCreateCommand = cliparse.command('create', {
|
|
560
570
|
description: 'Create an addon',
|
|
561
571
|
args: [args.addonProvider, args.addonName],
|
|
562
|
-
options: [opts.linkAddon, opts.confirmAddonCreation, opts.addonPlan, opts.addonRegion, opts.addonVersion, opts.addonOptions],
|
|
572
|
+
options: [opts.linkAddon, opts.confirmAddonCreation, opts.addonPlan, opts.addonRegion, opts.addonVersion, opts.addonOptions, opts.humanJsonOutputFormat],
|
|
563
573
|
}, addon('create'));
|
|
564
574
|
const addonDeleteCommand = cliparse.command('delete', {
|
|
565
575
|
description: 'Delete an addon',
|
|
@@ -643,7 +653,7 @@ function run () {
|
|
|
643
653
|
const deploy = lazyRequirePromiseModule('../src/commands/deploy.js');
|
|
644
654
|
const deployCommand = cliparse.command('deploy', {
|
|
645
655
|
description: 'Deploy an application',
|
|
646
|
-
options: [opts.alias, opts.branch, opts.quiet, opts.forceDeploy, opts.followDeployLogs, opts.sameCommitPolicy],
|
|
656
|
+
options: [opts.alias, opts.branch, opts.gitTag, opts.quiet, opts.forceDeploy, opts.followDeployLogs, opts.sameCommitPolicy],
|
|
647
657
|
}, deploy('deploy'));
|
|
648
658
|
|
|
649
659
|
// DIAG COMMAND
|
|
@@ -754,7 +764,7 @@ function run () {
|
|
|
754
764
|
const logs = lazyRequirePromiseModule('../src/commands/logs.js');
|
|
755
765
|
const logsCommand = cliparse.command('logs', {
|
|
756
766
|
description: 'Fetch application logs, continuously',
|
|
757
|
-
options: [opts.alias, opts.before, opts.after, opts.search, opts.deploymentId, opts.addonId],
|
|
767
|
+
options: [opts.alias, opts.before, opts.after, opts.search, opts.deploymentId, opts.addonId, opts.logsFormat],
|
|
758
768
|
}, logs('appLogs'));
|
|
759
769
|
|
|
760
770
|
// MAKE DEFAULT COMMAND
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clever-tools",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Command Line Interface for Clever Cloud.",
|
|
5
5
|
"main": "bin/clever.js",
|
|
6
6
|
"keywords": [
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"scripts/*.sh"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@clevercloud/client": "^8.
|
|
28
|
+
"@clevercloud/client": "^8.1.2",
|
|
29
29
|
"clf-date": "^0.2.0",
|
|
30
30
|
"cliparse": "^0.3.3",
|
|
31
31
|
"colors": "1.4.0",
|
|
32
32
|
"common-env": "^6.4.0",
|
|
33
33
|
"curlconverter": "^3.21.0",
|
|
34
34
|
"eventsource": "^1.1.0",
|
|
35
|
-
"isomorphic-git": "^1.25.
|
|
35
|
+
"isomorphic-git": "^1.25.3",
|
|
36
36
|
"linux-release-info": "^3.0.0",
|
|
37
37
|
"lodash": "^4.17.21",
|
|
38
38
|
"mkdirp": "^1.0.4",
|
package/src/commands/addon.js
CHANGED
|
@@ -34,7 +34,14 @@ async function list (params) {
|
|
|
34
34
|
|
|
35
35
|
async function create (params) {
|
|
36
36
|
const [providerName, name] = params.args;
|
|
37
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
link: linkedAppAlias,
|
|
39
|
+
plan: planName,
|
|
40
|
+
region,
|
|
41
|
+
yes: skipConfirmation,
|
|
42
|
+
org: orgaIdOrName,
|
|
43
|
+
format,
|
|
44
|
+
} = params.options;
|
|
38
45
|
const version = params.options['addon-version'];
|
|
39
46
|
const addonOptions = parseAddonOptions(params.options.option);
|
|
40
47
|
|
|
@@ -42,36 +49,55 @@ async function create (params) {
|
|
|
42
49
|
? await Organisation.getId(orgaIdOrName)
|
|
43
50
|
: await User.getCurrentId();
|
|
44
51
|
|
|
52
|
+
const addonToCreate = {
|
|
53
|
+
ownerId,
|
|
54
|
+
name,
|
|
55
|
+
providerName,
|
|
56
|
+
planName,
|
|
57
|
+
region,
|
|
58
|
+
skipConfirmation,
|
|
59
|
+
version,
|
|
60
|
+
addonOptions,
|
|
61
|
+
};
|
|
62
|
+
|
|
45
63
|
if (linkedAppAlias != null) {
|
|
46
64
|
const linkedAppData = await AppConfig.getAppDetails({ alias: linkedAppAlias });
|
|
47
|
-
if (orgaIdOrName != null && linkedAppData.ownerId !== ownerId) {
|
|
65
|
+
if (orgaIdOrName != null && linkedAppData.ownerId !== ownerId && format === 'human') {
|
|
48
66
|
Logger.warn('The specified application does not belong to the specified organisation. Ignoring the `--org` option');
|
|
49
67
|
}
|
|
50
68
|
const newAddon = await Addon.create({
|
|
69
|
+
...addonToCreate,
|
|
51
70
|
ownerId: linkedAppData.ownerId,
|
|
52
|
-
name,
|
|
53
|
-
providerName,
|
|
54
|
-
planName,
|
|
55
|
-
region,
|
|
56
|
-
skipConfirmation,
|
|
57
|
-
version,
|
|
58
|
-
addonOptions,
|
|
59
71
|
});
|
|
60
72
|
await Addon.link(linkedAppData.ownerId, linkedAppData.appId, { addon_id: newAddon.id });
|
|
61
|
-
|
|
73
|
+
displayAddon(format, newAddon, `Add-on created and linked to application ${linkedAppAlias} successfully!`);
|
|
62
74
|
}
|
|
63
75
|
else {
|
|
64
|
-
const newAddon = await Addon.create(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
const newAddon = await Addon.create(addonToCreate);
|
|
77
|
+
displayAddon(format, newAddon, 'Add-on created successfully!');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function displayAddon (format, addon, message) {
|
|
82
|
+
switch (format) {
|
|
83
|
+
|
|
84
|
+
case 'json': {
|
|
85
|
+
Logger.printJson({
|
|
86
|
+
id: addon.id,
|
|
87
|
+
realId: addon.realId,
|
|
88
|
+
name: addon.name,
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'human':
|
|
94
|
+
default:
|
|
95
|
+
Logger.println([
|
|
96
|
+
message,
|
|
97
|
+
`ID: ${addon.id}`,
|
|
98
|
+
`Real ID: ${addon.realId}`,
|
|
99
|
+
`Name: ${addon.name}`,
|
|
100
|
+
].join('\n'));
|
|
75
101
|
}
|
|
76
102
|
}
|
|
77
103
|
|
package/src/commands/create.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
3
4
|
const Application = require('../models/application.js');
|
|
4
5
|
const AppConfig = require('../models/app_configuration.js');
|
|
5
6
|
const Logger = require('../logger.js');
|
|
6
7
|
|
|
7
8
|
async function create (params) {
|
|
8
9
|
const { type: typeName } = params.options;
|
|
9
|
-
const [
|
|
10
|
+
const [rawName] = params.args;
|
|
10
11
|
const { org: orgaIdOrName, alias, region, github: githubOwnerRepo, format } = params.options;
|
|
11
12
|
const { apps } = await AppConfig.loadApplicationConf();
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
// Application name is optionnal, use current directory name if not specified (empty string)
|
|
15
|
+
const name = (rawName !== '') ? rawName : getCurrentDirectoryName();
|
|
16
|
+
|
|
13
17
|
AppConfig.checkAlreadyLinked(apps, name, alias);
|
|
14
|
-
|
|
18
|
+
|
|
15
19
|
const github = getGithubDetails(githubOwnerRepo);
|
|
16
20
|
const app = await Application.create(name, typeName, region, orgaIdOrName, github);
|
|
17
21
|
await AppConfig.addLinkedApplication(app, alias);
|
|
@@ -19,13 +23,19 @@ async function create (params) {
|
|
|
19
23
|
switch (format) {
|
|
20
24
|
|
|
21
25
|
case 'json': {
|
|
22
|
-
|
|
26
|
+
Logger.printJson({
|
|
27
|
+
id: app.id,
|
|
28
|
+
name: app.name,
|
|
29
|
+
deployUrl: app.deployUrl,
|
|
30
|
+
});
|
|
23
31
|
break;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
case 'human':
|
|
27
35
|
default:
|
|
28
|
-
Logger.println('
|
|
36
|
+
Logger.println('Application created successfully!');
|
|
37
|
+
Logger.println(`ID: ${app.id}`);
|
|
38
|
+
Logger.println(`Name: ${name}`);
|
|
29
39
|
}
|
|
30
40
|
};
|
|
31
41
|
|
|
@@ -36,4 +46,8 @@ function getGithubDetails (githubOwnerRepo) {
|
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
function getCurrentDirectoryName () {
|
|
50
|
+
return path.basename(process.cwd());
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
module.exports = { create };
|
package/src/commands/deploy.js
CHANGED
|
@@ -13,12 +13,12 @@ const { sendToApi } = require('../models/send-to-api.js');
|
|
|
13
13
|
// Once the API call to redeploy() has been triggered successfully,
|
|
14
14
|
// the rest (waiting for deployment state to evolve and displaying logs) is done with auto retry (resilient to network failures)
|
|
15
15
|
async function deploy (params) {
|
|
16
|
-
const { alias, branch: branchName, quiet, force, follow, 'same-commit-policy': sameCommitPolicy } = params.options;
|
|
16
|
+
const { alias, branch: branchName, tag: tagName, quiet, force, follow, 'same-commit-policy': sameCommitPolicy } = params.options;
|
|
17
17
|
|
|
18
18
|
const appData = await AppConfig.getAppDetails({ alias });
|
|
19
19
|
const { ownerId, appId } = appData;
|
|
20
|
-
const branchRefspec = await git.getFullBranch(branchName);
|
|
21
20
|
|
|
21
|
+
const branchRefspec = await getBranchToDeploy(branchName, tagName);
|
|
22
22
|
const commitIdToPush = await git.getBranchCommit(branchRefspec);
|
|
23
23
|
const remoteHeadCommitId = await git.getRemoteCommit(appData.deployUrl);
|
|
24
24
|
const deployedCommitId = await Application.get(ownerId, appId)
|
|
@@ -43,7 +43,7 @@ async function deploy (params) {
|
|
|
43
43
|
default: {
|
|
44
44
|
const upToDateMessage = `The clever-cloud application is up-to-date (${colors.green(remoteHeadCommitId)}).\nYou can set a policy with 'same-commit-policy' to handle differently when remote HEAD has the same commit as the one to push.\nOr try this command to restart the application:`;
|
|
45
45
|
if (commitIdToPush !== deployedCommitId) {
|
|
46
|
-
const restartWithId = `clever restart --commit ${commitIdToPush}
|
|
46
|
+
const restartWithId = `clever restart --commit ${commitIdToPush}`;
|
|
47
47
|
throw new Error(`${upToDateMessage}\n${colors.yellow(restartWithId)}`);
|
|
48
48
|
}
|
|
49
49
|
throw new Error(`${upToDateMessage}\n${colors.yellow('clever restart')}`);
|
|
@@ -66,7 +66,8 @@ async function deploy (params) {
|
|
|
66
66
|
const knownDeployments = await getAllDeployments({ id: ownerId, appId, limit: 5 }).then(sendToApi);
|
|
67
67
|
|
|
68
68
|
Logger.println('Pushing source code to Clever Cloud…');
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
await git.push(appData.deployUrl, commitIdToPush, force)
|
|
70
71
|
.catch(async (e) => {
|
|
71
72
|
const isShallow = await git.isShallow();
|
|
72
73
|
if (isShallow) {
|
|
@@ -86,4 +87,20 @@ async function restartOnSameCommit (ownerId, appId, commitIdToPush, quiet, follo
|
|
|
86
87
|
return Log.watchDeploymentAndDisplayLogs({ ownerId, appId, deploymentId: restart.deploymentId, quiet, follow });
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
async function getBranchToDeploy (branchName, tagName) {
|
|
91
|
+
if (tagName) {
|
|
92
|
+
const useTag = await git.isExistingTag(tagName);
|
|
93
|
+
if (useTag) {
|
|
94
|
+
const tagRefspec = await git.getFullBranch(tagName);
|
|
95
|
+
return tagRefspec;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
throw new Error(`Tag ${tagName} doesn't exist locally`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
return await git.getFullBranch(branchName);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
module.exports = { deploy };
|
package/src/commands/logs.js
CHANGED
|
@@ -5,26 +5,35 @@ const LogV2 = require('../models/log.js');
|
|
|
5
5
|
const Log = require('../models/log-v4.js');
|
|
6
6
|
const Logger = require('../logger.js');
|
|
7
7
|
const { Deferred } = require('../models/utils.js');
|
|
8
|
+
const colors = require('colors/safe');
|
|
8
9
|
|
|
9
10
|
async function appLogs (params) {
|
|
10
|
-
const { alias, after: since, before: until, search, 'deployment-id': deploymentId } = params.options;
|
|
11
|
+
const { alias, addon: addonId, after: since, before: until, search, 'deployment-id': deploymentId, format } = params.options;
|
|
11
12
|
|
|
12
13
|
// ignore --search ""
|
|
13
14
|
const filter = (search !== '') ? search : null;
|
|
14
15
|
|
|
15
16
|
const { appId, ownerId } = await AppConfig.getAppDetails({ alias });
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
const isForHuman = (format === 'human');
|
|
18
19
|
|
|
19
20
|
// TODO: drop when addons are migrated to the v4 API
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
if (addonId) {
|
|
22
|
+
if (isForHuman) {
|
|
23
|
+
Logger.println(colors.blue('Waiting for addon logs…'));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
throw new Error(`"${format}" format is not yet available for add-on logs`);
|
|
27
|
+
}
|
|
28
|
+
return LogV2.displayLogs({ appAddonId: addonId, since, until, filter, deploymentId });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isForHuman) {
|
|
32
|
+
Logger.println(colors.blue('Waiting for application logs…'));
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
const deferred = new Deferred();
|
|
27
|
-
await Log.displayLogs({ ownerId, appId, since, until, filter, deploymentId, deferred });
|
|
36
|
+
await Log.displayLogs({ ownerId, appId, since, until, filter, deploymentId, format, deferred });
|
|
28
37
|
return deferred.promise;
|
|
29
38
|
}
|
|
30
39
|
|
package/src/logger.js
CHANGED
|
@@ -52,6 +52,11 @@ const Logger = _(['debug', 'info', 'warn', 'error'])
|
|
|
52
52
|
// No decoration for Logger.println
|
|
53
53
|
Logger.println = console.log;
|
|
54
54
|
|
|
55
|
+
// No decoration for Logger.println
|
|
56
|
+
Logger.printJson = (obj) => {
|
|
57
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
58
|
+
};
|
|
59
|
+
|
|
55
60
|
// No decoration for Logger.printErrorLine
|
|
56
61
|
Logger.printErrorLine = console.error;
|
|
57
62
|
|
|
@@ -17,7 +17,7 @@ function listAvailableTypes () {
|
|
|
17
17
|
return autocomplete.words(['docker', 'elixir', 'go', 'gradle', 'haskell', 'jar', 'maven', 'meteor', 'node', 'php', 'play1', 'play2', 'python', 'ruby', 'rust', 'sbt', 'static-apache', 'war']);
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
const AVAILABLE_ZONES = ['par', 'rbx', 'rbxhds', 'scw', 'jed', 'mtl', 'sgp', 'syd', 'wsw'];
|
|
20
|
+
const AVAILABLE_ZONES = ['par', 'grahds', 'rbx', 'rbxhds', 'scw', 'jed', 'mtl', 'sgp', 'syd', 'wsw'];
|
|
21
21
|
|
|
22
22
|
function listAvailableZones () {
|
|
23
23
|
return autocomplete.words(AVAILABLE_ZONES);
|
package/src/models/git.js
CHANGED
|
@@ -78,7 +78,19 @@ async function getFullBranch (branchName) {
|
|
|
78
78
|
|
|
79
79
|
async function getBranchCommit (refspec) {
|
|
80
80
|
const repo = await getRepo();
|
|
81
|
-
|
|
81
|
+
const oid = await git.resolveRef({ ...repo, ref: refspec });
|
|
82
|
+
// When a refspec refers to an annotated tag, the OID ref represents the annotation and not the commit directly,
|
|
83
|
+
// that's why we need a call to `readCommit`.
|
|
84
|
+
const res = await git.readCommit({ ...repo, ref: refspec, oid });
|
|
85
|
+
return res.oid;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function isExistingTag (tag) {
|
|
89
|
+
const repo = await getRepo();
|
|
90
|
+
const tags = await git.listTags({
|
|
91
|
+
...repo,
|
|
92
|
+
});
|
|
93
|
+
return tags.includes(tag);
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
async function push (remoteUrl, branchRefspec, force) {
|
|
@@ -131,4 +143,5 @@ module.exports = {
|
|
|
131
143
|
push,
|
|
132
144
|
completeBranches,
|
|
133
145
|
isShallow,
|
|
146
|
+
isExistingTag,
|
|
134
147
|
};
|
package/src/models/log-v4.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { getHostAndTokens } = require('./send-to-api.js');
|
|
1
|
+
const { getHostAndTokens, processError } = require('./send-to-api.js');
|
|
2
2
|
const colors = require('colors/safe');
|
|
3
3
|
const { Deferred } = require('./utils.js');
|
|
4
4
|
const Logger = require('../logger.js');
|
|
@@ -9,17 +9,28 @@ const { ApplicationLogStream } = require('@clevercloud/client/cjs/streams/applic
|
|
|
9
9
|
const THROTTLE_ELEMENTS = 2000;
|
|
10
10
|
const THROTTLE_PER_IN_MILLISECONDS = 100;
|
|
11
11
|
|
|
12
|
+
const retryConfiguration = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
maxRetryCount: 6,
|
|
15
|
+
};
|
|
16
|
+
|
|
12
17
|
async function displayLogs (params) {
|
|
13
18
|
|
|
14
19
|
const deferred = params.deferred || new Deferred();
|
|
15
20
|
const { apiHost, tokens } = await getHostAndTokens();
|
|
16
|
-
const { ownerId, appId, filter, since, until, deploymentId } = params;
|
|
21
|
+
const { ownerId, appId, filter, since, until, deploymentId, format } = params;
|
|
22
|
+
|
|
23
|
+
if (format === 'json' && until == null) {
|
|
24
|
+
throw new Error('"json" format is only applicable with a limiting parameter such as `--until`');
|
|
25
|
+
}
|
|
17
26
|
|
|
18
27
|
const logStream = new ApplicationLogStream({
|
|
19
28
|
apiHost,
|
|
20
29
|
tokens,
|
|
21
30
|
ownerId,
|
|
22
31
|
appId,
|
|
32
|
+
connectionTimeout: 10_000,
|
|
33
|
+
retryConfiguration,
|
|
23
34
|
since,
|
|
24
35
|
until,
|
|
25
36
|
deploymentId,
|
|
@@ -30,21 +41,41 @@ async function displayLogs (params) {
|
|
|
30
41
|
|
|
31
42
|
// Properly close the stream
|
|
32
43
|
process.once('SIGINT', (signal) => logStream.close(signal));
|
|
44
|
+
const jsonArray = new JsonArray();
|
|
33
45
|
|
|
34
46
|
logStream
|
|
35
47
|
.on('open', (event) => {
|
|
36
|
-
Logger.debug(`stream
|
|
48
|
+
Logger.debug(colors.blue(`Logs stream (open) ${JSON.stringify({ appId, filter, deploymentId })}`));
|
|
49
|
+
if (format === 'json') {
|
|
50
|
+
jsonArray.open();
|
|
51
|
+
}
|
|
37
52
|
})
|
|
38
53
|
.on('error', (event) => {
|
|
39
|
-
Logger.
|
|
54
|
+
Logger.debug(colors.red(`Logs stream (error) ${event.error.message}`));
|
|
40
55
|
})
|
|
41
56
|
.onLog((log) => {
|
|
42
|
-
|
|
57
|
+
switch (format) {
|
|
58
|
+
case 'json':
|
|
59
|
+
jsonArray.push(log);
|
|
60
|
+
return;
|
|
61
|
+
case 'json-stream':
|
|
62
|
+
Logger.printJson(log);
|
|
63
|
+
return;
|
|
64
|
+
case 'human':
|
|
65
|
+
default:
|
|
66
|
+
Logger.println(formatLogLine(log));
|
|
67
|
+
}
|
|
43
68
|
});
|
|
44
69
|
|
|
45
70
|
// start() is blocking until end of stream
|
|
46
71
|
logStream.start()
|
|
47
|
-
.then((reason) =>
|
|
72
|
+
.then((reason) => {
|
|
73
|
+
if (format === 'json') {
|
|
74
|
+
jsonArray.close();
|
|
75
|
+
}
|
|
76
|
+
return deferred.resolve();
|
|
77
|
+
})
|
|
78
|
+
.catch(processError)
|
|
48
79
|
.catch((error) => deferred.reject(error));
|
|
49
80
|
|
|
50
81
|
return logStream;
|
|
@@ -134,3 +165,30 @@ function isBuildSucessMessage (log) {
|
|
|
134
165
|
};
|
|
135
166
|
|
|
136
167
|
module.exports = { displayLogs, watchDeploymentAndDisplayLogs };
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Helper to print a real JSON array with starting `[` and ending `]`
|
|
171
|
+
*/
|
|
172
|
+
class JsonArray {
|
|
173
|
+
constructor () {
|
|
174
|
+
this._isFirst = true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
open () {
|
|
178
|
+
process.stdout.write('[\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
push (log) {
|
|
182
|
+
if (this._isFirst) {
|
|
183
|
+
this._isFirst = false;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
process.stdout.write(',\n');
|
|
187
|
+
}
|
|
188
|
+
process.stdout.write(` ${JSON.stringify(log)}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
close () {
|
|
192
|
+
process.stdout.write('\n]');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -23,12 +23,22 @@ async function sendToApi (requestParams) {
|
|
|
23
23
|
.then(prefixUrl(conf.API_HOST))
|
|
24
24
|
.then(addOauthHeader(tokens))
|
|
25
25
|
.then((requestParams) => {
|
|
26
|
-
|
|
27
|
-
Logger.debug(`${requestParams.method.toUpperCase()} ${requestParams.url} ? ${JSON.stringify(requestParams.queryParams)}`);
|
|
28
|
-
}
|
|
26
|
+
Logger.debug(`${requestParams.method.toUpperCase()} ${requestParams.url} ? ${JSON.stringify(requestParams.queryParams)}`);
|
|
29
27
|
return requestParams;
|
|
30
28
|
})
|
|
31
|
-
.then(
|
|
29
|
+
.then(request)
|
|
30
|
+
.catch(processError);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function processError (error) {
|
|
34
|
+
const code = error.code ?? error?.cause?.code;
|
|
35
|
+
if (code === 'EAI_AGAIN') {
|
|
36
|
+
throw new Error('Cannot reach the Clever Cloud API, please check your internet connection.', { cause: error });
|
|
37
|
+
}
|
|
38
|
+
if (code === 'ECONNRESET') {
|
|
39
|
+
throw new Error('The connection to the Clever Cloud API was closed abruptly, please try again.', { cause: error });
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
function sendToWarp10 (requestParams) {
|
|
@@ -45,4 +55,4 @@ async function getHostAndTokens () {
|
|
|
45
55
|
};
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
module.exports = { sendToApi, sendToWarp10, getHostAndTokens };
|
|
58
|
+
module.exports = { sendToApi, sendToWarp10, getHostAndTokens, processError };
|