mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-21 15:46:43 +00:00
fix(networking): ignore verified Mesh attachments in drift (#1729)
This commit is contained in:
@@ -23,6 +23,8 @@ import type {
|
||||
NetworkDriftFacts, NetworkFactNetwork, NetworkFactService, NetworkRuntimeState, StackNetworkFacts,
|
||||
} from './types';
|
||||
import { classifyMissingExternalNetworks, type MissingExternalNetwork } from './missingExternalNetworks';
|
||||
import { resolveManagedMeshAttachment } from './managedMeshAttachment';
|
||||
import type { ManagedNetworkAttachmentPredicate } from './normalize';
|
||||
|
||||
import { getErrorMessage } from '../../utils/errors';
|
||||
import { redactSensitiveText, sanitizeForLog } from '../../utils/safeLog';
|
||||
@@ -43,6 +45,7 @@ export function assembleStackNetworkFacts(
|
||||
model: EffectiveModel | null,
|
||||
renderError: string | null,
|
||||
snapshot: DependencySnapshot | null,
|
||||
managedNetworkAttachment?: ManagedNetworkAttachmentPredicate,
|
||||
): StackNetworkFacts {
|
||||
const runtime: NetworkRuntimeState = snapshot ? 'available' : 'unavailable';
|
||||
|
||||
@@ -82,7 +85,9 @@ export function assembleStackNetworkFacts(
|
||||
extraHosts: s.extraHosts,
|
||||
}));
|
||||
|
||||
const drift = snapshot ? compareStackNetworks(fromEffectiveModel(model), snapshot, stackName) : EMPTY_DRIFT;
|
||||
const drift = snapshot
|
||||
? compareStackNetworks(fromEffectiveModel(model), snapshot, stackName, managedNetworkAttachment)
|
||||
: EMPTY_DRIFT;
|
||||
const missingExternalNetworks: MissingExternalNetwork[] = snapshot
|
||||
? classifyMissingExternalNetworks(
|
||||
model,
|
||||
@@ -147,5 +152,8 @@ export async function buildStackNetworkFacts(
|
||||
}
|
||||
}
|
||||
|
||||
return assembleStackNetworkFacts(stackName, model, renderError, snapshot);
|
||||
const managedNetworkAttachment = snapshot && model
|
||||
? await resolveManagedMeshAttachment(nodeId, stackName)
|
||||
: undefined;
|
||||
return assembleStackNetworkFacts(stackName, model, renderError, snapshot, managedNetworkAttachment);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { DatabaseService } from '../DatabaseService';
|
||||
import { SENCHO_MESH_NETWORK } from '../MeshComposeOverride';
|
||||
import SelfIdentityService from '../SelfIdentityService';
|
||||
import { getErrorMessage } from '../../utils/errors';
|
||||
import { sanitizeForLog } from '../../utils/safeLog';
|
||||
import { isPathWithinBase, isValidStackName } from '../../utils/validation';
|
||||
import type { ManagedNetworkAttachmentPredicate } from './normalize';
|
||||
|
||||
async function hasPilotMeshOverride(nodeId: number, stackName: string): Promise<boolean> {
|
||||
if (process.env.SENCHO_MODE !== 'pilot' || !isValidStackName(stackName)) return false;
|
||||
|
||||
const dataDir = process.env.DATA_DIR || '/app/data';
|
||||
const overrideDir = path.resolve(dataDir, 'mesh', 'overrides', String(nodeId));
|
||||
const overridePath = path.resolve(overrideDir, `${path.basename(stackName)}.override.yml`);
|
||||
if (!isPathWithinBase(overridePath, overrideDir)) return false;
|
||||
|
||||
try {
|
||||
await fs.access(overridePath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return false;
|
||||
console.warn(
|
||||
'[NetworkDrift] Could not verify Pilot Mesh override for %s:',
|
||||
sanitizeForLog(stackName),
|
||||
sanitizeForLog(getErrorMessage(error, 'unknown')),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveManagedMeshAttachment(
|
||||
nodeId: number,
|
||||
stackName: string,
|
||||
): Promise<ManagedNetworkAttachmentPredicate> {
|
||||
let stackManaged = false;
|
||||
try {
|
||||
stackManaged = DatabaseService.getInstance().isMeshStackEnabled(nodeId, stackName);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'[NetworkDrift] Could not verify Mesh opt-in state for %s:',
|
||||
sanitizeForLog(stackName),
|
||||
sanitizeForLog(getErrorMessage(error, 'unknown')),
|
||||
);
|
||||
}
|
||||
if (!stackManaged) stackManaged = await hasPilotMeshOverride(nodeId, stackName);
|
||||
const selfIdentity = SelfIdentityService.getInstance();
|
||||
|
||||
return (container, networkName) => networkName === SENCHO_MESH_NETWORK && (
|
||||
stackManaged
|
||||
|| selfIdentity.isOwnContainer(container.id)
|
||||
|| selfIdentity.isOwnContainer(container.name)
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { FileSystemService } from '../FileSystemService';
|
||||
import { DatabaseService } from '../DatabaseService';
|
||||
import { parseComposeDependencies } from '../../helpers/composeDependencyParse';
|
||||
import { assembleStackDrift } from '../DriftDetectionService';
|
||||
import { resolveManagedMeshAttachment } from './managedMeshAttachment';
|
||||
import { isHostNetwork, isLoopback } from './normalize';
|
||||
import { getErrorMessage } from '../../utils/errors';
|
||||
import { sanitizeForLog } from '../../utils/safeLog';
|
||||
@@ -86,7 +87,14 @@ export async function computeNodeNetworkingSummary(nodeId: number): Promise<Node
|
||||
if (snapshot) {
|
||||
// declared.parseError is already excluded above, so the drift report is authoritative.
|
||||
const containers = snapshot.containers.filter(c => c.stack === stack);
|
||||
const report = assembleStackDrift({ stack, declared, containers, networks: snapshot.networks });
|
||||
const managedNetworkAttachment = await resolveManagedMeshAttachment(nodeId, stack);
|
||||
const report = assembleStackDrift({
|
||||
stack,
|
||||
declared,
|
||||
containers,
|
||||
networks: snapshot.networks,
|
||||
managedNetworkAttachment,
|
||||
});
|
||||
if (report.findings.some(f => f.kind === 'network-undeclared' || f.kind === 'network-missing')) networkDrift.push(stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
*/
|
||||
import type { EffectiveModel } from '../preflight/effectiveModel';
|
||||
import type { DeclaredCompose } from '../../helpers/composeDependencyParse';
|
||||
import type { DependencySnapshot } from '../DockerController';
|
||||
import type { DependencyContainer, DependencySnapshot } from '../DockerController';
|
||||
import type { NetworkDriftFacts } from './types';
|
||||
import { SENCHO_MESH_NETWORK } from '../MeshComposeOverride';
|
||||
|
||||
/** Container states that count as "deployed" for drift, matching DriftDetectionService. */
|
||||
const RUNNING_STATES = new Set(['running', 'restarting']);
|
||||
@@ -62,6 +63,11 @@ export interface NormalizedNetworkModel {
|
||||
services: { name: string; networkKeys: string[]; networkMode?: string }[];
|
||||
}
|
||||
|
||||
export type ManagedNetworkAttachmentPredicate = (
|
||||
container: DependencyContainer,
|
||||
networkName: string,
|
||||
) => boolean;
|
||||
|
||||
/** Rendered model: resource names are already resolved by `docker compose config`. */
|
||||
export function fromEffectiveModel(m: EffectiveModel): NormalizedNetworkModel {
|
||||
const networks: NormalizedNetworkModel['networks'] = {};
|
||||
@@ -97,6 +103,7 @@ export function compareStackNetworks(
|
||||
declared: NormalizedNetworkModel,
|
||||
snapshot: DependencySnapshot,
|
||||
stackName: string,
|
||||
isManagedAttachment: ManagedNetworkAttachmentPredicate = () => false,
|
||||
): NetworkDriftFacts {
|
||||
const runtimeOnlyAttachments: NetworkDriftFacts['runtimeOnlyAttachments'] = [];
|
||||
const foreignNetworkAttachments: NetworkDriftFacts['foreignNetworkAttachments'] = [];
|
||||
@@ -118,6 +125,7 @@ export function compareStackNetworks(
|
||||
const net = networkByName.get(attached.name);
|
||||
if (SYSTEM_NETWORK_NAMES.has(attached.name) || net?.isSystem) continue;
|
||||
if (declaredRuntimeNames.has(attached.name)) { usedRuntimeNames.add(attached.name); continue; }
|
||||
if (attached.name === SENCHO_MESH_NETWORK && isManagedAttachment(c, attached.name)) continue;
|
||||
if (net?.stack === stackName || attached.name.startsWith(`${declared.projectName}_`)) {
|
||||
runtimeOnlyAttachments.push({ container: c.name, service: c.service, network: attached.name });
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user