mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
ci(ql3): expose content-free migration failure facts
This commit is contained in:
@@ -327,6 +327,37 @@ function administrationFailureEvidence(snapshot) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function migrationFailureEvidence(snapshot) {
|
||||||
|
const status = snapshot.pod.status.containerStatuses?.find(
|
||||||
|
(container) => container.name === 'migration',
|
||||||
|
);
|
||||||
|
const terminated = status?.state?.terminated;
|
||||||
|
const evidence = {
|
||||||
|
jobComplete: snapshot.complete,
|
||||||
|
jobFailed: snapshot.failed,
|
||||||
|
podPhase: snapshot.pod.status.phase ?? null,
|
||||||
|
exitCode: terminated?.exitCode ?? null,
|
||||||
|
reason: terminated?.reason ?? status?.state?.waiting?.reason ?? null,
|
||||||
|
};
|
||||||
|
const message = (terminated?.message || '').trim();
|
||||||
|
if (!message) return evidence;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(message);
|
||||||
|
const allowedKeys = ['schemaVersion', 'component', 'event', 'name', 'code'];
|
||||||
|
if (
|
||||||
|
!parsed ||
|
||||||
|
typeof parsed !== 'object' ||
|
||||||
|
Array.isArray(parsed) ||
|
||||||
|
Object.keys(parsed).some((key) => !allowedKeys.includes(key))
|
||||||
|
) {
|
||||||
|
return { ...evidence, failureMessage: 'rejected' };
|
||||||
|
}
|
||||||
|
return { ...evidence, failureMessage: parsed };
|
||||||
|
} catch {
|
||||||
|
return { ...evidence, failureMessage: 'unparseable' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function terminalJobSnapshot(fixture, name, timeoutMs = 180_000) {
|
async function terminalJobSnapshot(fixture, name, timeoutMs = 180_000) {
|
||||||
return (
|
return (
|
||||||
await waitFor(`${name} terminal`, timeoutMs, () => {
|
await waitFor(`${name} terminal`, timeoutMs, () => {
|
||||||
@@ -858,17 +889,24 @@ async function main(argv = process.argv.slice(2)) {
|
|||||||
'registry.example.com/qinglong/qinglong3-cluster-control',
|
'registry.example.com/qinglong/qinglong3-cluster-control',
|
||||||
controlImage,
|
controlImage,
|
||||||
);
|
);
|
||||||
fixture.kubectl(['create', '-f', '-'], {
|
const migrationJob = yaml.load(migrationManifest);
|
||||||
input: `${migrationManifest}\n`,
|
assert.equal(migrationJob?.kind, 'Job');
|
||||||
});
|
const migrationContainer = findNamed(
|
||||||
fixture.kubectl([
|
migrationJob.spec.template.spec.containers,
|
||||||
'-n',
|
'migration',
|
||||||
NAMESPACE,
|
);
|
||||||
'wait',
|
migrationContainer.terminationMessagePolicy = 'FallbackToLogsOnError';
|
||||||
'--for=condition=Complete',
|
fixture.create(migrationJob);
|
||||||
'job/ql3-cluster-migration',
|
const migrationSnapshot = await terminalJobSnapshot(
|
||||||
'--timeout=10m',
|
fixture,
|
||||||
]);
|
'ql3-cluster-migration',
|
||||||
|
10 * 60_000,
|
||||||
|
);
|
||||||
|
const migrationEvidence = JSON.stringify(
|
||||||
|
migrationFailureEvidence(migrationSnapshot),
|
||||||
|
);
|
||||||
|
assert.equal(migrationSnapshot.complete, true, migrationEvidence);
|
||||||
|
assert.equal(migrationSnapshot.failed, false, migrationEvidence);
|
||||||
const primary = currentPrimaryPod(fixture);
|
const primary = currentPrimaryPod(fixture);
|
||||||
const migrationState = JSON.parse(
|
const migrationState = JSON.parse(
|
||||||
psql(
|
psql(
|
||||||
@@ -1386,4 +1424,5 @@ module.exports = {
|
|||||||
identity,
|
identity,
|
||||||
identityRegisterCommand,
|
identityRegisterCommand,
|
||||||
inputAuthorityEvidenceSource,
|
inputAuthorityEvidenceSource,
|
||||||
|
migrationFailureEvidence,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const {
|
|||||||
identity,
|
identity,
|
||||||
identityRegisterCommand,
|
identityRegisterCommand,
|
||||||
inputAuthorityEvidenceSource,
|
inputAuthorityEvidenceSource,
|
||||||
|
migrationFailureEvidence,
|
||||||
} = require('../../scripts/ql3-security-administration-kubernetes-live-contract.cjs');
|
} = require('../../scripts/ql3-security-administration-kubernetes-live-contract.cjs');
|
||||||
|
|
||||||
const values = Object.freeze({
|
const values = Object.freeze({
|
||||||
@@ -112,6 +113,57 @@ test('constrains only the exact local-path fixture root without network authorit
|
|||||||
assert.doesNotMatch(source, /net|fetch|http/);
|
assert.doesNotMatch(source, /net|fetch|http/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('keeps migration failure evidence content-free', () => {
|
||||||
|
const base = {
|
||||||
|
complete: false,
|
||||||
|
failed: true,
|
||||||
|
pod: {
|
||||||
|
status: {
|
||||||
|
phase: 'Failed',
|
||||||
|
containerStatuses: [
|
||||||
|
{
|
||||||
|
name: 'migration',
|
||||||
|
state: {
|
||||||
|
terminated: {
|
||||||
|
exitCode: 1,
|
||||||
|
reason: 'Error',
|
||||||
|
message: JSON.stringify({
|
||||||
|
schemaVersion: 1,
|
||||||
|
component: 'qinglong3-cluster-migration',
|
||||||
|
event: 'migration_failed',
|
||||||
|
name: 'Error',
|
||||||
|
code: 'EAI_AGAIN',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.deepEqual(migrationFailureEvidence(base), {
|
||||||
|
jobComplete: false,
|
||||||
|
jobFailed: true,
|
||||||
|
podPhase: 'Failed',
|
||||||
|
exitCode: 1,
|
||||||
|
reason: 'Error',
|
||||||
|
failureMessage: {
|
||||||
|
schemaVersion: 1,
|
||||||
|
component: 'qinglong3-cluster-migration',
|
||||||
|
event: 'migration_failed',
|
||||||
|
name: 'Error',
|
||||||
|
code: 'EAI_AGAIN',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const rejected = structuredClone(base);
|
||||||
|
rejected.pod.status.containerStatuses[0].state.terminated.message =
|
||||||
|
JSON.stringify({ code: 'EAI_AGAIN', secret: 'must-not-escape' });
|
||||||
|
assert.equal(
|
||||||
|
migrationFailureEvidence(rejected).failureMessage,
|
||||||
|
'rejected',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('live runner remains opt-in, reviewed, cleanup-bound and log-free', () => {
|
test('live runner remains opt-in, reviewed, cleanup-bound and log-free', () => {
|
||||||
const source = fs.readFileSync(
|
const source = fs.readFileSync(
|
||||||
path.resolve(
|
path.resolve(
|
||||||
|
|||||||
Reference in New Issue
Block a user