Files
qinglong/test/back/ql3KubernetesDeploymentCeremony.test.cjs
T

1515 lines
46 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const crypto = require('node:crypto');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const test = require('node:test');
const yaml = require('js-yaml');
const {
COMMAND_SCHEMA,
FIELD_MANAGER,
HEAD_DATA_KEY,
HEAD_NAME,
PREFLIGHT_SCHEMA,
RECEIPT_SCHEMA,
RETIREMENT_PREFLIGHT_SCHEMA,
RETIREMENT_RECEIPT_SCHEMA,
canonicalJson,
executeCommand,
parseCommand,
validateLockReport,
} = require('../../scripts/lib/ql3-kubernetes-deployment-ceremony.cjs');
const {
commandFile,
} = require('../../scripts/ql3-kubernetes-deployment-ceremony.cjs');
const CLUSTER_UID = '123e4567-e89b-42d3-a456-426614174000';
const RELEASE_SET_DIGEST = digest('release-set');
const CATALOG_MANIFEST_DIGEST = digest('catalog-manifest');
const CATALOG_REPORT_DIGEST = digest('catalog-report');
const SOURCE_REVISION = 'd'.repeat(40);
const VERSION = '3.0.0-alpha.0';
const CONTEXT = 'qinglong-production';
const REFERENCES = Object.freeze({
control: image('qinglong3-cluster-control', '1'),
'control-ai': image('qinglong3-cluster-control-ai', '2'),
admin: image('qinglong3-cluster-admin', '3'),
worker: image('qinglong3-worker', '4'),
});
function digest(value) {
return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`;
}
function image(repository, digit) {
return `ghcr.io/qinglong-release/${repository}@sha256:${digit.repeat(64)}`;
}
function annotations(version = VERSION) {
return {
'qinglong.io/release-set-digest': RELEASE_SET_DIGEST,
'qinglong.io/release-catalog-manifest-digest': CATALOG_MANIFEST_DIGEST,
'qinglong.io/release-catalog-report-digest': CATALOG_REPORT_DIGEST,
'qinglong.io/release-source-revision': SOURCE_REVISION,
'qinglong.io/release-version': version,
};
}
function manifest({ version = VERSION, extraResource = false } = {}) {
const metadata = (name) => ({
name,
namespace: 'qinglong-system',
annotations: annotations(version),
});
const deployment = (name, containerName, reference) => ({
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: metadata(name),
spec: {
selector: { matchLabels: { app: name } },
template: {
metadata: {
labels: { app: name },
annotations: annotations(version),
},
spec: { containers: [{ name: containerName, image: reference }] },
},
},
});
const resources = [
deployment('ql3-control', 'control', REFERENCES.control),
deployment('ql3-control-ai', 'control-ai', REFERENCES['control-ai']),
{
apiVersion: 'batch/v1',
kind: 'Job',
metadata: metadata('ql3-admin'),
spec: {
template: {
metadata: { annotations: annotations(version) },
spec: {
restartPolicy: 'Never',
containers: [{ name: 'admin', image: REFERENCES.admin }],
},
},
},
},
deployment('ql3-worker', 'worker', REFERENCES.worker),
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: metadata('ql3-plugin-package-secret-action-admission'),
data: { image: REFERENCES.admin },
},
];
if (extraResource) {
resources.push({
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: metadata('ql3-extra-release-resource'),
data: { purpose: 'resource-inventory-closure-test' },
});
}
return `${resources
.map((resource) => JSON.stringify(resource))
.join('\n---\n')}\n`;
}
function lockReport(manifestContents = manifest(), version = VERSION) {
const resources = [];
yaml.loadAll(manifestContents, (resource) => resources.push(resource));
const unsigned = {
schemaVersion: 1,
schema: 'qinglong/kubernetes-deployment-lock@v2',
release: {
version,
sourceRevision: SOURCE_REVISION,
sourceRef: `refs/tags/v${version}`,
scope: 'cluster',
},
releaseSetDigest: RELEASE_SET_DIGEST,
catalog: {
schema: 'qinglong/release-catalog-consumption-ceremony@v1',
sourceRepository: 'qinglong-release/qinglong',
workflowIdentity: `https://github.com/qinglong-release/qinglong/.github/workflows/ql3-image-release.yml@refs/tags/v${version}`,
immutableReference: `ghcr.io/qinglong-release/qinglong3-release-catalog@${CATALOG_MANIFEST_DIGEST}`,
manifestDigest: CATALOG_MANIFEST_DIGEST,
consumptionReportDigest: CATALOG_REPORT_DIGEST,
releaseSetDigest: RELEASE_SET_DIGEST,
discoveryTagAuthority: 'none',
},
deploymentFamily: 'cluster',
requiredImages: ['control', 'control-ai', 'admin', 'worker'],
imageOccurrences: [
{ name: 'control', reference: REFERENCES.control, count: 1 },
{
name: 'control-ai',
reference: REFERENCES['control-ai'],
count: 1,
},
{ name: 'admin', reference: REFERENCES.admin, count: 2 },
{ name: 'worker', reference: REFERENCES.worker, count: 1 },
],
manifest: {
inputDigest: digest('source-render'),
outputDigest: digest(manifestContents),
resources: resources.length,
changedResources: 5,
admissionAuthorityCount: 1,
},
verification: {
releaseSet: 'standalone_structure_identity_and_self_digest',
sourceRecordsReplayed: false,
catalogConsumption: 'offline_reconstructed',
externalToolResultsReplayed: false,
unknownImageAuthorities: 0,
mutableQingLongImages: 0,
networkAccess: false,
kubernetesMutation: false,
},
};
return {
...unsigned,
lockDigest: digest(JSON.stringify(unsigned)),
};
}
function fixture(t, options = {}) {
const directory = fs.realpathSync(
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-kubernetes-deployment-')),
);
fs.chmodSync(directory, 0o700);
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
const manifestContents = manifest(options);
const lockedManifest = privateFile(
directory,
'locked.yaml',
manifestContents,
);
const report = lockReport(manifestContents, options.version ?? VERSION);
const lockReportPath = privateFile(
directory,
'lock.json',
canonicalJson(report),
);
const kubectl = privateFile(
directory,
'kubectl',
'#!/bin/sh\nexit 97\n',
0o700,
);
const curl = privateFile(directory, 'curl', '#!/bin/sh\nexit 96\n', 0o700);
const kubeconfig = privateFile(
directory,
'kubeconfig.yaml',
`apiVersion: v1
kind: Config
current-context: ${CONTEXT}
clusters:
- name: ql3
cluster:
server: https://cluster.example.test
certificate-authority-data: Y2E=
contexts:
- name: ${CONTEXT}
context:
cluster: ql3
user: operator
users:
- name: operator
user:
token: bounded-test-token
`,
);
return {
directory,
lockedManifest,
lockReportPath,
kubectl,
curl,
kubeconfig,
report,
manifestContents,
};
}
function privateFile(directory, name, contents, mode = 0o600) {
const target = path.join(directory, name);
fs.writeFileSync(target, contents, { mode });
return target;
}
function fileDigest(filePath) {
return digest(fs.readFileSync(filePath));
}
function writeCommand(directory, name, operation, request) {
const value = {
schemaVersion: 1,
schema: COMMAND_SCHEMA,
operation,
request,
};
return privateFile(directory, name, canonicalJson(value));
}
function commonRequest(fixtureValue) {
return {
lockedManifest: {
path: fixtureValue.lockedManifest,
expectedDigest: fixtureValue.report.manifest.outputDigest,
},
lockReport: {
path: fixtureValue.lockReportPath,
expectedDigest: fixtureValue.report.lockDigest,
},
kubectl: {
path: fixtureValue.kubectl,
expectedDigest: fileDigest(fixtureValue.kubectl),
},
kubeconfig: {
path: fixtureValue.kubeconfig,
expectedDigest: fileDigest(fixtureValue.kubeconfig),
},
context: CONTEXT,
expectedClusterUid: CLUSTER_UID,
transitionKind: 'install',
expectedHead: {
generation: 0,
deploymentDigest: null,
lockDigest: null,
stateDigest: null,
},
};
}
function successfulRunner(calls, server = {}, manifestContents = manifest()) {
server.head ??= null;
server.resourceVersion ??= 0;
const runner = (_executable, args, input) => {
calls.push({ args: [...args], input });
if (args.includes('get') && args.includes('-f=-')) {
return {
status: 0,
stdout: convergenceList(manifestContents),
stderr: '',
};
}
if (args.includes('get') && args.includes(HEAD_NAME)) {
return {
status: 0,
stdout: server.head === null ? '' : JSON.stringify(server.head),
stderr: '',
};
}
if (args.includes('get') && args.includes('kube-system')) {
return { status: 0, stdout: CLUSTER_UID, stderr: '' };
}
if (args.includes('--dry-run=server')) {
return { status: 0, stdout: 'deployment.apps/ql3-control\n', stderr: '' };
}
if (args.includes('create') || args.includes('replace')) {
const configMap = JSON.parse(input);
if (
(args.includes('create') && server.head !== null) ||
(args.includes('replace') &&
(server.head === null ||
configMap.metadata.resourceVersion !==
server.head.metadata.resourceVersion))
) {
return { status: 1, stdout: '', stderr: 'conflict' };
}
server.resourceVersion += 1;
server.head = {
...configMap,
metadata: {
...configMap.metadata,
resourceVersion: String(server.resourceVersion),
uid: '123e4567-e89b-42d3-a456-426614174099',
},
};
return { status: 0, stdout: JSON.stringify(server.head), stderr: '' };
}
return { status: 0, stdout: 'deployment.apps/ql3-control\n', stderr: '' };
};
runner.server = server;
return runner;
}
function convergenceList(manifestContents = manifest()) {
const resources = [];
yaml.loadAll(manifestContents, (resource) => resources.push(resource));
return JSON.stringify({
apiVersion: 'v1',
kind: 'List',
items: resources.map((resource, index) => ({
...resource,
metadata: {
...resource.metadata,
uid: `123e4567-e89b-42d3-a456-4266141741${String(index).padStart(
2,
'0',
)}`,
resourceVersion: String(index + 1),
managedFields: [
{
manager: FIELD_MANAGER,
operation: 'Apply',
apiVersion: resource.apiVersion,
},
],
},
})),
});
}
function prepare(t) {
const value = fixture(t);
const output = path.join(value.directory, 'preflight.json');
const command = writeCommand(
value.directory,
'preflight-command.json',
'cluster.deployment.preflight',
{
preflightId: '123e4567-e89b-42d3-a456-426614174001',
...commonRequest(value),
output,
},
);
const calls = [];
const runner = successfulRunner(calls, {}, value.manifestContents);
const report = executeCommand(command, {
runProcess: runner,
});
return {
...value,
preflightCommand: command,
preflightPath: output,
preflight: report,
calls,
runner,
};
}
function expectedFromReceipt(receipt) {
return {
generation: receipt.deploymentHead.generation,
deploymentDigest: receipt.deploymentHead.deploymentDigest,
lockDigest: receipt.deploymentHead.lockDigest,
stateDigest: receipt.deploymentHead.stateDigest,
};
}
function performTransition(
value,
runner,
transitionKind,
expectedHead,
suffix,
) {
const preflightPath = path.join(value.directory, `${suffix}-preflight.json`);
const preflightCommand = writeCommand(
value.directory,
`${suffix}-preflight-command.json`,
'cluster.deployment.preflight',
{
preflightId: crypto.randomUUID(),
...commonRequest(value),
transitionKind,
expectedHead,
output: preflightPath,
},
);
const preflight = executeCommand(preflightCommand, { runProcess: runner });
const receiptPath = path.join(value.directory, `${suffix}-receipt.json`);
const applyCommand = writeCommand(
value.directory,
`${suffix}-apply-command.json`,
'cluster.deployment.apply',
{
mutationId: crypto.randomUUID(),
preflight: {
path: preflightPath,
expectedDigest: preflight.preflightDigest,
},
...commonRequest(value),
transitionKind,
expectedHead,
output: receiptPath,
},
);
return {
preflight,
preflightPath,
applyCommand,
receiptPath,
receipt: executeCommand(applyCommand, { runProcess: runner }),
};
}
const RETIREMENT_TARGET = Object.freeze({
apiVersion: 'v1',
kind: 'ConfigMap',
namespace: 'qinglong-system',
name: 'ql3-extra-release-resource',
});
function retirementRequest(value, expectedHead) {
return {
lockedManifest: {
path: value.lockedManifest,
expectedDigest: value.report.manifest.outputDigest,
},
lockReport: {
path: value.lockReportPath,
expectedDigest: value.report.lockDigest,
},
kubectl: {
path: value.kubectl,
expectedDigest: fileDigest(value.kubectl),
},
curl: {
path: value.curl,
expectedDigest: fileDigest(value.curl),
},
kubeconfig: {
path: value.kubeconfig,
expectedDigest: fileDigest(value.kubeconfig),
},
context: CONTEXT,
expectedClusterUid: CLUSTER_UID,
expectedHead,
targets: [RETIREMENT_TARGET],
};
}
function apiObjectPath(resource) {
const namespace = encodeURIComponent(resource.metadata.namespace);
const name = encodeURIComponent(resource.metadata.name);
if (resource.apiVersion === 'v1' && resource.kind === 'ConfigMap') {
return `/api/v1/namespaces/${namespace}/configmaps/${name}`;
}
throw new Error('unsupported fake API resource');
}
class FakeRetirementApi {
constructor(manifestContents) {
this.objects = new Map();
this.requests = [];
this.loseNextDeleteResponse = false;
const list = JSON.parse(convergenceList(manifestContents));
for (const resource of list.items) {
if (resource.apiVersion === 'v1' && resource.kind === 'ConfigMap') {
this.objects.set(apiObjectPath(resource), resource);
}
}
}
request(method, requestPath, body) {
this.requests.push({ method, requestPath, body });
if (method === 'GET' && requestPath === '/api/v1') {
return {
status: 200,
body: JSON.stringify({
groupVersion: 'v1',
resources: [
{
name: 'configmaps',
namespaced: true,
kind: 'ConfigMap',
verbs: ['delete', 'get', 'list'],
},
],
}),
};
}
const live = this.objects.get(requestPath);
if (method === 'GET') {
return live === undefined
? { status: 404, body: JSON.stringify({ kind: 'Status' }) }
: { status: 200, body: JSON.stringify(live) };
}
if (method !== 'DELETE') throw new Error('unexpected fake API method');
if (live === undefined) {
return { status: 404, body: JSON.stringify({ kind: 'Status' }) };
}
if (
body?.preconditions?.uid !== live.metadata.uid ||
body?.preconditions?.resourceVersion !== live.metadata.resourceVersion
) {
return { status: 409, body: JSON.stringify({ kind: 'Status' }) };
}
if (body.dryRun?.includes('All')) {
return { status: 200, body: JSON.stringify({ kind: 'Status' }) };
}
this.objects.delete(requestPath);
if (this.loseNextDeleteResponse) {
this.loseNextDeleteResponse = false;
throw new Error('simulated response loss');
}
return { status: 200, body: JSON.stringify({ kind: 'Status' }) };
}
}
function prepareRetirement(t) {
const value = fixture(t, { extraResource: true });
const runner = successfulRunner([], {}, value.manifestContents);
const installed = performTransition(
value,
runner,
'install',
commonRequest(value).expectedHead,
'retirement-install',
);
const expectedHead = expectedFromReceipt(installed.receipt);
const api = new FakeRetirementApi(value.manifestContents);
const preflightPath = path.join(value.directory, 'retirement-preflight.json');
const preflightCommand = writeCommand(
value.directory,
'retirement-preflight-command.json',
'cluster.deployment.retirement.preflight',
{
preflightId: crypto.randomUUID(),
...retirementRequest(value, expectedHead),
output: preflightPath,
},
);
const preflight = executeCommand(preflightCommand, {
runProcess: runner,
retirementApi: api,
});
return {
...value,
runner,
installed,
expectedHead,
api,
preflight,
preflightPath,
preflightCommand,
};
}
function retirementApplyCommand(value, suffix = 'retirement') {
const receiptPath = path.join(value.directory, `${suffix}-receipt.json`);
const applyCommand = writeCommand(
value.directory,
`${suffix}-apply-command.json`,
'cluster.deployment.retirement.apply',
{
mutationId: crypto.randomUUID(),
preflight: {
path: value.preflightPath,
expectedDigest: value.preflight.preflightDigest,
},
...retirementRequest(value, value.expectedHead),
output: receiptPath,
},
);
return { applyCommand, receiptPath };
}
test('preflight binds one catalog lock to one Kubernetes target without mutation', (t) => {
const value = prepare(t);
assert.equal(value.preflight.schema, PREFLIGHT_SCHEMA);
assert.equal(value.preflight.lock.lockDigest, value.report.lockDigest);
assert.equal(
value.preflight.lock.manifestDigest,
value.report.manifest.outputDigest,
);
assert.equal(value.preflight.target.clusterUid, CLUSTER_UID);
assert.equal(value.preflight.target.fieldManager, FIELD_MANAGER);
assert.equal(value.preflight.verification.serverSideDryRun, true);
assert.equal(value.preflight.verification.kubernetesMutation, false);
assert.deepEqual(
value.preflight.steps.map(({ name }) => name),
['cluster_identity_before', 'deployment_head_read', 'server_side_dry_run'],
);
assert.equal(fs.statSync(value.preflightPath).mode & 0o777, 0o600);
assert.equal(value.calls.length, 3);
assert.equal(value.calls[0].args.includes('get'), true);
assert.equal(value.calls[1].args.includes(HEAD_NAME), true);
assert.equal(value.calls[2].args.includes('--dry-run=server'), true);
assert.equal(
value.calls[2].args.includes(`--field-manager=${FIELD_MANAGER}`),
true,
);
assert.equal(value.calls[2].input, manifest());
});
test('preflight rejects lock, manifest and annotation drift before network access', (t) => {
const cases = [
(target) => fs.appendFileSync(target.lockedManifest, '\n'),
(target) => {
const drifted = { ...target.report, lockDigest: digest('forged') };
fs.writeFileSync(target.lockReportPath, canonicalJson(drifted));
},
(target) => {
const driftedManifest = manifest().replace(
CATALOG_REPORT_DIGEST,
digest('different-catalog-report'),
);
fs.writeFileSync(target.lockedManifest, driftedManifest);
const report = lockReport(driftedManifest);
fs.writeFileSync(target.lockReportPath, canonicalJson(report));
target.report = report;
},
(target) => {
const implicitNamespaceManifest = manifest().replace(
'"namespace":"qinglong-system",',
'',
);
fs.writeFileSync(target.lockedManifest, implicitNamespaceManifest);
const report = lockReport(implicitNamespaceManifest);
fs.writeFileSync(target.lockReportPath, canonicalJson(report));
target.report = report;
},
];
for (const [index, mutate] of cases.entries()) {
const fresh = fixture(t);
mutate(fresh);
const output = path.join(fresh.directory, `rejected-${index}.json`);
const request = commonRequest(fresh);
request.lockedManifest.expectedDigest = fresh.report.manifest.outputDigest;
request.lockReport.expectedDigest = fresh.report.lockDigest;
const command = writeCommand(
fresh.directory,
`rejected-command-${index}.json`,
'cluster.deployment.preflight',
{
preflightId: `123e4567-e89b-42d3-a456-42661417400${index + 2}`,
...request,
output,
},
);
let calls = 0;
assert.throws(
() =>
executeCommand(command, {
runProcess() {
calls += 1;
return { status: 0, stdout: CLUSTER_UID, stderr: '' };
},
}),
/deployment ceremony failed/,
);
assert.equal(calls, 0);
assert.equal(fs.existsSync(output), false);
}
});
test('preflight rejects the wrong cluster and rejected server dry-run', (t) => {
for (const failure of ['identity', 'dry-run']) {
const value = fixture(t);
const output = path.join(value.directory, `${failure}.json`);
const command = writeCommand(
value.directory,
`${failure}-command.json`,
'cluster.deployment.preflight',
{
preflightId:
failure === 'identity'
? '123e4567-e89b-42d3-a456-426614174010'
: '123e4567-e89b-42d3-a456-426614174011',
...commonRequest(value),
output,
},
);
const base = successfulRunner([]);
assert.throws(() =>
executeCommand(command, {
runProcess(executable, args, input) {
if (args.includes('get') && args.includes('kube-system')) {
return {
status: 0,
stdout:
failure === 'identity' ? crypto.randomUUID() : CLUSTER_UID,
stderr: '',
};
}
if (failure === 'dry-run' && args.includes('--dry-run=server')) {
return {
status: 1,
stdout: '',
stderr: 'redacted admission error',
};
}
return base(executable, args, input);
},
}),
);
assert.equal(fs.existsSync(output), false);
}
});
test('kubeconfig executable authentication and weak private files fail closed', (t) => {
for (const mode of ['exec', 'public']) {
const value = fixture(t);
if (mode === 'exec') {
fs.writeFileSync(
value.kubeconfig,
fs
.readFileSync(value.kubeconfig, 'utf8')
.replace(
'token: bounded-test-token',
'exec:\n command: owned',
),
);
} else {
fs.chmodSync(value.kubeconfig, 0o644);
}
const output = path.join(value.directory, `${mode}.json`);
const command = writeCommand(
value.directory,
`${mode}-command.json`,
'cluster.deployment.preflight',
{
preflightId:
mode === 'exec'
? '123e4567-e89b-42d3-a456-426614174020'
: '123e4567-e89b-42d3-a456-426614174021',
...commonRequest(value),
output,
},
);
let calls = 0;
assert.throws(() =>
executeCommand(command, {
runProcess() {
calls += 1;
return { status: 0, stdout: CLUSTER_UID, stderr: '' };
},
}),
);
assert.equal(calls, 0);
assert.equal(fs.existsSync(output), false);
}
});
test('apply revalidates preflight, mutates explicitly and proves convergence', (t) => {
const value = prepare(t);
const receiptPath = path.join(value.directory, 'receipt.json');
const applyCommand = writeCommand(
value.directory,
'apply-command.json',
'cluster.deployment.apply',
{
mutationId: '123e4567-e89b-42d3-a456-426614174030',
preflight: {
path: value.preflightPath,
expectedDigest: value.preflight.preflightDigest,
},
...commonRequest(value),
output: receiptPath,
},
);
const calls = [];
value.calls.length = 0;
const receipt = executeCommand(applyCommand, {
runProcess(executable, args, input) {
calls.push({ args: [...args], input });
return value.runner(executable, args, input);
},
});
assert.equal(receipt.schema, RECEIPT_SCHEMA);
assert.equal(receipt.preflightDigest, value.preflight.preflightDigest);
assert.equal(receipt.verification.kubernetesMutation, true);
assert.equal(receipt.verification.crossResourceAtomicity, false);
assert.equal(
receipt.verification.recovery,
'resume_exact_transition_from_target_head',
);
assert.deepEqual(
receipt.steps.map(({ name }) => name),
[
'cluster_identity_before',
'server_side_dry_run',
'server_side_apply',
'server_side_convergence_read',
'cluster_identity_after',
],
);
assert.equal(calls.length, 8);
assert.equal(fs.statSync(receiptPath).mode & 0o777, 0o600);
const auditCommand = writeCommand(
value.directory,
'audit-command.json',
'cluster.deployment.receipt.audit',
{
applyCommand: {
path: applyCommand,
expectedDigest: fileDigest(applyCommand),
},
receipt: { path: receiptPath, expectedDigest: receipt.receiptDigest },
},
);
assert.deepEqual(executeCommand(auditCommand), {
compatible: true,
deploymentFamily: 'cluster',
mutationId: receipt.mutationId,
receiptDigest: receipt.receiptDigest,
preflightDigest: receipt.preflightDigest,
transitionKind: 'install',
lockDigest: value.report.lockDigest,
manifestDigest: value.report.manifest.outputDigest,
deploymentGeneration: 1,
deploymentDigest: receipt.deploymentHead.deploymentDigest,
deploymentHeadStateDigest: receipt.deploymentHead.stateDigest,
resourceCount: 5,
clusterUid: CLUSTER_UID,
externalResultsReplayed: false,
kubernetesMutation: false,
});
let replayCalls = 0;
assert.equal(
executeCommand(applyCommand, {
runProcess(executable, args, input) {
replayCalls += 1;
return value.runner(executable, args, input);
},
}).receiptDigest,
receipt.receiptDigest,
);
assert.equal(replayCalls, 1);
});
test('apply failure or post-apply drift never publishes a success receipt', (t) => {
for (const failure of ['apply', 'convergence', 'identity-after']) {
const value = prepare(t);
const receiptPath = path.join(value.directory, `${failure}-receipt.json`);
const applyCommand = writeCommand(
value.directory,
`${failure}-apply-command.json`,
'cluster.deployment.apply',
{
mutationId:
failure === 'apply'
? '123e4567-e89b-42d3-a456-426614174040'
: failure === 'convergence'
? '123e4567-e89b-42d3-a456-426614174041'
: '123e4567-e89b-42d3-a456-426614174042',
preflight: {
path: value.preflightPath,
expectedDigest: value.preflight.preflightDigest,
},
...commonRequest(value),
output: receiptPath,
},
);
let identityCount = 0;
const base = value.runner;
assert.throws(() =>
executeCommand(applyCommand, {
runProcess(executable, args, input) {
if (args.includes('get') && args.includes('-f=-')) {
return {
status: failure === 'convergence' ? 1 : 0,
stdout: failure === 'convergence' ? '' : convergenceList(),
stderr:
failure === 'convergence' ? 'redacted convergence error' : '',
};
}
if (args.includes('get') && args.includes('kube-system')) {
identityCount += 1;
return {
status: 0,
stdout:
failure === 'identity-after' && identityCount === 2
? crypto.randomUUID()
: CLUSTER_UID,
stderr: '',
};
}
if (args.includes('--dry-run=server')) {
return { status: 0, stdout: 'dry-run', stderr: '' };
}
if (failure === 'apply' && args.includes('apply')) {
return {
status: 1,
stdout: '',
stderr: 'redacted apply error',
};
}
return base(executable, args, input);
},
}),
);
assert.equal(fs.existsSync(receiptPath), false);
}
});
test('apply resumes the exact applying intent from the target head', (t) => {
const value = prepare(t);
const receiptPath = path.join(value.directory, 'resumed-receipt.json');
const applyCommand = writeCommand(
value.directory,
'resumed-apply-command.json',
'cluster.deployment.apply',
{
mutationId: '123e4567-e89b-42d3-a456-426614174043',
preflight: {
path: value.preflightPath,
expectedDigest: value.preflight.preflightDigest,
},
...commonRequest(value),
output: receiptPath,
},
);
let rejected = false;
assert.throws(() =>
executeCommand(applyCommand, {
runProcess(executable, args, input) {
if (
!rejected &&
args.includes('apply') &&
!args.includes('--dry-run=server')
) {
rejected = true;
return { status: 1, stdout: '', stderr: 'transient rejection' };
}
return value.runner(executable, args, input);
},
}),
);
assert.equal(fs.existsSync(receiptPath), false);
assert.equal(
JSON.parse(value.runner.server.head.data[HEAD_DATA_KEY]).phase,
'applying',
);
const receipt = executeCommand(applyCommand, {
runProcess: value.runner,
});
assert.equal(receipt.deploymentHead.generation, 1);
assert.equal(
JSON.parse(value.runner.server.head.data[HEAD_DATA_KEY]).phase,
'committed',
);
});
test('a stale preflight cannot overtake a committed deployment intent', (t) => {
const value = prepare(t);
const competingPreflightPath = path.join(
value.directory,
'competing-preflight.json',
);
const competingPreflightCommand = writeCommand(
value.directory,
'competing-preflight-command.json',
'cluster.deployment.preflight',
{
preflightId: '123e4567-e89b-42d3-a456-426614174044',
...commonRequest(value),
output: competingPreflightPath,
},
);
const competingPreflight = executeCommand(competingPreflightCommand, {
runProcess: value.runner,
});
const winner = performTransition(
value,
value.runner,
'install',
commonRequest(value).expectedHead,
'winner',
);
assert.equal(winner.receipt.deploymentHead.generation, 1);
const staleReceipt = path.join(value.directory, 'stale-receipt.json');
const staleApply = writeCommand(
value.directory,
'stale-apply-command.json',
'cluster.deployment.apply',
{
mutationId: '123e4567-e89b-42d3-a456-426614174045',
preflight: {
path: competingPreflightPath,
expectedDigest: competingPreflight.preflightDigest,
},
...commonRequest(value),
output: staleReceipt,
},
);
assert.throws(() => executeCommand(staleApply, { runProcess: value.runner }));
assert.equal(fs.existsSync(staleReceipt), false);
});
test('upgrade fails closed when its target omits an active resource', (t) => {
const server = {};
const installValue = fixture(t, { version: '3.0.0-alpha.0' });
const installRunner = successfulRunner(
[],
server,
installValue.manifestContents,
);
const installed = performTransition(
installValue,
installRunner,
'install',
commonRequest(installValue).expectedHead,
'inventory-install',
);
const expandedValue = fixture(t, {
version: '3.0.0-alpha.1',
extraResource: true,
});
const expandedRunner = successfulRunner(
[],
server,
expandedValue.manifestContents,
);
const expanded = performTransition(
expandedValue,
expandedRunner,
'upgrade',
expectedFromReceipt(installed.receipt),
'inventory-expand',
);
assert.equal(expanded.receipt.resourceInventory.length, 6);
const reducedValue = fixture(t, { version: '3.0.0-alpha.2' });
const reducedRunner = successfulRunner(
[],
server,
reducedValue.manifestContents,
);
const reducedPreflightPath = path.join(
reducedValue.directory,
'inventory-reduce-preflight.json',
);
const reducedPreflight = writeCommand(
reducedValue.directory,
'inventory-reduce-preflight-command.json',
'cluster.deployment.preflight',
{
preflightId: crypto.randomUUID(),
...commonRequest(reducedValue),
transitionKind: 'upgrade',
expectedHead: expectedFromReceipt(expanded.receipt),
output: reducedPreflightPath,
},
);
assert.throws(() =>
executeCommand(reducedPreflight, { runProcess: reducedRunner }),
);
assert.equal(fs.existsSync(reducedPreflightPath), false);
});
test('rollback restores only the exact previous lock with unchanged inventory', (t) => {
const server = {};
const installValue = fixture(t, { version: '3.0.0-alpha.0' });
const installRunner = successfulRunner(
[],
server,
installValue.manifestContents,
);
const installed = performTransition(
installValue,
installRunner,
'install',
commonRequest(installValue).expectedHead,
'rollback-install',
);
const upgradeValue = fixture(t, { version: '3.0.0-alpha.1' });
const upgradeRunner = successfulRunner(
[],
server,
upgradeValue.manifestContents,
);
const upgraded = performTransition(
upgradeValue,
upgradeRunner,
'upgrade',
expectedFromReceipt(installed.receipt),
'rollback-upgrade',
);
const rolledBack = performTransition(
installValue,
installRunner,
'rollback',
expectedFromReceipt(upgraded.receipt),
'rollback-restore',
);
assert.equal(rolledBack.receipt.transitionKind, 'rollback');
assert.equal(rolledBack.receipt.deploymentHead.generation, 3);
assert.equal(
rolledBack.receipt.lock.lockDigest,
installed.receipt.lock.lockDigest,
);
assert.deepEqual(
rolledBack.receipt.resourceInventory,
installed.receipt.resourceInventory,
);
});
test('receipt audit rejects a different command or recomputed receipt', (t) => {
const value = prepare(t);
const receiptPath = path.join(value.directory, 'receipt.json');
const applyCommand = writeCommand(
value.directory,
'apply-command.json',
'cluster.deployment.apply',
{
mutationId: '123e4567-e89b-42d3-a456-426614174050',
preflight: {
path: value.preflightPath,
expectedDigest: value.preflight.preflightDigest,
},
...commonRequest(value),
output: receiptPath,
},
);
const receipt = executeCommand(applyCommand, {
runProcess: successfulRunner([]),
});
const forged = { ...receipt, mutationId: crypto.randomUUID() };
const { receiptDigest: ignored, ...unsigned } = forged;
forged.receiptDigest = digest(JSON.stringify(unsigned));
fs.writeFileSync(receiptPath, canonicalJson(forged));
const auditCommand = writeCommand(
value.directory,
'audit-command.json',
'cluster.deployment.receipt.audit',
{
applyCommand: {
path: applyCommand,
expectedDigest: fileDigest(applyCommand),
},
receipt: { path: receiptPath, expectedDigest: receipt.receiptDigest },
},
);
assert.throws(() => executeCommand(auditCommand));
});
test('retirement preflight binds exact UID and resourceVersion without mutation', (t) => {
const value = prepareRetirement(t);
assert.equal(value.preflight.schema, RETIREMENT_PREFLIGHT_SCHEMA);
assert.deepEqual(value.preflight.activeResourceInventory.length, 6);
assert.deepEqual(value.preflight.survivorResourceInventory.length, 5);
assert.deepEqual(value.preflight.retirementTargets[0], {
...RETIREMENT_TARGET,
uid: '123e4567-e89b-42d3-a456-426614174105',
resourceVersion: '6',
});
assert.equal(value.preflight.verification.kubernetesMutation, false);
const deletes = value.api.requests.filter(
(request) => request.method === 'DELETE',
);
assert.equal(deletes.length, 1);
assert.deepEqual(deletes[0].body.preconditions, {
uid: value.preflight.retirementTargets[0].uid,
resourceVersion: value.preflight.retirementTargets[0].resourceVersion,
});
assert.deepEqual(deletes[0].body.dryRun, ['All']);
assert.equal(
value.api.objects.has(
apiObjectPath({
...RETIREMENT_TARGET,
metadata: {
namespace: RETIREMENT_TARGET.namespace,
name: RETIREMENT_TARGET.name,
},
}),
),
true,
);
});
test('retirement applies preconditioned deletion, closes inventory and audits receipt', (t) => {
const value = prepareRetirement(t);
const { applyCommand, receiptPath } = retirementApplyCommand(value);
const receipt = executeCommand(applyCommand, {
runProcess: value.runner,
retirementApi: value.api,
});
assert.equal(receipt.schema, RETIREMENT_RECEIPT_SCHEMA);
assert.equal(receipt.resourceInventory.length, 5);
assert.equal(receipt.retiredResources.length, 1);
assert.equal(
receipt.verification.uidResourceVersionDeletePreconditions,
true,
);
assert.equal(receipt.verification.deletionAbsenceConfirmed, true);
assert.equal(receipt.deploymentHead.generation, 2);
assert.equal(
JSON.parse(value.runner.server.head.data[HEAD_DATA_KEY]).transition.kind,
'retire',
);
const actualDelete = value.api.requests.find(
(request) =>
request.method === 'DELETE' && request.body?.dryRun === undefined,
);
assert.deepEqual(actualDelete.body.preconditions, {
uid: receipt.retiredResources[0].uid,
resourceVersion: receipt.retiredResources[0].resourceVersion,
});
const auditCommand = writeCommand(
value.directory,
'retirement-audit-command.json',
'cluster.deployment.retirement.receipt.audit',
{
applyCommand: {
path: applyCommand,
expectedDigest: fileDigest(applyCommand),
},
receipt: { path: receiptPath, expectedDigest: receipt.receiptDigest },
},
);
const audit = executeCommand(auditCommand);
assert.equal(audit.compatible, true);
assert.equal(audit.transitionKind, 'retire');
assert.equal(audit.retiredResourceCount, 1);
assert.equal(audit.resourceCount, 5);
assert.equal(audit.kubernetesMutation, false);
});
test('retirement rejects a replacement UID before acquiring the deployment head', (t) => {
const value = prepareRetirement(t);
const { applyCommand, receiptPath } = retirementApplyCommand(
value,
'replacement',
);
const requestPath = [...value.api.objects.keys()].find((candidate) =>
candidate.endsWith('/ql3-extra-release-resource'),
);
const live = value.api.objects.get(requestPath);
value.api.objects.set(requestPath, {
...live,
metadata: { ...live.metadata, uid: crypto.randomUUID() },
});
assert.throws(() =>
executeCommand(applyCommand, {
runProcess: value.runner,
retirementApi: value.api,
}),
);
assert.equal(fs.existsSync(receiptPath), false);
const head = JSON.parse(value.runner.server.head.data[HEAD_DATA_KEY]);
assert.equal(head.phase, 'committed');
assert.equal(head.generation, 1);
});
test('retirement resumes exact intent after a lost delete response', (t) => {
const value = prepareRetirement(t);
const { applyCommand, receiptPath } = retirementApplyCommand(
value,
'response-loss',
);
value.api.loseNextDeleteResponse = true;
assert.throws(() =>
executeCommand(applyCommand, {
runProcess: value.runner,
retirementApi: value.api,
}),
);
assert.equal(fs.existsSync(receiptPath), false);
assert.equal(
JSON.parse(value.runner.server.head.data[HEAD_DATA_KEY]).phase,
'applying',
);
const receipt = executeCommand(applyCommand, {
runProcess: value.runner,
retirementApi: value.api,
});
assert.equal(receipt.deploymentHead.generation, 2);
assert.equal(receipt.resourceInventory.length, 5);
assert.equal(
JSON.parse(value.runner.server.head.data[HEAD_DATA_KEY]).phase,
'committed',
);
});
test('rollback may restore the exact inventory retired at the same release version', (t) => {
const value = prepareRetirement(t);
const { applyCommand } = retirementApplyCommand(value, 'restore-source');
const retired = executeCommand(applyCommand, {
runProcess: value.runner,
retirementApi: value.api,
});
const restored = performTransition(
value,
value.runner,
'rollback',
expectedFromReceipt(retired),
'retirement-restore',
);
assert.equal(restored.receipt.transitionKind, 'rollback');
assert.equal(restored.receipt.deploymentHead.generation, 3);
assert.equal(restored.receipt.resourceInventory.length, 6);
assert.deepEqual(
restored.receipt.resourceInventory,
value.installed.receipt.resourceInventory,
);
});
test('command surface is closed and lock reports require exact canonical identity', () => {
assert.equal(
commandFile(['--command-file=/private/command.json']),
'/private/command.json',
);
assert.throws(() => commandFile([]));
assert.throws(() => commandFile(['--command-file=a', '--extra=b']));
assert.throws(() =>
parseCommand({
schemaVersion: 1,
schema: COMMAND_SCHEMA,
operation: 'cluster.deployment.apply-now',
request: {},
}),
);
const report = lockReport();
assert.equal(validateLockReport(report).report.lockDigest, report.lockDigest);
const drifted = JSON.parse(JSON.stringify(report));
drifted.catalog.discoveryTagAuthority = 'fallback';
const { lockDigest: ignored, ...unsigned } = drifted;
drifted.lockDigest = digest(JSON.stringify(unsigned));
assert.throws(() => validateLockReport(drifted));
});
test('repository exposes the reviewed ceremony and removes the bare apply handoff', () => {
const root = path.resolve(__dirname, '../..');
const packageManifest = JSON.parse(
fs.readFileSync(path.join(root, 'package.json'), 'utf8'),
);
assert.equal(
packageManifest.scripts['cluster-deployment:ql3'],
'node scripts/ql3-kubernetes-deployment-ceremony.cjs',
);
assert.equal(
packageManifest.scripts['test:cluster-deployment-live:ql3'],
'node scripts/ql3-kubernetes-deployment-live-contract.cjs',
);
const operations = fs.readFileSync(
path.join(root, 'docs/operations/ql3-release-set-deployment.md'),
'utf8',
);
assert.match(operations, /cluster\.deployment\.preflight/);
assert.match(operations, /cluster\.deployment\.apply/);
assert.match(operations, /cluster\.deployment\.receipt\.audit/);
assert.match(operations, /qinglong3-catalog-lock/);
assert.equal(
operations.includes('才由有权限的独立步骤执行 `kubectl apply'),
false,
);
});
test('CI runs the isolated deployment and retirement live contract', () => {
const root = path.resolve(__dirname, '../..');
const workflowPath = path.join(
root,
'.github/workflows/ql3-kubernetes-deployment-live.yml',
);
const workflowSource = fs.readFileSync(workflowPath, 'utf8');
const workflow = yaml.load(workflowSource);
const job = workflow.jobs?.['kubernetes-deployment-live'];
assert.equal(job?.['runs-on'], 'ubuntu-24.04');
assert.equal(job?.['timeout-minutes'], 25);
assert.match(workflowSource, /rancher\/k3s:v1\.34\.3-k3s1/u);
assert.match(workflowSource, /kubectl v1\.34\.3/u);
assert.match(
workflowSource,
/node --test test\/back\/ql3KubernetesDeploymentCeremony\.test\.cjs/u,
);
assert.match(
workflowSource,
/node scripts\/ql3-kubernetes-deployment-live-contract\.cjs/u,
);
assert.match(workflowSource, /uidResourceVersionDeletePreconditions/u);
assert.match(workflowSource, /ql3-deploy-live-/u);
});
test('live contract never resolves curl through the Docker kubectl fallback', function liveToolResolutionKeepsCurlDistinct() {
const root = path.resolve(__dirname, '../..');
const source = fs.readFileSync(
path.join(root, 'scripts/ql3-kubernetes-deployment-live-contract.cjs'),
'utf8',
);
assert.match(
source,
/const candidates = \[\s*input,\s*\.\.\.\(input === 'kubectl'\s*\? \['\/Applications\/Docker\.app\/Contents\/Resources\/bin\/kubectl'\]\s*: \[\]\),\s*\.\.\.\(process\.env\.PATH/u,
);
});
test('private retirement proxy rejects every unrelated mutation method', function privateRetirementProxyRejectsUnrelatedMutations() {
const root = path.resolve(__dirname, '../..');
const source = fs.readFileSync(
path.join(root, 'scripts/lib/ql3-kubernetes-deployment-ceremony.cjs'),
'utf8',
);
assert.match(
source,
/--reject-methods=\^\(POST\|PUT\|PATCH\|CONNECT\|OPTIONS\|TRACE\)\$/u,
);
assert.equal(source.includes('--reject-methods=POST,PUT,PATCH'), false);
});
test('thin CLI uses the pinned executable and keeps failures low-sensitive', (t) => {
const value = fixture(t);
fs.writeFileSync(
value.kubectl,
`#!${process.execPath}
'use strict';
const args = process.argv.slice(2);
if (args.includes('get') && args.includes('-f=-')) process.stdout.write(${JSON.stringify(
convergenceList(),
)});
else if (args.includes('get') && args.includes(${JSON.stringify(
HEAD_NAME,
)})) process.stdout.write('');
else if (args.includes('get')) process.stdout.write(${JSON.stringify(
CLUSTER_UID,
)});
else if (args.includes('--dry-run=server')) process.stdout.write('deployment.apps/ql3-control\\n');
else process.exitCode = 91;
`,
{ mode: 0o700 },
);
const output = path.join(value.directory, 'cli-preflight.json');
const command = writeCommand(
value.directory,
'cli-command.json',
'cluster.deployment.preflight',
{
preflightId: '123e4567-e89b-42d3-a456-426614174060',
...commonRequest(value),
output,
},
);
const cli = path.resolve(
__dirname,
'../../scripts/ql3-kubernetes-deployment-ceremony.cjs',
);
const accepted = spawnSync(
process.execPath,
[cli, `--command-file=${command}`],
{ encoding: 'utf8' },
);
assert.equal(
accepted.status,
0,
JSON.stringify({ stdout: accepted.stdout, stderr: accepted.stderr }),
);
assert.equal(accepted.stderr, '');
assert.equal(JSON.parse(accepted.stdout).schema, PREFLIGHT_SCHEMA);
fs.chmodSync(value.kubeconfig, 0o644);
const rejected = spawnSync(
process.execPath,
[cli, `--command-file=${command}`],
{ encoding: 'utf8' },
);
assert.equal(rejected.status, 1);
assert.equal(rejected.stdout, '');
assert.deepEqual(JSON.parse(rejected.stderr), {
schemaVersion: 1,
component: 'qinglong3-kubernetes-deployment-ceremony',
code: 'QL3_KUBERNETES_DEPLOYMENT_CEREMONY_FAILED',
message: 'QingLong 3 Kubernetes deployment ceremony failed',
});
assert.equal(rejected.stderr.includes(value.directory), false);
assert.equal(rejected.stderr.includes(CONTEXT), false);
});