mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-21 07:36:40 +00:00
fix(networking): treat host-network services as host-exposed in summaries (#1430)
The exposure summaries derived a stack's exposure solely from the declared published-port list, so a service running with network_mode: host (which publishes every container port on the host but declares no ports:) was under-reported as less exposed than it actually is. Capture network_mode in the lightweight dependency parser, add an isHostNetwork predicate, and treat a host-network service as exposed and publishing across the Fleet networking summary, the Stack Dossier export, and the Networking panel, matching how the Compose Doctor already flags host networking.
This commit is contained in:
@@ -66,6 +66,15 @@ describe('parseComposeDependencies - ports', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseComposeDependencies - network_mode', () => {
|
||||
it('captures network_mode and leaves it undefined when absent', () => {
|
||||
const host = parseComposeDependencies(svc('web', 'image: nginx\nnetwork_mode: host'));
|
||||
expect(host.services[0].networkMode).toBe('host');
|
||||
const none = parseComposeDependencies(svc('web', 'image: nginx'));
|
||||
expect(none.services[0].networkMode).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseComposeDependencies - top-level resources', () => {
|
||||
it('normalizes external (bool), legacy external object, and name: override', () => {
|
||||
const r = parseComposeDependencies('services:\n web:\n image: nginx\nnetworks:\n a:\n b:\n external: true\n c:\n external:\n name: legacy_net\n d:\n name: custom_net\nvolumes:\n v:\n external: true\n');
|
||||
|
||||
@@ -105,6 +105,47 @@ describe('networking summary', () => {
|
||||
expect(res.body.unknownExposure.stacks).toContain(STACK);
|
||||
});
|
||||
|
||||
it('marks a host-network stack exposed and unknown-exposure even with no published ports', async () => {
|
||||
fs.writeFileSync(path.join(stackDir, 'compose.yaml'), 'services:\n app:\n image: nginx:latest\n network_mode: host\n');
|
||||
const res = await request(app).get('/api/networking/summary').set('Authorization', authHeader);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.exposed.stacks).toContain(STACK);
|
||||
expect(res.body.unknownExposure.stacks).toContain(STACK);
|
||||
});
|
||||
|
||||
it('drops a host-network stack from unknown-exposure once an intent is set', async () => {
|
||||
fs.writeFileSync(path.join(stackDir, 'compose.yaml'), 'services:\n app:\n image: nginx:latest\n network_mode: host\n');
|
||||
DatabaseService.getInstance().setStackExposureIntent(1, STACK, '', 'lan', 'admin');
|
||||
const res = await request(app).get('/api/networking/summary').set('Authorization', authHeader);
|
||||
expect(res.body.exposed.stacks).toContain(STACK);
|
||||
expect(res.body.unknownExposure.stacks).not.toContain(STACK);
|
||||
});
|
||||
|
||||
it('keeps a stack unknown when an unclassified host-network service sits beside a classified ports service', async () => {
|
||||
fs.writeFileSync(path.join(stackDir, 'compose.yaml'),
|
||||
'services:\n metrics:\n image: nginx:latest\n network_mode: host\n web:\n image: nginx:latest\n ports:\n - "8080:80"\n');
|
||||
// web classified, the host-network metrics service still unset, so the stack stays unknown.
|
||||
DatabaseService.getInstance().setStackExposureIntent(1, STACK, 'web', 'public', 'admin');
|
||||
const res = await request(app).get('/api/networking/summary').set('Authorization', authHeader);
|
||||
expect(res.body.exposed.stacks).toContain(STACK);
|
||||
expect(res.body.unknownExposure.stacks).toContain(STACK);
|
||||
});
|
||||
|
||||
it('does not treat a non-host network_mode (none) as exposed', async () => {
|
||||
fs.writeFileSync(path.join(stackDir, 'compose.yaml'), 'services:\n app:\n image: nginx:latest\n network_mode: none\n');
|
||||
const res = await request(app).get('/api/networking/summary').set('Authorization', authHeader);
|
||||
expect(res.body.exposed.stacks).not.toContain(STACK);
|
||||
expect(res.body.unknownExposure.stacks).not.toContain(STACK);
|
||||
});
|
||||
|
||||
it('the fleet aggregate counts a host-network stack as exposed', async () => {
|
||||
fs.writeFileSync(path.join(stackDir, 'compose.yaml'), 'services:\n app:\n image: nginx:latest\n network_mode: host\n');
|
||||
const res = await request(app).get('/api/fleet/networking-summary').set('Authorization', authHeader);
|
||||
expect(res.status).toBe(200);
|
||||
const local = res.body.nodes.find((n: { summary: { exposed: { stacks: string[] } } | null }) => n.summary?.exposed.stacks.includes(STACK));
|
||||
expect(local).toBeDefined();
|
||||
});
|
||||
|
||||
it('the fleet aggregate returns a per-node summary for the hub', async () => {
|
||||
const res = await request(app).get('/api/fleet/networking-summary').set('Authorization', authHeader);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
Reference in New Issue
Block a user