@celilo/cli 1.8.0 → 1.9.1
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/CELILO_CORE_MODULES.md +2 -0
- package/CELILO_SUBSYSTEMS.md +2 -0
- package/drizzle/0028_capability_bindings.sql +26 -0
- package/drizzle/0029_module_instances.sql +58 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +2 -2
- package/src/cli/commands/module-show.ts +1 -0
- package/src/db/foreign-keys.test.ts +101 -0
- package/src/db/schema.ts +161 -5
- package/src/hooks/broker.test.ts +153 -0
- package/src/hooks/broker.ts +307 -0
- package/src/hooks/capability-loader-bindings.test.ts +163 -0
- package/src/hooks/capability-loader-firewall.test.ts +108 -0
- package/src/hooks/capability-loader.test.ts +10 -2
- package/src/hooks/capability-loader.ts +59 -2
- package/src/hooks/define-hook.test.ts +1 -0
- package/src/hooks/executor.test.ts +7 -0
- package/src/hooks/executor.ts +245 -111
- package/src/hooks/hook-protocol.test.ts +192 -0
- package/src/hooks/hook-protocol.ts +275 -0
- package/src/hooks/hook-runner.ts +231 -0
- package/src/hooks/hook-state-dir.test.ts +109 -0
- package/src/hooks/hook-timeout.test.ts +104 -0
- package/src/hooks/hook-trespass.test.ts +202 -0
- package/src/hooks/injected-capabilities.test.ts +75 -0
- package/src/hooks/mount-set.test.ts +148 -0
- package/src/hooks/mount-set.ts +234 -0
- package/src/hooks/test-fixtures/capability-calling-hook.ts +79 -0
- package/src/hooks/test-fixtures/runaway-hook.ts +26 -0
- package/src/hooks/test-fixtures/sigterm-ignoring-hook.ts +22 -0
- package/src/manifest/contracts/v1.ts +21 -6
- package/src/manifest/validate-provider-views.test.ts +61 -0
- package/src/manifest/validate.ts +21 -14
- package/src/module/packaging/module-state-directory.test.ts +105 -0
- package/src/module/packaging/package-rules.ts +10 -2
- package/src/policy/capability-shape-baseline.ts +8 -0
- package/src/policy/capability-shape.ts +13 -1
- package/src/policy/module-business-baseline.ts +36 -0
- package/src/policy/module-dep-reachability.test.ts +167 -0
- package/src/services/alerting/ack.test.ts +2 -2
- package/src/services/alerting/deferral.test.ts +2 -2
- package/src/services/alerting/delivery-loop.test.ts +2 -2
- package/src/services/alerting/deploy-hooks.test.ts +2 -2
- package/src/services/alerting/inbound-poller.test.ts +2 -2
- package/src/services/alerting/inbound.test.ts +2 -2
- package/src/services/alerting/notification-responder.test.ts +2 -2
- package/src/services/alerting/run-monitor.test.ts +2 -2
- package/src/services/alerting/store.test.ts +2 -2
- package/src/services/alerting/sweep-runner.test.ts +2 -2
- package/src/services/alerting/tokens.test.ts +2 -2
- package/src/services/capability-bindings.test.ts +104 -0
- package/src/services/capability-bindings.ts +107 -0
- package/src/services/capability-table-rows.test.ts +2 -2
- package/src/services/consumer-cleanup.test.ts +40 -3
- package/src/services/dns-internal-records.test.ts +3 -3
- package/src/services/fleet-checks.test.ts +4 -4
- package/src/services/module-instances.test.ts +198 -0
- package/src/services/module-instances.ts +96 -0
- package/src/services/module-journal.test.ts +2 -2
- package/src/services/module-subscriptions.test.ts +1 -1
- package/src/services/port-forwards.test.ts +2 -2
- package/src/services/trusted-sources.test.ts +3 -3
- package/src/templates/ingress-ip.test.ts +31 -0
- package/src/test-utils/database.ts +31 -1
- package/src/test-utils/module-fixtures.ts +147 -35
- package/src/test-utils/setup-test-db.ts +0 -80
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { CLIContext } from './cli-context';
|
|
11
|
+
import { getModuleTestConfig } from './fixtures';
|
|
11
12
|
import { type IntegrationTestContext, setupIntegrationTest } from './integration';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -38,6 +39,52 @@ interface HomebridgeModuleOptions extends BaseFixtureOptions {
|
|
|
38
39
|
withMockInfrastructure?: boolean;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* A verified container service covering every allocatable zone, so a fixture
|
|
44
|
+
* can reach `module generate`.
|
|
45
|
+
*
|
|
46
|
+
* Extracted rather than copied: generation refuses without infrastructure, so
|
|
47
|
+
* every fixture that generates needs this, and a second inline copy would drift
|
|
48
|
+
* from the first the moment `containerServices` gains a column.
|
|
49
|
+
*/
|
|
50
|
+
async function insertMockContainerService(dbPath: string): Promise<void> {
|
|
51
|
+
const { getDb } = await import('../db/client');
|
|
52
|
+
const { containerServices } = await import('../db/schema');
|
|
53
|
+
const originalDbPath = process.env.CELILO_DB_PATH;
|
|
54
|
+
process.env.CELILO_DB_PATH = dbPath;
|
|
55
|
+
try {
|
|
56
|
+
const db = getDb();
|
|
57
|
+
await db.insert(containerServices).values({
|
|
58
|
+
id: 'test-proxmox',
|
|
59
|
+
serviceId: 'test-proxmox',
|
|
60
|
+
name: 'Test Proxmox',
|
|
61
|
+
providerName: 'proxmox',
|
|
62
|
+
zones: ['internal', 'dmz', 'app', 'secure'],
|
|
63
|
+
apiCredentialsEncrypted: JSON.stringify({
|
|
64
|
+
encryptedValue: 'dummy',
|
|
65
|
+
iv: 'dummy',
|
|
66
|
+
authTag: 'dummy',
|
|
67
|
+
}),
|
|
68
|
+
providerConfig: {
|
|
69
|
+
default_target_node: 'pve',
|
|
70
|
+
lxc_template: 'local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst',
|
|
71
|
+
storage: 'local-lvm',
|
|
72
|
+
},
|
|
73
|
+
verified: true,
|
|
74
|
+
verifiedAt: new Date(),
|
|
75
|
+
verificationError: null,
|
|
76
|
+
createdAt: new Date(),
|
|
77
|
+
updatedAt: new Date(),
|
|
78
|
+
});
|
|
79
|
+
} finally {
|
|
80
|
+
if (originalDbPath) {
|
|
81
|
+
process.env.CELILO_DB_PATH = originalDbPath;
|
|
82
|
+
} else {
|
|
83
|
+
process.env.CELILO_DB_PATH = undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
41
88
|
/**
|
|
42
89
|
* Homebridge module fixture result
|
|
43
90
|
*/
|
|
@@ -83,41 +130,7 @@ export async function homebridgeModule(
|
|
|
83
130
|
|
|
84
131
|
// Setup mock infrastructure if requested (needed for generation)
|
|
85
132
|
if (options.withMockInfrastructure) {
|
|
86
|
-
|
|
87
|
-
const { containerServices } = await import('../db/schema');
|
|
88
|
-
const originalDbPath = process.env.CELILO_DB_PATH;
|
|
89
|
-
process.env.CELILO_DB_PATH = ctx.dbPath;
|
|
90
|
-
try {
|
|
91
|
-
const db = getDb();
|
|
92
|
-
await db.insert(containerServices).values({
|
|
93
|
-
id: 'test-proxmox',
|
|
94
|
-
serviceId: 'test-proxmox',
|
|
95
|
-
name: 'Test Proxmox',
|
|
96
|
-
providerName: 'proxmox',
|
|
97
|
-
zones: ['internal', 'dmz', 'app', 'secure'],
|
|
98
|
-
apiCredentialsEncrypted: JSON.stringify({
|
|
99
|
-
encryptedValue: 'dummy',
|
|
100
|
-
iv: 'dummy',
|
|
101
|
-
authTag: 'dummy',
|
|
102
|
-
}),
|
|
103
|
-
providerConfig: {
|
|
104
|
-
default_target_node: 'pve',
|
|
105
|
-
lxc_template: 'local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst',
|
|
106
|
-
storage: 'local-lvm',
|
|
107
|
-
},
|
|
108
|
-
verified: true,
|
|
109
|
-
verifiedAt: new Date(),
|
|
110
|
-
verificationError: null,
|
|
111
|
-
createdAt: new Date(),
|
|
112
|
-
updatedAt: new Date(),
|
|
113
|
-
});
|
|
114
|
-
} finally {
|
|
115
|
-
if (originalDbPath) {
|
|
116
|
-
process.env.CELILO_DB_PATH = originalDbPath;
|
|
117
|
-
} else {
|
|
118
|
-
process.env.CELILO_DB_PATH = undefined;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
133
|
+
await insertMockContainerService(ctx.dbPath);
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
// Create CLI context with integration test env
|
|
@@ -195,6 +208,105 @@ export async function homebridgeModule(
|
|
|
195
208
|
};
|
|
196
209
|
}
|
|
197
210
|
|
|
211
|
+
/**
|
|
212
|
+
* `dnsmasq-dhcp` fixture, for the golden comparison.
|
|
213
|
+
*
|
|
214
|
+
* ⚠️ IMPORTS THE REAL MODULE, `../../modules/dnsmasq-dhcp`, and not a copy under
|
|
215
|
+
* `test-fixtures/modules/`. That is the whole point of it.
|
|
216
|
+
*
|
|
217
|
+
* `homebridgeModule` imports a copy, and the copy has drifted 29 lines in the
|
|
218
|
+
* manifest and 6 in the Terraform template. So the golden gate compares
|
|
219
|
+
* generated-from-a-copy against golden-from-the-same-copy: both sides move
|
|
220
|
+
* together, and the shipped module is on neither. Worse, the copy is frozen at
|
|
221
|
+
* `version: 1.0.0` with the container-infrastructure variables `required: true`
|
|
222
|
+
* — the pre-fix state of a real bug (marking them required made homebridge
|
|
223
|
+
* container-only). The gate still passes against that shape and cannot notice
|
|
224
|
+
* it returning. Tracked as celilo#1084.
|
|
225
|
+
*
|
|
226
|
+
* Pointing at the shipped module means this golden fails when the generator's
|
|
227
|
+
* output for the shipped module changes, which is the only version worth having.
|
|
228
|
+
*/
|
|
229
|
+
export interface DnsmasqDhcpModuleFixture {
|
|
230
|
+
cli: CLIContext;
|
|
231
|
+
moduleId: string;
|
|
232
|
+
config: Record<string, unknown>;
|
|
233
|
+
context: IntegrationTestContext;
|
|
234
|
+
cleanup: () => Promise<void>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface DnsmasqDhcpModuleOptions extends BaseFixtureOptions {
|
|
238
|
+
/** Seed a container service, without which generation has no infrastructure. */
|
|
239
|
+
withMockInfrastructure?: boolean;
|
|
240
|
+
/** Set the `network.internal.*` system config the templates resolve against. */
|
|
241
|
+
withSystemConfig?: boolean;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function dnsmasqDhcpModule(
|
|
245
|
+
options: DnsmasqDhcpModuleOptions = {},
|
|
246
|
+
): Promise<DnsmasqDhcpModuleFixture> {
|
|
247
|
+
const ctx = await setupIntegrationTest();
|
|
248
|
+
|
|
249
|
+
if (options.withMockInfrastructure) {
|
|
250
|
+
await insertMockContainerService(ctx.dbPath);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const cli = await CLIContext.create('src/cli/index.ts', {
|
|
254
|
+
CELILO_DB_PATH: ctx.dbPath,
|
|
255
|
+
CELILO_DATA_DIR: ctx.dataDir,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const moduleId = 'dnsmasq-dhcp';
|
|
259
|
+
|
|
260
|
+
if (options.withSystemConfig) {
|
|
261
|
+
// `internal` only — it is the single zone this module declares, and adding
|
|
262
|
+
// others would put values in the golden that nothing generated from.
|
|
263
|
+
(await cli.run('system config set network.bridge vmbr0')).expectSuccess();
|
|
264
|
+
(await cli.run('system config set network.internal.vlan 1')).expectSuccess();
|
|
265
|
+
(await cli.run('system config set network.internal.gateway 10.99.1.1')).expectSuccess();
|
|
266
|
+
(await cli.run('system config set network.internal.subnet 10.99.1.0/24')).expectSuccess();
|
|
267
|
+
(
|
|
268
|
+
await cli.run(
|
|
269
|
+
'system config set ssh.public_key ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... test@celilo',
|
|
270
|
+
)
|
|
271
|
+
).expectSuccess();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Read from `test-fixtures/test-values.yml` rather than written out here, so
|
|
275
|
+
// that file's entry for this module is load-bearing rather than decorative. A
|
|
276
|
+
// golden generated from values nothing else references would drift from the
|
|
277
|
+
// canonical set with nothing noticing.
|
|
278
|
+
//
|
|
279
|
+
// That entry carries EVERY `required: true` variable, including the ones with
|
|
280
|
+
// defaults: celilo asks for a required variable whether or not it has one,
|
|
281
|
+
// and an unanswered ask has no headless answer, so the command blocks until
|
|
282
|
+
// its timeout naming no variable.
|
|
283
|
+
const config = await getModuleTestConfig(moduleId);
|
|
284
|
+
if (Object.keys(config).length === 0) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`test-values.yml carries no entry for '${moduleId}', so this fixture would generate from an empty config and the golden would record that. Add one.`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
(await cli.run(`module import ../../modules/${moduleId}`)).expectSuccess();
|
|
291
|
+
|
|
292
|
+
for (const [key, value] of Object.entries(config)) {
|
|
293
|
+
// An array has to reach the CLI as JSON; a scalar is quoted as-is.
|
|
294
|
+
const encoded = Array.isArray(value) ? `'${JSON.stringify(value)}'` : `"${value}"`;
|
|
295
|
+
(await cli.run(`module config set ${moduleId} ${key} ${encoded}`)).expectSuccess();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (options.generated) {
|
|
299
|
+
(await cli.run(`module generate ${moduleId}`)).expectSuccess();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const cleanup = async () => {
|
|
303
|
+
await cli.dispose();
|
|
304
|
+
await ctx.cleanup();
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return { cli, moduleId, config, context: ctx, cleanup };
|
|
308
|
+
}
|
|
309
|
+
|
|
198
310
|
/**
|
|
199
311
|
* Caddy module fixture options
|
|
200
312
|
*/
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { unlink } from 'node:fs/promises';
|
|
3
|
-
import { migrate } from 'drizzle-orm/bun-sqlite/migrator';
|
|
4
|
-
import { type DbClient, createDbClient, findMigrationsFolder } from '../db/client';
|
|
5
|
-
|
|
6
|
-
// `findMigrationsFolder` is imported from db/client rather than redefined here.
|
|
7
|
-
// This file used to carry its own copy, and the copy had drifted: it listed
|
|
8
|
-
// only CWD-RELATIVE candidates (`./drizzle`, `process.cwd()/drizzle`, …) and was
|
|
9
|
-
// missing the `join(currentDir, '../../drizzle')` fallback that resolves
|
|
10
|
-
// relative to the source file.
|
|
11
|
-
//
|
|
12
|
-
// The effect was invisible from `apps/celilo` and total from the repo root —
|
|
13
|
-
// which is where CLAUDE.md says to run the gate. Every suite using
|
|
14
|
-
// setupTestDatabase threw here, left `db` unassigned, and then failed again in
|
|
15
|
-
// its own afterEach on `db.$client`, so one missing path candidate presented as
|
|
16
|
-
// hundreds of unrelated-looking assertion failures (celilo: 668 across the
|
|
17
|
-
// repo, 341 in apps/celilo/src alone).
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Setup test database with real migrations
|
|
21
|
-
*
|
|
22
|
-
* This helper ensures tests use the same schema as production by running
|
|
23
|
-
* actual migrations instead of manual CREATE TABLE statements.
|
|
24
|
-
*
|
|
25
|
-
* Benefits:
|
|
26
|
-
* - Single source of truth for schema
|
|
27
|
-
* - Tests automatically use latest schema
|
|
28
|
-
* - Validates migration correctness
|
|
29
|
-
*
|
|
30
|
-
* @param testDbPath - Path to test database file
|
|
31
|
-
* @returns Database client instance
|
|
32
|
-
*/
|
|
33
|
-
export async function setupTestDatabase(testDbPath: string) {
|
|
34
|
-
// Create database client
|
|
35
|
-
const db = createDbClient({ path: testDbPath });
|
|
36
|
-
|
|
37
|
-
// Find and run migrations
|
|
38
|
-
const migrationsFolder = findMigrationsFolder();
|
|
39
|
-
await migrate(db, { migrationsFolder });
|
|
40
|
-
|
|
41
|
-
return db;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Cleanup test database
|
|
46
|
-
*
|
|
47
|
-
* @param db - Database client to close
|
|
48
|
-
* @param testDbPath - Path to test database file to delete
|
|
49
|
-
*/
|
|
50
|
-
export async function cleanupTestDatabase(db: DbClient, testDbPath: string) {
|
|
51
|
-
// Close database connection
|
|
52
|
-
db.$client.close();
|
|
53
|
-
|
|
54
|
-
// Delete test database file
|
|
55
|
-
if (existsSync(testDbPath)) {
|
|
56
|
-
await unlink(testDbPath);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Also delete WAL and SHM files if they exist
|
|
60
|
-
const walPath = `${testDbPath}-wal`;
|
|
61
|
-
const shmPath = `${testDbPath}-shm`;
|
|
62
|
-
|
|
63
|
-
if (existsSync(walPath)) {
|
|
64
|
-
await unlink(walPath);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (existsSync(shmPath)) {
|
|
68
|
-
await unlink(shmPath);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Generate unique test database path
|
|
74
|
-
*
|
|
75
|
-
* @param prefix - Optional prefix for the database file (defaults to 'test-celilo')
|
|
76
|
-
* @returns Unique database file path
|
|
77
|
-
*/
|
|
78
|
-
export function generateTestDbPath(prefix = 'test-celilo'): string {
|
|
79
|
-
return `./${prefix}-${Date.now()}-${Math.random()}.db`;
|
|
80
|
-
}
|