hetzner-cli 2.0.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/LICENSE +21 -0
- package/README.md +907 -0
- package/dist/auction/client.d.ts +4 -0
- package/dist/auction/client.js +103 -0
- package/dist/auction/commands.d.ts +2 -0
- package/dist/auction/commands.js +138 -0
- package/dist/auction/formatter.d.ts +3 -0
- package/dist/auction/formatter.js +87 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +39 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +4 -0
- package/dist/cloud/client.d.ts +511 -0
- package/dist/cloud/client.js +706 -0
- package/dist/cloud/commands/certificate.d.ts +2 -0
- package/dist/cloud/commands/certificate.js +77 -0
- package/dist/cloud/commands/context.d.ts +2 -0
- package/dist/cloud/commands/context.js +78 -0
- package/dist/cloud/commands/datacenter.d.ts +2 -0
- package/dist/cloud/commands/datacenter.js +20 -0
- package/dist/cloud/commands/firewall.d.ts +2 -0
- package/dist/cloud/commands/firewall.js +77 -0
- package/dist/cloud/commands/floating-ip.d.ts +2 -0
- package/dist/cloud/commands/floating-ip.js +83 -0
- package/dist/cloud/commands/image.d.ts +2 -0
- package/dist/cloud/commands/image.js +60 -0
- package/dist/cloud/commands/index.d.ts +2 -0
- package/dist/cloud/commands/index.js +41 -0
- package/dist/cloud/commands/iso.d.ts +2 -0
- package/dist/cloud/commands/iso.js +22 -0
- package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
- package/dist/cloud/commands/load-balancer-type.js +20 -0
- package/dist/cloud/commands/load-balancer.d.ts +2 -0
- package/dist/cloud/commands/load-balancer.js +177 -0
- package/dist/cloud/commands/location.d.ts +2 -0
- package/dist/cloud/commands/location.js +20 -0
- package/dist/cloud/commands/network.d.ts +2 -0
- package/dist/cloud/commands/network.js +96 -0
- package/dist/cloud/commands/placement-group.d.ts +2 -0
- package/dist/cloud/commands/placement-group.js +53 -0
- package/dist/cloud/commands/primary-ip.d.ts +2 -0
- package/dist/cloud/commands/primary-ip.js +83 -0
- package/dist/cloud/commands/server-type.d.ts +2 -0
- package/dist/cloud/commands/server-type.js +20 -0
- package/dist/cloud/commands/server.d.ts +2 -0
- package/dist/cloud/commands/server.js +260 -0
- package/dist/cloud/commands/ssh-key.d.ts +2 -0
- package/dist/cloud/commands/ssh-key.js +63 -0
- package/dist/cloud/commands/volume.d.ts +2 -0
- package/dist/cloud/commands/volume.js +92 -0
- package/dist/cloud/context.d.ts +28 -0
- package/dist/cloud/context.js +172 -0
- package/dist/cloud/formatter.d.ts +37 -0
- package/dist/cloud/formatter.js +413 -0
- package/dist/cloud/helpers.d.ts +18 -0
- package/dist/cloud/helpers.js +48 -0
- package/dist/cloud/types.d.ts +398 -0
- package/dist/cloud/types.js +5 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/formatter.d.ts +3 -0
- package/dist/formatter.js +6 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +17 -0
- package/dist/robot/client.d.ts +256 -0
- package/dist/robot/client.js +656 -0
- package/dist/robot/commands/auth.d.ts +2 -0
- package/dist/robot/commands/auth.js +54 -0
- package/dist/robot/commands/boot.d.ts +2 -0
- package/dist/robot/commands/boot.js +72 -0
- package/dist/robot/commands/cancel.d.ts +2 -0
- package/dist/robot/commands/cancel.js +36 -0
- package/dist/robot/commands/failover.d.ts +2 -0
- package/dist/robot/commands/failover.js +42 -0
- package/dist/robot/commands/firewall.d.ts +2 -0
- package/dist/robot/commands/firewall.js +66 -0
- package/dist/robot/commands/index.d.ts +2 -0
- package/dist/robot/commands/index.js +36 -0
- package/dist/robot/commands/interactive.d.ts +2 -0
- package/dist/robot/commands/interactive.js +134 -0
- package/dist/robot/commands/ip.d.ts +2 -0
- package/dist/robot/commands/ip.js +52 -0
- package/dist/robot/commands/key.d.ts +2 -0
- package/dist/robot/commands/key.js +64 -0
- package/dist/robot/commands/order.d.ts +2 -0
- package/dist/robot/commands/order.js +33 -0
- package/dist/robot/commands/rdns.d.ts +2 -0
- package/dist/robot/commands/rdns.js +41 -0
- package/dist/robot/commands/reset.d.ts +2 -0
- package/dist/robot/commands/reset.js +77 -0
- package/dist/robot/commands/server.d.ts +2 -0
- package/dist/robot/commands/server.js +29 -0
- package/dist/robot/commands/storagebox.d.ts +2 -0
- package/dist/robot/commands/storagebox.js +116 -0
- package/dist/robot/commands/subnet.d.ts +2 -0
- package/dist/robot/commands/subnet.js +21 -0
- package/dist/robot/commands/traffic.d.ts +2 -0
- package/dist/robot/commands/traffic.js +20 -0
- package/dist/robot/commands/vswitch.d.ts +2 -0
- package/dist/robot/commands/vswitch.js +64 -0
- package/dist/robot/commands/wol.d.ts +2 -0
- package/dist/robot/commands/wol.js +20 -0
- package/dist/robot/formatter.d.ts +58 -0
- package/dist/robot/formatter.js +500 -0
- package/dist/robot/types.d.ts +352 -0
- package/dist/robot/types.js +5 -0
- package/dist/shared/config.d.ts +86 -0
- package/dist/shared/config.js +273 -0
- package/dist/shared/formatter.d.ts +29 -0
- package/dist/shared/formatter.js +118 -0
- package/dist/shared/helpers.d.ts +17 -0
- package/dist/shared/helpers.js +72 -0
- package/dist/shared/reference.d.ts +2 -0
- package/dist/shared/reference.js +626 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
|
|
3
|
+
import * as fmt from '../../shared/formatter.js';
|
|
4
|
+
import * as cloudFmt from '../formatter.js';
|
|
5
|
+
export function registerCertificateCommands(parent) {
|
|
6
|
+
const cert = parent.command('certificate').description('Certificate management');
|
|
7
|
+
cert.command('list').alias('ls').description('List all certificates')
|
|
8
|
+
.option('-l, --label-selector <selector>', 'Label selector')
|
|
9
|
+
.option('-s, --sort <field>', 'Sort by field')
|
|
10
|
+
.option('--type <type>', 'Filter by type (uploaded, managed)')
|
|
11
|
+
.action(cloudAction(async (client, options) => {
|
|
12
|
+
const certs = await client.listCertificates({ label_selector: options.labelSelector, sort: options.sort, type: options.type });
|
|
13
|
+
cloudOutput(certs, cloudFmt.formatCertificateList, options);
|
|
14
|
+
}));
|
|
15
|
+
cert.command('describe <id>').description('Show certificate details')
|
|
16
|
+
.action(cloudAction(async (client, id, options) => {
|
|
17
|
+
const certificate = await client.getCertificate(parseInt(id));
|
|
18
|
+
cloudOutput(certificate, cloudFmt.formatCertificateDetails, options);
|
|
19
|
+
}));
|
|
20
|
+
cert.command('create').description('Create a certificate')
|
|
21
|
+
.requiredOption('--name <name>', 'Certificate name')
|
|
22
|
+
.option('--type <type>', 'Certificate type (uploaded, managed)', 'uploaded')
|
|
23
|
+
.option('--cert-file <path>', 'Path to certificate PEM file (for uploaded)')
|
|
24
|
+
.option('--key-file <path>', 'Path to private key PEM file (for uploaded)')
|
|
25
|
+
.option('--domain <domains...>', 'Domain names (for managed)')
|
|
26
|
+
.action(cloudAction(async (client, options) => {
|
|
27
|
+
const createOpts = {
|
|
28
|
+
name: options.name,
|
|
29
|
+
type: options.type,
|
|
30
|
+
};
|
|
31
|
+
if (options.type === 'managed') {
|
|
32
|
+
if (!options.domain || options.domain.length === 0) {
|
|
33
|
+
console.error(fmt.error('Managed certificates require --domain'));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
createOpts.domain_names = options.domain;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (!options.certFile || !options.keyFile) {
|
|
40
|
+
console.error(fmt.error('Uploaded certificates require --cert-file and --key-file'));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
createOpts.certificate = readFileSync(options.certFile, 'utf-8');
|
|
44
|
+
createOpts.private_key = readFileSync(options.keyFile, 'utf-8');
|
|
45
|
+
}
|
|
46
|
+
const { certificate: created } = await client.createCertificate(createOpts);
|
|
47
|
+
console.log(fmt.success(`Certificate '${created.name}' created (ID: ${created.id})`));
|
|
48
|
+
}));
|
|
49
|
+
cert.command('delete <id>').description('Delete a certificate')
|
|
50
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
51
|
+
.action(cloudAction(async (client, id, options) => {
|
|
52
|
+
if (!await cloudConfirm(`Delete certificate ${id}?`, options))
|
|
53
|
+
return;
|
|
54
|
+
await client.deleteCertificate(parseInt(id));
|
|
55
|
+
console.log(fmt.success(`Certificate ${id} deleted.`));
|
|
56
|
+
}));
|
|
57
|
+
cert.command('update <id>').description('Update certificate')
|
|
58
|
+
.option('--name <name>', 'New name')
|
|
59
|
+
.action(cloudAction(async (client, id, options) => {
|
|
60
|
+
await client.updateCertificate(parseInt(id), { name: options.name });
|
|
61
|
+
console.log(fmt.success(`Certificate ${id} updated.`));
|
|
62
|
+
}));
|
|
63
|
+
cert.command('add-label <id> <label>').description('Add a label (key=value)')
|
|
64
|
+
.action(cloudAction(async (client, id, label) => {
|
|
65
|
+
const certificate = await client.getCertificate(parseInt(id));
|
|
66
|
+
const [key, value] = label.split('=');
|
|
67
|
+
await client.updateCertificate(parseInt(id), { labels: { ...certificate.labels, [key]: value || '' } });
|
|
68
|
+
console.log(fmt.success(`Label '${key}' added.`));
|
|
69
|
+
}));
|
|
70
|
+
cert.command('remove-label <id> <key>').description('Remove a label')
|
|
71
|
+
.action(cloudAction(async (client, id, key) => {
|
|
72
|
+
const certificate = await client.getCertificate(parseInt(id));
|
|
73
|
+
const labels = Object.fromEntries(Object.entries(certificate.labels).filter(([k]) => k !== key));
|
|
74
|
+
await client.updateCertificate(parseInt(id), { labels });
|
|
75
|
+
console.log(fmt.success(`Label '${key}' removed.`));
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { password as passwordPrompt } from '@inquirer/prompts';
|
|
2
|
+
import * as fmt from '../../shared/formatter.js';
|
|
3
|
+
import { createContext, useContext, deleteContext, listContexts, getActiveContext, } from '../context.js';
|
|
4
|
+
import { formatContextList } from '../formatter.js';
|
|
5
|
+
export function registerContextCommands(parent) {
|
|
6
|
+
const context = parent.command('context').description('Cloud context (token) management');
|
|
7
|
+
context
|
|
8
|
+
.command('create <name>')
|
|
9
|
+
.description('Create a new cloud context')
|
|
10
|
+
.option('-t, --token <token>', 'API token (will prompt if not provided)')
|
|
11
|
+
.action(async (name, options) => {
|
|
12
|
+
try {
|
|
13
|
+
let token = options.token;
|
|
14
|
+
if (!token) {
|
|
15
|
+
token = await passwordPrompt({
|
|
16
|
+
message: `Enter Hetzner Cloud API token for '${name}':`,
|
|
17
|
+
validate: (v) => v.length > 0 || 'Token is required',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
await createContext(name, token);
|
|
21
|
+
console.log(fmt.success(`Context '${name}' created and activated.`));
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
console.error(fmt.error(error instanceof Error ? error.message : 'Unknown error'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
context
|
|
32
|
+
.command('use <name>')
|
|
33
|
+
.description('Switch to a different cloud context')
|
|
34
|
+
.action((name) => {
|
|
35
|
+
try {
|
|
36
|
+
useContext(name);
|
|
37
|
+
console.log(fmt.success(`Switched to context '${name}'.`));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(fmt.error(error instanceof Error ? error.message : 'Unknown error'));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
context
|
|
45
|
+
.command('delete <name>')
|
|
46
|
+
.alias('rm')
|
|
47
|
+
.description('Delete a cloud context')
|
|
48
|
+
.action(async (name) => {
|
|
49
|
+
try {
|
|
50
|
+
await deleteContext(name);
|
|
51
|
+
console.log(fmt.success(`Context '${name}' deleted.`));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(fmt.error(error instanceof Error ? error.message : 'Unknown error'));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
context
|
|
59
|
+
.command('list')
|
|
60
|
+
.alias('ls')
|
|
61
|
+
.description('List all cloud contexts')
|
|
62
|
+
.action(() => {
|
|
63
|
+
const contexts = listContexts();
|
|
64
|
+
console.log(formatContextList(contexts));
|
|
65
|
+
});
|
|
66
|
+
context
|
|
67
|
+
.command('active')
|
|
68
|
+
.description('Show the active cloud context')
|
|
69
|
+
.action(() => {
|
|
70
|
+
const active = getActiveContext();
|
|
71
|
+
if (active) {
|
|
72
|
+
console.log(fmt.success(`Active context: ${active}`));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(fmt.warning('No active context. Run: hetzner cloud context create'));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cloudAction, cloudOutput } from '../helpers.js';
|
|
2
|
+
import * as cloudFmt from '../formatter.js';
|
|
3
|
+
export function registerDatacenterCommands(parent) {
|
|
4
|
+
const datacenter = parent.command('datacenter').description('Datacenter information');
|
|
5
|
+
datacenter
|
|
6
|
+
.command('list')
|
|
7
|
+
.alias('ls')
|
|
8
|
+
.description('List all datacenters')
|
|
9
|
+
.action(cloudAction(async (client, options) => {
|
|
10
|
+
const datacenters = await client.listDatacenters();
|
|
11
|
+
cloudOutput(datacenters, cloudFmt.formatDatacenterList, options);
|
|
12
|
+
}));
|
|
13
|
+
datacenter
|
|
14
|
+
.command('describe <id>')
|
|
15
|
+
.description('Show datacenter details')
|
|
16
|
+
.action(cloudAction(async (client, id, options) => {
|
|
17
|
+
const datacenter = await client.getDatacenter(parseInt(id));
|
|
18
|
+
cloudOutput(datacenter, cloudFmt.formatDatacenterDetails, options);
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
|
|
2
|
+
import * as fmt from '../../shared/formatter.js';
|
|
3
|
+
import * as cloudFmt from '../formatter.js';
|
|
4
|
+
export function registerCloudFirewallCommands(parent) {
|
|
5
|
+
const firewall = parent.command('firewall').description('Cloud firewall management');
|
|
6
|
+
firewall.command('list').alias('ls').description('List all firewalls')
|
|
7
|
+
.option('-l, --label-selector <selector>', 'Label selector')
|
|
8
|
+
.action(cloudAction(async (client, options) => {
|
|
9
|
+
const firewalls = await client.listFirewalls({ label_selector: options.labelSelector });
|
|
10
|
+
cloudOutput(firewalls, cloudFmt.formatCloudFirewallList, options);
|
|
11
|
+
}));
|
|
12
|
+
firewall.command('describe <id>').description('Show firewall details')
|
|
13
|
+
.action(cloudAction(async (client, id, options) => {
|
|
14
|
+
const fw = await client.getFirewall(parseInt(id));
|
|
15
|
+
cloudOutput(fw, cloudFmt.formatCloudFirewallDetails, options);
|
|
16
|
+
}));
|
|
17
|
+
firewall.command('create').description('Create a firewall')
|
|
18
|
+
.requiredOption('--name <name>', 'Firewall name')
|
|
19
|
+
.action(cloudAction(async (client, options) => {
|
|
20
|
+
const { firewall: fw } = await client.createFirewall({ name: options.name });
|
|
21
|
+
console.log(fmt.success(`Firewall '${fw.name}' created (ID: ${fw.id})`));
|
|
22
|
+
}));
|
|
23
|
+
firewall.command('delete <id>').description('Delete a firewall')
|
|
24
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
25
|
+
.action(cloudAction(async (client, id, options) => {
|
|
26
|
+
if (!await cloudConfirm(`Delete firewall ${id}?`, options))
|
|
27
|
+
return;
|
|
28
|
+
await client.deleteFirewall(parseInt(id));
|
|
29
|
+
console.log(fmt.success(`Firewall ${id} deleted.`));
|
|
30
|
+
}));
|
|
31
|
+
firewall.command('update <id>').description('Update firewall')
|
|
32
|
+
.option('--name <name>', 'New name')
|
|
33
|
+
.action(cloudAction(async (client, id, options) => {
|
|
34
|
+
await client.updateFirewall(parseInt(id), { name: options.name });
|
|
35
|
+
console.log(fmt.success(`Firewall ${id} updated.`));
|
|
36
|
+
}));
|
|
37
|
+
firewall.command('apply-to-resource <id>').description('Apply firewall to resource')
|
|
38
|
+
.requiredOption('--type <type>', 'Resource type (server, label_selector)')
|
|
39
|
+
.option('--server <server>', 'Server ID')
|
|
40
|
+
.option('--label-selector <selector>', 'Label selector')
|
|
41
|
+
.action(cloudAction(async (client, id, options) => {
|
|
42
|
+
const applyTo = [{
|
|
43
|
+
type: options.type,
|
|
44
|
+
...(options.server ? { server: { id: parseInt(options.server) } } : {}),
|
|
45
|
+
...(options.labelSelector ? { label_selector: { selector: options.labelSelector } } : {}),
|
|
46
|
+
}];
|
|
47
|
+
await client.applyFirewall(parseInt(id), applyTo);
|
|
48
|
+
console.log(fmt.success(`Firewall ${id} applied.`));
|
|
49
|
+
}));
|
|
50
|
+
firewall.command('remove-from-resource <id>').description('Remove firewall from resource')
|
|
51
|
+
.requiredOption('--type <type>', 'Resource type (server, label_selector)')
|
|
52
|
+
.option('--server <server>', 'Server ID')
|
|
53
|
+
.option('--label-selector <selector>', 'Label selector')
|
|
54
|
+
.action(cloudAction(async (client, id, options) => {
|
|
55
|
+
const removeFrom = [{
|
|
56
|
+
type: options.type,
|
|
57
|
+
...(options.server ? { server: { id: parseInt(options.server) } } : {}),
|
|
58
|
+
...(options.labelSelector ? { label_selector: { selector: options.labelSelector } } : {}),
|
|
59
|
+
}];
|
|
60
|
+
await client.removeFirewallFromResources(parseInt(id), removeFrom);
|
|
61
|
+
console.log(fmt.success(`Firewall ${id} removed from resource.`));
|
|
62
|
+
}));
|
|
63
|
+
firewall.command('add-label <id> <label>').description('Add a label (key=value)')
|
|
64
|
+
.action(cloudAction(async (client, id, label) => {
|
|
65
|
+
const fw = await client.getFirewall(parseInt(id));
|
|
66
|
+
const [key, value] = label.split('=');
|
|
67
|
+
await client.updateFirewall(parseInt(id), { labels: { ...fw.labels, [key]: value || '' } });
|
|
68
|
+
console.log(fmt.success(`Label '${key}' added.`));
|
|
69
|
+
}));
|
|
70
|
+
firewall.command('remove-label <id> <key>').description('Remove a label')
|
|
71
|
+
.action(cloudAction(async (client, id, key) => {
|
|
72
|
+
const fw = await client.getFirewall(parseInt(id));
|
|
73
|
+
const labels = Object.fromEntries(Object.entries(fw.labels).filter(([k]) => k !== key));
|
|
74
|
+
await client.updateFirewall(parseInt(id), { labels });
|
|
75
|
+
console.log(fmt.success(`Label '${key}' removed.`));
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
|
|
2
|
+
import * as fmt from '../../shared/formatter.js';
|
|
3
|
+
import * as cloudFmt from '../formatter.js';
|
|
4
|
+
export function registerFloatingIpCommands(parent) {
|
|
5
|
+
const fip = parent.command('floating-ip').description('Floating IP management');
|
|
6
|
+
fip.command('list').alias('ls').description('List all floating IPs')
|
|
7
|
+
.option('-l, --label-selector <selector>', 'Label selector')
|
|
8
|
+
.action(cloudAction(async (client, options) => {
|
|
9
|
+
const ips = await client.listFloatingIps({ label_selector: options.labelSelector });
|
|
10
|
+
cloudOutput(ips, cloudFmt.formatFloatingIpList, options);
|
|
11
|
+
}));
|
|
12
|
+
fip.command('describe <id>').description('Show floating IP details')
|
|
13
|
+
.action(cloudAction(async (client, id, options) => {
|
|
14
|
+
const ip = await client.getFloatingIp(parseInt(id));
|
|
15
|
+
cloudOutput(ip, cloudFmt.formatFloatingIpDetails, options);
|
|
16
|
+
}));
|
|
17
|
+
fip.command('create').description('Create a floating IP')
|
|
18
|
+
.requiredOption('--type <type>', 'IP type (ipv4, ipv6)')
|
|
19
|
+
.option('--name <name>', 'Name')
|
|
20
|
+
.option('--description <desc>', 'Description')
|
|
21
|
+
.option('--home-location <loc>', 'Home location')
|
|
22
|
+
.option('--server <id>', 'Server to assign to')
|
|
23
|
+
.action(cloudAction(async (client, options) => {
|
|
24
|
+
const { floating_ip: ip } = await client.createFloatingIp({ type: options.type, name: options.name, description: options.description, home_location: options.homeLocation, server: options.server ? parseInt(options.server) : undefined });
|
|
25
|
+
console.log(fmt.success(`Floating IP created (ID: ${ip.id}, IP: ${ip.ip})`));
|
|
26
|
+
}));
|
|
27
|
+
fip.command('delete <id>').description('Delete a floating IP')
|
|
28
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
29
|
+
.action(cloudAction(async (client, id, options) => {
|
|
30
|
+
if (!await cloudConfirm(`Delete floating IP ${id}?`, options))
|
|
31
|
+
return;
|
|
32
|
+
await client.deleteFloatingIp(parseInt(id));
|
|
33
|
+
console.log(fmt.success(`Floating IP ${id} deleted.`));
|
|
34
|
+
}));
|
|
35
|
+
fip.command('update <id>').description('Update floating IP')
|
|
36
|
+
.option('--name <name>', 'New name')
|
|
37
|
+
.option('--description <desc>', 'New description')
|
|
38
|
+
.action(cloudAction(async (client, id, options) => {
|
|
39
|
+
await client.updateFloatingIp(parseInt(id), { name: options.name, description: options.description });
|
|
40
|
+
console.log(fmt.success(`Floating IP ${id} updated.`));
|
|
41
|
+
}));
|
|
42
|
+
fip.command('assign <id> <server>').description('Assign floating IP to server')
|
|
43
|
+
.action(cloudAction(async (client, id, server) => {
|
|
44
|
+
await client.assignFloatingIp(parseInt(id), parseInt(server));
|
|
45
|
+
console.log(fmt.success(`Floating IP ${id} assigned to server ${server}.`));
|
|
46
|
+
}));
|
|
47
|
+
fip.command('unassign <id>').description('Unassign floating IP')
|
|
48
|
+
.action(cloudAction(async (client, id) => {
|
|
49
|
+
await client.unassignFloatingIp(parseInt(id));
|
|
50
|
+
console.log(fmt.success(`Floating IP ${id} unassigned.`));
|
|
51
|
+
}));
|
|
52
|
+
fip.command('enable-protection <id>').description('Enable delete protection')
|
|
53
|
+
.action(cloudAction(async (client, id) => {
|
|
54
|
+
await client.changeFloatingIpProtection(parseInt(id), true);
|
|
55
|
+
console.log(fmt.success(`Protection enabled for floating IP ${id}.`));
|
|
56
|
+
}));
|
|
57
|
+
fip.command('disable-protection <id>').description('Disable delete protection')
|
|
58
|
+
.action(cloudAction(async (client, id) => {
|
|
59
|
+
await client.changeFloatingIpProtection(parseInt(id), false);
|
|
60
|
+
console.log(fmt.success(`Protection disabled for floating IP ${id}.`));
|
|
61
|
+
}));
|
|
62
|
+
fip.command('set-rdns <id>').description('Set reverse DNS')
|
|
63
|
+
.requiredOption('--ip <ip>', 'IP address')
|
|
64
|
+
.requiredOption('--dns-ptr <ptr>', 'DNS pointer (empty to reset)')
|
|
65
|
+
.action(cloudAction(async (client, id, options) => {
|
|
66
|
+
await client.changeFloatingIpDnsPtr(parseInt(id), options.ip, options.dnsPtr || null);
|
|
67
|
+
console.log(fmt.success('rDNS updated.'));
|
|
68
|
+
}));
|
|
69
|
+
fip.command('add-label <id> <label>').description('Add a label (key=value)')
|
|
70
|
+
.action(cloudAction(async (client, id, label) => {
|
|
71
|
+
const ip = await client.getFloatingIp(parseInt(id));
|
|
72
|
+
const [key, value] = label.split('=');
|
|
73
|
+
await client.updateFloatingIp(parseInt(id), { labels: { ...ip.labels, [key]: value || '' } });
|
|
74
|
+
console.log(fmt.success(`Label '${key}' added.`));
|
|
75
|
+
}));
|
|
76
|
+
fip.command('remove-label <id> <key>').description('Remove a label')
|
|
77
|
+
.action(cloudAction(async (client, id, key) => {
|
|
78
|
+
const ip = await client.getFloatingIp(parseInt(id));
|
|
79
|
+
const labels = Object.fromEntries(Object.entries(ip.labels).filter(([k]) => k !== key));
|
|
80
|
+
await client.updateFloatingIp(parseInt(id), { labels });
|
|
81
|
+
console.log(fmt.success(`Label '${key}' removed.`));
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
|
|
2
|
+
import * as fmt from '../../shared/formatter.js';
|
|
3
|
+
import * as cloudFmt from '../formatter.js';
|
|
4
|
+
export function registerImageCommands(parent) {
|
|
5
|
+
const image = parent.command('image').description('Image management');
|
|
6
|
+
image.command('list').alias('ls').description('List all images')
|
|
7
|
+
.option('-l, --label-selector <selector>', 'Label selector')
|
|
8
|
+
.option('-s, --sort <field>', 'Sort by field')
|
|
9
|
+
.option('--type <type>', 'Filter by type (system, snapshot, backup, app)')
|
|
10
|
+
.option('--architecture <arch>', 'Filter by architecture (x86, arm)')
|
|
11
|
+
.option('--status <status>', 'Filter by status')
|
|
12
|
+
.action(cloudAction(async (client, options) => {
|
|
13
|
+
const images = await client.listImages({ label_selector: options.labelSelector, sort: options.sort, type: options.type, architecture: options.architecture, status: options.status });
|
|
14
|
+
cloudOutput(images, cloudFmt.formatImageList, options);
|
|
15
|
+
}));
|
|
16
|
+
image.command('describe <id>').description('Show image details')
|
|
17
|
+
.action(cloudAction(async (client, id, options) => {
|
|
18
|
+
const img = await client.getImage(parseInt(id));
|
|
19
|
+
cloudOutput(img, cloudFmt.formatImageDetails, options);
|
|
20
|
+
}));
|
|
21
|
+
image.command('update <id>').description('Update image')
|
|
22
|
+
.option('--description <desc>', 'New description')
|
|
23
|
+
.option('--type <type>', 'New type (snapshot)')
|
|
24
|
+
.action(cloudAction(async (client, id, options) => {
|
|
25
|
+
await client.updateImage(parseInt(id), { description: options.description, type: options.type });
|
|
26
|
+
console.log(fmt.success(`Image ${id} updated.`));
|
|
27
|
+
}));
|
|
28
|
+
image.command('delete <id>').description('Delete an image')
|
|
29
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
30
|
+
.action(cloudAction(async (client, id, options) => {
|
|
31
|
+
if (!await cloudConfirm(`Delete image ${id}?`, options))
|
|
32
|
+
return;
|
|
33
|
+
await client.deleteImage(parseInt(id));
|
|
34
|
+
console.log(fmt.success(`Image ${id} deleted.`));
|
|
35
|
+
}));
|
|
36
|
+
image.command('enable-protection <id>').description('Enable delete protection')
|
|
37
|
+
.action(cloudAction(async (client, id) => {
|
|
38
|
+
await client.changeImageProtection(parseInt(id), true);
|
|
39
|
+
console.log(fmt.success(`Protection enabled for image ${id}.`));
|
|
40
|
+
}));
|
|
41
|
+
image.command('disable-protection <id>').description('Disable delete protection')
|
|
42
|
+
.action(cloudAction(async (client, id) => {
|
|
43
|
+
await client.changeImageProtection(parseInt(id), false);
|
|
44
|
+
console.log(fmt.success(`Protection disabled for image ${id}.`));
|
|
45
|
+
}));
|
|
46
|
+
image.command('add-label <id> <label>').description('Add a label (key=value)')
|
|
47
|
+
.action(cloudAction(async (client, id, label) => {
|
|
48
|
+
const img = await client.getImage(parseInt(id));
|
|
49
|
+
const [key, value] = label.split('=');
|
|
50
|
+
await client.updateImage(parseInt(id), { labels: { ...img.labels, [key]: value || '' } });
|
|
51
|
+
console.log(fmt.success(`Label '${key}' added.`));
|
|
52
|
+
}));
|
|
53
|
+
image.command('remove-label <id> <key>').description('Remove a label')
|
|
54
|
+
.action(cloudAction(async (client, id, key) => {
|
|
55
|
+
const img = await client.getImage(parseInt(id));
|
|
56
|
+
const labels = Object.fromEntries(Object.entries(img.labels).filter(([k]) => k !== key));
|
|
57
|
+
await client.updateImage(parseInt(id), { labels });
|
|
58
|
+
console.log(fmt.success(`Label '${key}' removed.`));
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { registerContextCommands } from './context.js';
|
|
2
|
+
import { registerDatacenterCommands } from './datacenter.js';
|
|
3
|
+
import { registerLocationCommands } from './location.js';
|
|
4
|
+
import { registerServerTypeCommands } from './server-type.js';
|
|
5
|
+
import { registerLoadBalancerTypeCommands } from './load-balancer-type.js';
|
|
6
|
+
import { registerIsoCommands } from './iso.js';
|
|
7
|
+
import { registerCloudServerCommands } from './server.js';
|
|
8
|
+
import { registerNetworkCommands } from './network.js';
|
|
9
|
+
import { registerCloudFirewallCommands } from './firewall.js';
|
|
10
|
+
import { registerFloatingIpCommands } from './floating-ip.js';
|
|
11
|
+
import { registerPrimaryIpCommands } from './primary-ip.js';
|
|
12
|
+
import { registerVolumeCommands } from './volume.js';
|
|
13
|
+
import { registerLoadBalancerCommands } from './load-balancer.js';
|
|
14
|
+
import { registerImageCommands } from './image.js';
|
|
15
|
+
import { registerCloudSshKeyCommands } from './ssh-key.js';
|
|
16
|
+
import { registerCertificateCommands } from './certificate.js';
|
|
17
|
+
import { registerPlacementGroupCommands } from './placement-group.js';
|
|
18
|
+
export function registerCloudCommands(parent) {
|
|
19
|
+
const cloud = parent.command('cloud').description('Hetzner Cloud API management');
|
|
20
|
+
cloud.option('--token <token>', 'Cloud API token (overrides context)');
|
|
21
|
+
registerContextCommands(cloud);
|
|
22
|
+
// Read-only resources
|
|
23
|
+
registerDatacenterCommands(cloud);
|
|
24
|
+
registerLocationCommands(cloud);
|
|
25
|
+
registerServerTypeCommands(cloud);
|
|
26
|
+
registerLoadBalancerTypeCommands(cloud);
|
|
27
|
+
registerIsoCommands(cloud);
|
|
28
|
+
// Core resources
|
|
29
|
+
registerCloudServerCommands(cloud);
|
|
30
|
+
registerNetworkCommands(cloud);
|
|
31
|
+
registerCloudFirewallCommands(cloud);
|
|
32
|
+
registerFloatingIpCommands(cloud);
|
|
33
|
+
registerPrimaryIpCommands(cloud);
|
|
34
|
+
registerVolumeCommands(cloud);
|
|
35
|
+
registerLoadBalancerCommands(cloud);
|
|
36
|
+
registerImageCommands(cloud);
|
|
37
|
+
// Ancillary resources
|
|
38
|
+
registerCloudSshKeyCommands(cloud);
|
|
39
|
+
registerCertificateCommands(cloud);
|
|
40
|
+
registerPlacementGroupCommands(cloud);
|
|
41
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cloudAction, cloudOutput } from '../helpers.js';
|
|
2
|
+
import * as cloudFmt from '../formatter.js';
|
|
3
|
+
export function registerIsoCommands(parent) {
|
|
4
|
+
const iso = parent.command('iso').description('ISO image management');
|
|
5
|
+
iso
|
|
6
|
+
.command('list')
|
|
7
|
+
.alias('ls')
|
|
8
|
+
.description('List all ISOs')
|
|
9
|
+
.option('-n, --name <name>', 'Filter by name')
|
|
10
|
+
.option('-a, --architecture <arch>', 'Filter by architecture')
|
|
11
|
+
.action(cloudAction(async (client, options) => {
|
|
12
|
+
const isos = await client.listIsos({ name: options.name, architecture: options.architecture });
|
|
13
|
+
cloudOutput(isos, cloudFmt.formatIsoList, options);
|
|
14
|
+
}));
|
|
15
|
+
iso
|
|
16
|
+
.command('describe <id>')
|
|
17
|
+
.description('Show ISO details')
|
|
18
|
+
.action(cloudAction(async (client, id, options) => {
|
|
19
|
+
const iso = await client.getIso(parseInt(id));
|
|
20
|
+
cloudOutput(iso, cloudFmt.formatIsoDetails, options);
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cloudAction, cloudOutput } from '../helpers.js';
|
|
2
|
+
import * as cloudFmt from '../formatter.js';
|
|
3
|
+
export function registerLoadBalancerTypeCommands(parent) {
|
|
4
|
+
const lbType = parent.command('load-balancer-type').description('Load balancer type information');
|
|
5
|
+
lbType
|
|
6
|
+
.command('list')
|
|
7
|
+
.alias('ls')
|
|
8
|
+
.description('List all load balancer types')
|
|
9
|
+
.action(cloudAction(async (client, options) => {
|
|
10
|
+
const types = await client.listLoadBalancerTypes();
|
|
11
|
+
cloudOutput(types, cloudFmt.formatLoadBalancerTypeList, options);
|
|
12
|
+
}));
|
|
13
|
+
lbType
|
|
14
|
+
.command('describe <id>')
|
|
15
|
+
.description('Show load balancer type details')
|
|
16
|
+
.action(cloudAction(async (client, id, options) => {
|
|
17
|
+
const type = await client.getLoadBalancerType(parseInt(id));
|
|
18
|
+
cloudOutput(type, cloudFmt.formatLoadBalancerTypeDetails, options);
|
|
19
|
+
}));
|
|
20
|
+
}
|