mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
feat(ql3): bind local compose revisions to release catalogs
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
function sha256(value) {
|
||||
return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`;
|
||||
}
|
||||
|
||||
function writeSyntheticLocalReleaseSelection(options) {
|
||||
const releaseSetDigest = sha256(`release-set:${options.image}`);
|
||||
const manifestDigest = sha256(`catalog-manifest:${options.image}`);
|
||||
const consumptionReportDigest = sha256(`catalog-report:${options.image}`);
|
||||
const unsigned = {
|
||||
schemaVersion: 1,
|
||||
schema: 'qinglong/local-compose-release-image@v2',
|
||||
release: {
|
||||
version: '3.0.0-alpha.0',
|
||||
sourceRevision: options.sourceRevision ?? '3'.repeat(40),
|
||||
sourceRef: 'refs/tags/v3.0.0-alpha.0',
|
||||
scope: 'local',
|
||||
},
|
||||
releaseSetDigest,
|
||||
catalog: {
|
||||
schema: 'qinglong/release-catalog-consumption-ceremony@v1',
|
||||
sourceRepository: 'example/qinglong',
|
||||
workflowIdentity:
|
||||
'https://github.com/example/qinglong/.github/workflows/ql3-image-release.yml@refs/tags/v3.0.0-alpha.0',
|
||||
immutableReference: `ghcr.io/example/qinglong3-release-catalog@${manifestDigest}`,
|
||||
manifestDigest,
|
||||
consumptionReportDigest,
|
||||
releaseSetDigest,
|
||||
discoveryTagAuthority: 'none',
|
||||
},
|
||||
deploymentFamily: 'local',
|
||||
service: {
|
||||
kind: 'compose',
|
||||
image: options.image,
|
||||
allowRootService: options.allowRootService,
|
||||
},
|
||||
verification: {
|
||||
releaseSet: 'standalone_structure_identity_and_self_digest',
|
||||
sourceRecordsReplayed: false,
|
||||
catalogConsumption: 'offline_reconstructed',
|
||||
externalToolResultsReplayed: false,
|
||||
networkAccess: false,
|
||||
deploymentMutation: false,
|
||||
},
|
||||
};
|
||||
const selectionDigest = sha256(JSON.stringify(unsigned));
|
||||
const filePath = path.join(
|
||||
options.directory,
|
||||
`synthetic-local-release-selection-${selectionDigest.slice(7)}.json`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
filePath,
|
||||
`${JSON.stringify({ ...unsigned, selectionDigest })}\n`,
|
||||
{ encoding: 'utf8', mode: 0o600, flag: 'wx' },
|
||||
);
|
||||
return Object.freeze({
|
||||
path: filePath,
|
||||
expectedSelectionDigest: selectionDigest,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { writeSyntheticLocalReleaseSelection };
|
||||
@@ -4,6 +4,9 @@ const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const {
|
||||
writeSyntheticLocalReleaseSelection,
|
||||
} = require('./lib/ql3-local-release-selection-test-fixture.cjs');
|
||||
|
||||
const IMAGE_PATTERN =
|
||||
/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}@sha256:[0-9a-f]{64}$/;
|
||||
@@ -74,6 +77,12 @@ async function main() {
|
||||
const deploymentRoot = path.join(temporaryRoot, 'deployment');
|
||||
const commandPath = path.join(temporaryRoot, 'preflight.json');
|
||||
const uid = process.getuid();
|
||||
const releaseSelection = writeSyntheticLocalReleaseSelection({
|
||||
directory: temporaryRoot,
|
||||
image: input.image,
|
||||
allowRootService: uid === 0,
|
||||
sourceRevision: process.env.GITHUB_SHA,
|
||||
});
|
||||
|
||||
try {
|
||||
const setup = await prepareLocalDeployment({
|
||||
@@ -86,7 +95,7 @@ async function main() {
|
||||
busyTimeoutMs: 100,
|
||||
service: {
|
||||
kind: 'compose',
|
||||
image: input.image,
|
||||
releaseSelection,
|
||||
allowRootService: uid === 0,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,6 +7,9 @@ const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const {
|
||||
writeSyntheticLocalReleaseSelection,
|
||||
} = require('./lib/ql3-local-release-selection-test-fixture.cjs');
|
||||
|
||||
const IMAGE_PATTERN =
|
||||
/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}@sha256:[0-9a-f]{64}$/;
|
||||
@@ -178,6 +181,12 @@ async function main() {
|
||||
const deploymentRoot = path.join(temporaryRoot, 'deployment');
|
||||
const commandPath = path.join(temporaryRoot, 'rollout.json');
|
||||
const uid = process.getuid();
|
||||
const releaseSelection = writeSyntheticLocalReleaseSelection({
|
||||
directory: temporaryRoot,
|
||||
image: input.image,
|
||||
allowRootService: uid === 0,
|
||||
sourceRevision: process.env.GITHUB_SHA,
|
||||
});
|
||||
let composeResourcesPresent = false;
|
||||
|
||||
try {
|
||||
@@ -191,7 +200,7 @@ async function main() {
|
||||
busyTimeoutMs: 100,
|
||||
service: {
|
||||
kind: 'compose',
|
||||
image: input.image,
|
||||
releaseSelection,
|
||||
allowRootService: uid === 0,
|
||||
},
|
||||
},
|
||||
@@ -275,7 +284,7 @@ async function main() {
|
||||
},
|
||||
request: {
|
||||
expectedGeneration: 1,
|
||||
image: input.image,
|
||||
releaseSelection,
|
||||
mutationId: '019f8680-143d-4000-8000-000000000205',
|
||||
changedAtMs: 1_785_254_600_004,
|
||||
},
|
||||
@@ -392,7 +401,7 @@ async function main() {
|
||||
},
|
||||
request: {
|
||||
expectedGeneration: 2,
|
||||
image: input.image,
|
||||
releaseSelection,
|
||||
mutationId: '019f8680-143d-4000-8000-000000000208',
|
||||
changedAtMs: 1_785_254_600_007,
|
||||
},
|
||||
@@ -711,7 +720,7 @@ async function main() {
|
||||
},
|
||||
request: {
|
||||
expectedGeneration: evidenceGeneration,
|
||||
image: input.image,
|
||||
releaseSelection,
|
||||
mutationId: transaction.revisionId,
|
||||
changedAtMs: 1_785_254_600_012 + index * 3,
|
||||
},
|
||||
|
||||
@@ -6,6 +6,9 @@ const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawn, spawnSync } = require('node:child_process');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const {
|
||||
writeSyntheticLocalReleaseSelection,
|
||||
} = require('./lib/ql3-local-release-selection-test-fixture.cjs');
|
||||
|
||||
const MAX_OUTPUT_BYTES = 64 * 1024;
|
||||
const ACTIVE_TIMEOUT_MS = 45_000;
|
||||
@@ -15,11 +18,7 @@ function fail(message) {
|
||||
}
|
||||
|
||||
function imageArgument(argv) {
|
||||
if (
|
||||
argv.length < 1 ||
|
||||
argv.length > 2 ||
|
||||
!argv[0].startsWith('--image=')
|
||||
) {
|
||||
if (argv.length < 1 || argv.length > 2 || !argv[0].startsWith('--image=')) {
|
||||
fail('usage: --image=immutable-local-image [--profile=edge|standalone]');
|
||||
}
|
||||
const image = argv[0].slice('--image='.length);
|
||||
@@ -104,13 +103,7 @@ function parseLines(buffer, events) {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
async function runApplication(
|
||||
image,
|
||||
deploymentRoot,
|
||||
uid,
|
||||
gid,
|
||||
profile,
|
||||
) {
|
||||
async function runApplication(image, deploymentRoot, uid, gid, profile) {
|
||||
const containerName = `ql3-local-image-${process.pid}-${crypto
|
||||
.randomUUID()
|
||||
.slice(0, 8)}`;
|
||||
@@ -173,10 +166,7 @@ async function runApplication(
|
||||
return;
|
||||
}
|
||||
stdout = parseLines(stdout, events);
|
||||
if (
|
||||
!stopped &&
|
||||
events.some(({ event }) => event === 'active')
|
||||
) {
|
||||
if (!stopped && events.some(({ event }) => event === 'active')) {
|
||||
stopped = true;
|
||||
runDocker(['stop', '--time', '30', containerName]);
|
||||
}
|
||||
@@ -254,20 +244,22 @@ async function main() {
|
||||
const profile = profileArgument(process.argv.slice(2));
|
||||
const imageIdentity = inspectImage(image);
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const {
|
||||
prepareLocalDeployment,
|
||||
} = require(path.join(
|
||||
const { prepareLocalDeployment } = require(path.join(
|
||||
root,
|
||||
'packages/ql3-local-owner-cli/dist/deployment/localDeployment.js',
|
||||
));
|
||||
const temporaryRoot = fs.realpathSync(
|
||||
fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'ql3-local-image-live-'),
|
||||
),
|
||||
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-local-image-live-')),
|
||||
);
|
||||
fs.chmodSync(temporaryRoot, 0o700);
|
||||
const deploymentRoot = path.join(temporaryRoot, 'deployment');
|
||||
const uid = process.getuid();
|
||||
const gid = process.getgid();
|
||||
const releaseSelection = writeSyntheticLocalReleaseSelection({
|
||||
directory: temporaryRoot,
|
||||
image: `ghcr.io/example/qinglong3-local-application@${imageIdentity.id}`,
|
||||
allowRootService: uid === 0,
|
||||
});
|
||||
|
||||
try {
|
||||
const setup = await prepareLocalDeployment({
|
||||
@@ -280,7 +272,7 @@ async function main() {
|
||||
busyTimeoutMs: 100,
|
||||
service: {
|
||||
kind: 'compose',
|
||||
image: `local/qinglong3@${imageIdentity.id}`,
|
||||
releaseSelection,
|
||||
allowRootService: uid === 0,
|
||||
},
|
||||
},
|
||||
@@ -309,8 +301,9 @@ async function main() {
|
||||
);
|
||||
let integrity;
|
||||
try {
|
||||
integrity = database.prepare('PRAGMA integrity_check').get()
|
||||
.integrity_check;
|
||||
integrity = database
|
||||
.prepare('PRAGMA integrity_check')
|
||||
.get().integrity_check;
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
@@ -323,8 +316,7 @@ async function main() {
|
||||
architecture: imageIdentity.architecture,
|
||||
user: imageIdentity.user,
|
||||
profile,
|
||||
memoryBytes:
|
||||
(profile === 'edge' ? 128 : 256) * 1024 * 1024,
|
||||
memoryBytes: (profile === 'edge' ? 128 : 256) * 1024 * 1024,
|
||||
pids: profile === 'edge' ? 64 : 256,
|
||||
network: 'none',
|
||||
readOnlyRoot: true,
|
||||
|
||||
@@ -13,6 +13,9 @@ const {
|
||||
mountForPath,
|
||||
parseMountTable,
|
||||
} = require('./ql3-physical-edge-evidence.cjs');
|
||||
const {
|
||||
writeSyntheticLocalReleaseSelection,
|
||||
} = require('./lib/ql3-local-release-selection-test-fixture.cjs');
|
||||
|
||||
const MIB = 1024 * 1024;
|
||||
const MAX_INPUT_BYTES = 256 * 1024;
|
||||
@@ -792,7 +795,15 @@ async function preparePhase(options, manifest) {
|
||||
);
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
const image = `physical.invalid/qinglong3-local@sha256:${'a'.repeat(64)}`;
|
||||
const image = `ghcr.io/example/qinglong3-local-application@sha256:${'a'.repeat(
|
||||
64,
|
||||
)}`;
|
||||
fs.mkdirSync(deploymentRoot, { mode: 0o700 });
|
||||
const releaseSelection = writeSyntheticLocalReleaseSelection({
|
||||
directory: deploymentRoot,
|
||||
image,
|
||||
allowRootService: uid === 0,
|
||||
});
|
||||
await products.deployment.prepareLocalDeployment({
|
||||
schemaVersion: 1,
|
||||
operation: 'local.deployment.prepare',
|
||||
@@ -803,7 +814,7 @@ async function preparePhase(options, manifest) {
|
||||
busyTimeoutMs: 100,
|
||||
service: {
|
||||
kind: 'compose',
|
||||
image,
|
||||
releaseSelection,
|
||||
allowRootService: uid === 0,
|
||||
},
|
||||
},
|
||||
@@ -829,7 +840,7 @@ async function preparePhase(options, manifest) {
|
||||
},
|
||||
request: {
|
||||
expectedGeneration: generation - 1,
|
||||
image,
|
||||
releaseSelection,
|
||||
mutationId: crypto.randomUUID(),
|
||||
changedAtMs: timestamp + generation,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user