clever-tools 4.8.0 → 4.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.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/src/clever-client/k8s.js +197 -0
  3. package/src/commands/addon/addon.docs.md +1 -1
  4. package/src/commands/global.commands.js +34 -0
  5. package/src/commands/global.options.js +1 -1
  6. package/src/commands/k8s/k8s.activity.command.js +53 -0
  7. package/src/commands/k8s/k8s.args.js +6 -0
  8. package/src/commands/k8s/k8s.create.command.js +87 -3
  9. package/src/commands/k8s/k8s.delete.command.js +13 -18
  10. package/src/commands/k8s/k8s.docs.md +264 -1
  11. package/src/commands/k8s/k8s.get.command.js +65 -4
  12. package/src/commands/k8s/k8s.nodegroups.command.js +7 -0
  13. package/src/commands/k8s/k8s.nodegroups.create.command.js +79 -0
  14. package/src/commands/k8s/k8s.nodegroups.delete.command.js +34 -0
  15. package/src/commands/k8s/k8s.nodegroups.get.command.js +66 -0
  16. package/src/commands/k8s/k8s.nodegroups.list.command.js +49 -0
  17. package/src/commands/k8s/k8s.nodegroups.update.command.js +86 -0
  18. package/src/commands/k8s/k8s.quota.command.js +77 -0
  19. package/src/commands/k8s/k8s.update.command.js +72 -0
  20. package/src/commands/k8s/k8s.version.check.command.js +18 -0
  21. package/src/commands/k8s/k8s.version.command.js +18 -0
  22. package/src/commands/k8s/k8s.version.update.command.js +18 -0
  23. package/src/commands/oauth-consumers/oauth-consumers.docs.md +1 -1
  24. package/src/commands/otoroshi/otoroshi.docs.md +14 -0
  25. package/src/commands/otoroshi/otoroshi.open.swaggerui.command.js +13 -0
  26. package/src/lib/k8s.js +411 -11
  27. package/src/lib/operator-commands.js +15 -1
  28. package/src/lib/prompts.js +6 -2
  29. package/src/parsers.js +10 -0
@@ -12,6 +12,28 @@ Manage Kubernetes clusters
12
12
  clever k8s
