@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.
- package/LICENSE +21 -0
- package/README.md +1066 -2
- package/dist/auction/cache.d.ts +6 -0
- package/dist/auction/cache.js +55 -0
- package/dist/auction/client.d.ts +24 -0
- package/dist/auction/client.js +236 -0
- package/dist/auction/commands.d.ts +2 -0
- package/dist/auction/commands.js +440 -0
- package/dist/auction/diff-formatter.d.ts +3 -0
- package/dist/auction/diff-formatter.js +61 -0
- package/dist/auction/diff.d.ts +11 -0
- package/dist/auction/diff.js +28 -0
- package/dist/auction/formatter.d.ts +21 -0
- package/dist/auction/formatter.js +182 -0
- package/dist/auction/store.d.ts +24 -0
- package/dist/auction/store.js +177 -0
- package/dist/auction/types.d.ts +74 -0
- package/dist/auction/types.js +5 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +74 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +4 -0
- package/dist/cloud/browser-bootstrap.d.ts +16 -0
- package/dist/cloud/browser-bootstrap.js +438 -0
- package/dist/cloud/client.d.ts +520 -0
- package/dist/cloud/client.js +755 -0
- package/dist/cloud/commands/certificate.d.ts +2 -0
- package/dist/cloud/commands/certificate.js +108 -0
- package/dist/cloud/commands/context.d.ts +2 -0
- package/dist/cloud/commands/context.js +133 -0
- package/dist/cloud/commands/datacenter.d.ts +2 -0
- package/dist/cloud/commands/datacenter.js +24 -0
- package/dist/cloud/commands/firewall.d.ts +2 -0
- package/dist/cloud/commands/firewall.js +142 -0
- package/dist/cloud/commands/floating-ip.d.ts +2 -0
- package/dist/cloud/commands/floating-ip.js +138 -0
- package/dist/cloud/commands/image.d.ts +2 -0
- package/dist/cloud/commands/image.js +98 -0
- package/dist/cloud/commands/index.d.ts +2 -0
- package/dist/cloud/commands/index.js +47 -0
- package/dist/cloud/commands/inventory.d.ts +2 -0
- package/dist/cloud/commands/inventory.js +13 -0
- package/dist/cloud/commands/iso.d.ts +2 -0
- package/dist/cloud/commands/iso.js +27 -0
- package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
- package/dist/cloud/commands/load-balancer-type.js +24 -0
- package/dist/cloud/commands/load-balancer.d.ts +2 -0
- package/dist/cloud/commands/load-balancer.js +269 -0
- package/dist/cloud/commands/location.d.ts +2 -0
- package/dist/cloud/commands/location.js +24 -0
- package/dist/cloud/commands/network.d.ts +2 -0
- package/dist/cloud/commands/network.js +157 -0
- package/dist/cloud/commands/placement-group.d.ts +2 -0
- package/dist/cloud/commands/placement-group.js +79 -0
- package/dist/cloud/commands/pricing.d.ts +2 -0
- package/dist/cloud/commands/pricing.js +15 -0
- package/dist/cloud/commands/primary-ip.d.ts +2 -0
- package/dist/cloud/commands/primary-ip.js +139 -0
- package/dist/cloud/commands/server-type.d.ts +2 -0
- package/dist/cloud/commands/server-type.js +24 -0
- package/dist/cloud/commands/server.d.ts +2 -0
- package/dist/cloud/commands/server.js +346 -0
- package/dist/cloud/commands/ssh-key.d.ts +2 -0
- package/dist/cloud/commands/ssh-key.js +94 -0
- package/dist/cloud/commands/volume.d.ts +2 -0
- package/dist/cloud/commands/volume.js +138 -0
- package/dist/cloud/context.d.ts +34 -0
- package/dist/cloud/context.js +215 -0
- package/dist/cloud/formatter.d.ts +40 -0
- package/dist/cloud/formatter.js +824 -0
- package/dist/cloud/helpers.d.ts +27 -0
- package/dist/cloud/helpers.js +67 -0
- package/dist/cloud/inventory.d.ts +100 -0
- package/dist/cloud/inventory.js +229 -0
- package/dist/cloud/pricing.d.ts +27 -0
- package/dist/cloud/pricing.js +103 -0
- package/dist/cloud/token-instructions.d.ts +2 -0
- package/dist/cloud/token-instructions.js +21 -0
- package/dist/cloud/types.d.ts +437 -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 +12 -0
- package/dist/index.js +21 -0
- package/dist/robot/client.d.ts +246 -0
- package/dist/robot/client.js +719 -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 +76 -0
- package/dist/robot/commands/cancel.d.ts +2 -0
- package/dist/robot/commands/cancel.js +37 -0
- package/dist/robot/commands/failover.d.ts +2 -0
- package/dist/robot/commands/failover.js +46 -0
- package/dist/robot/commands/firewall.d.ts +2 -0
- package/dist/robot/commands/firewall.js +72 -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 +152 -0
- package/dist/robot/commands/ip.d.ts +2 -0
- package/dist/robot/commands/ip.js +55 -0
- package/dist/robot/commands/key.d.ts +2 -0
- package/dist/robot/commands/key.js +68 -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 +32 -0
- package/dist/robot/commands/storagebox.d.ts +2 -0
- package/dist/robot/commands/storagebox.js +126 -0
- package/dist/robot/commands/subnet.d.ts +2 -0
- package/dist/robot/commands/subnet.js +24 -0
- package/dist/robot/commands/traffic.d.ts +2 -0
- package/dist/robot/commands/traffic.js +23 -0
- package/dist/robot/commands/vswitch.d.ts +2 -0
- package/dist/robot/commands/vswitch.js +65 -0
- package/dist/robot/commands/wol.d.ts +2 -0
- package/dist/robot/commands/wol.js +20 -0
- package/dist/robot/formatter.d.ts +56 -0
- package/dist/robot/formatter.js +546 -0
- package/dist/robot/types.d.ts +353 -0
- package/dist/robot/types.js +5 -0
- package/dist/shared/config.d.ts +86 -0
- package/dist/shared/config.js +289 -0
- package/dist/shared/formatter.d.ts +29 -0
- package/dist/shared/formatter.js +121 -0
- package/dist/shared/helpers.d.ts +39 -0
- package/dist/shared/helpers.js +86 -0
- package/dist/shared/init.d.ts +2 -0
- package/dist/shared/init.js +107 -0
- package/dist/shared/reference.d.ts +5 -0
- package/dist/shared/reference.js +688 -0
- package/dist/shared/schemas.d.ts +1 -0
- package/dist/shared/schemas.js +1022 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/package.json +131 -4
|
@@ -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,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,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,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,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
|
+
}
|