@celilo/cli 2.2.0 → 2.3.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/CELILO_CORE_MODULES.md +1 -0
- package/CELILO_SUBSYSTEMS.md +1 -1
- package/README.md +1 -1
- package/drizzle/0032_module_jail_policies.sql +28 -0
- package/drizzle/0033_build_bus_hook_runs.sql +37 -0
- package/drizzle/meta/_journal.json +15 -1
- package/package.json +3 -3
- package/schemas/system_config.json +5 -0
- package/src/__integration__/container-services-cli.integration.test.ts +1 -1
- package/src/ansible/inventory.test.ts +2 -1
- package/src/api/sessions.test.ts +5 -4
- package/src/api-clients/proxmox.ts +10 -7
- package/src/cli/backup-rename.test.ts +5 -4
- package/src/cli/cli.test.ts +3 -2
- package/src/cli/commands/console-get-chain.test.ts +2 -1
- package/src/cli/commands/events.test.ts +2 -2
- package/src/cli/commands/firewall-interface-list.test.ts +4 -3
- package/src/cli/commands/machine-list.test.ts +57 -0
- package/src/cli/commands/machine-list.ts +35 -4
- package/src/cli/commands/module-config.test.ts +2 -1
- package/src/cli/commands/module-deploy.ts +8 -2
- package/src/cli/commands/module-generate.test.ts +53 -0
- package/src/cli/commands/module-generate.ts +31 -26
- package/src/cli/commands/module-health.ts +1 -0
- package/src/cli/commands/module-import-aspect.test.ts +2 -1
- package/src/cli/commands/module-import-registry.test.ts +3 -2
- package/src/cli/commands/module-jail.test.ts +242 -0
- package/src/cli/commands/module-jail.ts +227 -0
- package/src/cli/commands/module-list-jail.test.ts +136 -0
- package/src/cli/commands/module-list.ts +30 -3
- package/src/cli/commands/module-operations.test.ts +2 -1
- package/src/cli/commands/module-publish.test.ts +11 -18
- package/src/cli/commands/module-update.test.ts +28 -12
- package/src/cli/commands/module-update.ts +4 -1
- package/src/cli/commands/module-upgrade.test.ts +130 -1
- package/src/cli/commands/module-upgrade.ts +77 -3
- package/src/cli/commands/module-verify.test.ts +3 -4
- package/src/cli/commands/module-verify.ts +0 -1
- package/src/cli/commands/notify-config.test.ts +5 -3
- package/src/cli/commands/publish/execute.ts +4 -1
- package/src/cli/commands/publish/index.ts +11 -1
- package/src/cli/commands/publish/module-registry.test.ts +24 -1
- package/src/cli/commands/publish/module-registry.ts +23 -4
- package/src/cli/commands/publish/plan.ts +1 -1
- package/src/cli/commands/publish/types.ts +7 -0
- package/src/cli/commands/registry-owner.test.ts +3 -2
- package/src/cli/commands/registry-token.test.ts +3 -2
- package/src/cli/commands/system-apply-config-equivalence.test.ts +5 -7
- package/src/cli/commands/system-audit.ts +20 -6
- package/src/cli/commands/system-config.test.ts +148 -0
- package/src/cli/commands/system-config.ts +26 -1
- package/src/cli/commands/system-doctor-remediation-gate.test.ts +295 -0
- package/src/cli/commands/system-doctor.test.ts +143 -6
- package/src/cli/commands/system-doctor.ts +142 -25
- package/src/cli/commands/system-init-deprecation.test.ts +7 -4
- package/src/cli/commands/system-migrate.test.ts +2 -1
- package/src/cli/commands/system-update.ts +5 -0
- package/src/cli/completion.ts +8 -2
- package/src/cli/flag-surface-gate.test.ts +279 -0
- package/src/cli/index.ts +70 -3
- package/src/cli/parser.test.ts +37 -1
- package/src/cli/restore-command.test.ts +5 -4
- package/src/cli/restore-migration-failure.test.ts +4 -3
- package/src/cli/tui/audit-state.ts +2 -0
- package/src/config/paths.test.ts +22 -22
- package/src/db/client.test.ts +46 -1
- package/src/db/client.ts +26 -0
- package/src/db/migrate.test.ts +2 -1
- package/src/db/migrate.ts +16 -16
- package/src/db/schema.ts +63 -0
- package/src/hooks/capability-loader-firewall.test.ts +9 -1
- package/src/hooks/capability-loader.test.ts +78 -0
- package/src/hooks/capability-loader.ts +45 -2
- package/src/hooks/executor.test.ts +161 -1
- package/src/hooks/executor.ts +87 -21
- package/src/hooks/hook-jail-unreachability.test.ts +17 -18
- package/src/hooks/hook-store.test.ts +6 -0
- package/src/hooks/hook-trespass.test.ts +26 -9
- package/src/hooks/jail.test.ts +152 -5
- package/src/hooks/jail.ts +93 -4
- package/src/manifest/contracts/v1.ts +13 -0
- package/src/module/packaging/audit.ts +9 -26
- package/src/module/packaging/build-paths.test.ts +127 -0
- package/src/module/packaging/build-paths.ts +175 -0
- package/src/module/packaging/build.test.ts +71 -1
- package/src/module/packaging/build.ts +60 -0
- package/src/module/packaging/extract.ts +1 -5
- package/src/policy/module-script-scan.ts +51 -107
- package/src/policy/no-hand-built-ssh.test.ts +5 -1
- package/src/policy/no-swallowed-refusal.test.ts +14 -15
- package/src/registry/client.test.ts +2 -2
- package/src/secrets/storage.test.ts +1 -1
- package/src/services/alerting/ack.test.ts +2 -1
- package/src/services/alerting/cadence-migration.test.ts +3 -2
- package/src/services/alerting/coverage-source.test.ts +2 -1
- package/src/services/alerting/deferral.test.ts +2 -1
- package/src/services/alerting/delivery-loop.test.ts +2 -1
- package/src/services/alerting/deploy-hooks.test.ts +2 -1
- package/src/services/alerting/inbound-poller.test.ts +2 -1
- package/src/services/alerting/inbound.test.ts +2 -1
- package/src/services/alerting/keys.test.ts +4 -0
- package/src/services/alerting/keys.ts +12 -2
- package/src/services/alerting/notification-responder.test.ts +2 -1
- package/src/services/alerting/run-monitor.test.ts +2 -1
- package/src/services/alerting/store.test.ts +2 -1
- package/src/services/alerting/sweep-runner.test.ts +2 -1
- package/src/services/alerting/tokens.test.ts +2 -1
- package/src/services/aspect-approvals.test.ts +2 -1
- package/src/services/aspect-reconcile.test.ts +4 -3
- package/src/services/aspect-runner.test.ts +2 -1
- package/src/services/audit/health.test.ts +97 -2
- package/src/services/audit/health.ts +64 -2
- package/src/services/audit/index.test.ts +22 -0
- package/src/services/audit/index.ts +8 -2
- package/src/services/audit/jail-exemptions.test.ts +42 -0
- package/src/services/audit/jail-exemptions.ts +44 -0
- package/src/services/audit/module-integrity.test.ts +23 -22
- package/src/services/audit/module-integrity.ts +7 -16
- package/src/services/audit/types.ts +2 -1
- package/src/services/backup-age-agreement.test.ts +2 -1
- package/src/services/backup-create.ts +9 -0
- package/src/services/backup-envelope-roundtrip.test.ts +3 -2
- package/src/services/backup-in-flight-refusal.test.ts +3 -2
- package/src/services/build-bus/hook-dispatch-mgmt.test.ts +48 -0
- package/src/services/build-bus/hook-dispatcher.ts +49 -1
- package/src/services/bus-ensure-flow.test.ts +3 -2
- package/src/services/bus-interview-park.test.ts +4 -3
- package/src/services/bus-interview.test.ts +2 -2
- package/src/services/bus-secret-flow.test.ts +3 -2
- package/src/services/capability-table-rows.test.ts +2 -1
- package/src/services/celilo-events.test.ts +1 -1
- package/src/services/celilo-mgmt-hooks.test.ts +23 -5
- package/src/services/consumer-cleanup.test.ts +3 -2
- package/src/services/container-service.test.ts +3 -2
- package/src/services/control-plane-bootstrap.test.ts +2 -1
- package/src/services/cross-module-read.test.ts +4 -3
- package/src/services/deploy-preflight.ts +7 -0
- package/src/services/deploy-validation.test.ts +3 -2
- package/src/services/deployed-systems.ts +1 -1
- package/src/services/dns-internal-records.test.ts +3 -2
- package/src/services/dns-provider-backfill.test.ts +2 -1
- package/src/services/dns-registrations.test.ts +2 -1
- package/src/services/ensure-interview.test.ts +3 -2
- package/src/services/fleet-checks.test.ts +126 -6
- package/src/services/fleet-checks.ts +176 -3
- package/src/services/fleet-key.test.ts +3 -2
- package/src/services/health-runner.test.ts +87 -2
- package/src/services/health-runner.ts +40 -11
- package/src/services/infrastructure-selector.test.ts +3 -2
- package/src/services/infrastructure-variable-resolver.test.ts +2 -1
- package/src/services/jail-exemptions.test.ts +125 -0
- package/src/services/jail-exemptions.ts +81 -0
- package/src/services/machine-pool.test.ts +3 -2
- package/src/services/module-config.test.ts +2 -1
- package/src/services/module-deploy.dns-repoint.test.ts +2 -1
- package/src/services/module-deploy.ts +48 -24
- package/src/services/module-operations.test.ts +2 -1
- package/src/services/module-subscriptions.test.ts +4 -3
- package/src/services/network-discovery.test.ts +64 -1
- package/src/services/network-discovery.ts +30 -5
- package/src/services/port-forwards.test.ts +2 -1
- package/src/services/programmatic-responder.aspect.test.ts +2 -1
- package/src/services/proxmox-reconcile.test.ts +2 -1
- package/src/services/responder-probe.test.ts +1 -1
- package/src/services/restore-from-file.test.ts +7 -6
- package/src/services/restore-preflight.test.ts +3 -2
- package/src/services/secret-schema-loader.test.ts +2 -1
- package/src/services/ssh-key-manager.test.ts +4 -3
- package/src/services/static-content-converge.test.ts +5 -4
- package/src/services/static-content-converge.ts +33 -10
- package/src/services/system-state-stage.test.ts +4 -3
- package/src/services/trusted-sources.test.ts +3 -2
- package/src/services/update/orchestrator.test.ts +2 -0
- package/src/services/zone-detector.test.ts +2 -1
- package/src/test-utils/bus-responder.ts +6 -4
- package/src/test-utils/db-path.ts +25 -0
- package/src/test-utils/integration.ts +7 -0
- package/src/test-utils/module-fixtures.ts +5 -6
- package/src/module/packaging/generated-plane.test.ts +0 -79
- package/src/module/packaging/generated-plane.ts +0 -134
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
import { beforeEach, describe, expect, test } from 'bun:test';
|
|
16
16
|
import { eq } from 'drizzle-orm';
|
|
17
17
|
import type { DbClient } from '../db/client';
|
|
18
|
-
import { systemConfig } from '../db/schema';
|
|
18
|
+
import { modules, systemConfig } from '../db/schema';
|
|
19
19
|
import { setupTestDatabase } from '../test-utils/database';
|
|
20
|
+
import { CONTROL_PLANE_MODULE_ID, upsertDeployedSystem } from './deployed-systems';
|
|
20
21
|
import {
|
|
21
22
|
discoverAndRecordNetwork,
|
|
22
23
|
discoveredNetworkKeys,
|
|
@@ -102,6 +103,33 @@ describe('discoveredNetworkKeys (issue #300 redeploy idempotency)', () => {
|
|
|
102
103
|
});
|
|
103
104
|
});
|
|
104
105
|
|
|
106
|
+
test('first install with control plane in secure-mgmt records there (celilo#1356)', () => {
|
|
107
|
+
// The default topology (ce-8mxz) puts celilo-mgr on secure-mgmt, so a fresh
|
|
108
|
+
// segmented bootstrap must not file the control-plane subnet under
|
|
109
|
+
// `network.internal.*` — celilo would otherwise be blind to its own
|
|
110
|
+
// internal zone.
|
|
111
|
+
expect(discoveredNetworkKeys(internal, false, { controlPlaneZone: 'secure-mgmt' })).toEqual({
|
|
112
|
+
'network.secure-mgmt.subnet': '192.168.1.0/24',
|
|
113
|
+
'network.secure-mgmt.gateway': '192.168.1.1',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('first install with control plane in internal is unchanged', () => {
|
|
118
|
+
expect(discoveredNetworkKeys(internal, false, { controlPlaneZone: 'internal' })).toEqual({
|
|
119
|
+
'network.internal.subnet': '192.168.1.0/24',
|
|
120
|
+
'network.internal.gateway': '192.168.1.1',
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('first install never clobbers an already-set control-plane zone value', () => {
|
|
125
|
+
expect(
|
|
126
|
+
discoveredNetworkKeys(internal, false, {
|
|
127
|
+
controlPlaneZone: 'secure-mgmt',
|
|
128
|
+
secureMgmtAlreadySet: true,
|
|
129
|
+
}),
|
|
130
|
+
).toEqual({});
|
|
131
|
+
});
|
|
132
|
+
|
|
105
133
|
test('preserves an already-set subnet on redeploy (no override)', () => {
|
|
106
134
|
// The regression: a redeploy on a box off the internal network discovers the
|
|
107
135
|
// WRONG subnet — it must not clobber the operator's value.
|
|
@@ -165,6 +193,41 @@ describe('discoverAndRecordNetwork', () => {
|
|
|
165
193
|
expect(read('network.internal.gateway')).toBe('10.31.7.1');
|
|
166
194
|
});
|
|
167
195
|
|
|
196
|
+
test('first install for a control plane deployed in secure-mgmt records secure-mgmt (celilo#1356)', () => {
|
|
197
|
+
// Seed the module_systems row the deploy pipeline writes before
|
|
198
|
+
// bootstrapControlPlane runs, placing celilo-mgmt in secure-mgmt.
|
|
199
|
+
db.insert(modules)
|
|
200
|
+
.values({
|
|
201
|
+
id: CONTROL_PLANE_MODULE_ID,
|
|
202
|
+
name: 'Celilo Management Server',
|
|
203
|
+
version: '0.9.0',
|
|
204
|
+
manifestData: {},
|
|
205
|
+
sourcePath: '/tmp/modules/celilo-mgmt',
|
|
206
|
+
})
|
|
207
|
+
.run();
|
|
208
|
+
upsertDeployedSystem(db, CONTROL_PLANE_MODULE_ID, {
|
|
209
|
+
name: 'main',
|
|
210
|
+
hostname: 'celilo-mgr',
|
|
211
|
+
ipv4Address: '10.226.120.10',
|
|
212
|
+
zone: 'secure-mgmt',
|
|
213
|
+
infraType: 'machine',
|
|
214
|
+
machineId: null,
|
|
215
|
+
serviceId: null,
|
|
216
|
+
vmid: null,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const result = discoverAndRecordNetwork(db, () =>
|
|
220
|
+
ipRouteFor('10.226.120.0', '10.226.120.1', '10.226.120.10'),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
expect(result.applied).toEqual([
|
|
224
|
+
'network.secure-mgmt.subnet = 10.226.120.0/24',
|
|
225
|
+
'network.secure-mgmt.gateway = 10.226.120.1',
|
|
226
|
+
]);
|
|
227
|
+
expect(read('network.internal.subnet')).toBeUndefined();
|
|
228
|
+
expect(read('network.secure-mgmt.subnet')).toBe('10.226.120.0/24');
|
|
229
|
+
});
|
|
230
|
+
|
|
168
231
|
test('a redeploy on the same box changes nothing and says so', () => {
|
|
169
232
|
const routes = () => ipRouteFor('10.31.7.0', '10.31.7.1', '10.31.7.20');
|
|
170
233
|
discoverAndRecordNetwork(db, routes);
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
import { execFileSync } from 'node:child_process';
|
|
25
25
|
import { eq } from 'drizzle-orm';
|
|
26
26
|
import type { DbClient } from '../db/client';
|
|
27
|
-
import { systemConfig } from '../db/schema';
|
|
27
|
+
import { type NetworkZone, systemConfig } from '../db/schema';
|
|
28
|
+
import { CONTROL_PLANE_MODULE_ID, asZone, getModuleSystems } from './deployed-systems';
|
|
28
29
|
|
|
29
30
|
export interface DiscoveredNetwork {
|
|
30
31
|
subnet: string;
|
|
@@ -61,7 +62,14 @@ export function parseInternalNetwork(ipRouteOutput: string): DiscoveredNetwork |
|
|
|
61
62
|
* LAN, so what it discovered IS `internal`. First install records it there.
|
|
62
63
|
* - Segmented deployment: the box sits on a dedicated control-plane network.
|
|
63
64
|
* `internal` is then someone else's subnet — the semi-trusted LAN — and the
|
|
64
|
-
* discovered value belongs under `secure-mgmt
|
|
65
|
+
* discovered value belongs under the control plane's own zone (`secure-mgmt`
|
|
66
|
+
* in the default topology, celilo#1356).
|
|
67
|
+
*
|
|
68
|
+
* On FIRST INSTALL the zone is decided the same way the rest of celilo decides
|
|
69
|
+
* it: from where the control plane actually landed. `discoverAndRecordNetwork`
|
|
70
|
+
* reads `celilo-mgmt`'s deployed-system row and passes that zone in as
|
|
71
|
+
* `opts.controlPlaneZone`; with no row recorded yet the fallback stays
|
|
72
|
+
* `internal`, the pre-#1356 behaviour.
|
|
65
73
|
*
|
|
66
74
|
* Issue #300 spotted the second case as "discovery returns the WRONG subnet" and
|
|
67
75
|
* mitigated it by DISCARDING the value whenever `internal` was already set. That
|
|
@@ -76,14 +84,21 @@ export function parseInternalNetwork(ipRouteOutput: string): DiscoveredNetwork |
|
|
|
76
84
|
export function discoveredNetworkKeys(
|
|
77
85
|
host: DiscoveredNetwork | null,
|
|
78
86
|
internalAlreadySet: boolean,
|
|
79
|
-
opts: {
|
|
87
|
+
opts: {
|
|
88
|
+
internalSubnet?: string;
|
|
89
|
+
secureMgmtAlreadySet?: boolean;
|
|
90
|
+
/** Zone the control plane itself occupies; first install records there, not `internal`. */
|
|
91
|
+
controlPlaneZone?: NetworkZone;
|
|
92
|
+
} = {},
|
|
80
93
|
): Record<string, string> {
|
|
81
94
|
if (!host) return {};
|
|
82
95
|
|
|
83
96
|
if (!internalAlreadySet) {
|
|
97
|
+
const targetZone = opts.controlPlaneZone ?? 'internal';
|
|
98
|
+
if (targetZone !== 'internal' && opts.secureMgmtAlreadySet) return {};
|
|
84
99
|
return {
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
[`network.${targetZone}.subnet`]: host.subnet,
|
|
101
|
+
[`network.${targetZone}.gateway`]: host.gateway,
|
|
87
102
|
};
|
|
88
103
|
}
|
|
89
104
|
|
|
@@ -118,6 +133,15 @@ function readValue(db: DbClient, key: string): string | undefined {
|
|
|
118
133
|
return db.select().from(systemConfig).where(eq(systemConfig.key, key)).get()?.value ?? undefined;
|
|
119
134
|
}
|
|
120
135
|
|
|
136
|
+
/**
|
|
137
|
+
* The zone the control plane actually landed in, from its deployed-system row
|
|
138
|
+
* (same source `loadControlPlaneSubnet` reads). Undefined when celilo-mgmt has
|
|
139
|
+
* no system recorded yet, so the caller falls back to `internal`.
|
|
140
|
+
*/
|
|
141
|
+
function controlPlaneZone(db: DbClient): NetworkZone | undefined {
|
|
142
|
+
return asZone(getModuleSystems(CONTROL_PLANE_MODULE_ID, db)[0]?.zone) ?? undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
121
145
|
/**
|
|
122
146
|
* Discover the network this box sits on and record it. Idempotent, and never
|
|
123
147
|
* overwrites a value that is already set.
|
|
@@ -143,6 +167,7 @@ export function discoverAndRecordNetwork(
|
|
|
143
167
|
const keys = discoveredNetworkKeys(host, internalSubnet !== undefined, {
|
|
144
168
|
internalSubnet,
|
|
145
169
|
secureMgmtAlreadySet: readValue(db, 'network.secure-mgmt.subnet') !== undefined,
|
|
170
|
+
controlPlaneZone: controlPlaneZone(db),
|
|
146
171
|
});
|
|
147
172
|
|
|
148
173
|
const applied: string[] = [];
|
|
@@ -7,6 +7,7 @@ import { eq } from 'drizzle-orm';
|
|
|
7
7
|
import type { DbClient } from '../db/client';
|
|
8
8
|
import { portForwards } from '../db/schema';
|
|
9
9
|
import { setupTestDatabaseAt } from '../test-utils/database';
|
|
10
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
10
11
|
import { deleteClaimedRows } from './capability-table-rows';
|
|
11
12
|
import { buildPortForwardStore } from './port-forwards';
|
|
12
13
|
|
|
@@ -27,7 +28,7 @@ describe('port-forward store', () => {
|
|
|
27
28
|
});
|
|
28
29
|
afterEach(() => {
|
|
29
30
|
db.$client.close();
|
|
30
|
-
|
|
31
|
+
resetTestDbPath();
|
|
31
32
|
try {
|
|
32
33
|
rmSync(dir, { recursive: true, force: true });
|
|
33
34
|
} catch {
|
|
@@ -17,6 +17,7 @@ import { join } from 'node:path';
|
|
|
17
17
|
import { defineEvents, openBus } from '@celilo/event-bus';
|
|
18
18
|
import { closeDb, getDb } from '../db/client';
|
|
19
19
|
import { runMigrations } from '../db/migrate';
|
|
20
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
20
21
|
import { type AspectRequiredPayload, EVENT_TYPES } from './bus-interview';
|
|
21
22
|
import { startProgrammaticResponder } from './programmatic-responder';
|
|
22
23
|
|
|
@@ -72,7 +73,7 @@ describe('programmatic responder — aspect.required consent (#262)', () => {
|
|
|
72
73
|
afterEach(() => {
|
|
73
74
|
closeDb();
|
|
74
75
|
rmSync(dir, { recursive: true, force: true });
|
|
75
|
-
|
|
76
|
+
resetTestDbPath();
|
|
76
77
|
});
|
|
77
78
|
|
|
78
79
|
it('approves (consented=true) when the policy approves the module', async () => {
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
modules,
|
|
28
28
|
} from '../db/schema';
|
|
29
29
|
import type { BaseModuleAspect } from '../manifest/schema';
|
|
30
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
30
31
|
import { upsertModuleConfig } from './module-config';
|
|
31
32
|
import { executeProxmoxReconcile, planProxmoxReconcile } from './proxmox-reconcile';
|
|
32
33
|
|
|
@@ -153,7 +154,7 @@ describe('proxmox-reconcile', () => {
|
|
|
153
154
|
|
|
154
155
|
afterEach(() => {
|
|
155
156
|
closeDb();
|
|
156
|
-
|
|
157
|
+
resetTestDbPath();
|
|
157
158
|
try {
|
|
158
159
|
rmSync(dir, { recursive: true, force: true });
|
|
159
160
|
} catch {
|
|
@@ -32,7 +32,7 @@ describe('ensureResponderForInterview (ISS-0025)', () => {
|
|
|
32
32
|
origTTY = process.stdin.isTTY;
|
|
33
33
|
});
|
|
34
34
|
afterEach(() => {
|
|
35
|
-
if (origEnv === undefined) process.env.EVENT_BUS_DB
|
|
35
|
+
if (origEnv === undefined) delete process.env.EVENT_BUS_DB;
|
|
36
36
|
else process.env.EVENT_BUS_DB = origEnv;
|
|
37
37
|
setTTY(origTTY);
|
|
38
38
|
try {
|
|
@@ -23,6 +23,7 @@ import { tmpdir } from 'node:os';
|
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import { closeDb } from '../db/client';
|
|
25
25
|
import { runMigrations } from '../db/migrate';
|
|
26
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
26
27
|
import { applyStagedSystemFiles, restoreFromArtifactFile } from './restore-from-file';
|
|
27
28
|
|
|
28
29
|
describe('applyStagedSystemFiles', () => {
|
|
@@ -44,9 +45,9 @@ describe('applyStagedSystemFiles', () => {
|
|
|
44
45
|
|
|
45
46
|
afterEach(() => {
|
|
46
47
|
closeDb();
|
|
47
|
-
|
|
48
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
49
|
-
process.env.CELILO_DATA_DIR
|
|
48
|
+
resetTestDbPath();
|
|
49
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
50
|
+
delete process.env.CELILO_DATA_DIR;
|
|
50
51
|
try {
|
|
51
52
|
rmSync(dir, { recursive: true, force: true });
|
|
52
53
|
} catch {
|
|
@@ -205,9 +206,9 @@ describe('restoreFromArtifactFile error paths', () => {
|
|
|
205
206
|
|
|
206
207
|
afterEach(() => {
|
|
207
208
|
closeDb();
|
|
208
|
-
|
|
209
|
-
process.env.CELILO_DATA_DIR
|
|
210
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
209
|
+
resetTestDbPath();
|
|
210
|
+
delete process.env.CELILO_DATA_DIR;
|
|
211
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
211
212
|
try {
|
|
212
213
|
rmSync(dir, { recursive: true, force: true });
|
|
213
214
|
} catch {
|
|
@@ -5,6 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { closeDb, getDb } from '../db/client';
|
|
6
6
|
import { runMigrations } from '../db/migrate';
|
|
7
7
|
import { modules } from '../db/schema';
|
|
8
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
8
9
|
import {
|
|
9
10
|
NonEmptyRestoreTargetError,
|
|
10
11
|
assertRestoreTargetEmpty,
|
|
@@ -23,8 +24,8 @@ describe('restore pre-flight', () => {
|
|
|
23
24
|
|
|
24
25
|
afterEach(() => {
|
|
25
26
|
closeDb();
|
|
26
|
-
|
|
27
|
-
process.env.CELILO_DATA_DIR
|
|
27
|
+
resetTestDbPath();
|
|
28
|
+
delete process.env.CELILO_DATA_DIR;
|
|
28
29
|
try {
|
|
29
30
|
rmSync(dir, { recursive: true, force: true });
|
|
30
31
|
} catch {
|
|
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { type DbClient, getDb } from '../db/client';
|
|
6
6
|
import { modules } from '../db/schema';
|
|
7
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
7
8
|
import {
|
|
8
9
|
type SecretsSchema,
|
|
9
10
|
getAllSecretMetadata,
|
|
@@ -40,7 +41,7 @@ describe('secret-schema-loader', () => {
|
|
|
40
41
|
afterEach(() => {
|
|
41
42
|
// Clean up temp directory
|
|
42
43
|
rmSync(tempDir, { recursive: true, force: true });
|
|
43
|
-
|
|
44
|
+
resetTestDbPath();
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
describe('loadSecretsSchema', () => {
|
|
@@ -8,6 +8,7 @@ import { tmpdir } from 'node:os';
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { closeDb } from '../db/client';
|
|
10
10
|
import { runMigrations } from '../db/migrate';
|
|
11
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
11
12
|
import { addMachine } from './machine-pool';
|
|
12
13
|
import {
|
|
13
14
|
LOCAL_MACHINE_IP,
|
|
@@ -97,9 +98,9 @@ describe('ssh-key-manager', () => {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// Clear environment variables
|
|
100
|
-
|
|
101
|
-
process.env.CELILO_DATA_DIR
|
|
102
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
101
|
+
resetTestDbPath();
|
|
102
|
+
delete process.env.CELILO_DATA_DIR;
|
|
103
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
103
104
|
});
|
|
104
105
|
|
|
105
106
|
describe('writeTemporarySshKey', () => {
|
|
@@ -14,6 +14,7 @@ import { eq } from 'drizzle-orm';
|
|
|
14
14
|
import type { DbClient } from '../db/client';
|
|
15
15
|
import { moduleConfigs, modules, webRoutes } from '../db/schema';
|
|
16
16
|
import { setupTestDatabaseAt } from '../test-utils/database';
|
|
17
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
17
18
|
import {
|
|
18
19
|
ansibleTreeMentions,
|
|
19
20
|
buildStaticContentPlan,
|
|
@@ -64,7 +65,7 @@ describe('planStaticContent — the declared release set (D10 task 4.1)', () =>
|
|
|
64
65
|
|
|
65
66
|
afterEach(() => {
|
|
66
67
|
db.$client.close();
|
|
67
|
-
|
|
68
|
+
resetTestDbPath();
|
|
68
69
|
try {
|
|
69
70
|
rmSync(dir, { recursive: true, force: true });
|
|
70
71
|
} catch {
|
|
@@ -177,7 +178,7 @@ describe('resolveStaticContentRetention — operator config with a default', ()
|
|
|
177
178
|
|
|
178
179
|
afterEach(() => {
|
|
179
180
|
db.$client.close();
|
|
180
|
-
|
|
181
|
+
resetTestDbPath();
|
|
181
182
|
try {
|
|
182
183
|
rmSync(dir, { recursive: true, force: true });
|
|
183
184
|
} catch {
|
|
@@ -279,7 +280,7 @@ describe('the state web overlay (celilo#1265)', () => {
|
|
|
279
280
|
|
|
280
281
|
afterEach(() => {
|
|
281
282
|
db.$client.close();
|
|
282
|
-
|
|
283
|
+
resetTestDbPath();
|
|
283
284
|
try {
|
|
284
285
|
rmSync(dir, { recursive: true, force: true });
|
|
285
286
|
} catch {
|
|
@@ -359,7 +360,7 @@ describe('writeStaticContentVars — lands in the generated inventory', () => {
|
|
|
359
360
|
|
|
360
361
|
afterEach(() => {
|
|
361
362
|
db.$client.close();
|
|
362
|
-
|
|
363
|
+
resetTestDbPath();
|
|
363
364
|
try {
|
|
364
365
|
rmSync(dir, { recursive: true, force: true });
|
|
365
366
|
} catch {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* `module verify` (celilo#1018, deliberately left to that thread).
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
26
|
+
import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
27
27
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
28
28
|
import { join } from 'node:path';
|
|
29
29
|
import { and, eq, isNotNull } from 'drizzle-orm';
|
|
@@ -237,9 +237,10 @@ export function staticContentVarsYaml(plan: StaticContentPlan): string {
|
|
|
237
237
|
*
|
|
238
238
|
* The shared half of both converge triggers: the capability's callback adds
|
|
239
239
|
* `executeAnsible` after this, and the provider's own deploy already runs it.
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
240
|
+
* Nothing inspects verbatim role assets any more — the staleness pre-flight
|
|
241
|
+
* retired with the ephemeral generated tree (D5 of
|
|
242
|
+
* control-plane-stops-building-modules), so an injected vars file cannot read
|
|
243
|
+
* as a stale generation because nothing reads staleness.
|
|
243
244
|
*/
|
|
244
245
|
export async function writeStaticContentVars(
|
|
245
246
|
db: DbClient,
|
|
@@ -308,13 +309,31 @@ export async function convergeStaticContent(
|
|
|
308
309
|
return { success: false, error: `Provider module '${providerModuleId}' is not installed` };
|
|
309
310
|
}
|
|
310
311
|
const generatedPath = join(module.sourcePath, 'generated');
|
|
312
|
+
// D4 (control-plane-stops-building-modules): the generated project is
|
|
313
|
+
// ephemeral, so a successful provider deploy deletes it and nothing is
|
|
314
|
+
// persisted for this converge to read. Render it here, on demand, from the
|
|
315
|
+
// signed payload plus resolved config — generation is fast and local. A
|
|
316
|
+
// render failure fails loudly with the deploy-once remedy, same as the old
|
|
317
|
+
// missing-project refusal did; a tree that predates the converge support is
|
|
318
|
+
// still refused by the static_releases check below.
|
|
311
319
|
if (!existsSync(generatedPath)) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
320
|
+
const { generateTemplates } = await import('../templates/generator');
|
|
321
|
+
const rendered = await generateTemplates({
|
|
322
|
+
moduleId: providerModuleId,
|
|
323
|
+
modulePath: module.sourcePath,
|
|
324
|
+
outputPath: generatedPath,
|
|
325
|
+
db,
|
|
326
|
+
skipVariableValidation: false,
|
|
327
|
+
});
|
|
328
|
+
if (!rendered.success) {
|
|
329
|
+
return {
|
|
330
|
+
success: false,
|
|
331
|
+
error:
|
|
332
|
+
`public_web provider '${providerModuleId}' has no generated deploy project at ${generatedPath} ` +
|
|
333
|
+
`and rendering one failed: ${rendered.error ?? 'unknown error'}. ` +
|
|
334
|
+
`Run \`celilo module deploy ${providerModuleId}\` once, then retry the publish.`,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
318
337
|
}
|
|
319
338
|
|
|
320
339
|
const written = await writeStaticContentVars(db, providerModuleId, generatedPath);
|
|
@@ -342,5 +361,9 @@ export async function convergeStaticContent(
|
|
|
342
361
|
varsPath: written.varsPath,
|
|
343
362
|
};
|
|
344
363
|
}
|
|
364
|
+
// D4: the converge rendered the tree, used it, and is done with it. The same
|
|
365
|
+
// no-secrets-at-rest rule the deploy follows applies here; on the failure
|
|
366
|
+
// paths above the tree stays for inspection.
|
|
367
|
+
rmSync(generatedPath, { recursive: true, force: true });
|
|
345
368
|
return { success: true, varsPath: written.varsPath };
|
|
346
369
|
}
|
|
@@ -13,6 +13,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
|
13
13
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
14
14
|
import { tmpdir } from 'node:os';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
16
17
|
import { snapshotDatabase, stageSystemState } from './system-state-stage';
|
|
17
18
|
|
|
18
19
|
describe('stageSystemState', () => {
|
|
@@ -37,9 +38,9 @@ describe('stageSystemState', () => {
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
afterEach(() => {
|
|
40
|
-
process.env.CELILO_DATA_DIR
|
|
41
|
-
|
|
42
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
41
|
+
delete process.env.CELILO_DATA_DIR;
|
|
42
|
+
resetTestDbPath();
|
|
43
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
43
44
|
rmSync(dataDir, { recursive: true, force: true });
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -6,6 +6,7 @@ import type { DbClient } from '../db/client';
|
|
|
6
6
|
import { systemConfig } from '../db/schema';
|
|
7
7
|
import { loadTrustedSubnets } from '../hooks/capability-loader';
|
|
8
8
|
import { setupTestDatabaseAt } from '../test-utils/database';
|
|
9
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
9
10
|
import {
|
|
10
11
|
buildTrustedSourceStore,
|
|
11
12
|
composeTrustedSubnets,
|
|
@@ -30,7 +31,7 @@ describe('trusted-source store', () => {
|
|
|
30
31
|
});
|
|
31
32
|
afterEach(() => {
|
|
32
33
|
db.$client.close();
|
|
33
|
-
|
|
34
|
+
resetTestDbPath();
|
|
34
35
|
try {
|
|
35
36
|
rmSync(dir, { recursive: true, force: true });
|
|
36
37
|
} catch {
|
|
@@ -147,7 +148,7 @@ describe('the render input excludes registrations; the reporting view includes t
|
|
|
147
148
|
});
|
|
148
149
|
afterEach(() => {
|
|
149
150
|
db.$client.close();
|
|
150
|
-
|
|
151
|
+
resetTestDbPath();
|
|
151
152
|
try {
|
|
152
153
|
rmSync(dir, { recursive: true, force: true });
|
|
153
154
|
} catch {
|
|
@@ -78,6 +78,7 @@ const cleanAudit: AuditDeps = {
|
|
|
78
78
|
moduleConfigs: { modules: [] },
|
|
79
79
|
moduleIntegrity: { results: [] },
|
|
80
80
|
detectWithoutConverge: { modules: [] },
|
|
81
|
+
jailExemptions: { exemptions: [] },
|
|
81
82
|
health: { results: [] },
|
|
82
83
|
backups: { modules: [] },
|
|
83
84
|
abandonedOperations: { records: [] },
|
|
@@ -87,6 +88,7 @@ const cleanAudit: AuditDeps = {
|
|
|
87
88
|
secretsDecryptable: { results: [] },
|
|
88
89
|
servicesReachable: { results: [] },
|
|
89
90
|
machinesReachable: { results: [] },
|
|
91
|
+
diskSpace: { results: [] },
|
|
90
92
|
transportReads: { statuses: [], now: new Date(), staleAfterMs: 30 * 60_000 },
|
|
91
93
|
trustedSources: { firewalls: [] },
|
|
92
94
|
publicDns: { records: [], probe: unusedPublicDnsProbe },
|
|
@@ -9,6 +9,7 @@ import { join } from 'node:path';
|
|
|
9
9
|
import { closeDb, createDbClient } from '../db/client';
|
|
10
10
|
import { runMigrations } from '../db/migrate';
|
|
11
11
|
import { systemConfig } from '../db/schema';
|
|
12
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
12
13
|
import { detectZoneFromIp, isValidIp } from './zone-detector';
|
|
13
14
|
|
|
14
15
|
describe('zone-detector', () => {
|
|
@@ -46,7 +47,7 @@ describe('zone-detector', () => {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Clear environment variables
|
|
49
|
-
|
|
50
|
+
resetTestDbPath();
|
|
50
51
|
});
|
|
51
52
|
|
|
52
53
|
describe('detectZoneFromIp', () => {
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
type ResponderValues,
|
|
51
51
|
startProgrammaticResponder,
|
|
52
52
|
} from '../services/programmatic-responder';
|
|
53
|
+
import { resetTestDbPath } from './db-path';
|
|
53
54
|
|
|
54
55
|
export interface BusResponderFixtureOptions extends ResponderValues {
|
|
55
56
|
/** Path to the bus sqlite db (shared with the celilo subprocess). */
|
|
@@ -98,7 +99,6 @@ export function startBusResponderFixture(opts: BusResponderFixtureOptions): BusR
|
|
|
98
99
|
const prevDataDir = process.env.CELILO_DATA_DIR;
|
|
99
100
|
process.env.CELILO_DATA_DIR = opts.dataDir;
|
|
100
101
|
|
|
101
|
-
const prevDbPath = process.env.CELILO_DB_PATH;
|
|
102
102
|
process.env.CELILO_DB_PATH = opts.celiloDbPath;
|
|
103
103
|
const db: DbClient = createDbClient();
|
|
104
104
|
|
|
@@ -130,10 +130,12 @@ export function startBusResponderFixture(opts: BusResponderFixtureOptions): BusR
|
|
|
130
130
|
close: () => {
|
|
131
131
|
handle.close();
|
|
132
132
|
db.$client.close();
|
|
133
|
-
if (prevDataDir === undefined) process.env.CELILO_DATA_DIR
|
|
133
|
+
if (prevDataDir === undefined) delete process.env.CELILO_DATA_DIR;
|
|
134
134
|
else process.env.CELILO_DATA_DIR = prevDataDir;
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
// Reset to the scratch path, never restore the previous value: it may
|
|
136
|
+
// be another suite's temp database, or unset — which sends the next
|
|
137
|
+
// var-less reader to the operator's real celilo.db (celilo#1315).
|
|
138
|
+
resetTestDbPath();
|
|
137
139
|
},
|
|
138
140
|
};
|
|
139
141
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { tmpdir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The neutral database path the test preload arms `CELILO_DB_PATH` with
|
|
6
|
+
* (celilo#1315). The file does not exist, so a reader falls into whatever
|
|
7
|
+
* "no database" branch its feature has — and no test ever opens the
|
|
8
|
+
* operator's real `~/Library/.../celilo.db`, which is what a suite that
|
|
9
|
+
* reads the variable without setting it would otherwise reach.
|
|
10
|
+
*
|
|
11
|
+
* Why teardowns RESET to this path instead of `delete`: bunfig's `[test]`
|
|
12
|
+
* preload runs ONCE PER PROCESS, not once per test file. A teardown that
|
|
13
|
+
* clears the variable leaves every later var-less reader on the real
|
|
14
|
+
* database for the rest of the run. Resetting to the scratch path keeps the
|
|
15
|
+
* preload's invariant standing from the first test to the last.
|
|
16
|
+
*/
|
|
17
|
+
export const SCRATCH_DB_PATH = join(tmpdir(), 'celilo-test-absent.db');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Point `CELILO_DB_PATH` at the neutral scratch path. Call from a test's
|
|
21
|
+
* afterEach (or any teardown) that pointed the variable somewhere else.
|
|
22
|
+
*/
|
|
23
|
+
export function resetTestDbPath(): void {
|
|
24
|
+
process.env.CELILO_DB_PATH = SCRATCH_DB_PATH;
|
|
25
|
+
}
|
|
@@ -54,6 +54,13 @@ export interface IntegrationTestContext {
|
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
56
|
export async function setupIntegrationTest(): Promise<IntegrationTestContext> {
|
|
57
|
+
// celilo#1246: the shell on a dev host carries CELILO_HOME pointing at the
|
|
58
|
+
// live install (DB and master.key included). Every CLI child this harness
|
|
59
|
+
// spawns would inherit it, and isolation would then rest on the per-run
|
|
60
|
+
// CELILO_DB_PATH / CELILO_DATA_DIR overrides below never being missed.
|
|
61
|
+
// Unset it once, here, so no child ever sees the live home.
|
|
62
|
+
delete process.env.CELILO_HOME;
|
|
63
|
+
|
|
57
64
|
// Setup isolated database
|
|
58
65
|
const testDb = await setupTestDatabaseFile();
|
|
59
66
|
const dbPath = testDb.path;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { CLIContext } from './cli-context';
|
|
11
|
+
import { resetTestDbPath } from './db-path';
|
|
11
12
|
import { getModuleTestConfig } from './fixtures';
|
|
12
13
|
import { type IntegrationTestContext, setupIntegrationTest } from './integration';
|
|
13
14
|
|
|
@@ -50,7 +51,6 @@ interface HomebridgeModuleOptions extends BaseFixtureOptions {
|
|
|
50
51
|
async function insertMockContainerService(dbPath: string): Promise<void> {
|
|
51
52
|
const { getDb } = await import('../db/client');
|
|
52
53
|
const { containerServices } = await import('../db/schema');
|
|
53
|
-
const originalDbPath = process.env.CELILO_DB_PATH;
|
|
54
54
|
process.env.CELILO_DB_PATH = dbPath;
|
|
55
55
|
try {
|
|
56
56
|
const db = getDb();
|
|
@@ -77,11 +77,10 @@ async function insertMockContainerService(dbPath: string): Promise<void> {
|
|
|
77
77
|
updatedAt: new Date(),
|
|
78
78
|
});
|
|
79
79
|
} finally {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
80
|
+
// Reset to the scratch path, never restore the previous value: it may
|
|
81
|
+
// be another suite's temp database, or unset — which sends the next
|
|
82
|
+
// var-less reader to the operator's real celilo.db (celilo#1315).
|
|
83
|
+
resetTestDbPath();
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
|