feat(ql3): enforce architecture support tiers

This commit is contained in:
whyour
2026-08-20 11:02:17 +08:00
parent 3979707f6d
commit 6e11258d45
9 changed files with 299 additions and 33 deletions
+29 -2
View File
@@ -5,7 +5,14 @@ const path = require('node:path');
const semver = require('semver');
const RELEASE_IDENTITY_PATH = 'ql3-release.json';
const RELEASE_IDENTITY_SCHEMA = 'qinglong/release-identity@v1';
const RELEASE_IDENTITY_SCHEMA = 'qinglong/release-identity@v2';
const ARCHITECTURE_SUPPORT = Object.freeze({
tier1: Object.freeze(['amd64', 'arm64']),
candidates: Object.freeze(['ppc64le', 's390x']),
experimentalBlocked: Object.freeze(['arm/v7']),
legacyOnly: Object.freeze(['arm/v6', '386']),
legacyLine: '2.x',
});
const MAX_RELEASE_IDENTITY_BYTES = 4096;
const VERSION_PATTERN =
/^3\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$/u;
@@ -38,10 +45,11 @@ function normalizeReleaseIdentity(value) {
'product',
'version',
'node',
'architectureSupport',
'workspacePackageCount',
'legacyRootPackageExcluded',
]) ||
value.schemaVersion !== 1 ||
value.schemaVersion !== 2 ||
value.schema !== RELEASE_IDENTITY_SCHEMA ||
value.product !== 'qinglong3' ||
typeof value.version !== 'string' ||
@@ -50,6 +58,15 @@ function normalizeReleaseIdentity(value) {
!exactKeys(value.node, ['version', 'engine']) ||
value.node.version !== '24.18.0' ||
value.node.engine !== '>=24.18.0 <25' ||
!exactKeys(value.architectureSupport, [
'tier1',
'candidates',
'experimentalBlocked',
'legacyOnly',
'legacyLine',
]) ||
JSON.stringify(value.architectureSupport) !==
JSON.stringify(ARCHITECTURE_SUPPORT) ||
value.workspacePackageCount !== 18 ||
value.legacyRootPackageExcluded !== true
) {
@@ -58,6 +75,15 @@ function normalizeReleaseIdentity(value) {
return Object.freeze({
...value,
node: Object.freeze({ ...value.node }),
architectureSupport: Object.freeze({
...value.architectureSupport,
tier1: Object.freeze([...value.architectureSupport.tier1]),
candidates: Object.freeze([...value.architectureSupport.candidates]),
experimentalBlocked: Object.freeze([
...value.architectureSupport.experimentalBlocked,
]),
legacyOnly: Object.freeze([...value.architectureSupport.legacyOnly]),
}),
});
}
@@ -89,6 +115,7 @@ function readReleaseIdentity(root) {
}
module.exports = Object.freeze({
ARCHITECTURE_SUPPORT,
MAX_RELEASE_IDENTITY_BYTES,
RELEASE_IDENTITY_PATH,
RELEASE_IDENTITY_SCHEMA,
+45 -16
View File
@@ -17,6 +17,18 @@ const PREDICATE_TYPE =
'https://qinglong.dev/attestations/release-candidate-contract/v1';
const MAX_REPORT_BYTES = 1024 * 1024;
const RELEASE_SCOPES = Object.freeze(['all', 'cluster', 'local']);
const NATIVE_ARCHITECTURES = Object.freeze({
amd64: Object.freeze({
runner: 'ubuntu-24.04',
node_arch: 'x64',
image_arch: 'amd64',
}),
arm64: Object.freeze({
runner: 'ubuntu-24.04-arm',
node_arch: 'arm64',
image_arch: 'arm64',
}),
});
const LOCAL_IMAGES = Object.freeze([
Object.freeze({
image: 'local',
@@ -92,6 +104,18 @@ function selectedImages(scope) {
return [...CLUSTER_IMAGES, ...LOCAL_IMAGES];
}
function nativeArchitectureMatrix(architectureSupport) {
return architectureSupport.tier1.map((architecture) => {
const native = NATIVE_ARCHITECTURES[architecture];
if (!native) {
fail(
`Tier 1 architecture lacks a native release runner: ${architecture}`,
);
}
return native;
});
}
function validateIdentity(options) {
if (
typeof options.version !== 'string' ||
@@ -147,6 +171,9 @@ function createReleaseCandidateContract(options) {
})
.sort((left, right) => left.name.localeCompare(right.name, 'en'));
const images = selectedImages(options.releaseScope);
const nativeArchitectures = nativeArchitectureMatrix(
releaseIdentity.architectureSupport,
);
const imageManifests = images.map((image) => {
const manifest = readJson(
path.join(root, image.runtime_root, 'package.json'),
@@ -182,24 +209,14 @@ function createReleaseCandidateContract(options) {
});
});
const publishMatrix = images.map(({ dockerfile, target, ...image }) => image);
const osMatrix = images.flatMap((image) => [
{
const osMatrix = images.flatMap((image) =>
nativeArchitectures.map((architecture) => ({
image: image.image,
runner: 'ubuntu-24.04',
node_arch: 'x64',
image_arch: 'amd64',
...architecture,
dockerfile: image.dockerfile,
target: image.target,
},
{
image: image.image,
runner: 'ubuntu-24.04-arm',
node_arch: 'arm64',
image_arch: 'arm64',
dockerfile: image.dockerfile,
target: image.target,
},
]);
})),
);
const unsigned = {
schemaVersion: 1,
schema: SCHEMA,
@@ -220,7 +237,18 @@ function createReleaseCandidateContract(options) {
),
nodeVersion: releaseIdentity.node.version,
nodeEngine: releaseIdentity.node.engine,
platforms: ['linux/amd64', 'linux/arm64'],
architectureSupport: {
tier1: [...releaseIdentity.architectureSupport.tier1],
candidates: [...releaseIdentity.architectureSupport.candidates],
experimentalBlocked: [
...releaseIdentity.architectureSupport.experimentalBlocked,
],
legacyOnly: [...releaseIdentity.architectureSupport.legacyOnly],
legacyLine: releaseIdentity.architectureSupport.legacyLine,
},
platforms: releaseIdentity.architectureSupport.tier1.map(
(architecture) => `linux/${architecture}`,
),
},
workspace: {
packageCount: workspacePackages.length,
@@ -248,6 +276,7 @@ function createReleaseCandidateContract(options) {
requiredGates: [
'package-boundary',
'source-tag-version-identity',
'architecture-support-tier',
'native-os-vulnerability',
'multiarch-oci-layout',
'production-dependency-audit',
+10 -1
View File
@@ -284,11 +284,20 @@ function auditReleaseVersionContract(rootInput = DEFAULT_ROOT) {
}
return Object.freeze({
schemaVersion: 1,
schemaVersion: identity.schemaVersion,
schema: RELEASE_IDENTITY_SCHEMA,
version: identity.version,
nodeVersion: identity.node.version,
nodeEngine: identity.node.engine,
architectureSupport: Object.freeze({
tier1: Object.freeze([...identity.architectureSupport.tier1]),
candidates: Object.freeze([...identity.architectureSupport.candidates]),
experimentalBlocked: Object.freeze([
...identity.architectureSupport.experimentalBlocked,
]),
legacyOnly: Object.freeze([...identity.architectureSupport.legacyOnly]),
legacyLine: identity.architectureSupport.legacyLine,
}),
legacyRootPackageVersion: legacyRoot.version,
legacyRootExcluded: true,
workspacePackageCount: workspacePaths.length,