clever-tools 4.3.0 → 4.4.1
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 +9 -9
- package/bin/clever.js +32 -17
- package/package.json +3 -2
- package/src/clever-client/auth-bridge.js +0 -9
- package/src/clever-client/drains.js +110 -0
- package/src/clever-client/operators.js +16 -0
- package/src/commands/drain.js +118 -62
- package/src/commands/k8s.js +1 -1
- package/src/commands/otoroshi.js +11 -0
- package/src/lib/k8s.js +1 -1
- package/src/lib/operator-commands.js +8 -0
- package/src/models/addon.js +0 -16
- package/src/models/app_configuration.js +0 -30
- package/src/models/drain.js +28 -132
- package/src/models/log-v4.js +10 -4
- package/src/parsers.js +0 -19
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The official CLI for [Clever Cloud](https://www.clever.cloud) - Deploy and manag
|
|
|
7
7
|
|
|
8
8
|
## Quick Start
|
|
9
9
|
|
|
10
|
-
**Prerequisites:** Node.js 22+
|
|
10
|
+
**Prerequisites:** Node.js 22+
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
# Install globally
|
|
@@ -38,7 +38,7 @@ For Node.js users, npm is the fastest way. For other installation methods includ
|
|
|
38
38
|
|
|
39
39
|
- Docker images
|
|
40
40
|
- Binary downloads
|
|
41
|
-
- Native packages (RPM, DEB)
|
|
41
|
+
- Native packages (RPM, DEB)
|
|
42
42
|
- Package managers (Homebrew, WinGet, AUR)
|
|
43
43
|
|
|
44
44
|
See our complete [setup guide](docs/setup-systems.md).
|
|
@@ -51,15 +51,15 @@ Enable smart autocompletion for bash or zsh:
|
|
|
51
51
|
# Bash
|
|
52
52
|
clever --bash-autocomplete-script $(which clever) | sudo tee /usr/share/bash-completion/completions/clever
|
|
53
53
|
|
|
54
|
-
# Zsh
|
|
54
|
+
# Zsh
|
|
55
55
|
clever --zsh-autocomplete-script $(which clever) | sudo tee /usr/share/zsh/site-functions/_clever
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
## Documentation
|
|
59
59
|
|
|
60
|
-
- **[Complete CLI Documentation](https://www.clever
|
|
61
|
-
- **[CLI Reference](https://www.clever
|
|
62
|
-
- **[Deployment Examples](https://www.clever
|
|
60
|
+
- **[Complete CLI Documentation](https://www.clever.cloud/developers/doc/cli/)** - Official user guide
|
|
61
|
+
- **[CLI Reference](https://www.clever.cloud/developers/doc/reference/cli/)** - Complete command reference
|
|
62
|
+
- **[Deployment Examples](https://www.clever.cloud/developers/guides/)** - Real-world tutorials
|
|
63
63
|
|
|
64
64
|
## Basic Usage
|
|
65
65
|
|
|
@@ -100,7 +100,7 @@ clever logs --since 1h
|
|
|
100
100
|
clever restart --app <app_id>
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
Learn more in our [Application Management Guide](https://www.clever
|
|
103
|
+
Learn more in our [Application Management Guide](https://www.clever.cloud/developers/doc/cli/applications/).
|
|
104
104
|
|
|
105
105
|
### Add-ons & Services Management
|
|
106
106
|
|
|
@@ -121,7 +121,7 @@ clever features enable operatos
|
|
|
121
121
|
clever keycloak get myKetcloak
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
Learn more in our [Add-ons & Services Guide](https://www.clever
|
|
124
|
+
Learn more in our [Add-ons & Services Guide](https://www.clever.cloud/developers/doc/cli/addons/).
|
|
125
125
|
|
|
126
126
|
# Create and manage
|
|
127
127
|
|
|
@@ -144,7 +144,7 @@ clever tokens create myTokenName
|
|
|
144
144
|
clever tokens create myTokenName --expiration 2w --format json
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
Learn more about API integration in our [API How-to Guide](https://www.clever
|
|
147
|
+
Learn more about API integration in our [API How-to Guide](https://www.clever.cloud/developers/api/howto).
|
|
148
148
|
|
|
149
149
|
### Get Help
|
|
150
150
|
|
package/bin/clever.js
CHANGED
|
@@ -168,8 +168,8 @@ async function run() {
|
|
|
168
168
|
}),
|
|
169
169
|
drainId: cliparse.argument('drain-id', { description: 'Drain ID' }),
|
|
170
170
|
drainType: cliparse.argument('drain-type', {
|
|
171
|
-
description: '
|
|
172
|
-
complete: Drain.
|
|
171
|
+
description: Drain.DRAIN_TYPE_CLI_CODES.join(', '),
|
|
172
|
+
complete: Drain.DRAIN_TYPE_CLI_CODES,
|
|
173
173
|
}),
|
|
174
174
|
drainUrl: cliparse.argument('drain-url', { description: 'Drain URL' }),
|
|
175
175
|
fqdn: cliparse.argument('fqdn', { description: 'Domain name of the application' }),
|
|
@@ -206,9 +206,6 @@ async function run() {
|
|
|
206
206
|
aliases: ['w'],
|
|
207
207
|
description: 'Watch the deployment until the cluster is deployed',
|
|
208
208
|
}),
|
|
209
|
-
kubeConfigForceDownload: cliparse.flag('force', {
|
|
210
|
-
description: 'Force the download of the kubeconfig file, even if it already exists',
|
|
211
|
-
}),
|
|
212
209
|
targetVersion: cliparse.option('target', {
|
|
213
210
|
metavar: 'version',
|
|
214
211
|
description: 'Target version to upgrade to (e.g.: 24, 2.4, 2.4.1)',
|
|
@@ -420,7 +417,7 @@ async function run() {
|
|
|
420
417
|
drainPassword: cliparse.option('password', {
|
|
421
418
|
aliases: ['p'],
|
|
422
419
|
metavar: 'password',
|
|
423
|
-
description: '(
|
|
420
|
+
description: 'Basic auth password (for elasticsearch or raw-http)',
|
|
424
421
|
}),
|
|
425
422
|
addonPlan: cliparse.option('plan', {
|
|
426
423
|
aliases: ['p'],
|
|
@@ -492,22 +489,22 @@ async function run() {
|
|
|
492
489
|
drainUsername: cliparse.option('username', {
|
|
493
490
|
aliases: ['u'],
|
|
494
491
|
metavar: 'username',
|
|
495
|
-
description: '(
|
|
492
|
+
description: 'Basic auth username (for elasticsearch or raw-http)',
|
|
496
493
|
}),
|
|
497
|
-
|
|
494
|
+
drainApiKey: cliparse.option('api-key', {
|
|
498
495
|
aliases: ['k'],
|
|
499
496
|
metavar: 'api_key',
|
|
500
|
-
description: '(
|
|
497
|
+
description: 'API key (for newrelic)',
|
|
501
498
|
}),
|
|
502
499
|
drainIndexPrefix: cliparse.option('index-prefix', {
|
|
503
500
|
aliases: ['i'],
|
|
504
501
|
metavar: 'index_prefix',
|
|
505
|
-
description: '
|
|
502
|
+
description: 'Optional index prefix (for elasticsearch), `logstash` value is used if not set',
|
|
506
503
|
}),
|
|
507
|
-
|
|
504
|
+
drainSdParameters: cliparse.option('sd-params', {
|
|
508
505
|
aliases: ['s'],
|
|
509
506
|
metavar: 'sd_params',
|
|
510
|
-
description: '
|
|
507
|
+
description: 'RFC5424 structured data parameters (for ovh-tcp), e.g.: `X-OVH-TOKEN=\\"REDACTED\\"`',
|
|
511
508
|
}),
|
|
512
509
|
verbose: cliparse.flag('verbose', { aliases: ['v'], description: 'Verbose output' }),
|
|
513
510
|
color: cliparse.flag('color', {
|
|
@@ -870,20 +867,29 @@ async function run() {
|
|
|
870
867
|
options: [
|
|
871
868
|
opts.drainUsername,
|
|
872
869
|
opts.drainPassword,
|
|
873
|
-
opts.
|
|
870
|
+
opts.drainApiKey,
|
|
874
871
|
opts.drainIndexPrefix,
|
|
875
|
-
opts.
|
|
872
|
+
opts.drainSdParameters,
|
|
876
873
|
],
|
|
877
874
|
},
|
|
878
875
|
drain.create,
|
|
879
876
|
);
|
|
877
|
+
const drainGetCommand = cliparse.command(
|
|
878
|
+
'get',
|
|
879
|
+
{
|
|
880
|
+
description: 'Get drain info',
|
|
881
|
+
args: [args.drainId],
|
|
882
|
+
privateOptions: [opts.humanJsonOutputFormat],
|
|
883
|
+
},
|
|
884
|
+
drain.get,
|
|
885
|
+
);
|
|
880
886
|
const drainRemoveCommand = cliparse.command(
|
|
881
887
|
'remove',
|
|
882
888
|
{
|
|
883
889
|
description: 'Remove a drain',
|
|
884
890
|
args: [args.drainId],
|
|
885
891
|
},
|
|
886
|
-
drain.
|
|
892
|
+
drain.remove,
|
|
887
893
|
);
|
|
888
894
|
const drainEnableCommand = cliparse.command(
|
|
889
895
|
'enable',
|
|
@@ -905,9 +911,9 @@ async function run() {
|
|
|
905
911
|
'drain',
|
|
906
912
|
{
|
|
907
913
|
description: 'Manage drains',
|
|
908
|
-
options: [opts.alias, opts.appIdOrName
|
|
914
|
+
options: [opts.alias, opts.appIdOrName],
|
|
909
915
|
privateOptions: [opts.humanJsonOutputFormat],
|
|
910
|
-
commands: [drainCreateCommand, drainRemoveCommand, drainEnableCommand, drainDisableCommand],
|
|
916
|
+
commands: [drainCreateCommand, drainGetCommand, drainRemoveCommand, drainEnableCommand, drainDisableCommand],
|
|
911
917
|
},
|
|
912
918
|
drain.list,
|
|
913
919
|
);
|
|
@@ -1611,6 +1617,14 @@ async function run() {
|
|
|
1611
1617
|
},
|
|
1612
1618
|
otoroshi.get,
|
|
1613
1619
|
);
|
|
1620
|
+
const otoroshiGetConfigCommand = cliparse.command(
|
|
1621
|
+
'get-config',
|
|
1622
|
+
{
|
|
1623
|
+
description: 'Get configuration of a deployed Otoroshi in otoroshictl format',
|
|
1624
|
+
args: [args.addonIdOrName],
|
|
1625
|
+
},
|
|
1626
|
+
otoroshi.getConfig,
|
|
1627
|
+
);
|
|
1614
1628
|
const otoroshiEnableNgCommand = cliparse.command(
|
|
1615
1629
|
'enable-ng',
|
|
1616
1630
|
{
|
|
@@ -1703,6 +1717,7 @@ async function run() {
|
|
|
1703
1717
|
privateOptions: [opts.humanJsonOutputFormat],
|
|
1704
1718
|
commands: [
|
|
1705
1719
|
otoroshiGetCommand,
|
|
1720
|
+
otoroshiGetConfigCommand,
|
|
1706
1721
|
otoroshiEnableNgCommand,
|
|
1707
1722
|
otoroshiDisableNgCommand,
|
|
1708
1723
|
otoroshiOpenCommand,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clever-tools",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"description": "Command Line Interface for Clever Cloud.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"node": ">=22"
|
|
91
91
|
},
|
|
92
92
|
"volta": {
|
|
93
|
-
"node": "22.17.0"
|
|
93
|
+
"node": "22.17.0",
|
|
94
|
+
"npm": "11.6.2"
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -15,15 +15,6 @@ export function listApiTokens() {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function updateApiToken(apiTokenId, { name, description }) {
|
|
19
|
-
return Promise.resolve({
|
|
20
|
-
method: 'put',
|
|
21
|
-
url: `/api-tokens/${apiTokenId}`,
|
|
22
|
-
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
23
|
-
body: { name, description },
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
18
|
export function deleteApiToken(apiTokenId) {
|
|
28
19
|
return Promise.resolve({
|
|
29
20
|
method: 'delete',
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// TODO: Move this to the Clever Cloud JS Client
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GET /v4/drains/organisations/{ownerId}/applications/{applicationId}/drains?status={status}&executionStatus={executionStatus}&executionStatusNotIn={executionStatusNotIn}
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {String} params.ownerId
|
|
7
|
+
* @param {String} params.applicationId
|
|
8
|
+
* @param {String} params.status
|
|
9
|
+
* @param {String} params.executionStatus
|
|
10
|
+
* @param {String} params.executionStatusNotIn
|
|
11
|
+
*/
|
|
12
|
+
export function getDrains(params) {
|
|
13
|
+
// no multipath for /self or /organisations/{id}
|
|
14
|
+
return Promise.resolve({
|
|
15
|
+
method: 'get',
|
|
16
|
+
url: `/v4/drains/organisations/${params.ownerId}/applications/${params.applicationId}/drains`,
|
|
17
|
+
headers: { Accept: 'application/json' },
|
|
18
|
+
queryParams: {
|
|
19
|
+
status: params.status,
|
|
20
|
+
executionStatus: params.executionStatus,
|
|
21
|
+
executionStatusNotIn: params.executionStatusNotIn,
|
|
22
|
+
},
|
|
23
|
+
// no body
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* POST /v4/drains/organisations/{ownerId}/applications/{applicationId}/drains
|
|
29
|
+
* @param {Object} params
|
|
30
|
+
* @param {String} params.ownerId
|
|
31
|
+
* @param {String} params.applicationId
|
|
32
|
+
* @param {Object} params.body
|
|
33
|
+
*/
|
|
34
|
+
export function createDrain(params) {
|
|
35
|
+
// no multipath for /self or /organisations/{id}
|
|
36
|
+
return Promise.resolve({
|
|
37
|
+
method: 'post',
|
|
38
|
+
url: `/v4/drains/organisations/${params.ownerId}/applications/${params.applicationId}/drains`,
|
|
39
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
40
|
+
body: params.body,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* DELETE /v4/drains/organisations/{ownerId}/applications/{applicationId}/drains/{drainId}
|
|
46
|
+
* @param {Object} params
|
|
47
|
+
* @param {String} params.ownerId
|
|
48
|
+
* @param {String} params.applicationId
|
|
49
|
+
* @param {String} params.drainId
|
|
50
|
+
*/
|
|
51
|
+
export function deleteDrain(params) {
|
|
52
|
+
// no multipath for /self or /organisations/{id}
|
|
53
|
+
return Promise.resolve({
|
|
54
|
+
method: 'delete',
|
|
55
|
+
url: `/v4/drains/organisations/${params.ownerId}/applications/${params.applicationId}/drains/${params.drainId}`,
|
|
56
|
+
headers: { Accept: 'application/json' },
|
|
57
|
+
// no body
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* GET /v4/drains/organisations/{ownerId}/applications/{applicationId}/drains/{drainId}
|
|
63
|
+
* @param {Object} params
|
|
64
|
+
* @param {String} params.ownerId
|
|
65
|
+
* @param {String} params.applicationId
|
|
66
|
+
* @param {String} params.drainId
|
|
67
|
+
*/
|
|
68
|
+
export function getDrain(params) {
|
|
69
|
+
// no multipath for /self or /organisations/{id}
|
|
70
|
+
return Promise.resolve({
|
|
71
|
+
method: 'get',
|
|
72
|
+
url: `/v4/drains/organisations/${params.ownerId}/applications/${params.applicationId}/drains/${params.drainId}`,
|
|
73
|
+
headers: { Accept: 'application/json' },
|
|
74
|
+
// no body
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* PUT /v4/drains/organisations/{ownerId}/applications/{applicationId}/drains/{drainId}/disable
|
|
80
|
+
* @param {Object} params
|
|
81
|
+
* @param {String} params.ownerId
|
|
82
|
+
* @param {String} params.applicationId
|
|
83
|
+
* @param {String} params.drainId
|
|
84
|
+
*/
|
|
85
|
+
export function disableDrain(params) {
|
|
86
|
+
// no multipath for /self or /organisations/{id}
|
|
87
|
+
return Promise.resolve({
|
|
88
|
+
method: 'put',
|
|
89
|
+
url: `/v4/drains/organisations/${params.ownerId}/applications/${params.applicationId}/drains/${params.drainId}/disable`,
|
|
90
|
+
headers: { Accept: 'application/json' },
|
|
91
|
+
// no body
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* PUT /v4/drains/organisations/{ownerId}/applications/{applicationId}/drains/{drainId}/enable
|
|
97
|
+
* @param {Object} params
|
|
98
|
+
* @param {String} params.ownerId
|
|
99
|
+
* @param {String} params.applicationId
|
|
100
|
+
* @param {String} params.drainId
|
|
101
|
+
*/
|
|
102
|
+
export function enableDrain(params) {
|
|
103
|
+
// no multipath for /self or /organisations/{id}
|
|
104
|
+
return Promise.resolve({
|
|
105
|
+
method: 'put',
|
|
106
|
+
url: `/v4/drains/organisations/${params.ownerId}/applications/${params.applicationId}/drains/${params.drainId}/enable`,
|
|
107
|
+
headers: { Accept: 'application/json' },
|
|
108
|
+
// no body
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -119,3 +119,19 @@ export function versionUpdate(params, body) {
|
|
|
119
119
|
body,
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* GET /v4/addon-providers/addon-otoroshi/addons/{realId}/config.yaml
|
|
125
|
+
* @param {Object} params
|
|
126
|
+
* @param {String} params.realId
|
|
127
|
+
*/
|
|
128
|
+
export function getOtoroshiConfig(params) {
|
|
129
|
+
// no multipath for /self or /organisations/{id}
|
|
130
|
+
return Promise.resolve({
|
|
131
|
+
method: 'get',
|
|
132
|
+
url: `/v4/addon-providers/addon-otoroshi/addons/${params.realId}/config.yaml`,
|
|
133
|
+
headers: { Accept: 'application/yaml' },
|
|
134
|
+
// no queryParams
|
|
135
|
+
// no body
|
|
136
|
+
});
|
|
137
|
+
}
|
package/src/commands/drain.js
CHANGED
|
@@ -1,108 +1,164 @@
|
|
|
1
|
-
import { createDrain, deleteDrain,
|
|
1
|
+
import { createDrain, deleteDrain, disableDrain, enableDrain, getDrain, getDrains } from '../clever-client/drains.js';
|
|
2
|
+
import { styleText } from '../lib/style-text.js';
|
|
2
3
|
import { Logger } from '../logger.js';
|
|
3
4
|
import * as Application from '../models/application.js';
|
|
4
|
-
import {
|
|
5
|
+
import { DRAIN_TYPE_CLI_CODES, DRAIN_TYPES, formatDrain } from '../models/drain.js';
|
|
5
6
|
import { sendToApi } from '../models/send-to-api.js';
|
|
6
7
|
|
|
7
|
-
// TODO: This could be useful in other commands
|
|
8
|
-
async function getAppOrAddonId({ alias, appIdOrName, addonId }) {
|
|
9
|
-
return addonId != null ? addonId : await Application.resolveId(appIdOrName, alias).then(({ appId }) => appId);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
8
|
export async function list(params) {
|
|
13
|
-
const { alias, app: appIdOrName,
|
|
9
|
+
const { alias, app: appIdOrName, format } = params.options;
|
|
10
|
+
|
|
11
|
+
const { ownerId, appId: applicationId } = await Application.resolveId(appIdOrName, alias);
|
|
14
12
|
|
|
15
|
-
const
|
|
16
|
-
const drains = await getDrains({ appId: appIdOrAddonId }).then(sendToApi);
|
|
13
|
+
const drains = await getDrains({ ownerId, applicationId }).then(sendToApi);
|
|
17
14
|
|
|
18
15
|
switch (format) {
|
|
19
16
|
case 'json': {
|
|
20
|
-
|
|
21
|
-
id: drain.id,
|
|
22
|
-
target: drain.target,
|
|
23
|
-
state: drain.state,
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
Logger.printJson(formattedDrains);
|
|
17
|
+
Logger.printJson(drains);
|
|
27
18
|
break;
|
|
28
19
|
}
|
|
29
20
|
case 'human':
|
|
30
21
|
default: {
|
|
31
22
|
if (drains.length === 0) {
|
|
32
|
-
Logger.println(`There are no drains for ${
|
|
23
|
+
Logger.println(`There are no drains for ${applicationId}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (drains.length === 1) {
|
|
28
|
+
const formattedDrain = formatDrain(drains[0]);
|
|
29
|
+
console.table(formattedDrain);
|
|
30
|
+
return;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
drains.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
if (structuredDataParameters != null) {
|
|
44
|
-
drainView += `, sd-params: '${structuredDataParameters}'`;
|
|
45
|
-
}
|
|
46
|
-
Logger.println(drainView);
|
|
33
|
+
const formattedDrains = drains.map((drain) => {
|
|
34
|
+
return {
|
|
35
|
+
ID: drain.id,
|
|
36
|
+
Status: drain.status.status,
|
|
37
|
+
'Execution status': drain.execution.status,
|
|
38
|
+
URL: drain.recipient.url,
|
|
39
|
+
};
|
|
47
40
|
});
|
|
41
|
+
|
|
42
|
+
console.table(formattedDrains);
|
|
48
43
|
}
|
|
49
44
|
}
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
export async function create(params) {
|
|
53
|
-
const
|
|
48
|
+
const { alias, app: appIdOrName } = params.options;
|
|
54
49
|
const {
|
|
55
|
-
alias,
|
|
56
|
-
app: appIdOrName,
|
|
57
|
-
addon: addonId,
|
|
58
50
|
username,
|
|
59
51
|
password,
|
|
60
52
|
'api-key': apiKey,
|
|
61
53
|
'index-prefix': indexPrefix,
|
|
62
|
-
'sd-params':
|
|
54
|
+
'sd-params': rfc5424StructuredDataParameters,
|
|
63
55
|
} = params.options;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
const [drainTypeCliCode, url] = params.args;
|
|
57
|
+
|
|
58
|
+
const drainType = Object.values(DRAIN_TYPES).find((drainType) => drainType.cliCode === drainTypeCliCode);
|
|
59
|
+
if (!drainType) {
|
|
60
|
+
throw new Error(`Invalid drain type. Supported types are: ${DRAIN_TYPE_CLI_CODES.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { ownerId, appId: applicationId } = await Application.resolveId(appIdOrName, alias);
|
|
64
|
+
|
|
65
|
+
const body = {
|
|
66
|
+
kind: 'LOG',
|
|
67
|
+
recipient: {
|
|
68
|
+
type: drainType.apiCode,
|
|
69
|
+
url,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (drainTypeCliCode === DRAIN_TYPES.ELASTICSEARCH.cliCode) {
|
|
74
|
+
if (!indexPrefix) {
|
|
75
|
+
throw new Error(`${DRAIN_TYPES.ELASTICSEARCH.cliCode} drains require an index prefix (--index-prefix) to be set`);
|
|
76
|
+
}
|
|
77
|
+
if (!url.endsWith('/_bulk')) {
|
|
78
|
+
throw new Error(`${DRAIN_TYPES.ELASTICSEARCH.cliCode} drain URL must end with '/_bulk'`);
|
|
79
|
+
}
|
|
80
|
+
body.recipient.index = indexPrefix;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (drainTypeCliCode === DRAIN_TYPES.ELASTICSEARCH.cliCode || drainTypeCliCode === DRAIN_TYPES.RAW_HTTP.cliCode) {
|
|
84
|
+
if (username) {
|
|
85
|
+
body.recipient.username = username;
|
|
86
|
+
}
|
|
87
|
+
if (password) {
|
|
88
|
+
body.recipient.password = password;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (drainTypeCliCode === DRAIN_TYPES.NEWRELIC.cliCode) {
|
|
93
|
+
if (!apiKey) {
|
|
94
|
+
throw new Error(`${DRAIN_TYPES.NEWRELIC.cliCode} drains require an API key (--api-key) to be set`);
|
|
95
|
+
}
|
|
96
|
+
body.recipient.apiKey = apiKey;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
drainTypeCliCode === DRAIN_TYPES.OVH_TCP.cliCode ||
|
|
101
|
+
drainTypeCliCode === DRAIN_TYPES.SYSLOG_TCP.cliCode ||
|
|
102
|
+
drainTypeCliCode === DRAIN_TYPES.SYSLOG_UDP.cliCode
|
|
103
|
+
) {
|
|
104
|
+
if (rfc5424StructuredDataParameters) {
|
|
105
|
+
body.recipient.rfc5424StructuredDataParameters = rfc5424StructuredDataParameters;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const drain = await createDrain({ ownerId, applicationId, body }).then(sendToApi);
|
|
110
|
+
Logger.printSuccess(`Drain ${styleText(['bold', 'green'], drain.id)} has been successfully created and enabled!`);
|
|
78
111
|
}
|
|
79
112
|
|
|
80
|
-
export async function
|
|
113
|
+
export async function get(params) {
|
|
81
114
|
const [drainId] = params.args;
|
|
82
|
-
const { alias, app: appIdOrName,
|
|
115
|
+
const { alias, app: appIdOrName, format } = params.options;
|
|
116
|
+
|
|
117
|
+
const { ownerId, appId: applicationId } = await Application.resolveId(appIdOrName, alias);
|
|
83
118
|
|
|
84
|
-
const
|
|
85
|
-
await deleteDrain({ appId: appIdOrAddonId, drainId }).then(sendToApi);
|
|
119
|
+
const drain = await getDrain({ ownerId, applicationId, drainId }).then(sendToApi);
|
|
86
120
|
|
|
87
|
-
|
|
121
|
+
switch (format) {
|
|
122
|
+
case 'json': {
|
|
123
|
+
Logger.printJson(drain);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'human':
|
|
127
|
+
default: {
|
|
128
|
+
const formattedDrain = formatDrain(drain);
|
|
129
|
+
console.table(formattedDrain);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function remove(params) {
|
|
135
|
+
const [drainId] = params.args;
|
|
136
|
+
const { alias, app: appIdOrName } = params.options;
|
|
137
|
+
|
|
138
|
+
const { ownerId, appId: applicationId } = await Application.resolveId(appIdOrName, alias);
|
|
139
|
+
|
|
140
|
+
await deleteDrain({ ownerId, applicationId, drainId }).then(sendToApi);
|
|
141
|
+
Logger.printSuccess(`Drain ${styleText(['bold', 'green'], drainId)} has been successfully removed!`);
|
|
88
142
|
}
|
|
89
143
|
|
|
90
144
|
export async function enable(params) {
|
|
91
145
|
const [drainId] = params.args;
|
|
92
|
-
const { alias, app: appIdOrName
|
|
146
|
+
const { alias, app: appIdOrName } = params.options;
|
|
93
147
|
|
|
94
|
-
const
|
|
95
|
-
await updateDrainState({ appId: appIdOrAddonId, drainId }, { state: 'ENABLED' }).then(sendToApi);
|
|
148
|
+
const { ownerId, appId: applicationId } = await Application.resolveId(appIdOrName, alias);
|
|
96
149
|
|
|
97
|
-
|
|
150
|
+
await enableDrain({ ownerId, applicationId, drainId }).then(sendToApi);
|
|
151
|
+
|
|
152
|
+
Logger.printSuccess(`Drain ${styleText(['bold', 'green'], drainId)} has been successfully enabled!`);
|
|
98
153
|
}
|
|
99
154
|
|
|
100
155
|
export async function disable(params) {
|
|
101
156
|
const [drainId] = params.args;
|
|
102
|
-
const { alias, app: appIdOrName
|
|
157
|
+
const { alias, app: appIdOrName } = params.options;
|
|
158
|
+
|
|
159
|
+
const { ownerId, appId: applicationId } = await Application.resolveId(appIdOrName, alias);
|
|
103
160
|
|
|
104
|
-
|
|
105
|
-
await updateDrainState({ appId: appIdOrAddonId, drainId }, { state: 'DISABLED' }).then(sendToApi);
|
|
161
|
+
await disableDrain({ ownerId, applicationId, drainId }).then(sendToApi);
|
|
106
162
|
|
|
107
|
-
Logger.
|
|
163
|
+
Logger.printSuccess(`Drain ${styleText(['bold', 'green'], drainId)} has been successfully disabled!`);
|
|
108
164
|
}
|
package/src/commands/k8s.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function del(params) {
|
|
|
73
73
|
const [clusterIdOrName] = params.args;
|
|
74
74
|
const { org: orgIdOrName, yes: confirmDeletion } = params.options;
|
|
75
75
|
|
|
76
|
-
let proceedDeletion
|
|
76
|
+
let proceedDeletion;
|
|
77
77
|
if (confirmDeletion) {
|
|
78
78
|
proceedDeletion = true;
|
|
79
79
|
} else {
|
package/src/commands/otoroshi.js
CHANGED
|
@@ -50,6 +50,17 @@ export async function get(params) {
|
|
|
50
50
|
await operatorPrint('otoroshi', addonIdOrName, format);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Print the configuration of an Otoroshi operator in otoroshictl format
|
|
55
|
+
* @param {object} params The command's parameters
|
|
56
|
+
* @param {string} params.args[0] The operator's name or ID
|
|
57
|
+
* @returns {Promise<void>}
|
|
58
|
+
*/
|
|
59
|
+
export async function getConfig(params) {
|
|
60
|
+
const [addonIdOrName] = params.args;
|
|
61
|
+
await operatorPrint('otoroshi', addonIdOrName, 'otoroshictl');
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
/**
|
|
54
65
|
* List all Otoroshi operators
|
|
55
66
|
* @returns {Promise<void>}
|
package/src/lib/k8s.js
CHANGED
|
@@ -42,7 +42,7 @@ export async function k8sCreate(name, ownerId) {
|
|
|
42
42
|
* @param {string} format The output format
|
|
43
43
|
* @returns {Promise<void>}
|
|
44
44
|
*/
|
|
45
|
-
export async function k8sList(orgIdOrName
|
|
45
|
+
export async function k8sList(orgIdOrName) {
|
|
46
46
|
const ownerId = await getOwnerIdFromOrgIdOrName(orgIdOrName);
|
|
47
47
|
const deployed = await listK8sClusters({ ownerId }).then(sendToApi);
|
|
48
48
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import {
|
|
4
|
+
getOtoroshiConfig,
|
|
4
5
|
ngDisableOperator,
|
|
5
6
|
ngEnableOperator,
|
|
6
7
|
rebootOperator,
|
|
@@ -249,6 +250,13 @@ export async function operatorRebuild(provider, addonIdOrName) {
|
|
|
249
250
|
export async function operatorPrint(provider, addonIdOrName, format = 'human') {
|
|
250
251
|
const operator = await Operator.getDetails(provider, addonIdOrName);
|
|
251
252
|
|
|
253
|
+
if (provider === 'otoroshi' && format === 'otoroshictl') {
|
|
254
|
+
const config = await getOtoroshiConfig({ realId: operator.resourceId }).then(sendToApi);
|
|
255
|
+
|
|
256
|
+
Logger.println(config);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
252
260
|
const dataToPrint = {
|
|
253
261
|
Name: operator.name,
|
|
254
262
|
ID: operator.resourceId,
|
package/src/models/addon.js
CHANGED
|
@@ -228,22 +228,6 @@ export function completePlan() {
|
|
|
228
228
|
return cliparse.autocomplete.words(['dev', 's', 'm', 'l', 'xl', 'xxl']);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
export async function findById(addonId) {
|
|
232
|
-
const { user, organisations } = await getSummary({}).then(sendToApi);
|
|
233
|
-
for (const orga of [user, ...organisations]) {
|
|
234
|
-
for (const simpleAddon of orga.addons) {
|
|
235
|
-
if (simpleAddon.id === addonId) {
|
|
236
|
-
const addon = await getAddon({ id: orga.id, addonId }).then(sendToApi);
|
|
237
|
-
return {
|
|
238
|
-
...addon,
|
|
239
|
-
orgaId: orga.id,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
throw new Error(`Could not find add-on with ID: ${addonId}`);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
231
|
export async function findByName(addonName) {
|
|
248
232
|
const { user, organisations } = await getSummary({}).then(sendToApi);
|
|
249
233
|
for (const orga of [user, ...organisations]) {
|
|
@@ -131,27 +131,6 @@ function findDefaultApp(config) {
|
|
|
131
131
|
);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
async function getAppDetailsForId(appId) {
|
|
135
|
-
const config = await loadApplicationConf();
|
|
136
|
-
|
|
137
|
-
if (_.isEmpty(config.apps)) {
|
|
138
|
-
throw new Error('There is no linked or targeted application. Use `--app` option or `clever link` command.');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const [appById, secondAppById] = _.filter(config.apps, { app_id: appId });
|
|
142
|
-
if (appById == null) {
|
|
143
|
-
throw new Error(`There are no applications matching id '${appId}'`);
|
|
144
|
-
}
|
|
145
|
-
if (secondAppById != null) {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`There are several applications matching id '${appId}'.` +
|
|
148
|
-
'This should not happen, your `.clever.json` should be fixed.',
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return appById;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
134
|
export async function getAppDetails({ alias }) {
|
|
156
135
|
const config = await loadApplicationConf();
|
|
157
136
|
const app = findApp(config, alias);
|
|
@@ -165,15 +144,6 @@ export async function getAppDetails({ alias }) {
|
|
|
165
144
|
};
|
|
166
145
|
}
|
|
167
146
|
|
|
168
|
-
export async function getMostNaturalName(appId) {
|
|
169
|
-
try {
|
|
170
|
-
const details = await getAppDetailsForId(appId);
|
|
171
|
-
return details.alias || details.name || appId;
|
|
172
|
-
} catch {
|
|
173
|
-
return appId;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
147
|
function persistConfig(modifiedConfig) {
|
|
178
148
|
const jsonContents = JSON.stringify(modifiedConfig, null, 2);
|
|
179
149
|
return fs.writeFile(conf.APP_CONFIGURATION_FILE, jsonContents);
|
package/src/models/drain.js
CHANGED
|
@@ -1,133 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
{
|
|
5
|
-
{
|
|
6
|
-
{
|
|
7
|
-
{
|
|
8
|
-
{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (keyExist(drainTargetConfig)) {
|
|
30
|
-
body.APIKey = drainTargetConfig.apiKey;
|
|
31
|
-
}
|
|
32
|
-
if (indexPrefixExist(drainTargetConfig)) {
|
|
33
|
-
body.indexPrefix = drainTargetConfig.indexPrefix;
|
|
34
|
-
}
|
|
35
|
-
if (structuredDataParametersExist(drainTargetConfig)) {
|
|
36
|
-
body.structuredDataParameters = drainTargetConfig.structuredDataParameters;
|
|
37
|
-
}
|
|
38
|
-
return body;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function authorizeDrainCreation(drainTargetType, drainTargetCredentials, drainTargetConfig) {
|
|
42
|
-
if (drainTypeExists(drainTargetType)) {
|
|
43
|
-
// retrieve field for drain type ('mandatory', 'optional', undefined)
|
|
44
|
-
const credStatus = fieldStatus(drainTargetType).credentials;
|
|
45
|
-
const keyStatus = fieldStatus(drainTargetType).apiKey;
|
|
46
|
-
const indexPrefixStatus = fieldStatus(drainTargetType).indexPrefix;
|
|
47
|
-
const structuredDataParametersStatus = fieldStatus(drainTargetType).structuredDataParameters;
|
|
48
|
-
|
|
49
|
-
if (credStatus === 'MANDATORY') {
|
|
50
|
-
return credentialsExist(drainTargetCredentials);
|
|
51
|
-
}
|
|
52
|
-
if (credStatus === 'OPTIONAL') {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
if (!credStatus && !keyStatus) {
|
|
56
|
-
return credentialsEmpty(drainTargetCredentials);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (keyStatus === 'MANDATORY') {
|
|
60
|
-
return keyExist(drainTargetConfig);
|
|
61
|
-
}
|
|
62
|
-
if (keyStatus === 'OPTIONAL') {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
if (!keyStatus) {
|
|
66
|
-
return keyEmpty(drainTargetConfig);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (indexPrefixStatus === 'MANDATORY') {
|
|
70
|
-
return indexPrefixExist(drainTargetConfig);
|
|
71
|
-
}
|
|
72
|
-
if (indexPrefixStatus === 'OPTIONAL') {
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
if (!indexPrefixStatus) {
|
|
76
|
-
return indexPrefixEmpty(drainTargetConfig);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (structuredDataParametersStatus === 'MANDATORY') {
|
|
80
|
-
return structuredDataParametersExist(drainTargetConfig);
|
|
81
|
-
}
|
|
82
|
-
if (structuredDataParametersStatus === 'OPTIONAL') {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
if (!structuredDataParametersStatus) {
|
|
86
|
-
return structuredDataParametersEmpty(drainTargetConfig);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function fieldStatus(drainTargetType) {
|
|
92
|
-
return DRAIN_TYPES.find(({ id }) => id === drainTargetType);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function drainTypeExists(drainTargetType) {
|
|
96
|
-
return DRAIN_TYPES.some(({ id }) => id === drainTargetType);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function credentialsExist({ username, password }) {
|
|
100
|
-
return username != null && password != null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function credentialsEmpty({ username, password }) {
|
|
104
|
-
return username == null && password == null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function indexPrefixExist({ indexPrefix }) {
|
|
108
|
-
return indexPrefix != null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function indexPrefixEmpty({ indexPrefix }) {
|
|
112
|
-
return indexPrefix == null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function structuredDataParametersExist({ structuredDataParameters }) {
|
|
116
|
-
return structuredDataParameters != null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function structuredDataParametersEmpty({ structuredDataParameters }) {
|
|
120
|
-
return structuredDataParameters == null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function keyExist({ apiKey }) {
|
|
124
|
-
return apiKey != null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function keyEmpty({ apiKey }) {
|
|
128
|
-
return apiKey == null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function listDrainTypes() {
|
|
132
|
-
return cliparse.autocomplete.words(DRAIN_TYPES.map((type) => type.id));
|
|
1
|
+
export const DRAIN_TYPES = {
|
|
2
|
+
DATADOG: { apiCode: 'DATADOG', cliCode: 'datadog', label: 'Datadog' },
|
|
3
|
+
ELASTICSEARCH: { apiCode: 'ELASTICSEARCH', cliCode: 'elasticsearch', label: 'Elasticsearch' },
|
|
4
|
+
NEWRELIC: { apiCode: 'NEWRELIC', cliCode: 'newrelic', label: 'New Relic' },
|
|
5
|
+
OVH_TCP: { apiCode: 'OVH_TCP', cliCode: 'ovh-tcp', label: 'OVH TCP' },
|
|
6
|
+
RAW_HTTP: { apiCode: 'RAW_HTTP', cliCode: 'raw-http', label: 'Raw HTTP' },
|
|
7
|
+
SYSLOG_TCP: { apiCode: 'SYSLOG_TCP', cliCode: 'syslog-tcp', label: 'Syslog TCP' },
|
|
8
|
+
SYSLOG_UDP: { apiCode: 'SYSLOG_UDP', cliCode: 'syslog-udp', label: 'Syslog UDP' },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const DRAIN_TYPE_CLI_CODES = Object.values(DRAIN_TYPES).map(({ cliCode }) => cliCode);
|
|
12
|
+
|
|
13
|
+
export function formatDrain(rawDrain) {
|
|
14
|
+
const drainType = DRAIN_TYPES[rawDrain.recipient.type];
|
|
15
|
+
const drainDetails = [
|
|
16
|
+
['ID', rawDrain.id],
|
|
17
|
+
['Status', rawDrain.status.status],
|
|
18
|
+
['Execution status', rawDrain.execution.status],
|
|
19
|
+
['URL', rawDrain.recipient.url],
|
|
20
|
+
['Type', drainType.label],
|
|
21
|
+
['Custom index', rawDrain.recipient.index],
|
|
22
|
+
['SD parameters', rawDrain.recipient.rfc5424StructuredDataParameters],
|
|
23
|
+
['Message output rate', Math.floor(rawDrain.backlog.msgRateOut * 60) + ' messages/minute'],
|
|
24
|
+
['Message throughput', Math.floor(rawDrain.backlog.msgRateOut) + ' bytes/second'],
|
|
25
|
+
['Backlog', rawDrain.backlog.msgBacklog + ' pending messages'],
|
|
26
|
+
['Last error', rawDrain.execution.lastError],
|
|
27
|
+
];
|
|
28
|
+
return Object.fromEntries(drainDetails.filter(([_name, value]) => value != null));
|
|
133
29
|
}
|
package/src/models/log-v4.js
CHANGED
|
@@ -134,11 +134,17 @@ export async function watchDeploymentAndDisplayLogs(options) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
if (deploymentEnded.state === 'OK') {
|
|
137
|
-
const favouriteDomain = await getBest(appId, ownerId);
|
|
138
137
|
Logger.println('');
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
);
|
|
138
|
+
|
|
139
|
+
// There can be applications without any domain, so we don't fail if we can't get one
|
|
140
|
+
const favouriteDomain = await getBest(appId, ownerId).catch(() => null);
|
|
141
|
+
|
|
142
|
+
if (favouriteDomain) {
|
|
143
|
+
Logger.println(
|
|
144
|
+
`${styleText(['bold', 'green'], '✓ Access your application:')} ${styleText(['underline', 'bold'], `https://${favouriteDomain.fqdn}`)}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
142
148
|
Logger.println(
|
|
143
149
|
`${styleText(['bold', 'blue'], '→ Manage your application:')} ${styleText(['underline', 'bold'], `${conf.GOTO_URL}/${appId}`)}`,
|
|
144
150
|
);
|
package/src/parsers.js
CHANGED
|
@@ -154,25 +154,6 @@ export function tags(string) {
|
|
|
154
154
|
return cliparse.parsers.success(tags);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
export const ipAddressRegex =
|
|
158
|
-
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])$/;
|
|
159
|
-
|
|
160
|
-
export function ipAddress(string) {
|
|
161
|
-
if (string.match(ipAddressRegex)) {
|
|
162
|
-
return cliparse.parsers.success(string);
|
|
163
|
-
}
|
|
164
|
-
return cliparse.parsers.error(`Invalid IP address '${string}'. Should match ${ipAddressRegex}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export const portNumberRegex = /^\d{1,5}$/;
|
|
168
|
-
|
|
169
|
-
export function portNumber(number) {
|
|
170
|
-
if (String(number).match(portNumberRegex)) {
|
|
171
|
-
return cliparse.parsers.success(number);
|
|
172
|
-
}
|
|
173
|
-
return cliparse.parsers.error(`Invalid port number '${number}'. Should match ${portNumberRegex}`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
157
|
/**
|
|
177
158
|
* Parse a duration into seconds
|
|
178
159
|
* A Zero seconds duration is allowed
|