@celilo/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1566 -0
- package/bin/celilo +16 -0
- package/drizzle/0000_complex_puma.sql +179 -0
- package/drizzle/0001_dizzy_wolfpack.sql +2 -0
- package/drizzle/0002_web_routes.sql +16 -0
- package/drizzle/0003_backup_storage.sql +32 -0
- package/drizzle/meta/0000_snapshot.json +1151 -0
- package/drizzle/meta/0001_snapshot.json +1167 -0
- package/drizzle/meta/0002_snapshot.json +1257 -0
- package/drizzle/meta/_journal.json +27 -0
- package/package.json +64 -0
- package/schemas/system_config.json +106 -0
- package/src/__integration__/container-services-cli.integration.test.ts +246 -0
- package/src/ansible/dependencies.test.ts +309 -0
- package/src/ansible/dependencies.ts +896 -0
- package/src/ansible/inventory.test.ts +463 -0
- package/src/ansible/inventory.ts +445 -0
- package/src/ansible/secrets.ts +222 -0
- package/src/ansible/validation.test.ts +92 -0
- package/src/ansible/validation.ts +272 -0
- package/src/api-clients/digitalocean.ts +94 -0
- package/src/api-clients/proxmox.ts +655 -0
- package/src/capabilities/logging-wrapper.test.ts +217 -0
- package/src/capabilities/lookup.test.ts +149 -0
- package/src/capabilities/lookup.ts +89 -0
- package/src/capabilities/public-web-helpers.test.ts +198 -0
- package/src/capabilities/public-web-publish.test.ts +458 -0
- package/src/capabilities/registration.test.ts +395 -0
- package/src/capabilities/registration.ts +200 -0
- package/src/capabilities/route-validation.test.ts +121 -0
- package/src/capabilities/route-validation.ts +96 -0
- package/src/capabilities/secret-ref.test.ts +313 -0
- package/src/capabilities/secret-validation.ts +157 -0
- package/src/capabilities/secrets.test.ts +750 -0
- package/src/capabilities/secrets.ts +244 -0
- package/src/capabilities/validation.test.ts +613 -0
- package/src/capabilities/validation.ts +160 -0
- package/src/capabilities/well-known.test.ts +238 -0
- package/src/capabilities/well-known.ts +222 -0
- package/src/cli/cli.test.ts +654 -0
- package/src/cli/command-registry.ts +742 -0
- package/src/cli/command-tree-parser.test.ts +180 -0
- package/src/cli/command-tree-parser.ts +193 -0
- package/src/cli/commands/backup-create.ts +137 -0
- package/src/cli/commands/backup-delete.ts +74 -0
- package/src/cli/commands/backup-import.ts +97 -0
- package/src/cli/commands/backup-list.ts +132 -0
- package/src/cli/commands/backup-name.ts +73 -0
- package/src/cli/commands/backup-prune.ts +98 -0
- package/src/cli/commands/backup-restore.ts +122 -0
- package/src/cli/commands/capability-info.ts +121 -0
- package/src/cli/commands/capability-list.ts +47 -0
- package/src/cli/commands/completion.ts +87 -0
- package/src/cli/commands/hook-run.ts +176 -0
- package/src/cli/commands/ipam.ts +607 -0
- package/src/cli/commands/machine-add.ts +235 -0
- package/src/cli/commands/machine-earmark.ts +82 -0
- package/src/cli/commands/machine-list.ts +77 -0
- package/src/cli/commands/machine-remove.ts +90 -0
- package/src/cli/commands/machine-status.ts +131 -0
- package/src/cli/commands/module-audit.ts +51 -0
- package/src/cli/commands/module-build.ts +60 -0
- package/src/cli/commands/module-config.ts +170 -0
- package/src/cli/commands/module-deploy.ts +71 -0
- package/src/cli/commands/module-generate.ts +236 -0
- package/src/cli/commands/module-health.ts +108 -0
- package/src/cli/commands/module-import.ts +80 -0
- package/src/cli/commands/module-list.ts +43 -0
- package/src/cli/commands/module-logs.ts +73 -0
- package/src/cli/commands/module-remove.ts +162 -0
- package/src/cli/commands/module-show.ts +208 -0
- package/src/cli/commands/module-status.ts +131 -0
- package/src/cli/commands/module-types.ts +189 -0
- package/src/cli/commands/module-upgrade.ts +192 -0
- package/src/cli/commands/package.ts +68 -0
- package/src/cli/commands/secret-list.ts +99 -0
- package/src/cli/commands/secret-set.ts +134 -0
- package/src/cli/commands/service-add-digitalocean.ts +133 -0
- package/src/cli/commands/service-add-proxmox.ts +342 -0
- package/src/cli/commands/service-config-get.ts +83 -0
- package/src/cli/commands/service-config-set.ts +145 -0
- package/src/cli/commands/service-list.ts +74 -0
- package/src/cli/commands/service-reconfigure.ts +230 -0
- package/src/cli/commands/service-remove.ts +103 -0
- package/src/cli/commands/service-verify.ts +240 -0
- package/src/cli/commands/status.ts +216 -0
- package/src/cli/commands/storage-add-local.ts +106 -0
- package/src/cli/commands/storage-add-s3.ts +114 -0
- package/src/cli/commands/storage-list.ts +72 -0
- package/src/cli/commands/storage-remove.ts +54 -0
- package/src/cli/commands/storage-set-default.ts +44 -0
- package/src/cli/commands/storage-verify.ts +54 -0
- package/src/cli/commands/system-config.ts +168 -0
- package/src/cli/commands/system-init.ts +314 -0
- package/src/cli/commands/system-secret-get.ts +98 -0
- package/src/cli/commands/system-secret-set.ts +76 -0
- package/src/cli/commands/system-vault-password.ts +34 -0
- package/src/cli/completion.test.ts +37 -0
- package/src/cli/completion.ts +482 -0
- package/src/cli/fuel-gauge.test.ts +208 -0
- package/src/cli/fuel-gauge.ts +405 -0
- package/src/cli/generate-zsh-completion.test.ts +95 -0
- package/src/cli/generate-zsh-completion.ts +497 -0
- package/src/cli/index.ts +1583 -0
- package/src/cli/interactive-config.test.ts +201 -0
- package/src/cli/interactive-config.ts +62 -0
- package/src/cli/parser.test.ts +227 -0
- package/src/cli/parser.ts +244 -0
- package/src/cli/prompts.test.ts +33 -0
- package/src/cli/prompts.ts +121 -0
- package/src/cli/types.ts +38 -0
- package/src/cli/validators.test.ts +235 -0
- package/src/cli/validators.ts +188 -0
- package/src/config/env.ts +41 -0
- package/src/config/paths.test.ts +172 -0
- package/src/config/paths.ts +108 -0
- package/src/db/client.ts +190 -0
- package/src/db/migrate.ts +30 -0
- package/src/db/schema.test.ts +221 -0
- package/src/db/schema.ts +434 -0
- package/src/hooks/capability-loader-firewall.test.ts +246 -0
- package/src/hooks/capability-loader.test.ts +100 -0
- package/src/hooks/capability-loader.ts +520 -0
- package/src/hooks/define-hook.test.ts +488 -0
- package/src/hooks/executor.test.ts +462 -0
- package/src/hooks/executor.ts +469 -0
- package/src/hooks/logger.test.ts +54 -0
- package/src/hooks/logger.ts +95 -0
- package/src/hooks/test-fixtures/failing-hook.ts +13 -0
- package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
- package/src/hooks/test-fixtures/success-hook.ts +20 -0
- package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
- package/src/hooks/test-fixtures/void-hook.ts +13 -0
- package/src/hooks/types.ts +89 -0
- package/src/infrastructure/property-extractor.test.ts +194 -0
- package/src/infrastructure/property-extractor.ts +151 -0
- package/src/ipam/allocator.test.ts +442 -0
- package/src/ipam/allocator.ts +369 -0
- package/src/ipam/auto-allocator.test.ts +247 -0
- package/src/ipam/auto-allocator.ts +270 -0
- package/src/ipam/subnet-parser.test.ts +107 -0
- package/src/ipam/subnet-parser.ts +136 -0
- package/src/manifest/contracts/index.ts +61 -0
- package/src/manifest/contracts/v1.ts +118 -0
- package/src/manifest/json-schema-roundtrip.test.ts +99 -0
- package/src/manifest/schema.ts +367 -0
- package/src/manifest/template-validator.test.ts +231 -0
- package/src/manifest/template-validator.ts +322 -0
- package/src/manifest/validate.test.ts +1180 -0
- package/src/manifest/validate.ts +415 -0
- package/src/module/import.test.ts +355 -0
- package/src/module/import.ts +676 -0
- package/src/module/packaging/audit.ts +169 -0
- package/src/module/packaging/build.ts +228 -0
- package/src/module/packaging/checksum.ts +41 -0
- package/src/module/packaging/extract.ts +234 -0
- package/src/module/packaging/signature.ts +47 -0
- package/src/secrets/encryption.test.ts +284 -0
- package/src/secrets/encryption.ts +162 -0
- package/src/secrets/generators.test.ts +112 -0
- package/src/secrets/generators.ts +127 -0
- package/src/secrets/master-key.test.ts +159 -0
- package/src/secrets/master-key.ts +114 -0
- package/src/secrets/storage.test.ts +115 -0
- package/src/secrets/storage.ts +106 -0
- package/src/secrets/vault.test.ts +35 -0
- package/src/secrets/vault.ts +42 -0
- package/src/services/backup-create.ts +532 -0
- package/src/services/backup-metadata.ts +198 -0
- package/src/services/backup-restore.ts +229 -0
- package/src/services/backup-retention.ts +84 -0
- package/src/services/backup-storage.ts +281 -0
- package/src/services/build-stream.test.ts +122 -0
- package/src/services/build-stream.ts +201 -0
- package/src/services/config-interview.ts +694 -0
- package/src/services/container-service.test.ts +298 -0
- package/src/services/container-service.ts +401 -0
- package/src/services/cross-module-data-manager.test.ts +405 -0
- package/src/services/cross-module-data-manager.ts +412 -0
- package/src/services/deploy-ansible.ts +88 -0
- package/src/services/deploy-planner.ts +153 -0
- package/src/services/deploy-preflight.ts +274 -0
- package/src/services/deploy-ssh.ts +131 -0
- package/src/services/deploy-terraform.test.ts +55 -0
- package/src/services/deploy-terraform.ts +445 -0
- package/src/services/deploy-validation.ts +311 -0
- package/src/services/dns-auto-register.ts +211 -0
- package/src/services/health-runner.ts +184 -0
- package/src/services/infrastructure-selector.test.ts +485 -0
- package/src/services/infrastructure-selector.ts +245 -0
- package/src/services/infrastructure-variable-resolver.test.ts +751 -0
- package/src/services/infrastructure-variable-resolver.ts +234 -0
- package/src/services/machine-detector.ts +328 -0
- package/src/services/machine-pool.test.ts +405 -0
- package/src/services/machine-pool.ts +316 -0
- package/src/services/manifest-validation.ts +120 -0
- package/src/services/module-build.test.ts +290 -0
- package/src/services/module-build.ts +431 -0
- package/src/services/module-config.test.ts +237 -0
- package/src/services/module-config.ts +298 -0
- package/src/services/module-deploy.ts +862 -0
- package/src/services/module-types-drift.test.ts +73 -0
- package/src/services/module-types-generator.test.ts +288 -0
- package/src/services/module-types-generator.ts +189 -0
- package/src/services/proxmox-state-recovery.ts +140 -0
- package/src/services/schema-validation.ts +155 -0
- package/src/services/secret-schema-loader.test.ts +311 -0
- package/src/services/secret-schema-loader.ts +239 -0
- package/src/services/ssh-key-manager.test.ts +283 -0
- package/src/services/ssh-key-manager.ts +193 -0
- package/src/services/storage-providers/local.ts +105 -0
- package/src/services/storage-providers/s3.ts +182 -0
- package/src/services/storage-providers/types.ts +24 -0
- package/src/services/system-config-schema-types.ts +25 -0
- package/src/services/system-config-validator.test.ts +160 -0
- package/src/services/system-config-validator.ts +74 -0
- package/src/services/system-init.test.ts +153 -0
- package/src/services/system-init.ts +253 -0
- package/src/services/terraform-safety.ts +174 -0
- package/src/services/zone-detector.test.ts +110 -0
- package/src/services/zone-detector.ts +102 -0
- package/src/services/zone-policy.test.ts +97 -0
- package/src/services/zone-policy.ts +126 -0
- package/src/templates/generator.test.ts +645 -0
- package/src/templates/generator.ts +1119 -0
- package/src/templates/types.ts +62 -0
- package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
- package/src/test-utils/cli-context-interactive.test.ts +152 -0
- package/src/test-utils/cli-context-server.test.ts +66 -0
- package/src/test-utils/cli-context.test.ts +273 -0
- package/src/test-utils/cli-context.ts +677 -0
- package/src/test-utils/cli-result.test.ts +282 -0
- package/src/test-utils/cli-result.ts +241 -0
- package/src/test-utils/cli.ts +55 -0
- package/src/test-utils/completion-harness.test.ts +126 -0
- package/src/test-utils/completion-harness.ts +82 -0
- package/src/test-utils/database.test.ts +182 -0
- package/src/test-utils/database.ts +126 -0
- package/src/test-utils/filesystem.test.ts +208 -0
- package/src/test-utils/filesystem.ts +142 -0
- package/src/test-utils/fixtures.test.ts +123 -0
- package/src/test-utils/fixtures.ts +160 -0
- package/src/test-utils/golden-diff.ts +197 -0
- package/src/test-utils/index.ts +77 -0
- package/src/test-utils/integration.ts +81 -0
- package/src/test-utils/module-fixtures.ts +468 -0
- package/src/test-utils/modules.test.ts +144 -0
- package/src/test-utils/modules.ts +183 -0
- package/src/test-utils/setup-test-db.ts +90 -0
- package/src/test-utils/value-extractor.test.ts +231 -0
- package/src/test-utils/value-extractor.ts +228 -0
- package/src/types/infrastructure.ts +157 -0
- package/src/utils/shell.test.ts +365 -0
- package/src/utils/shell.ts +159 -0
- package/src/validation/schemas.ts +166 -0
- package/src/variables/ansible-resolver.test.ts +142 -0
- package/src/variables/ansible-resolver.ts +69 -0
- package/src/variables/capability-self-ref.test.ts +220 -0
- package/src/variables/context.test.ts +1265 -0
- package/src/variables/context.ts +624 -0
- package/src/variables/declarative-derivation.test.ts +743 -0
- package/src/variables/declarative-derivation.ts +200 -0
- package/src/variables/parser.test.ts +231 -0
- package/src/variables/parser.ts +76 -0
- package/src/variables/resolver.test.ts +458 -0
- package/src/variables/resolver.ts +282 -0
- package/src/variables/types.ts +59 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPAM (IP Address Management) commands
|
|
3
|
+
* Manage VMID and IP address reservations and allocations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getDb } from '../../db/client';
|
|
7
|
+
import {
|
|
8
|
+
inferZoneFromIP,
|
|
9
|
+
listReservations,
|
|
10
|
+
listVMIDReservations,
|
|
11
|
+
reserveIP,
|
|
12
|
+
reserveVMID,
|
|
13
|
+
unreserveIP,
|
|
14
|
+
unreserveVMID,
|
|
15
|
+
} from '../../ipam/allocator';
|
|
16
|
+
import { getArg, getFlag, validateRequiredArgs } from '../parser';
|
|
17
|
+
import type { CommandResult } from '../types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Handle IPAM VMID reserve command
|
|
21
|
+
*
|
|
22
|
+
* Usage: celilo ipam vmid reserve <vmid-or-range> --reason <reason>
|
|
23
|
+
*
|
|
24
|
+
* @param args - Command arguments
|
|
25
|
+
* @param flags - Command flags
|
|
26
|
+
* @returns Command result
|
|
27
|
+
*/
|
|
28
|
+
export async function handleIpamVmidReserve(
|
|
29
|
+
args: string[],
|
|
30
|
+
flags: Record<string, string | boolean>,
|
|
31
|
+
): Promise<CommandResult> {
|
|
32
|
+
const error = validateRequiredArgs(args, 1);
|
|
33
|
+
if (error) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: `${error}\n\nUsage: celilo ipam vmid reserve <vmid-or-range> --reason <reason>`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const vmidArg = getArg(args, 0);
|
|
41
|
+
if (!vmidArg) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: 'VMID or range is required',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse reason flag
|
|
49
|
+
const reason = getFlag(flags, 'reason');
|
|
50
|
+
if (!reason) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: 'Reason is required. Use --reason "description"',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const db = getDb();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Parse VMID or range
|
|
61
|
+
if (vmidArg.includes('-')) {
|
|
62
|
+
// Range format: "2100-2110"
|
|
63
|
+
const [startStr, endStr] = vmidArg.split('-');
|
|
64
|
+
const start = Number.parseInt(startStr, 10);
|
|
65
|
+
const end = Number.parseInt(endStr, 10);
|
|
66
|
+
|
|
67
|
+
if (Number.isNaN(start) || Number.isNaN(end)) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: `Invalid VMID range: ${vmidArg}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (start >= end) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: 'Invalid range: start must be less than end',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Reserve each VMID in range
|
|
82
|
+
for (let vmid = start; vmid <= end; vmid++) {
|
|
83
|
+
await reserveVMID(vmid, reason, db);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
message: `Reserved VMIDs ${start}-${end}: ${reason}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Single VMID
|
|
93
|
+
const vmid = Number.parseInt(vmidArg, 10);
|
|
94
|
+
if (Number.isNaN(vmid)) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
error: `Invalid VMID: ${vmidArg}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await reserveVMID(vmid, reason, db);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
message: `Reserved VMID ${vmid}: ${reason}`,
|
|
106
|
+
};
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: err instanceof Error ? err.message : String(err),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handle IPAM VMID unreserve command
|
|
117
|
+
*
|
|
118
|
+
* Usage: celilo ipam vmid unreserve <vmid-or-range>
|
|
119
|
+
*
|
|
120
|
+
* @param args - Command arguments
|
|
121
|
+
* @param flags - Command flags
|
|
122
|
+
* @returns Command result
|
|
123
|
+
*/
|
|
124
|
+
export async function handleIpamVmidUnreserve(
|
|
125
|
+
args: string[],
|
|
126
|
+
_flags: Record<string, string | boolean>,
|
|
127
|
+
): Promise<CommandResult> {
|
|
128
|
+
const error = validateRequiredArgs(args, 1);
|
|
129
|
+
if (error) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: `${error}\n\nUsage: celilo ipam vmid unreserve <vmid-or-range>`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const vmidArg = getArg(args, 0);
|
|
137
|
+
if (!vmidArg) {
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
error: 'VMID or range is required',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const db = getDb();
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Parse VMID or range
|
|
148
|
+
if (vmidArg.includes('-')) {
|
|
149
|
+
// Range format: "2100-2110"
|
|
150
|
+
const [startStr, endStr] = vmidArg.split('-');
|
|
151
|
+
const start = Number.parseInt(startStr, 10);
|
|
152
|
+
const end = Number.parseInt(endStr, 10);
|
|
153
|
+
|
|
154
|
+
if (Number.isNaN(start) || Number.isNaN(end)) {
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: `Invalid VMID range: ${vmidArg}`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (start >= end) {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
error: 'Invalid range: start must be less than end',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Unreserve each VMID in range
|
|
169
|
+
for (let vmid = start; vmid <= end; vmid++) {
|
|
170
|
+
await unreserveVMID(vmid, db);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
success: true,
|
|
175
|
+
message: `Unreserved VMIDs ${start}-${end}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Single VMID
|
|
180
|
+
const vmid = Number.parseInt(vmidArg, 10);
|
|
181
|
+
if (Number.isNaN(vmid)) {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
error: `Invalid VMID: ${vmidArg}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await unreserveVMID(vmid, db);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
success: true,
|
|
192
|
+
message: `Unreserved VMID ${vmid}`,
|
|
193
|
+
};
|
|
194
|
+
} catch (err) {
|
|
195
|
+
return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: err instanceof Error ? err.message : String(err),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Handle IPAM VMID list-reservations command
|
|
204
|
+
*
|
|
205
|
+
* Usage: celilo ipam vmid list-reservations
|
|
206
|
+
*
|
|
207
|
+
* @param args - Command arguments
|
|
208
|
+
* @param flags - Command flags
|
|
209
|
+
* @returns Command result
|
|
210
|
+
*/
|
|
211
|
+
export async function handleIpamVmidListReservations(
|
|
212
|
+
_args: string[],
|
|
213
|
+
_flags: Record<string, string | boolean>,
|
|
214
|
+
): Promise<CommandResult> {
|
|
215
|
+
const db = getDb();
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const reservations = await listVMIDReservations(db);
|
|
219
|
+
|
|
220
|
+
if (reservations.length === 0) {
|
|
221
|
+
return {
|
|
222
|
+
success: true,
|
|
223
|
+
message: 'No VMID reservations',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const lines = ['VMID Reservations:', ''];
|
|
228
|
+
for (const reservation of reservations) {
|
|
229
|
+
lines.push(`VMID ${reservation.vmid}: ${reservation.reason}`);
|
|
230
|
+
lines.push(` Reserved: ${new Date(reservation.reservedAt).toLocaleString()}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
message: lines.join('\n'),
|
|
236
|
+
data: reservations,
|
|
237
|
+
};
|
|
238
|
+
} catch (err) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: err instanceof Error ? err.message : String(err),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Handle IPAM IP exclude command
|
|
248
|
+
*
|
|
249
|
+
* Usage: celilo ipam ip exclude <ip-range> --reason <reason> [--zone <zone>]
|
|
250
|
+
*
|
|
251
|
+
* @param args - Command arguments
|
|
252
|
+
* @param flags - Command flags
|
|
253
|
+
* @returns Command result
|
|
254
|
+
*/
|
|
255
|
+
export async function handleIpamIpReserve(
|
|
256
|
+
args: string[],
|
|
257
|
+
flags: Record<string, string | boolean>,
|
|
258
|
+
): Promise<CommandResult> {
|
|
259
|
+
const error = validateRequiredArgs(args, 1);
|
|
260
|
+
if (error) {
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: `${error}\n\nUsage: celilo ipam ip exclude <ip-range> --reason <reason> [--zone <zone>]`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ipArg = getArg(args, 0);
|
|
268
|
+
if (!ipArg) {
|
|
269
|
+
return {
|
|
270
|
+
success: false,
|
|
271
|
+
error: 'IP or range is required',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Parse reason flag
|
|
276
|
+
const reason = getFlag(flags, 'reason');
|
|
277
|
+
if (!reason) {
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
error: 'Reason is required. Use --reason "description"',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const db = getDb();
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
// Parse IP or range
|
|
288
|
+
let ipStart: string;
|
|
289
|
+
let ipEnd: string | null = null;
|
|
290
|
+
|
|
291
|
+
if (ipArg.includes('-')) {
|
|
292
|
+
// Range format: "10.0.10.1-10.0.10.9"
|
|
293
|
+
const [start, end] = ipArg.split('-');
|
|
294
|
+
ipStart = start.trim();
|
|
295
|
+
ipEnd = end.trim();
|
|
296
|
+
} else {
|
|
297
|
+
// Single IP
|
|
298
|
+
ipStart = ipArg;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Infer zone from IP, or use explicit --zone override
|
|
302
|
+
const explicitZone = getFlag(flags, 'zone');
|
|
303
|
+
const zone = explicitZone || (await inferZoneFromIP(ipStart, db));
|
|
304
|
+
|
|
305
|
+
if (!zone) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
error: `Cannot determine zone for IP ${ipStart}. It doesn't match any configured zone subnet. Use --zone to specify manually.`,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (zone !== 'dmz' && zone !== 'app' && zone !== 'secure' && zone !== 'internal') {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
error: `Invalid zone: ${zone}. Must be internal, dmz, app, or secure`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await reserveIP(ipStart, zone, reason, ipEnd, db);
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
success: true,
|
|
323
|
+
message: `Excluded IP ${ipEnd ? `${ipStart}-${ipEnd}` : ipStart} in zone ${zone}: ${reason}`,
|
|
324
|
+
};
|
|
325
|
+
} catch (err) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: err instanceof Error ? err.message : String(err),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Handle IPAM IP include command (remove exclusion)
|
|
335
|
+
*
|
|
336
|
+
* Usage: celilo ipam ip include <ip-or-range> [--zone <zone>]
|
|
337
|
+
*
|
|
338
|
+
* @param args - Command arguments
|
|
339
|
+
* @param flags - Command flags
|
|
340
|
+
* @returns Command result
|
|
341
|
+
*/
|
|
342
|
+
export async function handleIpamIpUnreserve(
|
|
343
|
+
args: string[],
|
|
344
|
+
flags: Record<string, string | boolean>,
|
|
345
|
+
): Promise<CommandResult> {
|
|
346
|
+
const error = validateRequiredArgs(args, 1);
|
|
347
|
+
if (error) {
|
|
348
|
+
return {
|
|
349
|
+
success: false,
|
|
350
|
+
error: `${error}\n\nUsage: celilo ipam ip include <ip-or-range> [--zone <zone>]`,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const ipArg = getArg(args, 0);
|
|
355
|
+
if (!ipArg) {
|
|
356
|
+
return {
|
|
357
|
+
success: false,
|
|
358
|
+
error: 'IP is required',
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const db = getDb();
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
// Parse IP (take first part if range)
|
|
366
|
+
const ipStart = ipArg.includes('-') ? ipArg.split('-')[0].trim() : ipArg;
|
|
367
|
+
|
|
368
|
+
// Infer zone from IP, or use explicit --zone override
|
|
369
|
+
const explicitZone = getFlag(flags, 'zone');
|
|
370
|
+
const zone = explicitZone || (await inferZoneFromIP(ipStart, db));
|
|
371
|
+
|
|
372
|
+
if (!zone) {
|
|
373
|
+
return {
|
|
374
|
+
success: false,
|
|
375
|
+
error: `Cannot determine zone for IP ${ipStart}. Use --zone to specify manually.`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (zone !== 'dmz' && zone !== 'app' && zone !== 'secure' && zone !== 'internal') {
|
|
380
|
+
return {
|
|
381
|
+
success: false,
|
|
382
|
+
error: `Invalid zone: ${zone}. Must be internal, dmz, app, or secure`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
await unreserveIP(ipStart, zone, db);
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
success: true,
|
|
390
|
+
message: `Removed exclusion for IP ${ipArg} in zone ${zone}`,
|
|
391
|
+
};
|
|
392
|
+
} catch (err) {
|
|
393
|
+
return {
|
|
394
|
+
success: false,
|
|
395
|
+
error: err instanceof Error ? err.message : String(err),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Handle IPAM IP list-exclusions command
|
|
402
|
+
*
|
|
403
|
+
* Usage: celilo ipam ip list-exclusions
|
|
404
|
+
*
|
|
405
|
+
* @param args - Command arguments
|
|
406
|
+
* @param flags - Command flags
|
|
407
|
+
* @returns Command result
|
|
408
|
+
*/
|
|
409
|
+
export async function handleIpamIpListReservations(
|
|
410
|
+
_args: string[],
|
|
411
|
+
_flags: Record<string, string | boolean>,
|
|
412
|
+
): Promise<CommandResult> {
|
|
413
|
+
const db = getDb();
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
const reservations = await listReservations(db);
|
|
417
|
+
|
|
418
|
+
if (reservations.length === 0) {
|
|
419
|
+
return {
|
|
420
|
+
success: true,
|
|
421
|
+
message: 'No IP exclusions',
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const lines = ['IP Exclusions:', ''];
|
|
426
|
+
for (const reservation of reservations) {
|
|
427
|
+
const range = reservation.ipEnd
|
|
428
|
+
? `${reservation.ipStart}-${reservation.ipEnd}`
|
|
429
|
+
: reservation.ipStart;
|
|
430
|
+
lines.push(`${range} (${reservation.zone}): ${reservation.reason}`);
|
|
431
|
+
lines.push(` Excluded: ${new Date(reservation.reservedAt).toLocaleString()}`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
success: true,
|
|
436
|
+
message: lines.join('\n'),
|
|
437
|
+
data: reservations,
|
|
438
|
+
};
|
|
439
|
+
} catch (err) {
|
|
440
|
+
return {
|
|
441
|
+
success: false,
|
|
442
|
+
error: err instanceof Error ? err.message : String(err),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Handle IPAM show command - comprehensive summary
|
|
449
|
+
*
|
|
450
|
+
* Usage: celilo ipam show
|
|
451
|
+
*
|
|
452
|
+
* @param args - Command arguments
|
|
453
|
+
* @param flags - Command flags
|
|
454
|
+
* @returns Command result
|
|
455
|
+
*/
|
|
456
|
+
export async function handleIpamShow(
|
|
457
|
+
_args: string[],
|
|
458
|
+
_flags: Record<string, string | boolean>,
|
|
459
|
+
): Promise<CommandResult> {
|
|
460
|
+
const db = getDb();
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
const { ipAllocations } = await import('../../db/schema');
|
|
464
|
+
const allocations = await db.select().from(ipAllocations).all();
|
|
465
|
+
const ipReservations = await listReservations(db);
|
|
466
|
+
const vmidReservations = await listVMIDReservations(db);
|
|
467
|
+
|
|
468
|
+
const lines: string[] = ['IPAM Allocations Summary', ''];
|
|
469
|
+
|
|
470
|
+
// VMID section
|
|
471
|
+
lines.push('VMID Allocations:');
|
|
472
|
+
if (allocations.length === 0) {
|
|
473
|
+
lines.push(' (none)');
|
|
474
|
+
} else {
|
|
475
|
+
for (const allocation of allocations) {
|
|
476
|
+
lines.push(` ${allocation.vmid}: ${allocation.moduleId} (allocated)`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
lines.push('');
|
|
480
|
+
|
|
481
|
+
lines.push('VMID Reservations:');
|
|
482
|
+
if (vmidReservations.length === 0) {
|
|
483
|
+
lines.push(' (none)');
|
|
484
|
+
} else {
|
|
485
|
+
for (const reservation of vmidReservations) {
|
|
486
|
+
lines.push(` ${reservation.vmid}: ${reservation.reason}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
lines.push('');
|
|
490
|
+
|
|
491
|
+
// IP section - group by zone
|
|
492
|
+
const zones = ['internal', 'dmz', 'app', 'secure'];
|
|
493
|
+
for (const zone of zones) {
|
|
494
|
+
const zoneAllocations = allocations.filter((a) => a.zone === zone);
|
|
495
|
+
const zoneReservations = ipReservations.filter((r) => r.zone === zone);
|
|
496
|
+
|
|
497
|
+
if (zoneAllocations.length === 0 && zoneReservations.length === 0) {
|
|
498
|
+
continue; // Skip empty zones
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const zoneName = zone.toUpperCase();
|
|
502
|
+
lines.push(`IP Allocations (${zoneName}):`);
|
|
503
|
+
|
|
504
|
+
if (zoneAllocations.length === 0) {
|
|
505
|
+
lines.push(' (none)');
|
|
506
|
+
} else {
|
|
507
|
+
for (const allocation of zoneAllocations) {
|
|
508
|
+
lines.push(` ${allocation.containerIp}: ${allocation.moduleId} (allocated)`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
lines.push('');
|
|
512
|
+
|
|
513
|
+
lines.push(`IP Exclusions (${zoneName}):`);
|
|
514
|
+
if (zoneReservations.length === 0) {
|
|
515
|
+
lines.push(' (none)');
|
|
516
|
+
} else {
|
|
517
|
+
for (const reservation of zoneReservations) {
|
|
518
|
+
const range = reservation.ipEnd
|
|
519
|
+
? `${reservation.ipStart}-${reservation.ipEnd}`
|
|
520
|
+
: reservation.ipStart;
|
|
521
|
+
lines.push(` ${range}: ${reservation.reason}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
lines.push('');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Summary
|
|
528
|
+
lines.push('Summary:');
|
|
529
|
+
lines.push(` VMIDs: ${allocations.length} allocated, ${vmidReservations.length} reserved`);
|
|
530
|
+
|
|
531
|
+
const totalIpAllocations = allocations.length;
|
|
532
|
+
const totalIpReservations = ipReservations.reduce((sum, r) => {
|
|
533
|
+
if (r.ipEnd) {
|
|
534
|
+
// Calculate range size
|
|
535
|
+
const startOctet = Number.parseInt(r.ipStart.split('.')[3], 10);
|
|
536
|
+
const endOctet = Number.parseInt(r.ipEnd.split('.')[3], 10);
|
|
537
|
+
return sum + (endOctet - startOctet + 1);
|
|
538
|
+
}
|
|
539
|
+
return sum + 1;
|
|
540
|
+
}, 0);
|
|
541
|
+
lines.push(` IPs: ${totalIpAllocations} allocated, ${totalIpReservations} excluded`);
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
success: true,
|
|
545
|
+
message: lines.join('\n'),
|
|
546
|
+
data: {
|
|
547
|
+
allocations,
|
|
548
|
+
ipReservations,
|
|
549
|
+
vmidReservations,
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
} catch (err) {
|
|
553
|
+
return {
|
|
554
|
+
success: false,
|
|
555
|
+
error: err instanceof Error ? err.message : String(err),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Handle IPAM list-allocations command
|
|
562
|
+
*
|
|
563
|
+
* Usage: celilo ipam list-allocations
|
|
564
|
+
*
|
|
565
|
+
* @param args - Command arguments
|
|
566
|
+
* @param flags - Command flags
|
|
567
|
+
* @returns Command result
|
|
568
|
+
*/
|
|
569
|
+
export async function handleIpamListAllocations(
|
|
570
|
+
_args: string[],
|
|
571
|
+
_flags: Record<string, string | boolean>,
|
|
572
|
+
): Promise<CommandResult> {
|
|
573
|
+
const db = getDb();
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
const { ipAllocations } = await import('../../db/schema');
|
|
577
|
+
const allocations = await db.select().from(ipAllocations).all();
|
|
578
|
+
|
|
579
|
+
if (allocations.length === 0) {
|
|
580
|
+
return {
|
|
581
|
+
success: true,
|
|
582
|
+
message: 'No IPAM allocations',
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const lines = ['IPAM Allocations:', ''];
|
|
587
|
+
for (const allocation of allocations) {
|
|
588
|
+
lines.push(`Module: ${allocation.moduleId}`);
|
|
589
|
+
lines.push(` VMID: ${allocation.vmid}`);
|
|
590
|
+
lines.push(` IP: ${allocation.containerIp}`);
|
|
591
|
+
lines.push(` Zone: ${allocation.zone}`);
|
|
592
|
+
lines.push(` Allocated: ${new Date(allocation.allocatedAt).toLocaleString()}`);
|
|
593
|
+
lines.push('');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return {
|
|
597
|
+
success: true,
|
|
598
|
+
message: lines.join('\n'),
|
|
599
|
+
data: allocations,
|
|
600
|
+
};
|
|
601
|
+
} catch (err) {
|
|
602
|
+
return {
|
|
603
|
+
success: false,
|
|
604
|
+
error: err instanceof Error ? err.message : String(err),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|