@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,142 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { convertSecretsToJinja, isAnsibleTemplate } from './ansible-resolver';
|
|
3
|
+
import { buildContextFromData } from './context';
|
|
4
|
+
|
|
5
|
+
// Mock database for tests
|
|
6
|
+
const mockDb = {
|
|
7
|
+
$client: {
|
|
8
|
+
prepare: () => ({
|
|
9
|
+
get: () => null,
|
|
10
|
+
all: () => [],
|
|
11
|
+
run: () => ({ changes: 0 }),
|
|
12
|
+
}),
|
|
13
|
+
},
|
|
14
|
+
select: () => ({
|
|
15
|
+
from: () => ({
|
|
16
|
+
where: () => ({
|
|
17
|
+
get: () => null,
|
|
18
|
+
all: () => [],
|
|
19
|
+
}),
|
|
20
|
+
}),
|
|
21
|
+
}),
|
|
22
|
+
// biome-ignore lint/suspicious/noExplicitAny: mock database for testing, full type not needed
|
|
23
|
+
} as any;
|
|
24
|
+
|
|
25
|
+
describe('Ansible Resolver', () => {
|
|
26
|
+
describe('isAnsibleTemplate', () => {
|
|
27
|
+
test('should identify .j2 files', async () => {
|
|
28
|
+
expect(isAnsibleTemplate('config.json.j2')).toBe(true);
|
|
29
|
+
expect(isAnsibleTemplate('template.j2')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should identify ansible/ directory files', async () => {
|
|
33
|
+
expect(isAnsibleTemplate('ansible/playbook.yml')).toBe(true);
|
|
34
|
+
expect(isAnsibleTemplate('ansible/roles/main.yml')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should reject non-ansible files', async () => {
|
|
38
|
+
expect(isAnsibleTemplate('terraform/main.tf')).toBe(false);
|
|
39
|
+
expect(isAnsibleTemplate('config.json')).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('convertSecretsToJinja', () => {
|
|
44
|
+
test('should convert secret variables to Jinja2 format', async () => {
|
|
45
|
+
const context = buildContextFromData('test', {
|
|
46
|
+
secrets: {
|
|
47
|
+
api_key: 'secret123',
|
|
48
|
+
db_password: 'pass456',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const template = 'username: $secret:api_key\npassword: $secret:db_password';
|
|
53
|
+
const result = await convertSecretsToJinja(template, context, mockDb);
|
|
54
|
+
|
|
55
|
+
expect(result.success).toBe(true);
|
|
56
|
+
if (result.success) {
|
|
57
|
+
expect(result.content).toBe('username: {{ api_key }}\npassword: {{ db_password }}');
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should resolve non-secret variables normally', async () => {
|
|
62
|
+
const context = buildContextFromData('test', {
|
|
63
|
+
selfConfig: {
|
|
64
|
+
hostname: 'myhost',
|
|
65
|
+
port: '8080',
|
|
66
|
+
},
|
|
67
|
+
systemConfig: {
|
|
68
|
+
'dns.primary': '192.168.0.1',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const template = 'host: $self:hostname\nport: $self:port\ndns: $system:dns.primary';
|
|
73
|
+
const result = await convertSecretsToJinja(template, context, mockDb);
|
|
74
|
+
|
|
75
|
+
expect(result.success).toBe(true);
|
|
76
|
+
if (result.success) {
|
|
77
|
+
expect(result.content).toBe('host: myhost\nport: 8080\ndns: 192.168.0.1');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should mix secret conversion and normal resolution', async () => {
|
|
82
|
+
const context = buildContextFromData('test', {
|
|
83
|
+
selfConfig: {
|
|
84
|
+
username: 'admin',
|
|
85
|
+
},
|
|
86
|
+
secrets: {
|
|
87
|
+
password: 'secret123',
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const template = 'user: $self:username\npass: $secret:password';
|
|
92
|
+
const result = await convertSecretsToJinja(template, context, mockDb);
|
|
93
|
+
|
|
94
|
+
expect(result.success).toBe(true);
|
|
95
|
+
if (result.success) {
|
|
96
|
+
expect(result.content).toBe('user: admin\npass: {{ password }}');
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('should return errors for missing non-secret variables', async () => {
|
|
101
|
+
const context = buildContextFromData('test', {});
|
|
102
|
+
|
|
103
|
+
const template = 'host: $self:missing_hostname';
|
|
104
|
+
const result = await convertSecretsToJinja(template, context, mockDb);
|
|
105
|
+
|
|
106
|
+
expect(result.success).toBe(false);
|
|
107
|
+
if (!result.success) {
|
|
108
|
+
expect(result.errors).toHaveLength(1);
|
|
109
|
+
expect(result.errors[0]?.variable).toBe('$self:missing_hostname');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('should convert secrets even if not in context', async () => {
|
|
114
|
+
const context = buildContextFromData('test', {});
|
|
115
|
+
|
|
116
|
+
const template = 'pass: $secret:api_key';
|
|
117
|
+
const result = await convertSecretsToJinja(template, context, mockDb);
|
|
118
|
+
|
|
119
|
+
// Secrets are always converted to Jinja2, regardless of presence
|
|
120
|
+
expect(result.success).toBe(true);
|
|
121
|
+
if (result.success) {
|
|
122
|
+
expect(result.content).toBe('pass: {{ api_key }}');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('should handle multiple occurrences of same variable', async () => {
|
|
127
|
+
const context = buildContextFromData('test', {
|
|
128
|
+
secrets: {
|
|
129
|
+
token: 'xyz',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const template = 'auth: $secret:token\nbackup: $secret:token';
|
|
134
|
+
const result = await convertSecretsToJinja(template, context, mockDb);
|
|
135
|
+
|
|
136
|
+
expect(result.success).toBe(true);
|
|
137
|
+
if (result.success) {
|
|
138
|
+
expect(result.content).toBe('auth: {{ token }}\nbackup: {{ token }}');
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { parseVariables } from './parser';
|
|
2
|
+
import { resolveVariable } from './resolver';
|
|
3
|
+
import type { ResolutionContext, TemplateResolveResult } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert secret variables to Jinja2 format for Ansible
|
|
7
|
+
*
|
|
8
|
+
* Execution function (Rule 10.1) - may perform database access
|
|
9
|
+
*
|
|
10
|
+
* Converts $secret:name to {{ name }} for Ansible templates
|
|
11
|
+
* Resolves non-secret variables normally ($self:, $system:, $capability:)
|
|
12
|
+
*
|
|
13
|
+
* @param content - Template content with variables
|
|
14
|
+
* @param context - Resolution context
|
|
15
|
+
* @param db - Database connection (required for capability variables)
|
|
16
|
+
* @returns Resolved template with Jinja2 variables for secrets
|
|
17
|
+
*/
|
|
18
|
+
export async function convertSecretsToJinja(
|
|
19
|
+
content: string,
|
|
20
|
+
context: ResolutionContext,
|
|
21
|
+
db: ReturnType<typeof import('../db/client').getDb>,
|
|
22
|
+
): Promise<TemplateResolveResult> {
|
|
23
|
+
// Parse all variables
|
|
24
|
+
const variables = parseVariables(content);
|
|
25
|
+
|
|
26
|
+
if (variables.length === 0) {
|
|
27
|
+
return { success: true, content };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const errors: Array<{ variable: string; error: string }> = [];
|
|
31
|
+
let resolvedContent = content;
|
|
32
|
+
|
|
33
|
+
// Process each variable
|
|
34
|
+
for (const variable of variables) {
|
|
35
|
+
if (variable.type === 'secret') {
|
|
36
|
+
// Convert secret variables to Jinja2 format
|
|
37
|
+
// $secret:vesync_username -> {{ vesync_username }}
|
|
38
|
+
const jinjaVar = `{{ ${variable.path} }}`;
|
|
39
|
+
resolvedContent = resolvedContent.replaceAll(variable.raw, jinjaVar);
|
|
40
|
+
} else {
|
|
41
|
+
// Resolve non-secret variables normally
|
|
42
|
+
const result = await resolveVariable(variable, context, db);
|
|
43
|
+
|
|
44
|
+
if (!result.success) {
|
|
45
|
+
errors.push({ variable: result.variable, error: result.error });
|
|
46
|
+
} else {
|
|
47
|
+
resolvedContent = resolvedContent.replaceAll(variable.raw, result.value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (errors.length > 0) {
|
|
53
|
+
return { success: false, errors };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { success: true, content: resolvedContent };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if file is an Ansible template
|
|
61
|
+
*
|
|
62
|
+
* Policy function - validation only
|
|
63
|
+
*
|
|
64
|
+
* @param path - File path
|
|
65
|
+
* @returns True if file is Ansible template (.j2 extension or in ansible/ directory)
|
|
66
|
+
*/
|
|
67
|
+
export function isAnsibleTemplate(path: string): boolean {
|
|
68
|
+
return path.endsWith('.j2') || path.includes('/ansible/') || path.startsWith('ansible/');
|
|
69
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for capability data $self: variable resolution (lazy resolution)
|
|
3
|
+
* Verifies that capability data containing unresolved $self: variables
|
|
4
|
+
* gets resolved from the provider module's config when requested
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterEach, describe, expect, test } from 'bun:test';
|
|
8
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { createDbClient } from '@/db/client';
|
|
12
|
+
import { resolveVariable } from './resolver';
|
|
13
|
+
import type { ResolutionContext } from './types';
|
|
14
|
+
|
|
15
|
+
let testDirs: string[] = [];
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
for (const dir of testDirs) {
|
|
19
|
+
try {
|
|
20
|
+
rmSync(dir, { recursive: true, force: true });
|
|
21
|
+
} catch {
|
|
22
|
+
// Ignore
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
testDirs = [];
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Capability data $self: variable resolution', () => {
|
|
29
|
+
test('should lazily resolve $self: variables in capability data', async () => {
|
|
30
|
+
const testDir = mkdtempSync(join(tmpdir(), 'test-cap-self-'));
|
|
31
|
+
testDirs.push(testDir);
|
|
32
|
+
const testDbPath = join(testDir, 'test.db');
|
|
33
|
+
const db = createDbClient({ path: testDbPath });
|
|
34
|
+
|
|
35
|
+
// Create provider module (dns-external)
|
|
36
|
+
db.$client
|
|
37
|
+
.prepare(
|
|
38
|
+
`INSERT INTO modules (id, name, version, source_path, state, manifest_data)
|
|
39
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
40
|
+
)
|
|
41
|
+
.run(
|
|
42
|
+
'dns-external',
|
|
43
|
+
'DNS External',
|
|
44
|
+
'1.0.0',
|
|
45
|
+
'/tmp/modules/dns-external',
|
|
46
|
+
'CONFIGURED',
|
|
47
|
+
JSON.stringify({ id: 'dns-external', name: 'DNS External', version: '1.0.0' }),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Register capability with unresolved $self: variables
|
|
51
|
+
db.$client
|
|
52
|
+
.prepare(
|
|
53
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data)
|
|
54
|
+
VALUES (?, ?, ?, ?)`,
|
|
55
|
+
)
|
|
56
|
+
.run(
|
|
57
|
+
'dns-external',
|
|
58
|
+
'dns_external',
|
|
59
|
+
'1.0.0',
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
server: {
|
|
62
|
+
ip: {
|
|
63
|
+
primary: '$self:ip.primary', // Unresolved!
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
tsig: {
|
|
67
|
+
key_name: '$self:tsig_key_name', // Unresolved!
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Store provider module's config
|
|
73
|
+
db.$client
|
|
74
|
+
.prepare(
|
|
75
|
+
`INSERT INTO module_configs (module_id, key, value)
|
|
76
|
+
VALUES (?, ?, ?), (?, ?, ?)`,
|
|
77
|
+
)
|
|
78
|
+
.run(
|
|
79
|
+
'dns-external',
|
|
80
|
+
'ip.primary',
|
|
81
|
+
'203.0.113.42',
|
|
82
|
+
'dns-external',
|
|
83
|
+
'tsig_key_name',
|
|
84
|
+
'celilo-ddns',
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Create consumer module (caddy)
|
|
88
|
+
db.$client
|
|
89
|
+
.prepare(
|
|
90
|
+
`INSERT INTO modules (id, name, version, source_path, state, manifest_data)
|
|
91
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
92
|
+
)
|
|
93
|
+
.run(
|
|
94
|
+
'caddy',
|
|
95
|
+
'Caddy',
|
|
96
|
+
'1.0.0',
|
|
97
|
+
'/tmp/modules/caddy',
|
|
98
|
+
'CONFIGURED',
|
|
99
|
+
JSON.stringify({ id: 'caddy', name: 'Caddy', version: '1.0.0' }),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Test: Resolve capability variable with lazy $self: resolution
|
|
103
|
+
const context: ResolutionContext = {
|
|
104
|
+
moduleId: 'caddy',
|
|
105
|
+
selfConfig: {},
|
|
106
|
+
systemConfig: {},
|
|
107
|
+
secrets: {},
|
|
108
|
+
systemSecrets: {},
|
|
109
|
+
capabilities: {
|
|
110
|
+
dns_external: {
|
|
111
|
+
server: {
|
|
112
|
+
ip: {
|
|
113
|
+
primary: '$self:ip.primary', // Will be lazily resolved
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
tsig: {
|
|
117
|
+
key_name: '$self:tsig_key_name', // Will be lazily resolved
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result1 = await resolveVariable(
|
|
124
|
+
{
|
|
125
|
+
type: 'capability',
|
|
126
|
+
path: 'dns_external.server.ip.primary',
|
|
127
|
+
raw: '$capability:dns_external.server.ip.primary',
|
|
128
|
+
},
|
|
129
|
+
context,
|
|
130
|
+
db,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(result1.success).toBe(true);
|
|
134
|
+
if (result1.success) {
|
|
135
|
+
expect(result1.value).toBe('203.0.113.42');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const result2 = await resolveVariable(
|
|
139
|
+
{
|
|
140
|
+
type: 'capability',
|
|
141
|
+
path: 'dns_external.tsig.key_name',
|
|
142
|
+
raw: '$capability:dns_external.tsig.key_name',
|
|
143
|
+
},
|
|
144
|
+
context,
|
|
145
|
+
db,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(result2.success).toBe(true);
|
|
149
|
+
if (result2.success) {
|
|
150
|
+
expect(result2.value).toBe('celilo-ddns');
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should error when provider module has not configured the required value', async () => {
|
|
155
|
+
const testDir = mkdtempSync(join(tmpdir(), 'test-cap-self-'));
|
|
156
|
+
testDirs.push(testDir);
|
|
157
|
+
const testDbPath = join(testDir, 'test.db');
|
|
158
|
+
const db = createDbClient({ path: testDbPath });
|
|
159
|
+
|
|
160
|
+
// Create provider module
|
|
161
|
+
db.$client
|
|
162
|
+
.prepare(
|
|
163
|
+
`INSERT INTO modules (id, name, version, source_path, state, manifest_data)
|
|
164
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
165
|
+
)
|
|
166
|
+
.run(
|
|
167
|
+
'dns-external',
|
|
168
|
+
'DNS External',
|
|
169
|
+
'1.0.0',
|
|
170
|
+
'/tmp/modules/dns-external',
|
|
171
|
+
'CONFIGURED',
|
|
172
|
+
JSON.stringify({ id: 'dns-external', name: 'DNS External', version: '1.0.0' }),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Register capability with unresolved variable
|
|
176
|
+
db.$client
|
|
177
|
+
.prepare(
|
|
178
|
+
`INSERT INTO capabilities (module_id, capability_name, version, data)
|
|
179
|
+
VALUES (?, ?, ?, ?)`,
|
|
180
|
+
)
|
|
181
|
+
.run(
|
|
182
|
+
'dns-external',
|
|
183
|
+
'dns_external',
|
|
184
|
+
'1.0.0',
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
server: { ip: { primary: '$self:ip.primary' } },
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Provider module config is MISSING the required value
|
|
191
|
+
|
|
192
|
+
const context: ResolutionContext = {
|
|
193
|
+
moduleId: 'caddy',
|
|
194
|
+
selfConfig: {},
|
|
195
|
+
systemConfig: {},
|
|
196
|
+
secrets: {},
|
|
197
|
+
systemSecrets: {},
|
|
198
|
+
capabilities: {
|
|
199
|
+
dns_external: {
|
|
200
|
+
server: { ip: { primary: '$self:ip.primary' } },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const result = await resolveVariable(
|
|
206
|
+
{
|
|
207
|
+
type: 'capability',
|
|
208
|
+
path: 'dns_external.server.ip.primary',
|
|
209
|
+
raw: '$capability:dns_external.server.ip.primary',
|
|
210
|
+
},
|
|
211
|
+
context,
|
|
212
|
+
db,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(result.success).toBe(false);
|
|
216
|
+
if (!result.success) {
|
|
217
|
+
expect(result.error).toContain("has not configured 'ip.primary'");
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|