@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,458 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { resolveTemplate, resolveVariable } from './resolver';
|
|
3
|
+
import type { ResolutionContext, VariableReference } from './types';
|
|
4
|
+
|
|
5
|
+
// Mock database for tests (capability secrets not tested here - see integration tests)
|
|
6
|
+
const mockDb = {
|
|
7
|
+
$client: {
|
|
8
|
+
prepare: (_query: string) => ({
|
|
9
|
+
get: () => {
|
|
10
|
+
// Return null for capability/module lookups (non-secret in unit tests)
|
|
11
|
+
// This makes isCapabilityFieldSecret() return false
|
|
12
|
+
return null;
|
|
13
|
+
},
|
|
14
|
+
all: () => [],
|
|
15
|
+
run: () => ({ changes: 0 }),
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
select: () => ({
|
|
19
|
+
from: () => ({
|
|
20
|
+
where: () => ({
|
|
21
|
+
get: () => null,
|
|
22
|
+
all: () => [],
|
|
23
|
+
}),
|
|
24
|
+
}),
|
|
25
|
+
}),
|
|
26
|
+
// biome-ignore lint/suspicious/noExplicitAny: mock database for testing, full type not needed
|
|
27
|
+
} as any;
|
|
28
|
+
|
|
29
|
+
const createContext = (overrides?: Partial<ResolutionContext>): ResolutionContext => ({
|
|
30
|
+
moduleId: 'test-module',
|
|
31
|
+
selfConfig: {
|
|
32
|
+
container_ip: '192.168.0.50',
|
|
33
|
+
hostname: 'homebridge',
|
|
34
|
+
cores: '2',
|
|
35
|
+
'resources.machine.cpu': '2',
|
|
36
|
+
'resources.machine.memory': '2048',
|
|
37
|
+
'resources.machine.disk': '20',
|
|
38
|
+
'resources.machine.zone': 'app',
|
|
39
|
+
// Auto-derived inventory variables
|
|
40
|
+
'inventory.ansible_host': '192.168.0.50',
|
|
41
|
+
'inventory.ansible_user': 'root',
|
|
42
|
+
'inventory.groups': 'homebridge',
|
|
43
|
+
},
|
|
44
|
+
systemConfig: {
|
|
45
|
+
'management.ip': '192.168.0.10',
|
|
46
|
+
'network.domain': 'homelab.local',
|
|
47
|
+
},
|
|
48
|
+
systemSecrets: {},
|
|
49
|
+
secrets: {
|
|
50
|
+
api_key: 'secret123',
|
|
51
|
+
db_password: 'pass456',
|
|
52
|
+
},
|
|
53
|
+
capabilities: {
|
|
54
|
+
dns_external: {
|
|
55
|
+
nameserver: 'ns1.example.com',
|
|
56
|
+
zone: 'example.com',
|
|
57
|
+
config: {
|
|
58
|
+
port: 53,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
idp: {
|
|
62
|
+
issuer: 'https://auth.example.com',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
...overrides,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('resolveVariable', () => {
|
|
69
|
+
test('should resolve self variable', async () => {
|
|
70
|
+
const variable: VariableReference = {
|
|
71
|
+
type: 'self',
|
|
72
|
+
path: 'container_ip',
|
|
73
|
+
raw: '$self:container_ip',
|
|
74
|
+
};
|
|
75
|
+
const context = createContext();
|
|
76
|
+
|
|
77
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
78
|
+
|
|
79
|
+
expect(result.success).toBe(true);
|
|
80
|
+
if (result.success) {
|
|
81
|
+
expect(result.value).toBe('192.168.0.50');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should resolve system variable', async () => {
|
|
86
|
+
const variable: VariableReference = {
|
|
87
|
+
type: 'system',
|
|
88
|
+
path: 'management.ip',
|
|
89
|
+
raw: '$system:management.ip',
|
|
90
|
+
};
|
|
91
|
+
const context = createContext();
|
|
92
|
+
|
|
93
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
94
|
+
|
|
95
|
+
expect(result.success).toBe(true);
|
|
96
|
+
if (result.success) {
|
|
97
|
+
expect(result.value).toBe('192.168.0.10');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('should resolve secret variable', async () => {
|
|
102
|
+
const variable: VariableReference = {
|
|
103
|
+
type: 'secret',
|
|
104
|
+
path: 'api_key',
|
|
105
|
+
raw: '$secret:api_key',
|
|
106
|
+
};
|
|
107
|
+
const context = createContext();
|
|
108
|
+
|
|
109
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
110
|
+
|
|
111
|
+
expect(result.success).toBe(true);
|
|
112
|
+
if (result.success) {
|
|
113
|
+
expect(result.value).toBe('secret123');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should resolve capability variable', async () => {
|
|
118
|
+
const variable: VariableReference = {
|
|
119
|
+
type: 'capability',
|
|
120
|
+
path: 'dns_external.nameserver',
|
|
121
|
+
raw: '$capability:dns_external.nameserver',
|
|
122
|
+
};
|
|
123
|
+
const context = createContext();
|
|
124
|
+
|
|
125
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
126
|
+
|
|
127
|
+
expect(result.success).toBe(true);
|
|
128
|
+
if (result.success) {
|
|
129
|
+
expect(result.value).toBe('ns1.example.com');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('should resolve nested capability variable', async () => {
|
|
134
|
+
const variable: VariableReference = {
|
|
135
|
+
type: 'capability',
|
|
136
|
+
path: 'dns_external.config.port',
|
|
137
|
+
raw: '$capability:dns_external.config.port',
|
|
138
|
+
};
|
|
139
|
+
const context = createContext();
|
|
140
|
+
|
|
141
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
142
|
+
|
|
143
|
+
expect(result.success).toBe(true);
|
|
144
|
+
if (result.success) {
|
|
145
|
+
expect(result.value).toBe('53');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('should return error for missing self variable', async () => {
|
|
150
|
+
const variable: VariableReference = {
|
|
151
|
+
type: 'self',
|
|
152
|
+
path: 'missing_var',
|
|
153
|
+
raw: '$self:missing_var',
|
|
154
|
+
};
|
|
155
|
+
const context = createContext();
|
|
156
|
+
|
|
157
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
158
|
+
|
|
159
|
+
expect(result.success).toBe(false);
|
|
160
|
+
if (!result.success) {
|
|
161
|
+
expect(result.error).toContain('not found in module configuration');
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('should return error for missing system variable', async () => {
|
|
166
|
+
const variable: VariableReference = {
|
|
167
|
+
type: 'system',
|
|
168
|
+
path: 'missing.var',
|
|
169
|
+
raw: '$system:missing.var',
|
|
170
|
+
};
|
|
171
|
+
const context = createContext();
|
|
172
|
+
|
|
173
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
174
|
+
|
|
175
|
+
expect(result.success).toBe(false);
|
|
176
|
+
if (!result.success) {
|
|
177
|
+
expect(result.error).toContain('not found');
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('should return error for missing secret', async () => {
|
|
182
|
+
const variable: VariableReference = {
|
|
183
|
+
type: 'secret',
|
|
184
|
+
path: 'missing_secret',
|
|
185
|
+
raw: '$secret:missing_secret',
|
|
186
|
+
};
|
|
187
|
+
const context = createContext();
|
|
188
|
+
|
|
189
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
190
|
+
|
|
191
|
+
expect(result.success).toBe(false);
|
|
192
|
+
if (!result.success) {
|
|
193
|
+
expect(result.error).toContain('not found');
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('should return error for missing capability', async () => {
|
|
198
|
+
const variable: VariableReference = {
|
|
199
|
+
type: 'capability',
|
|
200
|
+
path: 'missing_capability.value',
|
|
201
|
+
raw: '$capability:missing_capability.value',
|
|
202
|
+
};
|
|
203
|
+
const context = createContext();
|
|
204
|
+
|
|
205
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
206
|
+
|
|
207
|
+
expect(result.success).toBe(false);
|
|
208
|
+
if (!result.success) {
|
|
209
|
+
expect(result.error).toContain('not found or not registered');
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('should return error for capability variable without path', async () => {
|
|
214
|
+
const variable: VariableReference = {
|
|
215
|
+
type: 'capability',
|
|
216
|
+
path: 'dns_external',
|
|
217
|
+
raw: '$capability:dns_external',
|
|
218
|
+
};
|
|
219
|
+
const context = createContext();
|
|
220
|
+
|
|
221
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
222
|
+
|
|
223
|
+
expect(result.success).toBe(false);
|
|
224
|
+
if (!result.success) {
|
|
225
|
+
expect(result.error).toContain('must specify data path');
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('should return error for missing capability data path', async () => {
|
|
230
|
+
const variable: VariableReference = {
|
|
231
|
+
type: 'capability',
|
|
232
|
+
path: 'dns_external.missing_field',
|
|
233
|
+
raw: '$capability:dns_external.missing_field',
|
|
234
|
+
};
|
|
235
|
+
const context = createContext();
|
|
236
|
+
|
|
237
|
+
const result = await resolveVariable(variable, context, mockDb);
|
|
238
|
+
|
|
239
|
+
expect(result.success).toBe(false);
|
|
240
|
+
if (!result.success) {
|
|
241
|
+
expect(result.error).toContain('not found in');
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('resolveTemplate', () => {
|
|
247
|
+
test('should resolve template with single variable', async () => {
|
|
248
|
+
const template = 'ip: $self:container_ip';
|
|
249
|
+
const context = createContext();
|
|
250
|
+
|
|
251
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
252
|
+
|
|
253
|
+
expect(result.success).toBe(true);
|
|
254
|
+
if (result.success) {
|
|
255
|
+
expect(result.content).toBe('ip: 192.168.0.50');
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('should resolve template with multiple variables', async () => {
|
|
260
|
+
const template = `
|
|
261
|
+
hostname: $self:hostname
|
|
262
|
+
ip: $self:container_ip
|
|
263
|
+
domain: $system:network.domain
|
|
264
|
+
`;
|
|
265
|
+
const context = createContext();
|
|
266
|
+
|
|
267
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
268
|
+
|
|
269
|
+
expect(result.success).toBe(true);
|
|
270
|
+
if (result.success) {
|
|
271
|
+
expect(result.content).toContain('hostname: homebridge');
|
|
272
|
+
expect(result.content).toContain('ip: 192.168.0.50');
|
|
273
|
+
expect(result.content).toContain('domain: homelab.local');
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('should resolve template with mixed variable types', async () => {
|
|
278
|
+
const template = `
|
|
279
|
+
container_ip: $self:container_ip
|
|
280
|
+
management_ip: $system:management.ip
|
|
281
|
+
dns_server: $capability:dns_external.nameserver
|
|
282
|
+
api_key: $secret:api_key
|
|
283
|
+
`;
|
|
284
|
+
const context = createContext();
|
|
285
|
+
|
|
286
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
287
|
+
|
|
288
|
+
expect(result.success).toBe(true);
|
|
289
|
+
if (result.success) {
|
|
290
|
+
expect(result.content).toContain('container_ip: 192.168.0.50');
|
|
291
|
+
expect(result.content).toContain('management_ip: 192.168.0.10');
|
|
292
|
+
expect(result.content).toContain('dns_server: ns1.example.com');
|
|
293
|
+
expect(result.content).toContain('api_key: secret123');
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('should resolve VM resource variables from manifest', async () => {
|
|
298
|
+
const template = `
|
|
299
|
+
resource "proxmox_lxc" "grafana" {
|
|
300
|
+
cores = $self:resources.machine.cpu
|
|
301
|
+
memory = $self:resources.machine.memory
|
|
302
|
+
rootfs {
|
|
303
|
+
size = "$self:resources.machine.disk"
|
|
304
|
+
storage = "$self:resources.machine.zone"
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
`;
|
|
308
|
+
const context = createContext();
|
|
309
|
+
|
|
310
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
311
|
+
|
|
312
|
+
expect(result.success).toBe(true);
|
|
313
|
+
if (result.success) {
|
|
314
|
+
expect(result.content).toContain('cores = 2');
|
|
315
|
+
expect(result.content).toContain('memory = 2048');
|
|
316
|
+
expect(result.content).toContain('size = "20"');
|
|
317
|
+
expect(result.content).toContain('storage = "app"');
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('should resolve template with variables in complex structure', async () => {
|
|
322
|
+
const template = `
|
|
323
|
+
resource "proxmox_lxc" "container" {
|
|
324
|
+
hostname = "$self:hostname"
|
|
325
|
+
cores = $self:cores
|
|
326
|
+
network {
|
|
327
|
+
ip = "$self:container_ip/24"
|
|
328
|
+
gateway = "$system:management.ip"
|
|
329
|
+
}
|
|
330
|
+
environment = {
|
|
331
|
+
DNS_SERVER = "$capability:dns_external.nameserver"
|
|
332
|
+
API_KEY = "$secret:api_key"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
`;
|
|
336
|
+
const context = createContext();
|
|
337
|
+
|
|
338
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
339
|
+
|
|
340
|
+
expect(result.success).toBe(true);
|
|
341
|
+
if (result.success) {
|
|
342
|
+
expect(result.content).toContain('hostname = "homebridge"');
|
|
343
|
+
expect(result.content).toContain('cores = 2');
|
|
344
|
+
expect(result.content).toContain('ip = "192.168.0.50/24"');
|
|
345
|
+
expect(result.content).toContain('gateway = "192.168.0.10"');
|
|
346
|
+
expect(result.content).toContain('DNS_SERVER = "ns1.example.com"');
|
|
347
|
+
expect(result.content).toContain('API_KEY = "secret123"');
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test('should return template unchanged if no variables', async () => {
|
|
352
|
+
const template = 'no variables here';
|
|
353
|
+
const context = createContext();
|
|
354
|
+
|
|
355
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
356
|
+
|
|
357
|
+
expect(result.success).toBe(true);
|
|
358
|
+
if (result.success) {
|
|
359
|
+
expect(result.content).toBe(template);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test('should return errors for missing variables', async () => {
|
|
364
|
+
const template = `
|
|
365
|
+
ip: $self:container_ip
|
|
366
|
+
missing: $self:missing_var
|
|
367
|
+
another_missing: $secret:missing_secret
|
|
368
|
+
`;
|
|
369
|
+
const context = createContext();
|
|
370
|
+
|
|
371
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
372
|
+
|
|
373
|
+
expect(result.success).toBe(false);
|
|
374
|
+
if (!result.success) {
|
|
375
|
+
expect(result.errors).toHaveLength(2);
|
|
376
|
+
expect(result.errors[0]?.variable).toBe('$self:missing_var');
|
|
377
|
+
expect(result.errors[1]?.variable).toBe('$secret:missing_secret');
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('should handle repeated variables', async () => {
|
|
382
|
+
const template = `
|
|
383
|
+
ip1: $self:container_ip
|
|
384
|
+
ip2: $self:container_ip
|
|
385
|
+
ip3: $self:container_ip
|
|
386
|
+
`;
|
|
387
|
+
const context = createContext();
|
|
388
|
+
|
|
389
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
390
|
+
|
|
391
|
+
expect(result.success).toBe(true);
|
|
392
|
+
if (result.success) {
|
|
393
|
+
expect(result.content).toContain('ip1: 192.168.0.50');
|
|
394
|
+
expect(result.content).toContain('ip2: 192.168.0.50');
|
|
395
|
+
expect(result.content).toContain('ip3: 192.168.0.50');
|
|
396
|
+
expect(result.content).not.toContain('$self:container_ip');
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('should resolve auto-derived inventory variables', async () => {
|
|
401
|
+
const template = `
|
|
402
|
+
[all]
|
|
403
|
+
$self:hostname ansible_host=$self:inventory.ansible_host ansible_user=$self:inventory.ansible_user
|
|
404
|
+
|
|
405
|
+
[homebridge]
|
|
406
|
+
$self:hostname
|
|
407
|
+
`;
|
|
408
|
+
const context = createContext();
|
|
409
|
+
|
|
410
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
411
|
+
|
|
412
|
+
expect(result.success).toBe(true);
|
|
413
|
+
if (result.success) {
|
|
414
|
+
expect(result.content).toContain('homebridge ansible_host=192.168.0.50 ansible_user=root');
|
|
415
|
+
expect(result.content).toContain('[homebridge]');
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('should use stripped IP in ansible_host', async () => {
|
|
420
|
+
const template = 'ansible_host: $self:inventory.ansible_host';
|
|
421
|
+
const context = createContext({
|
|
422
|
+
selfConfig: {
|
|
423
|
+
container_ip: '10.0.10.10/24',
|
|
424
|
+
'inventory.ansible_host': '10.0.10.10', // Auto-derived (CIDR stripped)
|
|
425
|
+
'inventory.ansible_user': 'root',
|
|
426
|
+
'inventory.groups': 'test',
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
431
|
+
|
|
432
|
+
expect(result.success).toBe(true);
|
|
433
|
+
if (result.success) {
|
|
434
|
+
expect(result.content).toContain('ansible_host: 10.0.10.10');
|
|
435
|
+
expect(result.content).not.toContain('/24');
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('should resolve inventory.groups from module ID', async () => {
|
|
440
|
+
const template = `
|
|
441
|
+
---
|
|
442
|
+
all:
|
|
443
|
+
children:
|
|
444
|
+
$self:inventory.groups:
|
|
445
|
+
hosts:
|
|
446
|
+
$self:hostname:
|
|
447
|
+
`;
|
|
448
|
+
const context = createContext();
|
|
449
|
+
|
|
450
|
+
const result = await resolveTemplate(template, context, mockDb);
|
|
451
|
+
|
|
452
|
+
expect(result.success).toBe(true);
|
|
453
|
+
if (result.success) {
|
|
454
|
+
expect(result.content).toContain('homebridge:');
|
|
455
|
+
expect(result.content).toContain('hosts:');
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
});
|