13
13
  ```
14
14
 
15
+ ## ➡️ `clever k8s activity` <kbd>Since 4.9.0</kbd>
16
+
17
+ Show recent deployment events of a Kubernetes cluster
18
+
19
+ ```bash
20
+ clever k8s activity <cluster-id|cluster-name> [options]
21
+ ```
22
+
23
+ ### 📥 Arguments
24
+
25
+ |Name|Description|
26
+ |---|---|
27
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
28
+
29
+ ### ⚙️ Options
30
+
31
+ |Name|Description|
32
+ |---|---|
33
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
34
+ |`--limit` `<limit>`|Number of events to fetch (1 to 1000) (default: 50)|
35
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
36
+
15
37
  ## ➡️ `clever k8s add-persistent-storage` <kbd>Since 4.3.0</kbd>
16
38
 
17
39
  Activate persistent storage to a deployed Kubernetes cluster
@@ -50,8 +72,18 @@ clever k8s create <cluster-name> [options]
50
72
 
51
73
  |Name|Description|
52
74
  |---|---|
75
+ |`--autoscaling`|Enable the cluster autoscaler|
76
+ |`--cluster-version` `<cluster-version>`|Kubernetes version to deploy (e.g.: 1.36)|
77
+ |`--description` `<description>`|Free-form cluster description|
78
+ |`--flavor` `<flavor>`|Control plane flavor|
79
+ |`--nodegroup` `<flavor:count>`|Initial node group (format: <flavor>:<count>, e.g.: XS:3)|
53
80
  |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
81
+ |`--persistent-storage`|Enable persistent storage (Ceph CSI)|
82
+ |`--replication-factor` `<replication-factor>`|Control plane replication factor|
83
+ |`--tag` `<tag[,tag...]>`|Semantic tags (comma-separated, e.g.: env:prod,team:platform)|
84
+ |`--topology` `<topology>`|Cluster topology (must be set with --flavor and --replication-factor)|
54
85
  |`-w`, `--watch`|Watch the deployment until the cluster is deployed|
86
+ |`-y`, `--yes`|Skip confirmation prompts|
55
87
 
56
88
  ## ➡️ `clever k8s delete` <kbd>Since 4.3.0</kbd>
57
89
 
@@ -72,7 +104,7 @@ clever k8s delete <cluster-id|cluster-name> [options]
72
104
  |Name|Description|
73
105
  |---|---|
74
106
  |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
75
- |`-y`, `--yes`|Skip confirmation and proceed with deletion directly|
107
+ |`-y`, `--yes`|Skip confirmation prompts|
76
108
 
77
109
  ## ➡️ `clever k8s get` <kbd>Since 4.3.0</kbd>
78
110
 
@@ -129,3 +161,234 @@ clever k8s list [options]
129
161
  |---|---|
130
162
  |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
131
163
  |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
164
+
165
+ ## ➡️ `clever k8s nodegroups` <kbd>Since 4.9.0</kbd>
166
+
167
+ Manage Kubernetes node groups
168
+
169
+ ```bash
170
+ clever k8s nodegroups
171
+ ```
172
+
173
+ ## ➡️ `clever k8s nodegroups create` <kbd>Since 4.9.0</kbd>
174
+
175
+ Create a node group on a Kubernetes cluster
176
+
177
+ ```bash
178
+ clever k8s nodegroups create <cluster-id|cluster-name> <nodegroup-name> <flavor:count> [options]
179
+ ```
180
+
181
+ ### 📥 Arguments
182
+
183
+ |Name|Description|
184
+ |---|---|
185
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
186
+ |`nodegroup-name`|Node group name (lowercase RFC 1123, max 63 chars)|
187
+ |`flavor:count`|Node group flavor and target node count (format: <flavor>:<count>, e.g.: XS:3)|
188
+
189
+ ### ⚙️ Options
190
+
191
+ |Name|Description|
192
+ |---|---|
193
+ |`--autoscaling`|Enable cluster autoscaler for this node group (requires --min and --max)|
194
+ |`--description` `<description>`|Free-form node group description|
195
+ |`--max` `<max>`|Maximum node count when autoscaling is enabled|
196
+ |`--min` `<min>`|Minimum node count when autoscaling is enabled|
197
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
198
+ |`--tag` `<tag>`|Arbitrary tag attached to the node group|
199
+
200
+ ## ➡️ `clever k8s nodegroups delete` <kbd>Since 4.9.0</kbd>
201
+
202
+ Delete a node group from a Kubernetes cluster
203
+
204
+ ```bash
205
+ clever k8s nodegroups delete <cluster-id|cluster-name> <nodegroup-id|nodegroup-name> [options]
206
+ ```
207
+
208
+ ### 📥 Arguments
209
+
210
+ |Name|Description|
211
+ |---|---|
212
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
213
+ |`nodegroup-id|nodegroup-name`|Kubernetes node group ID or name|
214
+
215
+ ### ⚙️ Options
216
+
217
+ |Name|Description|
218
+ |---|---|
219
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
220
+ |`-y`, `--yes`|Skip confirmation prompts|
221
+
222
+ ## ➡️ `clever k8s nodegroups get` <kbd>Since 4.9.0</kbd>
223
+
224
+ Get information about a Kubernetes node group
225
+
226
+ ```bash
227
+ clever k8s nodegroups get <cluster-id|cluster-name> <nodegroup-id|nodegroup-name> [options]
228
+ ```
229
+
230
+ ### 📥 Arguments
231
+
232
+ |Name|Description|
233
+ |---|---|
234
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
235
+ |`nodegroup-id|nodegroup-name`|Kubernetes node group ID or name|
236
+
237
+ ### ⚙️ Options
238
+
239
+ |Name|Description|
240
+ |---|---|
241
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
242
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
243
+
244
+ ## ➡️ `clever k8s nodegroups list` <kbd>Since 4.9.0</kbd>
245
+
246
+ List the node groups of a Kubernetes cluster
247
+
248
+ ```bash
249
+ clever k8s nodegroups list <cluster-id|cluster-name> [options]
250
+ ```
251
+
252
+ ### 📥 Arguments
253
+
254
+ |Name|Description|
255
+ |---|---|
256
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
257
+
258
+ ### ⚙️ Options
259
+
260
+ |Name|Description|
261
+ |---|---|
262
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
263
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
264
+
265
+ ## ➡️ `clever k8s nodegroups update` <kbd>Since 4.9.0</kbd>
266
+
267
+ Update a node group on a Kubernetes cluster
268
+
269
+ ```bash
270
+ clever k8s nodegroups update <cluster-id|cluster-name> <nodegroup-id|nodegroup-name> [options]
271
+ ```
272
+
273
+ ### 📥 Arguments
274
+
275
+ |Name|Description|
276
+ |---|---|
277
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
278
+ |`nodegroup-id|nodegroup-name`|Kubernetes node group ID or name|
279
+
280
+ ### ⚙️ Options
281
+
282
+ |Name|Description|
283
+ |---|---|
284
+ |`--autoscaling`|Enable the cluster autoscaler|
285
+ |`--count` `<count>`|Target node count|
286
+ |`--description` `<description>`|Free-form node group description|
287
+ |`--disable-autoscaling`|Disable the cluster autoscaler|
288
+ |`--max` `<max>`|Maximum node count (autoscaling bound)|
289
+ |`--min` `<min>`|Minimum node count (autoscaling bound)|
290
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
291
+ |`--tag` `<tag>`|Arbitrary tag attached to the node group|
292
+
293
+ ## ➡️ `clever k8s quota` <kbd>Since 4.9.0</kbd>
294
+
295
+ Get the Kubernetes quota, usage and remaining of an organisation
296
+
297
+ ```bash
298
+ clever k8s quota [options]
299
+ ```
300
+
301
+ ### ⚙️ Options
302
+
303
+ |Name|Description|
304
+ |---|---|
305
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
306
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
307
+
308
+ ## ➡️ `clever k8s update` <kbd>Since 4.9.0</kbd>
309
+
310
+ Update a Kubernetes cluster metadata or features
311
+
312
+ ```bash
313
+ clever k8s update <cluster-id|cluster-name> [options]
314
+ ```
315
+
316
+ ### 📥 Arguments
317
+
318
+ |Name|Description|
319
+ |---|---|
320
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
321
+
322
+ ### ⚙️ Options
323
+
324
+ |Name|Description|
325
+ |---|---|
326
+ |`--autoscaling`|Enable the cluster autoscaler|
327
+ |`--description` `<description>`|Free-form cluster description|
328
+ |`--disable-autoscaling`|Disable the cluster autoscaler|
329
+ |`--name` `<name>`|Rename the cluster|
330
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
331
+ |`--tag` `<tag[,tag...]>`|Replace tags (comma-separated, e.g.: env:prod,team:platform)|
332
+
333
+ ## ➡️ `clever k8s version` <kbd>Since 4.9.0</kbd>
334
+
335
+ Check a Kubernetes cluster deployed version
336
+
337
+ ```bash
338
+ clever k8s version <cluster-id|cluster-name> [options]
339
+ ```
340
+
341
+ ### 📥 Arguments
342
+
343
+ |Name|Description|
344
+ |---|---|
345
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
346
+
347
+ ### ⚙️ Options
348
+
349
+ |Name|Description|
350
+ |---|---|
351
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
352
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
353
+
354
+ ## ➡️ `clever k8s version check` <kbd>Since 4.9.0</kbd>
355
+
356
+ Check a Kubernetes cluster deployed version
357
+
358
+ ```bash
359
+ clever k8s version check <cluster-id|cluster-name> [options]
360
+ ```
361
+
362
+ ### 📥 Arguments
363
+
364
+ |Name|Description|
365
+ |---|---|
366
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
367
+
368
+ ### ⚙️ Options
369
+
370
+ |Name|Description|
371
+ |---|---|
372
+ |`-F`, `--format` `<format>`|Output format (human, json) (default: human)|
373
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
374
+
375
+ ## ➡️ `clever k8s version update` <kbd>Since 4.9.0</kbd>
376
+
377
+ Update a Kubernetes cluster to a target version
378
+
379
+ ```bash
380
+ clever k8s version update <cluster-id|cluster-name> [options]
381
+ ```
382
+
383
+ ### 📥 Arguments
384
+
385
+ |Name|Description|
386
+ |---|---|
387
+ |`cluster-id|cluster-name`|Kubernetes cluster ID or name|
388
+
389
+ ### ⚙️ Options
390
+
391
+ |Name|Description|
392
+ |---|---|
393
+ |`-o`, `--org`, `--owner` `<org-id\|org-name>`|Organisation to target by its ID (or name, if unambiguous)|
394
+ |`--target` `<version>`|Target version to upgrade to (e.g.: 24, 2.4, 2.4.1)|
@@ -23,13 +23,67 @@ export const k8sGetCommand = defineCommand({
23
23
  Logger.printJson(k8sInfo);
24
24
  break;
25
25
  case 'human':
26
- default:
27
- console.table({
26
+ default: {
27
+ const topo = k8sInfo.topologyConfig;
28
+ const overview = {
28
29
  Name: k8sInfo.name,
29
30
  ID: k8sInfo.id,
30
- Version: k8sInfo.version,
31
31
  Status: k8sInfo.status,
32
- });
32
+ Version: k8sInfo.version,
33
+ Topology: formatTopology(topo),
34
+ Autoscaling: k8sInfo.features?.autoscalingEnabled ? 'enabled' : 'disabled',
35
+ 'Persistent storage': k8sInfo.features?.csi != null ? 'enabled' : 'disabled',
36
+ };
37
+ if (k8sInfo.tags?.length) overview.Tags = k8sInfo.tags.join(', ');
38
+ if (k8sInfo.description) overview.Description = k8sInfo.description;
39
+ if (k8sInfo.storageUsageBytes != null) {
40
+ overview.Storage = `${Math.round((k8sInfo.storageUsageBytes / 1024 ** 3) * 100) / 100} GB`;
41
+ }
42
+ console.table(overview);
43
+
44
+ if (topo?.topology === 'DISTRIBUTED' && topo.components) {
45
+ Logger.println('');
46
+ Logger.println('🧩 Control plane components');
47
+ console.table(
48
+ Object.fromEntries(
49
+ Object.entries(topo.components).map(([name, c]) => [
50
+ name,
51
+ { Flavor: c?.flavor ?? '-', RF: c?.replicationFactor ?? '-' },
52
+ ]),
53
+ ),
54
+ );
55
+ }
56
+
57
+ if (k8sInfo.nodeGroups?.length) {
58
+ Logger.println('');
59
+ Logger.println(`🧮 Node groups (${k8sInfo.nodeGroups.length})`);
60
+ console.table(
61
+ Object.fromEntries(
62
+ k8sInfo.nodeGroups.map((ng) => [
63
+ ng.id,
64
+ {
65
+ Name: ng.name,
66
+ Status: ng.status,
67
+ Flavor: ng.flavor,
68
+ Nodes: `${ng.currentNodeCount}/${ng.targetNodeCount}`,
69
+ },
70
+ ]),
71
+ ),
72
+ );
73
+ }
74
+
75
+ if (k8sInfo.loadBalancers?.length) {
76
+ Logger.println('');
77
+ Logger.println(`🔀 Load balancers (${k8sInfo.loadBalancers.length})`);
78
+ k8sInfo.loadBalancers.forEach((lb) => {
79
+ console.table({
80
+ ID: lb.id,
81
+ Flavor: lb.flavor,
82
+ IPs: lb.ips?.join(', ') ?? '-',
83
+ Domain: lb.domainName ?? '-',
84
+ });
85
+ });
86
+ }
33
87
 
34
88
  Logger.println('');
35
89
  const orgMessageComplement = orgIdOrName ? `--org "${orgIdOrName.orga_id || orgIdOrName.orga_name}"` : '';
@@ -38,6 +92,13 @@ export const k8sGetCommand = defineCommand({
38
92
  `Once ACTIVE, get the kubeconfig with ${styleText('blue', `clever k8s get-kubeconfig ${k8sInfo.id} ${orgMessageComplement}`)}`,
39
93
  );
40
94
  break;
95
+ }
41
96
  }
42
97
  },
43
98
  });
99
+
100
+ function formatTopology(topo) {
101
+ if (topo == null) return '-';
102
+ if (topo.topology === 'DISTRIBUTED') return 'DISTRIBUTED';
103
+ return `${topo.topology} (${topo.flavor}, rf=${topo.replicationFactor})`;
104
+ }
@@ -0,0 +1,7 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+
3
+ export const k8sNodeGroupCommand = defineCommand({
4
+ description: 'Manage Kubernetes node groups',
5
+ since: '4.9.0',
6
+ featureFlag: 'k8s',
7
+ });
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ import { defineArgument } from '../../lib/define-argument.js';
3
+ import { defineCommand } from '../../lib/define-command.js';
4
+ import { defineOption } from '../../lib/define-option.js';
5
+ import { k8sCreateNodeGroup } from '../../lib/k8s.js';
6
+ import { styleText } from '../../lib/style-text.js';
7
+ import { Logger } from '../../logger.js';
8
+ import { flavorCount } from '../../parsers.js';
9
+ import { orgaIdOrNameOption } from '../global.options.js';
10
+ import { k8sIdOrNameArg } from './k8s.args.js';
11
+
12
+ export const k8sNodeGroupCreateCommand = defineCommand({
13
+ description: 'Create a node group on a Kubernetes cluster',
14
+ since: '4.9.0',
15
+ options: {
16
+ description: defineOption({
17
+ name: 'description',
18
+ schema: z.string().max(4096).optional(),
19
+ description: 'Free-form node group description',
20
+ placeholder: 'description',
21
+ }),
22
+ tag: defineOption({
23
+ name: 'tag',
24
+ schema: z.string().max(1024).optional(),
25
+ description: 'Arbitrary tag attached to the node group',
26
+ placeholder: 'tag',
27
+ }),
28
+ autoscaling: defineOption({
29
+ name: 'autoscaling',
30
+ schema: z.boolean().default(false),
31
+ description: 'Enable cluster autoscaler for this node group (requires --min and --max)',
32
+ }),
33
+ min: defineOption({
34
+ name: 'min',
35
+ schema: z.coerce.number().int().min(0).max(256).optional(),
36
+ description: 'Minimum node count when autoscaling is enabled',
37
+ placeholder: 'min',
38
+ }),
39
+ max: defineOption({
40
+ name: 'max',
41
+ schema: z.coerce.number().int().min(0).max(256).optional(),
42
+ description: 'Maximum node count when autoscaling is enabled',
43
+ placeholder: 'max',
44
+ }),
45
+ org: orgaIdOrNameOption,
46
+ },
47
+ args: [
48
+ k8sIdOrNameArg,
49
+ defineArgument({
50
+ schema: z
51
+ .string()
52
+ .regex(
53
+ /^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$/,
54
+ 'Must be lowercase RFC 1123 (letters, digits, "-"), start/end alphanumeric, max 63 chars',
55
+ ),
56
+ description: 'Node group name (lowercase RFC 1123, max 63 chars)',
57
+ placeholder: 'nodegroup-name',
58
+ }),
59
+ defineArgument({
60
+ schema: z.string().transform(flavorCount),
61
+ description: 'Node group flavor and target node count (format: <flavor>:<count>, e.g.: XS:3)',
62
+ placeholder: 'flavor:count',
63
+ }),
64
+ ],
65
+ async handler(options, clusterIdOrName, nodeGroupName, spec) {
66
+ const { org: orgIdOrName } = options;
67
+ const nodeGroup = await k8sCreateNodeGroup(orgIdOrName, clusterIdOrName, {
68
+ name: nodeGroupName,
69
+ flavor: spec.flavor,
70
+ targetNodeCount: spec.targetNodeCount,
71
+ description: options.description,
72
+ tag: options.tag,
73
+ autoscaling: options.autoscaling,
74
+ min: options.min,
75
+ max: options.max,
76
+ });
77
+ Logger.println(`🚀 Node group ${styleText('white', `${nodeGroup.name} (${nodeGroup.id})`)} is being deployed`);
78
+ },
79
+ });
@@ -0,0 +1,34 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+ import { k8sDeleteNodeGroup } from '../../lib/k8s.js';
3
+ import { ask } from '../../lib/prompts.js';
4
+ import { styleText } from '../../lib/style-text.js';
5
+ import { Logger } from '../../logger.js';
6
+ import { orgaIdOrNameOption, skipConfirmationOption } from '../global.options.js';
7
+ import { k8sIdOrNameArg, k8sNodeGroupIdOrNameArg } from './k8s.args.js';
8
+
9
+ export const k8sNodeGroupDeleteCommand = defineCommand({
10
+ description: 'Delete a node group from a Kubernetes cluster',
11
+ since: '4.9.0',
12
+ options: {
13
+ org: orgaIdOrNameOption,
14
+ yes: skipConfirmationOption,
15
+ },
16
+ args: [k8sIdOrNameArg, k8sNodeGroupIdOrNameArg],
17
+ async handler(options, clusterIdOrName, nodeGroupIdOrName) {
18
+ const { org: orgIdOrName, yes } = options;
19
+
20
+ if (!yes) {
21
+ const proceed = await ask(
22
+ `Are you sure you want to delete the node group ${styleText('blue', nodeGroupIdOrName)}?`,
23
+ false,
24
+ );
25
+ if (!proceed) {
26
+ Logger.println('Node group deletion cancelled');
27
+ return;
28
+ }
29
+ }
30
+
31
+ await k8sDeleteNodeGroup(orgIdOrName, clusterIdOrName, nodeGroupIdOrName);
32
+ Logger.printSuccess(`Node group ${styleText('green', nodeGroupIdOrName)} successfully deleted`);
33
+ },
34
+ });
@@ -0,0 +1,66 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+ import { k8sGetNodeGroup } from '../../lib/k8s.js';
3
+ import { Logger } from '../../logger.js';
4
+ import { humanJsonOutputFormatOption, orgaIdOrNameOption } from '../global.options.js';
5
+ import { k8sIdOrNameArg, k8sNodeGroupIdOrNameArg } from './k8s.args.js';
6
+
7
+ export const k8sNodeGroupGetCommand = defineCommand({
8
+ description: 'Get information about a Kubernetes node group',
9
+ since: '4.9.0',
10
+ options: {
11
+ org: orgaIdOrNameOption,
12
+ format: humanJsonOutputFormatOption,
13
+ },
14
+ args: [k8sIdOrNameArg, k8sNodeGroupIdOrNameArg],
15
+ async handler(options, clusterIdOrName, nodeGroupIdOrName) {
16
+ const { format, org: orgIdOrName } = options;
17
+ const nodeGroup = await k8sGetNodeGroup(orgIdOrName, clusterIdOrName, nodeGroupIdOrName);
18
+
19
+ switch (format) {
20
+ case 'json':
21
+ Logger.printJson(nodeGroup);
22
+ break;
23
+ case 'human':
24
+ default: {
25
+ const overview = {
26
+ Name: nodeGroup.name,
27
+ ID: nodeGroup.id,
28
+ Status: nodeGroup.status,
29
+ Flavor: nodeGroup.flavor,
30
+ Nodes: `${nodeGroup.currentNodeCount}/${nodeGroup.targetNodeCount}`,
31
+ Autoscaling: nodeGroup.autoscalingEnabled ? 'enabled' : 'disabled',
32
+ 'Min / Max': `${nodeGroup.minNodeCount} / ${nodeGroup.maxNodeCount}${nodeGroup.autoscalingEnabled ? '' : ' (inactive)'}`,
33
+ Created: formatDate(nodeGroup.createdAt),
34
+ };
35
+ const createdFmt = formatDate(nodeGroup.createdAt);
36
+ const updatedFmt = formatDate(nodeGroup.updatedAt);
37
+ if (nodeGroup.updatedAt && updatedFmt !== createdFmt) {
38
+ overview.Updated = updatedFmt;
39
+ }
40
+ if (nodeGroup.description) overview.Description = nodeGroup.description;
41
+ if (nodeGroup.tag) overview.Tag = nodeGroup.tag;
42
+ console.table(overview);
43
+
44
+ const labels = Object.entries(nodeGroup.labels ?? {});
45
+ if (labels.length > 0) {
46
+ Logger.println('');
47
+ Logger.println(`🏷️ Labels (${labels.length})`);
48
+ console.table(Object.fromEntries(labels.map(([k, v]) => [k, { Value: v }])));
49
+ }
50
+
51
+ const taints = nodeGroup.taints ?? [];
52
+ if (taints.length > 0) {
53
+ Logger.println('');
54
+ Logger.println(`🚫 Taints (${taints.length})`);
55
+ console.table(Object.fromEntries(taints.map((t) => [t.key, { Value: t.value ?? '-', Effect: t.effect }])));
56
+ }
57
+ break;
58
+ }
59
+ }
60
+ },
61
+ });
62
+
63
+ function formatDate(iso) {
64
+ if (iso == null) return '-';
65
+ return `${iso.slice(0, 16).replace('T', ' ')}Z`;
66
+ }
@@ -0,0 +1,49 @@
1
+ import { defineCommand } from '../../lib/define-command.js';
2
+ import { k8sListNodeGroups } from '../../lib/k8s.js';
3
+ import { styleText } from '../../lib/style-text.js';
4
+ import { Logger } from '../../logger.js';
5
+ import { humanJsonOutputFormatOption, orgaIdOrNameOption } from '../global.options.js';
6
+ import { k8sIdOrNameArg } from './k8s.args.js';
7
+
8
+ export const k8sNodeGroupListCommand = defineCommand({
9
+ description: 'List the node groups of a Kubernetes cluster',
10
+ since: '4.9.0',
11
+ options: {
12
+ org: orgaIdOrNameOption,
13
+ format: humanJsonOutputFormatOption,
14
+ },
15
+ args: [k8sIdOrNameArg],
16
+ async handler(options, clusterIdOrName) {
17
+ const { format, org: orgIdOrName } = options;
18
+ const nodeGroups = await k8sListNodeGroups(orgIdOrName, clusterIdOrName);
19
+
20
+ switch (format) {
21
+ case 'json':
22
+ Logger.printJson(nodeGroups);
23
+ break;
24
+ case 'human':
25
+ default:
26
+ if (nodeGroups.length === 0) {
27
+ Logger.println(
28
+ `🔎 No node group found, create one with ${styleText('blue', 'clever k8s nodegroups create')}`,
29
+ );
30
+ return;
31
+ }
32
+ console.table(
33
+ Object.fromEntries(
34
+ nodeGroups.map((ng) => [
35
+ ng.id,
36
+ {
37
+ Name: ng.name,
38
+ Status: ng.status,
39
+ Flavor: ng.flavor,
40
+ Nodes: `${ng.currentNodeCount}/${ng.targetNodeCount}`,
41
+ Autoscaling: ng.autoscalingEnabled ? `${ng.minNodeCount}-${ng.maxNodeCount}` : 'disabled',
42
+ },
43
+ ]),
44
+ ),
45
+ );
46
+ break;
47
+ }
48
+ },
49
+ });