fix(networking): ignore verified Mesh attachments in drift (#1729)

This commit is contained in:
Anso
2026-07-29 12:55:30 -04:00
committed by GitHub
parent 55644cf87d
commit 3c934066f3
14 changed files with 1206 additions and 50 deletions
@@ -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);
}
}
+9 -1
View File
@@ -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 {