mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-10 18:56:53 +00:00
fix(mesh): apply D-1 pushed override on pilot deploys via file-presence fallback (#1019)
When isMeshStackEnabled returns false, ensureStackOverride now checks for an override file already on disk before returning null. On pilot nodes the mesh_stacks table is intentionally empty (opt-in state lives on central per the C-3 design), so the DB gate blocked the override that central had already pushed via applyLocalOverride. File-presence is safe as the fallback because removeOverrideFromNode sends a DELETE to the pilot when a stack is opted out, so a stale file cannot survive past opt-out. Also aligns removeStackOverride to use path.basename consistently with all other override-path construction sites in the same file. Adds two tests: pilot node with a pushed file returns the path; pilot node with no file returns null.
This commit is contained in:
@@ -622,6 +622,47 @@ describe('MeshService.ensureStackOverride (BUG-1 fix)', () => {
|
||||
&& /orphaned-stack/.test(e.message),
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns a pushed override file on pilot nodes where isMeshStackEnabled is always false', async () => {
|
||||
const svc = MeshService.getInstance();
|
||||
const db = DatabaseService.getInstance();
|
||||
const localNodeId = db.getNodes()[0].id;
|
||||
|
||||
// Simulate the pilot scenario: no mesh_stacks row (isMeshStackEnabled → false),
|
||||
// but the override file already exists on disk, pushed by central via D-1.
|
||||
const dataDir = process.env.DATA_DIR as string;
|
||||
const overrideDir = path.join(dataDir, 'mesh', 'overrides', String(localNodeId));
|
||||
fsSync.mkdirSync(overrideDir, { recursive: true });
|
||||
const overrideFile = path.join(overrideDir, 'pilot-stack.override.yml');
|
||||
fsSync.writeFileSync(overrideFile, [
|
||||
'services:',
|
||||
' echo:',
|
||||
' networks:',
|
||||
' - sencho_mesh',
|
||||
' extra_hosts:',
|
||||
' - echo.pilot-stack.pilot.sencho:172.30.0.2',
|
||||
'networks:',
|
||||
' sencho_mesh:',
|
||||
' external: true',
|
||||
].join('\n'), 'utf8');
|
||||
|
||||
// No mesh_stacks row → isMeshStackEnabled returns false.
|
||||
const result = await svc.ensureStackOverride(localNodeId, 'pilot-stack');
|
||||
expect(result).toBe(overrideFile);
|
||||
|
||||
// Cleanup.
|
||||
fsSync.unlinkSync(overrideFile);
|
||||
});
|
||||
|
||||
it('returns null for pilot nodes when no pushed override file exists', async () => {
|
||||
const svc = MeshService.getInstance();
|
||||
const db = DatabaseService.getInstance();
|
||||
const localNodeId = db.getNodes()[0].id;
|
||||
|
||||
// No mesh_stacks row, no file on disk.
|
||||
const result = await svc.ensureStackOverride(localNodeId, 'no-such-stack');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('MeshService tunnel-up regen (BUG-2)', () => {
|
||||
|
||||
@@ -592,13 +592,26 @@ export class MeshService extends EventEmitter implements MeshForwarderHost {
|
||||
public async ensureStackOverride(nodeId: number, stackName: string): Promise<string | null> {
|
||||
if (!isValidStackName(stackName)) return null;
|
||||
const db = DatabaseService.getInstance();
|
||||
if (!db.isMeshStackEnabled(nodeId, stackName)) return null;
|
||||
const dir = this.overrideDirFor(nodeId);
|
||||
if (!db.isMeshStackEnabled(nodeId, stackName)) {
|
||||
// Pilot nodes intentionally have no mesh_stacks rows (opt-in state
|
||||
// lives on central per the C-3 design). Use file-presence as the
|
||||
// fallback: if central pushed an override via applyLocalOverride, return
|
||||
// that path so ComposeService picks it up on the next deploy.
|
||||
const file = path.resolve(dir, `${path.basename(stackName)}.override.yml`);
|
||||
if (!isPathWithinBase(file, dir)) return null;
|
||||
try {
|
||||
await fs.access(file);
|
||||
return file;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!this.senchoIp) return null;
|
||||
|
||||
const aliases: MeshAlias[] = Array.from(this.aliasCache.values()).map((a) => ({ host: a.host }));
|
||||
const serviceNames = await this.getDeclaredStackServiceNames(stackName, nodeId);
|
||||
|
||||
const dir = this.overrideDirFor(nodeId);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
// path.basename mirrors the applyLocalOverride pattern (and is
|
||||
// the form CodeQL's path-injection model recognizes).
|
||||
@@ -705,7 +718,7 @@ export class MeshService extends EventEmitter implements MeshForwarderHost {
|
||||
private async removeStackOverride(nodeId: number, stackName: string): Promise<void> {
|
||||
if (!isValidStackName(stackName)) return;
|
||||
const dir = this.overrideDirFor(nodeId);
|
||||
const file = path.resolve(dir, `${stackName}.override.yml`);
|
||||
const file = path.resolve(dir, `${path.basename(stackName)}.override.yml`);
|
||||
if (!isPathWithinBase(file, dir)) return;
|
||||
try { await fs.unlink(file); } catch { /* ignore not-exist */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user