feat(ql3): scope deployment-family release candidates

This commit is contained in:
whyour
2026-08-16 08:54:51 +08:00
parent d991deb7ee
commit 7130d77a76
27 changed files with 1332 additions and 303 deletions
+38 -9
View File
@@ -7,7 +7,13 @@ const path = require('node:path');
const { TextDecoder } = require('node:util');
const FIXTURE = 'qinglong/image-os-vulnerability-exceptions@v1';
const IMAGES = Object.freeze(['admin', 'control', 'control-ai', 'local']);
const IMAGES = Object.freeze([
'admin',
'control',
'control-ai',
'local',
'worker',
]);
const MAX_POLICY_BYTES = 256 * 1024;
const MAX_EXCEPTIONS = 128;
const MAX_EXCEPTION_DAYS = 30;
@@ -15,7 +21,8 @@ const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
const CVE_PATTERN = /^CVE-[0-9]{4}-[0-9]{4,}$/;
const OWNER_PATTERN = /^[a-z0-9][a-z0-9._/-]{1,127}$/;
const TICKET_PATTERN = /^[A-Z][A-Z0-9]{1,15}-[1-9][0-9]{0,9}$/;
const PURL_PATTERN = /^pkg:(?:apk|deb|rpm)\/[A-Za-z0-9._~%+-]+\/[A-Za-z0-9._~%+-]+@[A-Za-z0-9._~%+:-]+$/;
const PURL_PATTERN =
/^pkg:(?:apk|deb|rpm)\/[A-Za-z0-9._~%+-]+\/[A-Za-z0-9._~%+-]+@[A-Za-z0-9._~%+:-]+$/;
const DEFAULT_ROOT = path.resolve(__dirname, '..');
const POLICY_PATH = 'deploy/containers/ql3-os-vulnerability-exceptions.json';
@@ -69,8 +76,14 @@ function utcDay(value) {
: null;
}
function auditImageOsVulnerabilityPolicy(policy, dependencies = { now: Date.now }) {
if (!exactKeys(dependencies, ['now']) || typeof dependencies.now !== 'function') {
function auditImageOsVulnerabilityPolicy(
policy,
dependencies = { now: Date.now },
) {
if (
!exactKeys(dependencies, ['now']) ||
typeof dependencies.now !== 'function'
) {
fail('clock is invalid');
}
const nowMs = dependencies.now();
@@ -95,11 +108,18 @@ function auditImageOsVulnerabilityPolicy(policy, dependencies = { now: Date.now
control: 0,
'control-ai': 0,
local: 0,
worker: 0,
}),
});
}
const counts = { admin: 0, control: 0, 'control-ai': 0, local: 0 };
const counts = {
admin: 0,
control: 0,
'control-ai': 0,
local: 0,
worker: 0,
};
const seen = new Set();
let previousId = '';
for (const exception of policy.exceptions) {
@@ -192,7 +212,9 @@ function renderTrivyIgnore(policy, image, dependencies = { now: Date.now }) {
if (!IMAGES.includes(image)) fail('image is invalid');
const audit = auditImageOsVulnerabilityPolicy(policy, dependencies);
if (!audit.compatible) fail('policy is incompatible');
const selected = policy.exceptions.filter((entry) => entry.images.includes(image));
const selected = policy.exceptions.filter((entry) =>
entry.images.includes(image),
);
const lines = ['vulnerabilities:'];
if (selected.length === 0) lines.push(' []');
for (const exception of selected) {
@@ -203,7 +225,9 @@ function renderTrivyIgnore(policy, image, dependencies = { now: Date.now }) {
}
lines.push(` expired_at: ${exception.expiresOn}`);
lines.push(
` statement: ${JSON.stringify(`owner=${exception.owner}; ticket=${exception.ticket}; rationale=${exception.rationale}`)}`,
` statement: ${JSON.stringify(
`owner=${exception.owner}; ticket=${exception.ticket}; rationale=${exception.rationale}`,
)}`,
);
}
return `${lines.join('\n')}\n`;
@@ -241,7 +265,8 @@ function parseArguments(argv) {
for (const argument of argv) {
if (argument === '--') continue;
const match = /^--([a-z-]+)=(.+)$/.exec(argument);
if (!match || Object.hasOwn(values, match[1])) fail('arguments are invalid');
if (!match || Object.hasOwn(values, match[1]))
fail('arguments are invalid');
values[match[1]] = match[2];
}
if (
@@ -250,7 +275,11 @@ function parseArguments(argv) {
) {
fail('arguments are invalid');
}
return Object.freeze({ mode: 'render', image: values.image, output: values.output });
return Object.freeze({
mode: 'render',
image: values.image,
output: values.output,
});
}
function runCli(argv, root = DEFAULT_ROOT, dependencies = { now: Date.now }) {