clever-tools 4.2.0 → 4.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 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-cloud.com/developers/doc/cli/)** - Official user guide
61
- - **[CLI Reference](https://www.clever-cloud.com/developers/doc/reference/cli/)** - Complete command reference
62
- - **[Deployment Examples](https://www.clever-cloud.com/developers/guides/)** - Real-world tutorials
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-cloud.com/developers/doc/cli/applications/).
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-cloud.com/developers/doc/cli/addons/).
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-cloud.com/developers/api/howto).
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
@@ -28,6 +28,7 @@ import * as drain from '../src/commands/drain.js';
28
28
  import * as emails from '../src/commands/emails.js';
29
29
  import * as env from '../src/commands/env.js';
30
30
  import * as features from '../src/commands/features.js';
31
+ import * as k8s from '../src/commands/k8s.js';
31
32
  import * as keycloak from '../src/commands/keycloak.js';
32
33
  import * as kv from '../src/commands/kv.js';
33
34
  import * as link from '../src/commands/link.js';
@@ -99,6 +100,11 @@ function colorizeExperimentalCommand(command, id) {
99
100
  async function run() {
100
101
  // ARGUMENTS
101
102
  const args = {
103
+ k8sClusterName: cliparse.argument('cluster-name', { description: 'Kubernetes cluster name' }),
104
+ k8sIdOrName: cliparse.argument('id-or-name', {
105
+ description: 'Kubernetes cluster ID or name',
106
+ parser: Parsers.addonIdOrName,
107
+ }),
102
108
  kvRawCommand: cliparse.argument('command', {
103
109
  description: 'The raw command to send to the Materia KV or Redis® add-on',
104
110
  }),
@@ -162,8 +168,8 @@ async function run() {
162
168
  }),
163
169
  drainId: cliparse.argument('drain-id', { description: 'Drain ID' }),
164
170
  drainType: cliparse.argument('drain-type', {
165
- description: 'Drain type',
166
- complete: Drain.listDrainTypes,
171
+ description: Drain.DRAIN_TYPE_CLI_CODES.join(', '),
172
+ complete: Drain.DRAIN_TYPE_CLI_CODES,
167
173
  }),
168
174
  drainUrl: cliparse.argument('drain-url', { description: 'Drain URL' }),
169
175
  fqdn: cliparse.argument('fqdn', { description: 'Domain name of the application' }),
@@ -196,6 +202,13 @@ async function run() {
196
202
 
197
203
  // OPTIONS
198
204
  const opts = {
205
+ k8sDeployWatch: cliparse.flag('watch', {
206
+ aliases: ['w'],
207
+ description: 'Watch the deployment until the cluster is deployed',
208
+ }),
209
+ kubeConfigForceDownload: cliparse.flag('force', {
210
+ description: 'Force the download of the kubeconfig file, even if it already exists',
211
+ }),
199
212
  targetVersion: cliparse.option('target', {
200
213
  metavar: 'version',
201
214
  description: 'Target version to upgrade to (e.g.: 24, 2.4, 2.4.1)',
@@ -407,7 +420,7 @@ async function run() {
407
420
  drainPassword: cliparse.option('password', {
408
421
  aliases: ['p'],
409
422
  metavar: 'password',
410
- description: '(HTTP drains) basic auth password',
423
+ description: 'Basic auth password (for elasticsearch or raw-http)',
411
424
  }),
412
425
  addonPlan: cliparse.option('plan', {
413
426
  aliases: ['p'],
@@ -479,22 +492,22 @@ async function run() {
479
492
  drainUsername: cliparse.option('username', {
480
493
  aliases: ['u'],
481
494
  metavar: 'username',
482
- description: '(HTTP drains) basic auth username',
495
+ description: 'Basic auth username (for elasticsearch or raw-http)',
483
496
  }),
484
- drainAPIKey: cliparse.option('api-key', {
497
+ drainApiKey: cliparse.option('api-key', {
485
498
  aliases: ['k'],
486
499
  metavar: 'api_key',
487
- description: '(NewRelic drains) API key',
500
+ description: 'API key (for newrelic)',
488
501
  }),
489
502
  drainIndexPrefix: cliparse.option('index-prefix', {
490
503
  aliases: ['i'],
491
504
  metavar: 'index_prefix',
492
- description: '(ElasticSearch drains) optional index prefix. `logstash` value is used if not set',
505
+ description: 'Optional index prefix (for elasticsearch), `logstash` value is used if not set',
493
506
  }),
494
- drainSDParameters: cliparse.option('sd-params', {
507
+ drainSdParameters: cliparse.option('sd-params', {
495
508
  aliases: ['s'],
496
509
  metavar: 'sd_params',
497
- description: '(TCP and UDP drains) sd-params string (e.g.: `X-OVH-TOKEN=\\"REDACTED\\"`)',
510
+ description: 'RFC5424 structured data parameters (for ovh-tcp), e.g.: `X-OVH-TOKEN=\\"REDACTED\\"`',
498
511
  }),
499
512
  verbose: cliparse.flag('verbose', { aliases: ['v'], description: 'Verbose output' }),
500
513
  color: cliparse.flag('color', {
@@ -857,20 +870,29 @@ async function run() {
857
870
  options: [
858
871
  opts.drainUsername,
859
872
  opts.drainPassword,
860
- opts.drainAPIKey,
873
+ opts.drainApiKey,
861
874
  opts.drainIndexPrefix,
862
- opts.drainSDParameters,
875
+ opts.drainSdParameters,
863
876
  ],
864
877
  },
865
878
  drain.create,
866
879
  );
880
+ const drainGetCommand = cliparse.command(
881
+ 'get',
882
+ {
883
+ description: 'Get drain info',
884
+ args: [args.drainId],
885
+ privateOptions: [opts.humanJsonOutputFormat],
886
+ },
887
+ drain.get,
888
+ );
867
889
  const drainRemoveCommand = cliparse.command(
868
890
  'remove',
869
891
  {
870
892
  description: 'Remove a drain',
871
893
  args: [args.drainId],
872
894
  },
873
- drain.rm,
895
+ drain.remove,
874
896
  );
875
897
  const drainEnableCommand = cliparse.command(
876
898
  'enable',
@@ -892,9 +914,9 @@ async function run() {
892
914
  'drain',
893
915
  {
894
916
  description: 'Manage drains',
895
- options: [opts.alias, opts.appIdOrName, opts.addonId],
917
+ options: [opts.alias, opts.appIdOrName],
896
918
  privateOptions: [opts.humanJsonOutputFormat],
897
- commands: [drainCreateCommand, drainRemoveCommand, drainEnableCommand, drainDisableCommand],
919
+ commands: [drainCreateCommand, drainGetCommand, drainRemoveCommand, drainEnableCommand, drainDisableCommand],
898
920
  },
899
921
  drain.list,
900
922
  );
@@ -1043,6 +1065,73 @@ async function run() {
1043
1065
  features.list,
1044
1066
  );
1045
1067
 
1068
+ // K8S COMMAND
1069
+ const k8sListCommand = cliparse.command(
1070
+ 'list',
1071
+ {
1072
+ description: 'List Kubernetes clusters',
1073
+ options: [opts.orgaIdOrName, opts.humanJsonOutputFormat],
1074
+ },
1075
+ k8s.list,
1076
+ );
1077
+ const k8sCreateCommand = cliparse.command(
1078
+ 'create',
1079
+ {
1080
+ description: 'Create a Kubernetes cluster',
1081
+ args: [args.k8sClusterName],
1082
+ options: [opts.orgaIdOrName, opts.k8sDeployWatch],
1083
+ },
1084
+ k8s.create,
1085
+ );
1086
+ const k8sDeleteCommand = cliparse.command(
1087
+ 'delete',
1088
+ {
1089
+ description: 'Delete a Kubernetes cluster',
1090
+ options: [opts.orgaIdOrName, opts.confirmAddonDeletion],
1091
+ args: [args.k8sIdOrName],
1092
+ },
1093
+ k8s.del,
1094
+ );
1095
+ const k8sGetCommand = cliparse.command(
1096
+ 'get',
1097
+ {
1098
+ description: 'Get information about a Kubernetes cluster',
1099
+ args: [args.k8sIdOrName],
1100
+ options: [opts.orgaIdOrName, opts.humanJsonOutputFormat],
1101
+ },
1102
+ k8s.get,
1103
+ );
1104
+ const k8sGetConfigCommand = cliparse.command(
1105
+ 'get-kubeconfig',
1106
+ {
1107
+ description: 'Get configuration of a Kubernetes cluster',
1108
+ args: [args.k8sIdOrName],
1109
+ options: [opts.orgaIdOrName],
1110
+ },
1111
+ k8s.getConfig,
1112
+ );
1113
+ const k8sAddPersistentStorageCommand = cliparse.command(
1114
+ 'add-persistent-storage',
1115
+ {
1116
+ description: 'Activate persistent storage to a deployed Kubernetes cluster',
1117
+ args: [args.k8sIdOrName],
1118
+ options: [opts.orgaIdOrName],
1119
+ },
1120
+ k8s.addPersistentStorage,
1121
+ );
1122
+
1123
+ const k8sCommand = cliparse.command('k8s', {
1124
+ description: 'Manage Kubernetes clusters',
1125
+ commands: [
1126
+ k8sListCommand,
1127
+ k8sCreateCommand,
1128
+ k8sAddPersistentStorageCommand,
1129
+ k8sDeleteCommand,
1130
+ k8sGetCommand,
1131
+ k8sGetConfigCommand,
1132
+ ],
1133
+ });
1134
+
1046
1135
  // KEYCLOAK COMMAND
1047
1136
  const keycloakGetCommand = cliparse.command(
1048
1137
  'get',
@@ -1531,6 +1620,14 @@ async function run() {
1531
1620
  },
1532
1621
  otoroshi.get,
1533
1622
  );
1623
+ const otoroshiGetConfigCommand = cliparse.command(
1624
+ 'get-config',
1625
+ {
1626
+ description: 'Get configuration of a deployed Otoroshi in otoroshictl format',
1627
+ args: [args.addonIdOrName],
1628
+ },
1629
+ otoroshi.getConfig,
1630
+ );
1534
1631
  const otoroshiEnableNgCommand = cliparse.command(
1535
1632
  'enable-ng',
1536
1633
  {
@@ -1623,6 +1720,7 @@ async function run() {
1623
1720
  privateOptions: [opts.humanJsonOutputFormat],
1624
1721
  commands: [
1625
1722
  otoroshiGetCommand,
1723
+ otoroshiGetConfigCommand,
1626
1724
  otoroshiEnableNgCommand,
1627
1725
  otoroshiDisableNgCommand,
1628
1726
  otoroshiOpenCommand,
@@ -2048,6 +2146,10 @@ async function run() {
2048
2146
  commands.push(colorizeExperimentalCommand(otoroshiCommand, 'operators'));
2049
2147
  }
2050
2148
 
2149
+ if (featuresFromConf.k8s) {
2150
+ commands.push(colorizeExperimentalCommand(k8sCommand, 'k8s'));
2151
+ }
2152
+
2051
2153
  if (featuresFromConf.kv) {
2052
2154
  commands.push(colorizeExperimentalCommand(kvRawCommand, 'kv'));
2053
2155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
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
  }
@@ -0,0 +1,127 @@
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
+ }
111
+
112
+ /**
113
+ * PATCH /v4/drains/organisations/{ownerId}/applications/{applicationId}/drains/{drainId}/reset-cursor
114
+ * @param {Object} params
115
+ * @param {String} params.ownerId
116
+ * @param {String} params.applicationId
117
+ * @param {String} params.drainId
118
+ */
119
+ export function resetDrainCursor(params) {
120
+ // no multipath for /self or /organisations/{id}
121
+ return Promise.resolve({
122
+ method: 'patch',
123
+ url: `/v4/drains/organisations/${params.ownerId}/applications/${params.applicationId}/drains/${params.drainId}/reset-cursor`,
124
+ headers: { Accept: 'application/json' },
125
+ // no body
126
+ });
127
+ }
@@ -0,0 +1,96 @@
1
+ // TODO: Move this to the Clever Cloud JS Client
2
+
3
+ /**
4
+ * POST /v4/kubernetes/organisations/{ownerId}/clusters
5
+ * @param {Object} params
6
+ * @param {String} params.ownerId
7
+ * @param {Object} body
8
+ */
9
+ export function createK8sCluster(params, body) {
10
+ return Promise.resolve({
11
+ method: 'post',
12
+ url: `/v4/kubernetes/organisations/${params.ownerId}/clusters`,
13
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
14
+ // no queryParams
15
+ body,
16
+ });
17
+ }
18
+
19
+ /**
20
+ * DELETE /v4/kubernetes/organisations/{ownerId}/clusters/{clusterId}
21
+ * @param {Object} params
22
+ * @param {String} params.ownerId
23
+ * @param {String} params.clusterId
24
+ */
25
+ export function deleteK8sCluster(params) {
26
+ return Promise.resolve({
27
+ method: 'delete',
28
+ url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}`,
29
+ headers: { Accept: 'application/json' },
30
+ // no queryParams
31
+ // no body
32
+ });
33
+ }
34
+
35
+ /**
36
+ * GET /v4/kubernetes/organisations/{ownerId}/clusters
37
+ * @param {Object} params
38
+ * @param {String} params.ownerId
39
+ */
40
+ export function listK8sClusters(params) {
41
+ return Promise.resolve({
42
+ method: 'get',
43
+ url: `/v4/kubernetes/organisations/${params.ownerId}/clusters`,
44
+ headers: { Accept: 'application/json' },
45
+ // no queryParams
46
+ // no body
47
+ });
48
+ }
49
+
50
+ /**
51
+ * GET /v4/kubernetes/organisations/{ownerId}/clusters/{clusterId}
52
+ * @param {Object} params
53
+ * @param {String} params.ownerId
54
+ * @param {String} params.clusterId
55
+ */
56
+ export function getK8sAddon(params) {
57
+ return Promise.resolve({
58
+ method: 'get',
59
+ url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}`,
60
+ headers: { Accept: 'application/json' },
61
+ // no queryParams
62
+ // no body
63
+ });
64
+ }
65
+
66
+ /**
67
+ * GET /v4/kubernetes/organisations/{ownerId}/clusters/{clusterId}/kubeconfig.yaml
68
+ * @param {Object} params
69
+ * @param {String} params.ownerId
70
+ * @param {String} params.clusterId
71
+ */
72
+ export function getK8sConfig(params) {
73
+ return Promise.resolve({
74
+ method: 'get',
75
+ url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}/kubeconfig.yaml`,
76
+ // headers: { Accept: 'application/x-yaml' },
77
+ // no queryParams
78
+ // no body
79
+ });
80
+ }
81
+
82
+ /**
83
+ * POST /v4/kubernetes/organisations/ownerId}/clusters/{clusterId}/csi/ceph
84
+ * @param {Object} params
85
+ * @param {String} params.ownerId
86
+ * @param {String} params.clusterId
87
+ */
88
+ export function addK8sPersistentStorage(params) {
89
+ return Promise.resolve({
90
+ method: 'post',
91
+ url: `/v4/kubernetes/organisations/${params.ownerId}/clusters/${params.clusterId}/csi/ceph`,
92
+ headers: { Accept: 'application/json' },
93
+ // no queryParams
94
+ // no body
95
+ });
96
+ }
@@ -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
+ }