@rollupdate/hctl 0.0.0-stage → 3.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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1066 -2
  3. package/dist/auction/cache.d.ts +6 -0
  4. package/dist/auction/cache.js +55 -0
  5. package/dist/auction/client.d.ts +24 -0
  6. package/dist/auction/client.js +236 -0
  7. package/dist/auction/commands.d.ts +2 -0
  8. package/dist/auction/commands.js +440 -0
  9. package/dist/auction/diff-formatter.d.ts +3 -0
  10. package/dist/auction/diff-formatter.js +61 -0
  11. package/dist/auction/diff.d.ts +11 -0
  12. package/dist/auction/diff.js +28 -0
  13. package/dist/auction/formatter.d.ts +21 -0
  14. package/dist/auction/formatter.js +182 -0
  15. package/dist/auction/store.d.ts +24 -0
  16. package/dist/auction/store.js +177 -0
  17. package/dist/auction/types.d.ts +74 -0
  18. package/dist/auction/types.js +5 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +74 -0
  21. package/dist/client.d.ts +2 -0
  22. package/dist/client.js +4 -0
  23. package/dist/cloud/browser-bootstrap.d.ts +16 -0
  24. package/dist/cloud/browser-bootstrap.js +438 -0
  25. package/dist/cloud/client.d.ts +520 -0
  26. package/dist/cloud/client.js +755 -0
  27. package/dist/cloud/commands/certificate.d.ts +2 -0
  28. package/dist/cloud/commands/certificate.js +108 -0
  29. package/dist/cloud/commands/context.d.ts +2 -0
  30. package/dist/cloud/commands/context.js +133 -0
  31. package/dist/cloud/commands/datacenter.d.ts +2 -0
  32. package/dist/cloud/commands/datacenter.js +24 -0
  33. package/dist/cloud/commands/firewall.d.ts +2 -0
  34. package/dist/cloud/commands/firewall.js +142 -0
  35. package/dist/cloud/commands/floating-ip.d.ts +2 -0
  36. package/dist/cloud/commands/floating-ip.js +138 -0
  37. package/dist/cloud/commands/image.d.ts +2 -0
  38. package/dist/cloud/commands/image.js +98 -0
  39. package/dist/cloud/commands/index.d.ts +2 -0
  40. package/dist/cloud/commands/index.js +47 -0
  41. package/dist/cloud/commands/inventory.d.ts +2 -0
  42. package/dist/cloud/commands/inventory.js +13 -0
  43. package/dist/cloud/commands/iso.d.ts +2 -0
  44. package/dist/cloud/commands/iso.js +27 -0
  45. package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
  46. package/dist/cloud/commands/load-balancer-type.js +24 -0
  47. package/dist/cloud/commands/load-balancer.d.ts +2 -0
  48. package/dist/cloud/commands/load-balancer.js +269 -0
  49. package/dist/cloud/commands/location.d.ts +2 -0
  50. package/dist/cloud/commands/location.js +24 -0
  51. package/dist/cloud/commands/network.d.ts +2 -0
  52. package/dist/cloud/commands/network.js +157 -0
  53. package/dist/cloud/commands/placement-group.d.ts +2 -0
  54. package/dist/cloud/commands/placement-group.js +79 -0
  55. package/dist/cloud/commands/pricing.d.ts +2 -0
  56. package/dist/cloud/commands/pricing.js +15 -0
  57. package/dist/cloud/commands/primary-ip.d.ts +2 -0
  58. package/dist/cloud/commands/primary-ip.js +139 -0
  59. package/dist/cloud/commands/server-type.d.ts +2 -0
  60. package/dist/cloud/commands/server-type.js +24 -0
  61. package/dist/cloud/commands/server.d.ts +2 -0
  62. package/dist/cloud/commands/server.js +346 -0
  63. package/dist/cloud/commands/ssh-key.d.ts +2 -0
  64. package/dist/cloud/commands/ssh-key.js +94 -0
  65. package/dist/cloud/commands/volume.d.ts +2 -0
  66. package/dist/cloud/commands/volume.js +138 -0
  67. package/dist/cloud/context.d.ts +34 -0
  68. package/dist/cloud/context.js +215 -0
  69. package/dist/cloud/formatter.d.ts +40 -0
  70. package/dist/cloud/formatter.js +824 -0
  71. package/dist/cloud/helpers.d.ts +27 -0
  72. package/dist/cloud/helpers.js +67 -0
  73. package/dist/cloud/inventory.d.ts +100 -0
  74. package/dist/cloud/inventory.js +229 -0
  75. package/dist/cloud/pricing.d.ts +27 -0
  76. package/dist/cloud/pricing.js +103 -0
  77. package/dist/cloud/token-instructions.d.ts +2 -0
  78. package/dist/cloud/token-instructions.js +21 -0
  79. package/dist/cloud/types.d.ts +437 -0
  80. package/dist/cloud/types.js +5 -0
  81. package/dist/config.d.ts +1 -0
  82. package/dist/config.js +2 -0
  83. package/dist/formatter.d.ts +3 -0
  84. package/dist/formatter.js +6 -0
  85. package/dist/index.d.ts +12 -0
  86. package/dist/index.js +21 -0
  87. package/dist/robot/client.d.ts +246 -0
  88. package/dist/robot/client.js +719 -0
  89. package/dist/robot/commands/auth.d.ts +2 -0
  90. package/dist/robot/commands/auth.js +54 -0
  91. package/dist/robot/commands/boot.d.ts +2 -0
  92. package/dist/robot/commands/boot.js +76 -0
  93. package/dist/robot/commands/cancel.d.ts +2 -0
  94. package/dist/robot/commands/cancel.js +37 -0
  95. package/dist/robot/commands/failover.d.ts +2 -0
  96. package/dist/robot/commands/failover.js +46 -0
  97. package/dist/robot/commands/firewall.d.ts +2 -0
  98. package/dist/robot/commands/firewall.js +72 -0
  99. package/dist/robot/commands/index.d.ts +2 -0
  100. package/dist/robot/commands/index.js +36 -0
  101. package/dist/robot/commands/interactive.d.ts +2 -0
  102. package/dist/robot/commands/interactive.js +152 -0
  103. package/dist/robot/commands/ip.d.ts +2 -0
  104. package/dist/robot/commands/ip.js +55 -0
  105. package/dist/robot/commands/key.d.ts +2 -0
  106. package/dist/robot/commands/key.js +68 -0
  107. package/dist/robot/commands/order.d.ts +2 -0
  108. package/dist/robot/commands/order.js +33 -0
  109. package/dist/robot/commands/rdns.d.ts +2 -0
  110. package/dist/robot/commands/rdns.js +41 -0
  111. package/dist/robot/commands/reset.d.ts +2 -0
  112. package/dist/robot/commands/reset.js +77 -0
  113. package/dist/robot/commands/server.d.ts +2 -0
  114. package/dist/robot/commands/server.js +32 -0
  115. package/dist/robot/commands/storagebox.d.ts +2 -0
  116. package/dist/robot/commands/storagebox.js +126 -0
  117. package/dist/robot/commands/subnet.d.ts +2 -0
  118. package/dist/robot/commands/subnet.js +24 -0
  119. package/dist/robot/commands/traffic.d.ts +2 -0
  120. package/dist/robot/commands/traffic.js +23 -0
  121. package/dist/robot/commands/vswitch.d.ts +2 -0
  122. package/dist/robot/commands/vswitch.js +65 -0
  123. package/dist/robot/commands/wol.d.ts +2 -0
  124. package/dist/robot/commands/wol.js +20 -0
  125. package/dist/robot/formatter.d.ts +56 -0
  126. package/dist/robot/formatter.js +546 -0
  127. package/dist/robot/types.d.ts +353 -0
  128. package/dist/robot/types.js +5 -0
  129. package/dist/shared/config.d.ts +86 -0
  130. package/dist/shared/config.js +289 -0
  131. package/dist/shared/formatter.d.ts +29 -0
  132. package/dist/shared/formatter.js +121 -0
  133. package/dist/shared/helpers.d.ts +39 -0
  134. package/dist/shared/helpers.js +86 -0
  135. package/dist/shared/init.d.ts +2 -0
  136. package/dist/shared/init.js +107 -0
  137. package/dist/shared/reference.d.ts +5 -0
  138. package/dist/shared/reference.js +688 -0
  139. package/dist/shared/schemas.d.ts +1 -0
  140. package/dist/shared/schemas.js +1022 -0
  141. package/dist/types.d.ts +2 -0
  142. package/dist/types.js +2 -0
  143. package/package.json +131 -4
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerCertificateCommands(parent: Command): void;
@@ -0,0 +1,108 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { error, success } from "../../shared/formatter.js";
3
+ import { confirmAction, output } from "../../shared/helpers.js";
4
+ import { formatCertificateDetails, formatCertificateList, } from "../formatter.js";
5
+ import { cloudAction, resolveIdOrName, } from "../helpers.js";
6
+ export function registerCertificateCommands(parent) {
7
+ const cert = parent
8
+ .command("certificate")
9
+ .description("Certificate management");
10
+ cert
11
+ .command("list")
12
+ .alias("ls")
13
+ .description("List all certificates")
14
+ .option("-l, --label-selector <selector>", "Label selector")
15
+ .option("-s, --sort <field>", "Sort by field")
16
+ .option("--type <type>", "Filter by type (uploaded, managed)")
17
+ .action(cloudAction(async (client, options) => {
18
+ const certs = await client.listCertificates({
19
+ label_selector: options.labelSelector,
20
+ sort: options.sort,
21
+ type: options.type,
22
+ });
23
+ output(certs, formatCertificateList, options);
24
+ }));
25
+ cert
26
+ .command("describe <id-or-name>")
27
+ .description("Show certificate details")
28
+ .action(cloudAction(async (client, idOrName, options) => {
29
+ const id = await resolveIdOrName(idOrName, "certificate", (name) => client.listCertificates({ name }));
30
+ const certificate = await client.getCertificate(id);
31
+ output(certificate, formatCertificateDetails, options);
32
+ }));
33
+ cert
34
+ .command("create")
35
+ .description("Create a certificate")
36
+ .requiredOption("--name <name>", "Certificate name")
37
+ .option("--type <type>", "Certificate type (uploaded, managed)", "uploaded")
38
+ .option("--cert-file <path>", "Path to certificate PEM file (for uploaded)")
39
+ .option("--key-file <path>", "Path to private key PEM file (for uploaded)")
40
+ .option("--domain <domains...>", "Domain names (for managed)")
41
+ .action(cloudAction(async (client, options) => {
42
+ const createOpts = {
43
+ name: options.name,
44
+ type: options.type,
45
+ };
46
+ if (options.type === "managed") {
47
+ if (!options.domain || options.domain.length === 0) {
48
+ console.error(error("Managed certificates require --domain"));
49
+ process.exit(1);
50
+ }
51
+ createOpts.domain_names = options.domain;
52
+ }
53
+ else {
54
+ if (!(options.certFile && options.keyFile)) {
55
+ console.error(error("Uploaded certificates require --cert-file and --key-file"));
56
+ process.exit(1);
57
+ }
58
+ createOpts.certificate = readFileSync(options.certFile, "utf-8");
59
+ createOpts.private_key = readFileSync(options.keyFile, "utf-8");
60
+ }
61
+ const { certificate: created } = await client.createCertificate(createOpts);
62
+ console.log(success(`Certificate '${created.name}' created (ID: ${created.id})`));
63
+ }));
64
+ cert
65
+ .command("delete <id-or-name>")
66
+ .description("Delete a certificate")
67
+ .option("-y, --yes", "Skip confirmation")
68
+ .action(cloudAction(async (client, idOrName, options) => {
69
+ const id = await resolveIdOrName(idOrName, "certificate", (name) => client.listCertificates({ name }));
70
+ const certificate = await client.getCertificate(id);
71
+ if (!(await confirmAction(`Delete certificate '${certificate.name}' (ID: ${id})?`, options))) {
72
+ return;
73
+ }
74
+ await client.deleteCertificate(id);
75
+ console.log(success(`Certificate '${certificate.name}' (ID: ${id}) deleted.`));
76
+ }));
77
+ cert
78
+ .command("update <id-or-name>")
79
+ .description("Update certificate")
80
+ .option("--name <name>", "New name")
81
+ .action(cloudAction(async (client, idOrName, options) => {
82
+ const id = await resolveIdOrName(idOrName, "certificate", (name) => client.listCertificates({ name }));
83
+ await client.updateCertificate(id, { name: options.name });
84
+ console.log(success(`Certificate ${id} updated.`));
85
+ }));
86
+ cert
87
+ .command("add-label <id-or-name> <label>")
88
+ .description("Add a label (key=value)")
89
+ .action(cloudAction(async (client, idOrName, label) => {
90
+ const id = await resolveIdOrName(idOrName, "certificate", (name) => client.listCertificates({ name }));
91
+ const certificate = await client.getCertificate(id);
92
+ const [key, value] = label.split("=");
93
+ await client.updateCertificate(id, {
94
+ labels: { ...certificate.labels, [key]: value || "" },
95
+ });
96
+ console.log(success(`Label '${key}' added.`));
97
+ }));
98
+ cert
99
+ .command("remove-label <id-or-name> <key>")
100
+ .description("Remove a label")
101
+ .action(cloudAction(async (client, idOrName, key) => {
102
+ const id = await resolveIdOrName(idOrName, "certificate", (name) => client.listCertificates({ name }));
103
+ const certificate = await client.getCertificate(id);
104
+ const labels = Object.fromEntries(Object.entries(certificate.labels).filter(([k]) => k !== key));
105
+ await client.updateCertificate(id, { labels });
106
+ console.log(success(`Label '${key}' removed.`));
107
+ }));
108
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerContextCommands(parent: Command): void;
@@ -0,0 +1,133 @@
1
+ import { password as passwordPrompt } from "@inquirer/prompts";
2
+ import { error as fmtError, success as fmtSuccess, warning as fmtWarning, } from "../../shared/formatter.js";
3
+ import { normalizePermission, runCloudTokenBootstrap, } from "../browser-bootstrap.js";
4
+ import { createContext, deleteContext, getActiveContext, listContexts, resolveToken, useContext, validateCloudToken, } from "../context.js";
5
+ import { formatContextList } from "../formatter.js";
6
+ import { printCloudTokenInstructions } from "../token-instructions.js";
7
+ export function registerContextCommands(parent) {
8
+ const context = parent
9
+ .command("context")
10
+ .description("Cloud context (token) management");
11
+ context
12
+ .command("create <name>")
13
+ .description("Create a new cloud context")
14
+ .option("-t, --token <token>", "API token (will prompt if not provided)")
15
+ .option("--no-verify", "Skip validating the token with api.hetzner.cloud")
16
+ .action(async (name, options) => {
17
+ try {
18
+ let token = options.token;
19
+ if (!token) {
20
+ printCloudTokenInstructions();
21
+ token = await passwordPrompt({
22
+ message: `Paste your Cloud API token for '${name}':`,
23
+ validate: (v) => v.length > 0 || "Token is required",
24
+ });
25
+ }
26
+ await createContext(name, token, { verify: options.verify });
27
+ console.log(fmtSuccess(`Context '${name}' created and activated.`));
28
+ }
29
+ catch (error) {
30
+ if (error instanceof Error && error.name === "ExitPromptError") {
31
+ process.exit(0);
32
+ }
33
+ console.error(fmtError(error instanceof Error ? error.message : "Unknown error"));
34
+ process.exit(1);
35
+ }
36
+ });
37
+ context
38
+ .command("use <name>")
39
+ .description("Switch to a different cloud context")
40
+ .action((name) => {
41
+ try {
42
+ useContext(name);
43
+ console.log(fmtSuccess(`Switched to context '${name}'.`));
44
+ }
45
+ catch (error) {
46
+ console.error(fmtError(error instanceof Error ? error.message : "Unknown error"));
47
+ process.exit(1);
48
+ }
49
+ });
50
+ context
51
+ .command("delete <name>")
52
+ .alias("rm")
53
+ .description("Delete a cloud context")
54
+ .action(async (name) => {
55
+ try {
56
+ await deleteContext(name);
57
+ console.log(fmtSuccess(`Context '${name}' deleted.`));
58
+ }
59
+ catch (error) {
60
+ console.error(fmtError(error instanceof Error ? error.message : "Unknown error"));
61
+ process.exit(1);
62
+ }
63
+ });
64
+ context
65
+ .command("list")
66
+ .alias("ls")
67
+ .description("List all cloud contexts")
68
+ .action(() => {
69
+ const contexts = listContexts();
70
+ console.log(formatContextList(contexts));
71
+ });
72
+ context
73
+ .command("active")
74
+ .description("Show the active cloud context")
75
+ .action(() => {
76
+ const active = getActiveContext();
77
+ if (active) {
78
+ console.log(fmtSuccess(`Active context: ${active}`));
79
+ }
80
+ else {
81
+ console.log(fmtWarning("No active context. Run: hctl cloud context create"));
82
+ }
83
+ });
84
+ context
85
+ .command("test")
86
+ .description("Validate the resolved Cloud API token")
87
+ .option("-t, --token <token>", "API token to validate")
88
+ .action(async (options) => {
89
+ try {
90
+ const token = await resolveToken(options.token);
91
+ await validateCloudToken(token);
92
+ console.log(fmtSuccess("Cloud token is accepted by api.hetzner.cloud."));
93
+ console.log(fmtWarning("Hetzner does not expose the project name through this API; verify the context name matches the Cloud Console project."));
94
+ }
95
+ catch (error) {
96
+ console.error(fmtError(error instanceof Error ? error.message : "Unknown error"));
97
+ process.exit(1);
98
+ }
99
+ });
100
+ context
101
+ .command("bootstrap <name>")
102
+ .description("Open an explicit browser-assisted flow to create and save a Cloud API token")
103
+ .requiredOption("--project <name>", "Cloud project name")
104
+ .option("--create-project", "Guide through creating the project first")
105
+ .option("--token-name <name>", "API token name to use in Hetzner Console")
106
+ .option("--permission <permission>", "Token permission: read or read-write", "read-write")
107
+ .option("--write-env [path]", "Also write HETZNER_CLOUD_TOKEN to .env or the provided env file path")
108
+ .option("--no-verify", "Skip validating the token with api.hetzner.cloud")
109
+ .action(async (name, options) => {
110
+ try {
111
+ const permission = normalizePermission(options.permission);
112
+ const created = await runCloudTokenBootstrap({
113
+ contextName: name,
114
+ createProject: options.createProject,
115
+ envFile: options.writeEnv,
116
+ permission,
117
+ projectName: options.project,
118
+ tokenName: options.tokenName,
119
+ verify: options.verify,
120
+ });
121
+ if (created) {
122
+ console.log(fmtSuccess(`Context '${name}' created and activated.`));
123
+ }
124
+ }
125
+ catch (error) {
126
+ if (error instanceof Error && error.name === "ExitPromptError") {
127
+ process.exit(0);
128
+ }
129
+ console.error(fmtError(error instanceof Error ? error.message : "Unknown error"));
130
+ process.exit(1);
131
+ }
132
+ });
133
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerDatacenterCommands(parent: Command): void;
@@ -0,0 +1,24 @@
1
+ import { output } from "../../shared/helpers.js";
2
+ import { formatDatacenterDetails, formatDatacenterList } from "../formatter.js";
3
+ import { cloudAction, resolveIdOrName, } from "../helpers.js";
4
+ export function registerDatacenterCommands(parent) {
5
+ const datacenter = parent
6
+ .command("datacenter")
7
+ .description("Datacenter information");
8
+ datacenter
9
+ .command("list")
10
+ .alias("ls")
11
+ .description("List all datacenters")
12
+ .action(cloudAction(async (client, options) => {
13
+ const datacenters = await client.listDatacenters();
14
+ output(datacenters, formatDatacenterList, options);
15
+ }));
16
+ datacenter
17
+ .command("describe <id-or-name>")
18
+ .description("Show datacenter details")
19
+ .action(cloudAction(async (client, idOrName, options) => {
20
+ const id = await resolveIdOrName(idOrName, "datacenter", (name) => client.listDatacenters({ name }));
21
+ const datacenter = await client.getDatacenter(id);
22
+ output(datacenter, formatDatacenterDetails, options);
23
+ }));
24
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerCloudFirewallCommands(parent: Command): void;
@@ -0,0 +1,142 @@
1
+ import { success } from "../../shared/formatter.js";
2
+ import { confirmAction, output } from "../../shared/helpers.js";
3
+ import { formatCloudFirewallDetails, formatCloudFirewallList, } from "../formatter.js";
4
+ import { cloudAction, parseLabels, readJsonFile, resolveIdOrName, } from "../helpers.js";
5
+ export function registerCloudFirewallCommands(parent) {
6
+ const firewall = parent
7
+ .command("firewall")
8
+ .description("Cloud firewall management");
9
+ firewall
10
+ .command("list")
11
+ .alias("ls")
12
+ .description("List all firewalls")
13
+ .option("-l, --label-selector <selector>", "Label selector")
14
+ .action(cloudAction(async (client, options) => {
15
+ const firewalls = await client.listFirewalls({
16
+ label_selector: options.labelSelector,
17
+ });
18
+ output(firewalls, formatCloudFirewallList, options);
19
+ }));
20
+ firewall
21
+ .command("describe <id-or-name>")
22
+ .description("Show firewall details")
23
+ .action(cloudAction(async (client, idOrName, options) => {
24
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
25
+ const fw = await client.getFirewall(id);
26
+ output(fw, formatCloudFirewallDetails, options);
27
+ }));
28
+ firewall
29
+ .command("create")
30
+ .description("Create a firewall")
31
+ .requiredOption("--name <name>", "Firewall name")
32
+ .option("--rules-file <file>", "JSON file with firewall rules array")
33
+ .option("--labels <labels>", "Labels as key=value pairs (comma-separated)")
34
+ .action(cloudAction(async (client, options) => {
35
+ const { firewall: fw } = await client.createFirewall({
36
+ name: options.name,
37
+ rules: options.rulesFile
38
+ ? readJsonFile(options.rulesFile)
39
+ : undefined,
40
+ labels: options.labels ? parseLabels(options.labels) : undefined,
41
+ });
42
+ console.log(success(`Firewall '${fw.name}' created (ID: ${fw.id}, ${fw.rules.length} rules)`));
43
+ }));
44
+ firewall
45
+ .command("delete <id-or-name>")
46
+ .description("Delete a firewall")
47
+ .option("-y, --yes", "Skip confirmation")
48
+ .action(cloudAction(async (client, idOrName, options) => {
49
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
50
+ const fw = await client.getFirewall(id);
51
+ if (!(await confirmAction(`Delete firewall '${fw.name}' (ID: ${id})?`, options))) {
52
+ return;
53
+ }
54
+ await client.deleteFirewall(id);
55
+ console.log(success(`Firewall '${fw.name}' (ID: ${id}) deleted.`));
56
+ }));
57
+ firewall
58
+ .command("update <id-or-name>")
59
+ .description("Update firewall")
60
+ .option("--name <name>", "New name")
61
+ .action(cloudAction(async (client, idOrName, options) => {
62
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
63
+ await client.updateFirewall(id, { name: options.name });
64
+ console.log(success(`Firewall ${id} updated.`));
65
+ }));
66
+ firewall
67
+ .command("apply-to-resource <id-or-name>")
68
+ .description("Apply firewall to resource")
69
+ .requiredOption("--type <type>", "Resource type (server, label_selector)")
70
+ .option("--server <server>", "Server ID")
71
+ .option("--label-selector <selector>", "Label selector")
72
+ .action(cloudAction(async (client, idOrName, options) => {
73
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
74
+ const applyTo = [
75
+ {
76
+ type: options.type,
77
+ ...(options.server
78
+ ? { server: { id: Number.parseInt(options.server, 10) } }
79
+ : {}),
80
+ ...(options.labelSelector
81
+ ? { label_selector: { selector: options.labelSelector } }
82
+ : {}),
83
+ },
84
+ ];
85
+ await client.applyFirewall(id, applyTo);
86
+ console.log(success(`Firewall ${id} applied.`));
87
+ }));
88
+ firewall
89
+ .command("remove-from-resource <id-or-name>")
90
+ .description("Remove firewall from resource")
91
+ .requiredOption("--type <type>", "Resource type (server, label_selector)")
92
+ .option("--server <server>", "Server ID")
93
+ .option("--label-selector <selector>", "Label selector")
94
+ .action(cloudAction(async (client, idOrName, options) => {
95
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
96
+ const removeFrom = [
97
+ {
98
+ type: options.type,
99
+ ...(options.server
100
+ ? { server: { id: Number.parseInt(options.server, 10) } }
101
+ : {}),
102
+ ...(options.labelSelector
103
+ ? { label_selector: { selector: options.labelSelector } }
104
+ : {}),
105
+ },
106
+ ];
107
+ await client.removeFirewallFromResources(id, removeFrom);
108
+ console.log(success(`Firewall ${id} removed from resource.`));
109
+ }));
110
+ firewall
111
+ .command("add-label <id-or-name> <label>")
112
+ .description("Add a label (key=value)")
113
+ .action(cloudAction(async (client, idOrName, label) => {
114
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
115
+ const fw = await client.getFirewall(id);
116
+ const [key, value] = label.split("=");
117
+ await client.updateFirewall(id, {
118
+ labels: { ...fw.labels, [key]: value || "" },
119
+ });
120
+ console.log(success(`Label '${key}' added.`));
121
+ }));
122
+ firewall
123
+ .command("remove-label <id-or-name> <key>")
124
+ .description("Remove a label")
125
+ .action(cloudAction(async (client, idOrName, key) => {
126
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
127
+ const fw = await client.getFirewall(id);
128
+ const labels = Object.fromEntries(Object.entries(fw.labels).filter(([k]) => k !== key));
129
+ await client.updateFirewall(id, { labels });
130
+ console.log(success(`Label '${key}' removed.`));
131
+ }));
132
+ firewall
133
+ .command("set-rules <id-or-name>")
134
+ .description("Set firewall rules from a JSON file")
135
+ .requiredOption("--rules-file <file>", "JSON file with firewall rules array")
136
+ .action(cloudAction(async (client, idOrName, options) => {
137
+ const id = await resolveIdOrName(idOrName, "firewall", (name) => client.listFirewalls({ name }));
138
+ const rules = readJsonFile(options.rulesFile);
139
+ await client.setFirewallRules(id, rules);
140
+ console.log(success(`Firewall ${id} rules updated.`));
141
+ }));
142
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerFloatingIpCommands(parent: Command): void;
@@ -0,0 +1,138 @@
1
+ import { success } from "../../shared/formatter.js";
2
+ import { confirmAction, output } from "../../shared/helpers.js";
3
+ import { formatFloatingIpDetails, formatFloatingIpList } from "../formatter.js";
4
+ import { cloudAction, resolveIdOrName, } from "../helpers.js";
5
+ export function registerFloatingIpCommands(parent) {
6
+ const fip = parent
7
+ .command("floating-ip")
8
+ .description("Floating IP management");
9
+ fip
10
+ .command("list")
11
+ .alias("ls")
12
+ .description("List all floating IPs")
13
+ .option("-l, --label-selector <selector>", "Label selector")
14
+ .action(cloudAction(async (client, options) => {
15
+ const ips = await client.listFloatingIps({
16
+ label_selector: options.labelSelector,
17
+ });
18
+ output(ips, formatFloatingIpList, options);
19
+ }));
20
+ fip
21
+ .command("describe <id-or-name>")
22
+ .description("Show floating IP details")
23
+ .action(cloudAction(async (client, idOrName, options) => {
24
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
25
+ const ip = await client.getFloatingIp(id);
26
+ output(ip, formatFloatingIpDetails, options);
27
+ }));
28
+ fip
29
+ .command("create")
30
+ .description("Create a floating IP")
31
+ .requiredOption("--type <type>", "IP type (ipv4, ipv6)")
32
+ .option("--name <name>", "Name")
33
+ .option("--description <desc>", "Description")
34
+ .option("--home-location <loc>", "Home location")
35
+ .option("--server <id>", "Server to assign to")
36
+ .action(cloudAction(async (client, options) => {
37
+ const { floating_ip: ip } = await client.createFloatingIp({
38
+ type: options.type,
39
+ name: options.name,
40
+ description: options.description,
41
+ home_location: options.homeLocation,
42
+ server: options.server
43
+ ? Number.parseInt(options.server, 10)
44
+ : undefined,
45
+ });
46
+ console.log(success(`Floating IP created (ID: ${ip.id}, IP: ${ip.ip})`));
47
+ }));
48
+ fip
49
+ .command("delete <id-or-name>")
50
+ .description("Delete a floating IP")
51
+ .option("-y, --yes", "Skip confirmation")
52
+ .action(cloudAction(async (client, idOrName, options) => {
53
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
54
+ const ip = await client.getFloatingIp(id);
55
+ if (!(await confirmAction(`Delete floating IP '${ip.name}' (ID: ${id})?`, options))) {
56
+ return;
57
+ }
58
+ await client.deleteFloatingIp(id);
59
+ console.log(success(`Floating IP '${ip.name}' (ID: ${id}) deleted.`));
60
+ }));
61
+ fip
62
+ .command("update <id-or-name>")
63
+ .description("Update floating IP")
64
+ .option("--name <name>", "New name")
65
+ .option("--description <desc>", "New description")
66
+ .action(cloudAction(async (client, idOrName, options) => {
67
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
68
+ await client.updateFloatingIp(id, {
69
+ name: options.name,
70
+ description: options.description,
71
+ });
72
+ console.log(success(`Floating IP ${id} updated.`));
73
+ }));
74
+ fip
75
+ .command("assign <id-or-name> <server>")
76
+ .description("Assign floating IP to server")
77
+ .action(cloudAction(async (client, idOrName, server) => {
78
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
79
+ await client.assignFloatingIp(id, Number.parseInt(server, 10));
80
+ console.log(success(`Floating IP ${id} assigned to server ${server}.`));
81
+ }));
82
+ fip
83
+ .command("unassign <id-or-name>")
84
+ .description("Unassign floating IP")
85
+ .action(cloudAction(async (client, idOrName) => {
86
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
87
+ await client.unassignFloatingIp(id);
88
+ console.log(success(`Floating IP ${id} unassigned.`));
89
+ }));
90
+ fip
91
+ .command("enable-protection <id-or-name>")
92
+ .description("Enable delete protection")
93
+ .action(cloudAction(async (client, idOrName) => {
94
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
95
+ await client.changeFloatingIpProtection(id, true);
96
+ console.log(success(`Protection enabled for floating IP ${id}.`));
97
+ }));
98
+ fip
99
+ .command("disable-protection <id-or-name>")
100
+ .description("Disable delete protection")
101
+ .action(cloudAction(async (client, idOrName) => {
102
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
103
+ await client.changeFloatingIpProtection(id, false);
104
+ console.log(success(`Protection disabled for floating IP ${id}.`));
105
+ }));
106
+ fip
107
+ .command("set-rdns <id-or-name>")
108
+ .description("Set reverse DNS")
109
+ .requiredOption("--ip <ip>", "IP address")
110
+ .requiredOption("--dns-ptr <ptr>", "DNS pointer (empty to reset)")
111
+ .action(cloudAction(async (client, idOrName, options) => {
112
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
113
+ await client.changeFloatingIpDnsPtr(id, options.ip, options.dnsPtr || null);
114
+ console.log(success("rDNS updated."));
115
+ }));
116
+ fip
117
+ .command("add-label <id-or-name> <label>")
118
+ .description("Add a label (key=value)")
119
+ .action(cloudAction(async (client, idOrName, label) => {
120
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
121
+ const ip = await client.getFloatingIp(id);
122
+ const [key, value] = label.split("=");
123
+ await client.updateFloatingIp(id, {
124
+ labels: { ...ip.labels, [key]: value || "" },
125
+ });
126
+ console.log(success(`Label '${key}' added.`));
127
+ }));
128
+ fip
129
+ .command("remove-label <id-or-name> <key>")
130
+ .description("Remove a label")
131
+ .action(cloudAction(async (client, idOrName, key) => {
132
+ const id = await resolveIdOrName(idOrName, "floating IP", (name) => client.listFloatingIps({ name }));
133
+ const ip = await client.getFloatingIp(id);
134
+ const labels = Object.fromEntries(Object.entries(ip.labels).filter(([k]) => k !== key));
135
+ await client.updateFloatingIp(id, { labels });
136
+ console.log(success(`Label '${key}' removed.`));
137
+ }));
138
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerImageCommands(parent: Command): void;