mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): gate local releases through published catalogs
This commit is contained in:
@@ -124,6 +124,15 @@ test('accepts the reviewed native CI and digest release contracts', () => {
|
||||
fencedRetirementReceiptAudited: true,
|
||||
publicationAuthority: false,
|
||||
},
|
||||
localCatalogDeploymentGate: {
|
||||
scopes: ['local', 'all'],
|
||||
catalogAuthority: 'immutable_digest_after_public_consumption',
|
||||
selectionReconstructed: true,
|
||||
profiles: ['edge', 'standalone'],
|
||||
rolloutReceiptAudited: true,
|
||||
gracefulCleanup: true,
|
||||
publicationAuthority: false,
|
||||
},
|
||||
localRolloutPreflight: true,
|
||||
localRolloutApply: true,
|
||||
postPublishVerification: [
|
||||
@@ -136,6 +145,7 @@ test('accepts the reviewed native CI and digest release contracts', () => {
|
||||
'release-set',
|
||||
'durable-catalog',
|
||||
'catalog-consumption',
|
||||
'catalog-bound-local-compose-deployment',
|
||||
'catalog-bound-k3s-deployment',
|
||||
'release-tags',
|
||||
],
|
||||
@@ -729,7 +739,7 @@ test('rejects an image tag created before digest verification completes', () =>
|
||||
});
|
||||
|
||||
test('rejects any publisher step after the deployment lock is published', () => {
|
||||
const marker = '\n release-catalog-deployment-live:';
|
||||
const marker = '\n release-catalog-local-deployment-live:';
|
||||
assert.equal(releaseSource.includes(marker), true);
|
||||
const mutated = releaseSource.replace(
|
||||
marker,
|
||||
@@ -742,6 +752,76 @@ test('rejects any publisher step after the deployment lock is published', () =>
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects removal of the downstream Local catalog deployment gate', function rejectsMissingLocalCatalogDeploymentGate() {
|
||||
const localMarker = '\n release-catalog-local-deployment-live:';
|
||||
const clusterMarker = '\n release-catalog-deployment-live:';
|
||||
const localOffset = releaseSource.indexOf(localMarker);
|
||||
const clusterOffset = releaseSource.indexOf(clusterMarker);
|
||||
assert.notEqual(localOffset, -1);
|
||||
assert.equal(clusterOffset > localOffset, true);
|
||||
const mutated = `${releaseSource.slice(0, localOffset)}${releaseSource.slice(
|
||||
clusterOffset,
|
||||
)}`;
|
||||
assert.throws(
|
||||
() => auditReleaseWorkflow(mutated),
|
||||
/keep evidence read-only|Local release must independently consume/,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects running the Local catalog gate for Cluster-only releases', function rejectsClusterLocalCatalogDeploymentGate() {
|
||||
const mutated = releaseSource.replace(
|
||||
"inputs.release_scope != 'cluster'",
|
||||
"inputs.release_scope == 'cluster'",
|
||||
);
|
||||
assert.throws(
|
||||
() => auditReleaseWorkflow(mutated),
|
||||
/Local release must independently consume/,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects a Local rollout detached from the audited catalog selection', function rejectsDetachedLocalCatalogSelection() {
|
||||
const localMarker = '\n release-catalog-local-deployment-live:';
|
||||
const clusterMarker = '\n release-catalog-deployment-live:';
|
||||
const localOffset = releaseSource.indexOf(localMarker);
|
||||
const clusterOffset = releaseSource.indexOf(clusterMarker);
|
||||
const mutatedLocal = releaseSource
|
||||
.slice(localOffset, clusterOffset)
|
||||
.replace(
|
||||
'--release-selection="${RELEASE_SELECTION}"',
|
||||
'--release-selection="/tmp/unreviewed.json"',
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
auditReleaseWorkflow(
|
||||
`${releaseSource.slice(
|
||||
0,
|
||||
localOffset,
|
||||
)}${mutatedLocal}${releaseSource.slice(clusterOffset)}`,
|
||||
),
|
||||
/Local release must independently consume/,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects publication authority in the post-publish Local catalog consumer', function rejectsLocalCatalogConsumerPublicationAuthority() {
|
||||
const localMarker = '\n release-catalog-local-deployment-live:';
|
||||
const clusterMarker = '\n release-catalog-deployment-live:';
|
||||
const localOffset = releaseSource.indexOf(localMarker);
|
||||
const clusterOffset = releaseSource.indexOf(clusterMarker);
|
||||
const mutatedLocal = releaseSource
|
||||
.slice(localOffset, clusterOffset)
|
||||
.replace('packages: read', 'packages: write');
|
||||
assert.throws(
|
||||
() =>
|
||||
auditReleaseWorkflow(
|
||||
`${releaseSource.slice(
|
||||
0,
|
||||
localOffset,
|
||||
)}${mutatedLocal}${releaseSource.slice(clusterOffset)}`,
|
||||
),
|
||||
/keep evidence read-only|without publication authority/,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects removal of the downstream catalog deployment release gate', function rejectsMissingCatalogDeploymentGate() {
|
||||
const marker = '\n release-catalog-deployment-live:';
|
||||
const offset = releaseSource.indexOf(marker);
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const {
|
||||
argumentsValue,
|
||||
resolveReleaseSelection,
|
||||
} = require('../../scripts/ql3-local-compose-rollout-live-contract.cjs');
|
||||
const {
|
||||
writeSyntheticLocalReleaseSelection,
|
||||
} = require('../../scripts/lib/ql3-local-release-selection-test-fixture.cjs');
|
||||
|
||||
const IMAGE = `ghcr.io/example/qinglong3-local-application@sha256:${'a'.repeat(
|
||||
64,
|
||||
)}`;
|
||||
|
||||
function privateDirectory(t) {
|
||||
const directory = fs.realpathSync(
|
||||
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-local-catalog-selection-')),
|
||||
);
|
||||
fs.chmodSync(directory, 0o700);
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
return directory;
|
||||
}
|
||||
|
||||
function catalogSelection(t) {
|
||||
const directory = privateDirectory(t);
|
||||
return {
|
||||
directory,
|
||||
releaseSelection: writeSyntheticLocalReleaseSelection({
|
||||
directory,
|
||||
image: IMAGE,
|
||||
allowRootService: process.getuid() === 0,
|
||||
sourceRevision: 'b'.repeat(40),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
test('preserves the existing synthetic rollout arguments', (t) => {
|
||||
const directory = privateDirectory(t);
|
||||
const socket = path.join(directory, 'docker.sock');
|
||||
fs.writeFileSync(socket, 'fixture', { mode: 0o600 });
|
||||
assert.deepEqual(
|
||||
argumentsValue([
|
||||
`--image=${IMAGE}`,
|
||||
`--docker-executable=${fs.realpathSync(process.execPath)}`,
|
||||
`--docker-socket=${fs.realpathSync(socket)}`,
|
||||
'--profile=edge',
|
||||
]),
|
||||
{
|
||||
image: IMAGE,
|
||||
profile: 'edge',
|
||||
dockerExecutable: fs.realpathSync(process.execPath),
|
||||
dockerSocket: fs.realpathSync(socket),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('accepts only a complete catalog selection handoff', (t) => {
|
||||
const { directory, releaseSelection } = catalogSelection(t);
|
||||
const socket = path.join(directory, 'docker.sock');
|
||||
fs.writeFileSync(socket, 'fixture', { mode: 0o600 });
|
||||
const common = [
|
||||
`--image=${IMAGE}`,
|
||||
`--docker-executable=${fs.realpathSync(process.execPath)}`,
|
||||
`--docker-socket=${fs.realpathSync(socket)}`,
|
||||
'--profile=standalone',
|
||||
];
|
||||
assert.deepEqual(
|
||||
argumentsValue([
|
||||
...common,
|
||||
`--release-selection=${releaseSelection.path}`,
|
||||
`--expected-selection-digest=${releaseSelection.expectedSelectionDigest}`,
|
||||
]).releaseSelection,
|
||||
releaseSelection,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
argumentsValue([
|
||||
...common,
|
||||
`--release-selection=${releaseSelection.path}`,
|
||||
]),
|
||||
/usage/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
argumentsValue([
|
||||
...common,
|
||||
`--expected-selection-digest=${releaseSelection.expectedSelectionDigest}`,
|
||||
]),
|
||||
/usage/,
|
||||
);
|
||||
});
|
||||
|
||||
test('projects a content-free verified catalog authority from the exact selection', (t) => {
|
||||
const { directory, releaseSelection } = catalogSelection(t);
|
||||
const result = resolveReleaseSelection(
|
||||
{ image: IMAGE, releaseSelection },
|
||||
directory,
|
||||
process.getuid(),
|
||||
);
|
||||
assert.equal(result.releaseSelection, releaseSelection);
|
||||
assert.deepEqual(result.authority, {
|
||||
mode: 'verified_release_catalog',
|
||||
sourceRevision: 'b'.repeat(40),
|
||||
sourceRef: 'refs/tags/v3.0.0-alpha.0',
|
||||
scope: 'local',
|
||||
releaseSetDigest: JSON.parse(fs.readFileSync(releaseSelection.path, 'utf8'))
|
||||
.releaseSetDigest,
|
||||
catalogManifestDigest: JSON.parse(
|
||||
fs.readFileSync(releaseSelection.path, 'utf8'),
|
||||
).catalog.manifestDigest,
|
||||
catalogConsumptionDigest: JSON.parse(
|
||||
fs.readFileSync(releaseSelection.path, 'utf8'),
|
||||
).catalog.consumptionReportDigest,
|
||||
selectionDigest: releaseSelection.expectedSelectionDigest,
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects selection drift, image substitution and wider permissions', (t) => {
|
||||
const first = catalogSelection(t);
|
||||
assert.throws(
|
||||
() =>
|
||||
resolveReleaseSelection(
|
||||
{
|
||||
image: IMAGE.replace('qinglong3-local-application', 'other'),
|
||||
releaseSelection: first.releaseSelection,
|
||||
},
|
||||
first.directory,
|
||||
process.getuid(),
|
||||
),
|
||||
/incompatible/,
|
||||
);
|
||||
|
||||
const second = catalogSelection(t);
|
||||
fs.chmodSync(second.releaseSelection.path, 0o640);
|
||||
assert.throws(
|
||||
() =>
|
||||
resolveReleaseSelection(
|
||||
{ image: IMAGE, releaseSelection: second.releaseSelection },
|
||||
second.directory,
|
||||
process.getuid(),
|
||||
),
|
||||
/incompatible/,
|
||||
);
|
||||
|
||||
const third = catalogSelection(t);
|
||||
const selection = JSON.parse(
|
||||
fs.readFileSync(third.releaseSelection.path, 'utf8'),
|
||||
);
|
||||
selection.release.sourceRevision = 'c'.repeat(40);
|
||||
fs.writeFileSync(
|
||||
third.releaseSelection.path,
|
||||
`${JSON.stringify(selection)}\n`,
|
||||
{ mode: 0o600 },
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
resolveReleaseSelection(
|
||||
{ image: IMAGE, releaseSelection: third.releaseSelection },
|
||||
third.directory,
|
||||
process.getuid(),
|
||||
),
|
||||
/incompatible/,
|
||||
);
|
||||
});
|
||||
|
||||
test('marks an ordinary PR rollout as a synthetic fixture', (t) => {
|
||||
const directory = privateDirectory(t);
|
||||
const result = resolveReleaseSelection(
|
||||
{ image: IMAGE },
|
||||
directory,
|
||||
process.getuid(),
|
||||
);
|
||||
assert.deepEqual(result.authority, { mode: 'synthetic_live_fixture' });
|
||||
assert.equal(fs.existsSync(result.releaseSelection.path), true);
|
||||
});
|
||||
Reference in New Issue
Block a user