@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,645 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { type DbClient, createDbClient } from '../db/client';
|
|
6
|
+
import { capabilities, moduleConfigs } from '../db/schema';
|
|
7
|
+
import {
|
|
8
|
+
discoverTemplateFiles,
|
|
9
|
+
generateTemplates,
|
|
10
|
+
getOutputFilename,
|
|
11
|
+
isTemplateFile,
|
|
12
|
+
readTemplateFiles,
|
|
13
|
+
writeGeneratedFiles,
|
|
14
|
+
} from './generator';
|
|
15
|
+
import type { GeneratedFile } from './types';
|
|
16
|
+
|
|
17
|
+
const TEST_MODULE_DIR = './test-module-templates';
|
|
18
|
+
const TEST_OUTPUT_DIR = './test-generated';
|
|
19
|
+
const TEST_DB_PATH = './test-templates.db';
|
|
20
|
+
|
|
21
|
+
describe('Template Generator', () => {
|
|
22
|
+
let db: DbClient;
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
// Create test database
|
|
26
|
+
db = createDbClient({ path: TEST_DB_PATH });
|
|
27
|
+
|
|
28
|
+
db.$client.run(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS modules (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
name TEXT NOT NULL,
|
|
32
|
+
version TEXT NOT NULL,
|
|
33
|
+
description TEXT,
|
|
34
|
+
state TEXT NOT NULL DEFAULT 'IMPORTED',
|
|
35
|
+
manifest_data TEXT NOT NULL,
|
|
36
|
+
source_path TEXT NOT NULL,
|
|
37
|
+
imported_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
38
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
39
|
+
error_message TEXT
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
db.$client.run(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS module_configs (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
module_id TEXT NOT NULL,
|
|
47
|
+
key TEXT NOT NULL,
|
|
48
|
+
value TEXT NOT NULL,
|
|
49
|
+
value_json TEXT,
|
|
50
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
51
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
52
|
+
FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE
|
|
53
|
+
)
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
db.$client.run(`
|
|
57
|
+
CREATE TABLE IF NOT EXISTS capabilities (
|
|
58
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
59
|
+
module_id TEXT NOT NULL,
|
|
60
|
+
capability_name TEXT NOT NULL,
|
|
61
|
+
version TEXT NOT NULL,
|
|
62
|
+
data TEXT NOT NULL,
|
|
63
|
+
registered_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
64
|
+
FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE
|
|
65
|
+
)
|
|
66
|
+
`);
|
|
67
|
+
|
|
68
|
+
db.$client.run(`
|
|
69
|
+
CREATE TABLE IF NOT EXISTS secrets (
|
|
70
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
71
|
+
module_id TEXT NOT NULL,
|
|
72
|
+
name TEXT NOT NULL,
|
|
73
|
+
encrypted_value TEXT NOT NULL,
|
|
74
|
+
iv TEXT NOT NULL,
|
|
75
|
+
auth_tag TEXT NOT NULL,
|
|
76
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
77
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
78
|
+
FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE
|
|
79
|
+
)
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
db.$client.run(`
|
|
83
|
+
CREATE TABLE IF NOT EXISTS system_config (
|
|
84
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
85
|
+
key TEXT NOT NULL UNIQUE,
|
|
86
|
+
value TEXT NOT NULL,
|
|
87
|
+
description TEXT,
|
|
88
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
89
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
90
|
+
)
|
|
91
|
+
`);
|
|
92
|
+
|
|
93
|
+
db.$client.run(`
|
|
94
|
+
CREATE TABLE IF NOT EXISTS machines (
|
|
95
|
+
id TEXT PRIMARY KEY,
|
|
96
|
+
hostname TEXT NOT NULL,
|
|
97
|
+
zone TEXT NOT NULL,
|
|
98
|
+
ip_address TEXT NOT NULL,
|
|
99
|
+
ssh_user TEXT NOT NULL,
|
|
100
|
+
ssh_key_encrypted TEXT NOT NULL,
|
|
101
|
+
hardware TEXT NOT NULL,
|
|
102
|
+
assigned_module_ids TEXT DEFAULT '[]' NOT NULL,
|
|
103
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
104
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
105
|
+
)
|
|
106
|
+
`);
|
|
107
|
+
|
|
108
|
+
db.$client.run(`
|
|
109
|
+
CREATE TABLE IF NOT EXISTS container_services (
|
|
110
|
+
id TEXT PRIMARY KEY,
|
|
111
|
+
name TEXT NOT NULL,
|
|
112
|
+
provider_name TEXT NOT NULL,
|
|
113
|
+
zones TEXT NOT NULL,
|
|
114
|
+
api_credentials_encrypted TEXT NOT NULL,
|
|
115
|
+
provider_config TEXT NOT NULL,
|
|
116
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
117
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
118
|
+
)
|
|
119
|
+
`);
|
|
120
|
+
|
|
121
|
+
db.$client.run(`
|
|
122
|
+
CREATE TABLE IF NOT EXISTS module_infrastructure (
|
|
123
|
+
id TEXT PRIMARY KEY,
|
|
124
|
+
module_id TEXT NOT NULL,
|
|
125
|
+
infrastructure_type TEXT NOT NULL,
|
|
126
|
+
machine_id TEXT,
|
|
127
|
+
service_id TEXT,
|
|
128
|
+
container_metadata TEXT,
|
|
129
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
130
|
+
FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE,
|
|
131
|
+
FOREIGN KEY (machine_id) REFERENCES machines(id),
|
|
132
|
+
FOREIGN KEY (service_id) REFERENCES container_services(id)
|
|
133
|
+
)
|
|
134
|
+
`);
|
|
135
|
+
|
|
136
|
+
// Clean up test directories
|
|
137
|
+
if (existsSync(TEST_MODULE_DIR)) {
|
|
138
|
+
await rm(TEST_MODULE_DIR, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
if (existsSync(TEST_OUTPUT_DIR)) {
|
|
141
|
+
await rm(TEST_OUTPUT_DIR, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await mkdir(TEST_MODULE_DIR, { recursive: true });
|
|
145
|
+
await mkdir(TEST_OUTPUT_DIR, { recursive: true });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
afterEach(async () => {
|
|
149
|
+
db.$client.close();
|
|
150
|
+
|
|
151
|
+
if (existsSync(TEST_DB_PATH)) {
|
|
152
|
+
await rm(TEST_DB_PATH);
|
|
153
|
+
}
|
|
154
|
+
const walPath = `${TEST_DB_PATH}-wal`;
|
|
155
|
+
const shmPath = `${TEST_DB_PATH}-shm`;
|
|
156
|
+
if (existsSync(walPath)) {
|
|
157
|
+
await rm(walPath);
|
|
158
|
+
}
|
|
159
|
+
if (existsSync(shmPath)) {
|
|
160
|
+
await rm(shmPath);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (existsSync(TEST_MODULE_DIR)) {
|
|
164
|
+
await rm(TEST_MODULE_DIR, { recursive: true });
|
|
165
|
+
}
|
|
166
|
+
if (existsSync(TEST_OUTPUT_DIR)) {
|
|
167
|
+
await rm(TEST_OUTPUT_DIR, { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('isTemplateFile', () => {
|
|
172
|
+
test('should identify .tpl files', () => {
|
|
173
|
+
expect(isTemplateFile('main.tf.tpl')).toBe(true);
|
|
174
|
+
expect(isTemplateFile('playbook.yml.tpl')).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('should identify .j2 files', () => {
|
|
178
|
+
expect(isTemplateFile('config.json.j2')).toBe(true);
|
|
179
|
+
expect(isTemplateFile('template.yaml.j2')).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('should reject non-template files', () => {
|
|
183
|
+
expect(isTemplateFile('main.tf')).toBe(false);
|
|
184
|
+
expect(isTemplateFile('playbook.yml')).toBe(false);
|
|
185
|
+
expect(isTemplateFile('README.md')).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('getOutputFilename', () => {
|
|
190
|
+
test('should remove .tpl extension', () => {
|
|
191
|
+
expect(getOutputFilename('main.tf.tpl')).toBe('main.tf');
|
|
192
|
+
expect(getOutputFilename('variables.tf.tpl')).toBe('variables.tf');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('should keep .j2 extension (Ansible Jinja2 templates)', () => {
|
|
196
|
+
expect(getOutputFilename('config.json.j2')).toBe('config.json.j2');
|
|
197
|
+
expect(getOutputFilename('playbook.yml.j2')).toBe('playbook.yml.j2');
|
|
198
|
+
expect(getOutputFilename('knot.conf.j2')).toBe('knot.conf.j2');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should return original if no template extension', () => {
|
|
202
|
+
expect(getOutputFilename('main.tf')).toBe('main.tf');
|
|
203
|
+
expect(getOutputFilename('README.md')).toBe('README.md');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('should strip prefix before ansible/ directory', () => {
|
|
207
|
+
expect(getOutputFilename('celilo/ansible/playbook.yml.tpl')).toBe('ansible/playbook.yml');
|
|
208
|
+
expect(getOutputFilename('celilo/ansible/roles/app/tasks/main.yml.tpl')).toBe(
|
|
209
|
+
'ansible/roles/app/tasks/main.yml',
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('should strip prefix before terraform/ directory', () => {
|
|
214
|
+
expect(getOutputFilename('celilo/terraform/main.tf.tpl')).toBe('terraform/main.tf');
|
|
215
|
+
expect(getOutputFilename('celilo/terraform/variables.tf.tpl')).toBe('terraform/variables.tf');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('should not strip ansible/terraform at root level', () => {
|
|
219
|
+
expect(getOutputFilename('ansible/playbook.yml.tpl')).toBe('ansible/playbook.yml');
|
|
220
|
+
expect(getOutputFilename('terraform/main.tf.tpl')).toBe('terraform/main.tf');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('discoverTemplateFiles', () => {
|
|
225
|
+
test('should find template files in directory', async () => {
|
|
226
|
+
const dir = join(TEST_MODULE_DIR, 'terraform');
|
|
227
|
+
await mkdir(dir, { recursive: true });
|
|
228
|
+
await writeFile(join(dir, 'main.tf.tpl'), 'content');
|
|
229
|
+
await writeFile(join(dir, 'variables.tf.tpl'), 'content');
|
|
230
|
+
await writeFile(join(dir, 'README.md'), 'not a template');
|
|
231
|
+
|
|
232
|
+
const templates = await discoverTemplateFiles(dir);
|
|
233
|
+
|
|
234
|
+
expect(templates).toHaveLength(2);
|
|
235
|
+
expect(templates).toContain('main.tf.tpl');
|
|
236
|
+
expect(templates).toContain('variables.tf.tpl');
|
|
237
|
+
expect(templates).not.toContain('README.md');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('should find templates recursively', async () => {
|
|
241
|
+
const dir = join(TEST_MODULE_DIR, 'ansible');
|
|
242
|
+
await mkdir(join(dir, 'roles', 'app', 'tasks'), { recursive: true });
|
|
243
|
+
await writeFile(join(dir, 'playbook.yml.tpl'), 'content');
|
|
244
|
+
await writeFile(join(dir, 'roles', 'app', 'tasks', 'main.yml.tpl'), 'content');
|
|
245
|
+
|
|
246
|
+
const templates = await discoverTemplateFiles(dir);
|
|
247
|
+
|
|
248
|
+
expect(templates).toHaveLength(2);
|
|
249
|
+
expect(templates).toContain('playbook.yml.tpl');
|
|
250
|
+
expect(templates).toContain(join('roles', 'app', 'tasks', 'main.yml.tpl'));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('should return empty array for non-existent directory', async () => {
|
|
254
|
+
const templates = await discoverTemplateFiles('./nonexistent');
|
|
255
|
+
|
|
256
|
+
expect(templates).toHaveLength(0);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('should handle empty directory', async () => {
|
|
260
|
+
const dir = join(TEST_MODULE_DIR, 'empty');
|
|
261
|
+
await mkdir(dir, { recursive: true });
|
|
262
|
+
|
|
263
|
+
const templates = await discoverTemplateFiles(dir);
|
|
264
|
+
|
|
265
|
+
expect(templates).toHaveLength(0);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('readTemplateFiles', () => {
|
|
270
|
+
test('should read template files with content', async () => {
|
|
271
|
+
const dir = join(TEST_MODULE_DIR, 'terraform');
|
|
272
|
+
await mkdir(dir, { recursive: true });
|
|
273
|
+
await writeFile(join(dir, 'main.tf.tpl'), 'resource "test" {}');
|
|
274
|
+
await writeFile(join(dir, 'variables.tf.tpl'), 'variable "test" {}');
|
|
275
|
+
|
|
276
|
+
const templates = await readTemplateFiles(TEST_MODULE_DIR, [
|
|
277
|
+
'terraform/main.tf.tpl',
|
|
278
|
+
'terraform/variables.tf.tpl',
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
expect(templates).toHaveLength(2);
|
|
282
|
+
expect(templates[0]?.content).toBe('resource "test" {}');
|
|
283
|
+
expect(templates[0]?.targetPath).toBe('terraform/main.tf');
|
|
284
|
+
expect(templates[1]?.content).toBe('variable "test" {}');
|
|
285
|
+
expect(templates[1]?.targetPath).toBe('terraform/variables.tf');
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('writeGeneratedFiles', () => {
|
|
290
|
+
test('should write files to output directory', async () => {
|
|
291
|
+
const files: GeneratedFile[] = [
|
|
292
|
+
{ path: 'terraform/main.tf', content: 'resource "test" {}' },
|
|
293
|
+
{ path: 'ansible/playbook.yml', content: 'tasks: []' },
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
await writeGeneratedFiles(TEST_OUTPUT_DIR, files);
|
|
297
|
+
|
|
298
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'terraform', 'main.tf'))).toBe(true);
|
|
299
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'ansible', 'playbook.yml'))).toBe(true);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('should create nested directories', async () => {
|
|
303
|
+
const files: GeneratedFile[] = [
|
|
304
|
+
{ path: 'ansible/roles/app/tasks/main.yml', content: 'tasks: []' },
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
await writeGeneratedFiles(TEST_OUTPUT_DIR, files);
|
|
308
|
+
|
|
309
|
+
expect(
|
|
310
|
+
existsSync(join(TEST_OUTPUT_DIR, 'ansible', 'roles', 'app', 'tasks', 'main.yml')),
|
|
311
|
+
).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('generateTemplates', () => {
|
|
316
|
+
test('should generate templates with variable resolution', async () => {
|
|
317
|
+
// Setup module
|
|
318
|
+
db.$client.run(
|
|
319
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('test-module', 'Test', '1.0.0', '/path', '{}')`,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
db.insert(moduleConfigs)
|
|
323
|
+
.values([
|
|
324
|
+
{ moduleId: 'test-module', key: 'container_ip', value: '192.168.0.50' },
|
|
325
|
+
{ moduleId: 'test-module', key: 'hostname', value: 'test' },
|
|
326
|
+
])
|
|
327
|
+
.run();
|
|
328
|
+
|
|
329
|
+
// Insert system config
|
|
330
|
+
db.$client.run(
|
|
331
|
+
`INSERT INTO system_config (key, value) VALUES ('management.ip', '192.168.0.10')`,
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Create template files
|
|
335
|
+
const terraformDir = join(TEST_MODULE_DIR, 'terraform');
|
|
336
|
+
await mkdir(terraformDir, { recursive: true });
|
|
337
|
+
await writeFile(
|
|
338
|
+
join(terraformDir, 'main.tf.tpl'),
|
|
339
|
+
`
|
|
340
|
+
resource "proxmox_lxc" "container" {
|
|
341
|
+
hostname = "$self:hostname"
|
|
342
|
+
network {
|
|
343
|
+
ip = "$self:container_ip/24"
|
|
344
|
+
gateway = "$system:management.ip"
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
`,
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Generate
|
|
351
|
+
const result = await generateTemplates({
|
|
352
|
+
moduleId: 'test-module',
|
|
353
|
+
modulePath: TEST_MODULE_DIR,
|
|
354
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
355
|
+
db,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(result.success).toBe(true);
|
|
359
|
+
if (result.success) {
|
|
360
|
+
// Should include terraform template + inventory files (hosts.ini, host_vars, group_vars)
|
|
361
|
+
expect(result.files.length).toBeGreaterThanOrEqual(1);
|
|
362
|
+
|
|
363
|
+
const terraformFile = result.files.find((f) => f.path === 'terraform/main.tf');
|
|
364
|
+
expect(terraformFile).toBeDefined();
|
|
365
|
+
expect(terraformFile?.content).toContain('hostname = "test"');
|
|
366
|
+
expect(terraformFile?.content).toContain('ip = "192.168.0.50/24"');
|
|
367
|
+
expect(terraformFile?.content).toContain('gateway = "192.168.0.10"');
|
|
368
|
+
|
|
369
|
+
// Verify file was written
|
|
370
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'terraform', 'main.tf'))).toBe(true);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('should handle both terraform and ansible templates', async () => {
|
|
375
|
+
db.$client.run(
|
|
376
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('test-module', 'Test', '1.0.0', '/path', '{}')`,
|
|
377
|
+
);
|
|
378
|
+
db.insert(moduleConfigs)
|
|
379
|
+
.values({ moduleId: 'test-module', key: 'name', value: 'test' })
|
|
380
|
+
.run();
|
|
381
|
+
|
|
382
|
+
// Create templates
|
|
383
|
+
await mkdir(join(TEST_MODULE_DIR, 'terraform'), { recursive: true });
|
|
384
|
+
await mkdir(join(TEST_MODULE_DIR, 'ansible'), { recursive: true });
|
|
385
|
+
await writeFile(join(TEST_MODULE_DIR, 'terraform', 'main.tf.tpl'), 'name = "$self:name"');
|
|
386
|
+
await writeFile(join(TEST_MODULE_DIR, 'ansible', 'playbook.yml.tpl'), 'name: $self:name');
|
|
387
|
+
|
|
388
|
+
const result = await generateTemplates({
|
|
389
|
+
moduleId: 'test-module',
|
|
390
|
+
modulePath: TEST_MODULE_DIR,
|
|
391
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
392
|
+
db,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
expect(result.success).toBe(true);
|
|
396
|
+
if (result.success) {
|
|
397
|
+
expect(result.files).toHaveLength(2);
|
|
398
|
+
expect(result.files.some((f) => f.path.includes('terraform'))).toBe(true);
|
|
399
|
+
expect(result.files.some((f) => f.path.includes('ansible'))).toBe(true);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('should fail if module path does not exist', async () => {
|
|
404
|
+
const result = await generateTemplates({
|
|
405
|
+
moduleId: 'test-module',
|
|
406
|
+
modulePath: './nonexistent',
|
|
407
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
408
|
+
db,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
expect(result.success).toBe(false);
|
|
412
|
+
if (!result.success) {
|
|
413
|
+
expect(result.error).toContain('does not exist');
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('should succeed with empty files for config-only modules', async () => {
|
|
418
|
+
db.$client.run(
|
|
419
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('test-module', 'Test', '1.0.0', '/path', '{}')`,
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const result = await generateTemplates({
|
|
423
|
+
moduleId: 'test-module',
|
|
424
|
+
modulePath: TEST_MODULE_DIR,
|
|
425
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
426
|
+
db,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
expect(result.success).toBe(true);
|
|
430
|
+
if (result.success) {
|
|
431
|
+
expect(result.files).toEqual([]);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('should fail if variable resolution fails', async () => {
|
|
436
|
+
db.$client.run(
|
|
437
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('test-module', 'Test', '1.0.0', '/path', '{}')`,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
await mkdir(join(TEST_MODULE_DIR, 'terraform'), { recursive: true });
|
|
441
|
+
await writeFile(
|
|
442
|
+
join(TEST_MODULE_DIR, 'terraform', 'main.tf.tpl'),
|
|
443
|
+
'missing = "$self:missing_var"',
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const result = await generateTemplates({
|
|
447
|
+
moduleId: 'test-module',
|
|
448
|
+
modulePath: TEST_MODULE_DIR,
|
|
449
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
450
|
+
db,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
expect(result.success).toBe(false);
|
|
454
|
+
if (!result.success) {
|
|
455
|
+
expect(result.error).toContain('Failed to resolve variables');
|
|
456
|
+
expect(result.error).toContain('missing_var');
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('should resolve capability variables', async () => {
|
|
461
|
+
db.$client.run(
|
|
462
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('test-module', 'Test', '1.0.0', '/path', '{}')`,
|
|
463
|
+
);
|
|
464
|
+
db.$client.run(
|
|
465
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('dns-module', 'DNS', '1.0.0', '/path', '{}')`,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
db.insert(capabilities)
|
|
469
|
+
.values({
|
|
470
|
+
moduleId: 'dns-module',
|
|
471
|
+
capabilityName: 'dns_external',
|
|
472
|
+
version: '1.0.0',
|
|
473
|
+
data: { nameserver: 'ns1.example.com' },
|
|
474
|
+
})
|
|
475
|
+
.run();
|
|
476
|
+
|
|
477
|
+
await mkdir(join(TEST_MODULE_DIR, 'terraform'), { recursive: true });
|
|
478
|
+
await writeFile(
|
|
479
|
+
join(TEST_MODULE_DIR, 'terraform', 'main.tf.tpl'),
|
|
480
|
+
'dns = "$capability:dns_external.nameserver"',
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const result = await generateTemplates({
|
|
484
|
+
moduleId: 'test-module',
|
|
485
|
+
modulePath: TEST_MODULE_DIR,
|
|
486
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
487
|
+
db,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
expect(result.success).toBe(true);
|
|
491
|
+
if (result.success) {
|
|
492
|
+
expect(result.files[0]?.content).toContain('dns = "ns1.example.com"');
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test('should skip terraform generation for machine infrastructure', async () => {
|
|
497
|
+
// Setup module with machine infrastructure
|
|
498
|
+
const manifestWithResources = JSON.stringify({
|
|
499
|
+
provides: { capabilities: [] },
|
|
500
|
+
requires: {
|
|
501
|
+
capabilities: [],
|
|
502
|
+
machine: {
|
|
503
|
+
cpu: 1,
|
|
504
|
+
memory: 1024,
|
|
505
|
+
disk: 10,
|
|
506
|
+
zone: 'internal',
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
db.$client.run(
|
|
511
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('test-module', 'Test', '1.0.0', '/path', ?)`,
|
|
512
|
+
[manifestWithResources],
|
|
513
|
+
);
|
|
514
|
+
db.insert(moduleConfigs)
|
|
515
|
+
.values({ moduleId: 'test-module', key: 'name', value: 'test' })
|
|
516
|
+
.run();
|
|
517
|
+
|
|
518
|
+
// Insert dummy machine (required for foreign key)
|
|
519
|
+
db.$client.run(
|
|
520
|
+
`INSERT INTO machines (id, hostname, zone, ip_address, ssh_user, ssh_key_encrypted, hardware) VALUES ('machine-1', 'test-machine', 'internal', '192.168.0.100', 'root', 'encrypted', '{}')`,
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
// Insert infrastructure selection for machine
|
|
524
|
+
db.$client.run(
|
|
525
|
+
`INSERT INTO module_infrastructure (id, module_id, infrastructure_type, machine_id) VALUES ('test-infra-1', 'test-module', 'machine', 'machine-1')`,
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
// Create both terraform and ansible templates
|
|
529
|
+
await mkdir(join(TEST_MODULE_DIR, 'terraform'), { recursive: true });
|
|
530
|
+
await mkdir(join(TEST_MODULE_DIR, 'ansible'), { recursive: true });
|
|
531
|
+
await writeFile(join(TEST_MODULE_DIR, 'terraform', 'main.tf.tpl'), 'name = "$self:name"');
|
|
532
|
+
await writeFile(join(TEST_MODULE_DIR, 'ansible', 'playbook.yml.tpl'), 'name: $self:name');
|
|
533
|
+
|
|
534
|
+
const result = await generateTemplates({
|
|
535
|
+
moduleId: 'test-module',
|
|
536
|
+
modulePath: TEST_MODULE_DIR,
|
|
537
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
538
|
+
db,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
expect(result.success).toBe(true);
|
|
542
|
+
if (result.success) {
|
|
543
|
+
// Debug: print generated files
|
|
544
|
+
console.log(
|
|
545
|
+
'Generated files:',
|
|
546
|
+
result.files.map((f) => f.path),
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Should have ansible files, but NO terraform files
|
|
550
|
+
expect(result.files.length).toBeGreaterThanOrEqual(1);
|
|
551
|
+
const terraformFiles = result.files.filter((f) => f.path.includes('terraform'));
|
|
552
|
+
expect(terraformFiles).toEqual([]);
|
|
553
|
+
expect(result.files.some((f) => f.path.includes('ansible'))).toBe(true);
|
|
554
|
+
|
|
555
|
+
// Verify terraform file was NOT written
|
|
556
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'terraform', 'main.tf'))).toBe(false);
|
|
557
|
+
// Verify ansible file WAS written
|
|
558
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'ansible', 'playbook.yml'))).toBe(true);
|
|
559
|
+
|
|
560
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'ansible', 'inventory', 'hosts.ini'))).toBe(true);
|
|
561
|
+
const hostsIni = await readFile(
|
|
562
|
+
join(TEST_OUTPUT_DIR, 'ansible', 'inventory', 'hosts.ini'),
|
|
563
|
+
'utf-8',
|
|
564
|
+
);
|
|
565
|
+
expect(hostsIni).toContain('ansible_host=192.168.0.100'); // Machine IP
|
|
566
|
+
expect(hostsIni).toContain('ansible_user=root'); // Machine SSH user
|
|
567
|
+
expect(hostsIni).toContain('ansible_ssh_private_key_file='); // SSH key path
|
|
568
|
+
expect(hostsIni).toContain('/tmp/ansible-keys/machine-machine-1.key'); // Key filename
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('should generate terraform for container service infrastructure', async () => {
|
|
573
|
+
// Setup system config for IPAM (internal zone network)
|
|
574
|
+
db.$client.run(
|
|
575
|
+
`INSERT INTO system_config (key, value) VALUES ('network.internal.subnet', '192.168.100.0/24')`,
|
|
576
|
+
);
|
|
577
|
+
db.$client.run(
|
|
578
|
+
`INSERT INTO system_config (key, value) VALUES ('network.internal.gateway', '192.168.100.1')`,
|
|
579
|
+
);
|
|
580
|
+
db.$client.run(
|
|
581
|
+
`INSERT INTO system_config (key, value) VALUES ('network.internal.vlan', '100')`,
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Setup module with container service infrastructure
|
|
585
|
+
const manifestWithResources = JSON.stringify({
|
|
586
|
+
provides: { capabilities: [] },
|
|
587
|
+
requires: { capabilities: [] },
|
|
588
|
+
resources: {
|
|
589
|
+
machine: {
|
|
590
|
+
cpu: 1,
|
|
591
|
+
memory: 1024,
|
|
592
|
+
disk: 10,
|
|
593
|
+
zone: 'internal',
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
db.$client.run(
|
|
598
|
+
`INSERT INTO modules (id, name, version, source_path, manifest_data) VALUES ('test-module', 'Test', '1.0.0', '/path', ?)`,
|
|
599
|
+
[manifestWithResources],
|
|
600
|
+
);
|
|
601
|
+
db.insert(moduleConfigs)
|
|
602
|
+
.values({ moduleId: 'test-module', key: 'name', value: 'test' })
|
|
603
|
+
.run();
|
|
604
|
+
|
|
605
|
+
// Insert dummy container service (required for foreign key)
|
|
606
|
+
db.$client.run(
|
|
607
|
+
`INSERT INTO container_services (id, service_id, name, provider_name, zones, api_credentials_encrypted, provider_config, verified, created_at, updated_at) VALUES ('service-1', 'test-service', 'test-service', 'proxmox', '["app"]', '{}', '{"default_target_node":"pve","lxc_template":"local:vztmpl/ubuntu.tar.zst","storage":"local-lvm"}', 1, ${Date.now()}, ${Date.now()})`,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
// Insert infrastructure selection for container service
|
|
611
|
+
db.$client.run(
|
|
612
|
+
`INSERT INTO module_infrastructure (id, module_id, infrastructure_type, service_id) VALUES ('test-infra-1', 'test-module', 'container_service', 'service-1')`,
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
// Create both terraform and ansible templates
|
|
616
|
+
await mkdir(join(TEST_MODULE_DIR, 'terraform'), { recursive: true });
|
|
617
|
+
await mkdir(join(TEST_MODULE_DIR, 'ansible'), { recursive: true });
|
|
618
|
+
await writeFile(join(TEST_MODULE_DIR, 'terraform', 'main.tf.tpl'), 'name = "$self:name"');
|
|
619
|
+
await writeFile(join(TEST_MODULE_DIR, 'ansible', 'playbook.yml.tpl'), 'name: $self:name');
|
|
620
|
+
|
|
621
|
+
const result = await generateTemplates({
|
|
622
|
+
moduleId: 'test-module',
|
|
623
|
+
modulePath: TEST_MODULE_DIR,
|
|
624
|
+
outputPath: TEST_OUTPUT_DIR,
|
|
625
|
+
db,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
if (!result.success) {
|
|
629
|
+
console.error('Generation failed:', result.error);
|
|
630
|
+
if (result.details) console.error('Details:', result.details);
|
|
631
|
+
}
|
|
632
|
+
expect(result.success).toBe(true);
|
|
633
|
+
if (result.success) {
|
|
634
|
+
// Should have both terraform and ansible files
|
|
635
|
+
expect(result.files.length).toBe(2);
|
|
636
|
+
expect(result.files.some((f) => f.path.includes('terraform'))).toBe(true);
|
|
637
|
+
expect(result.files.some((f) => f.path.includes('ansible'))).toBe(true);
|
|
638
|
+
|
|
639
|
+
// Verify both files were written
|
|
640
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'terraform', 'main.tf'))).toBe(true);
|
|
641
|
+
expect(existsSync(join(TEST_OUTPUT_DIR, 'ansible', 'playbook.yml'))).toBe(true);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
});
|