78 lines
5.6 KiB
JavaScript
78 lines
5.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const releaseRoot = path.join(root, "release");
|
|
const reportPath = path.join(root, "tests/golden/M13-05E/supply-chain-report.json");
|
|
const manifestPath = path.join(root, "tests/golden/M13-05E/manifest.json");
|
|
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
const fileSha256 = (file) => sha256(fs.readFileSync(file));
|
|
const archive = path.join(releaseRoot, "blender-web-offline.tar.gz");
|
|
const sourceArchive = path.join(releaseRoot, "blender-web-corresponding-source.tar.gz");
|
|
const sumsPath = path.join(releaseRoot, "SHA256SUMS.txt");
|
|
const sbomPath = path.join(root, "docs/web/sbom.spdx.json");
|
|
const noticesPath = path.join(root, "docs/web/third-party-notices.json");
|
|
const lockPath = path.join(root, "web/package-lock.json");
|
|
const packagePath = path.join(root, "web/package.json");
|
|
const sbom = JSON.parse(fs.readFileSync(sbomPath, "utf8"));
|
|
const notices = JSON.parse(fs.readFileSync(noticesPath, "utf8"));
|
|
const lockBytes = fs.readFileSync(lockPath);
|
|
const noticesBytes = fs.readFileSync(noticesPath);
|
|
const lockHash = sha256(lockBytes);
|
|
const noticesHash = sha256(noticesBytes);
|
|
assert.equal(sbom.spdxVersion, "SPDX-2.3");
|
|
assert.equal(sbom.documentNamespace, `https://blender-web.local/spdx/${sha256(Buffer.concat([lockBytes, noticesBytes]))}`);
|
|
const rootPackage = sbom.packages.find((item) => item.SPDXID === "SPDXRef-Package-blender-web-editor");
|
|
assert.ok(rootPackage);
|
|
assert.equal(rootPackage.checksums?.find((item) => item.algorithm === "SHA256")?.checksumValue, lockHash);
|
|
assert.ok(notices.packages.length > 0);
|
|
assert.ok(fs.statSync(archive).isFile());
|
|
assert.ok(fs.statSync(sourceArchive).isFile());
|
|
const sums = new Map(fs.readFileSync(sumsPath, "utf8").trim().split(/\r?\n/u).map((line) => {
|
|
const match = line.match(/^([a-f0-9]{64}) (.+)$/u);
|
|
assert.ok(match, `invalid checksum line ${line}`);
|
|
return [match[2], match[1]];
|
|
}));
|
|
assert.equal(sums.get(path.basename(archive)), fileSha256(archive));
|
|
assert.equal(sums.get(path.basename(sourceArchive)), fileSha256(sourceArchive));
|
|
function archiveEntries(file) {
|
|
return execFileSync("tar", ["-tzf", file], { encoding: "utf8", maxBuffer: 32 * 1024 * 1024 }).split(/\r?\n/u).filter(Boolean);
|
|
}
|
|
function archiveFile(file, entry) {
|
|
return execFileSync("tar", ["-xOf", file, entry], { maxBuffer: 64 * 1024 * 1024 });
|
|
}
|
|
const binaryEntries = archiveEntries(archive);
|
|
const sourceEntries = archiveEntries(sourceArchive);
|
|
for (const entry of ["blender-web-offline/sbom.spdx.json", "blender-web-offline/third-party-notices.json", "blender-web-offline/SOURCE_OFFER.txt", "blender-web-offline/manifest.json"]) assert.ok(binaryEntries.includes(entry), `binary archive omits ${entry}`);
|
|
for (const entry of ["web/package.json", "web/package-lock.json", "docs/web/sbom.spdx.json", "docs/web/third-party-notices.json", "docs/web/DEPLOYMENT.md", "tools/web/create-offline-release.mjs"]) assert.ok(sourceEntries.includes(entry), `source archive omits ${entry}`);
|
|
const sourceOffer = archiveFile(archive, "blender-web-offline/SOURCE_OFFER.txt").toString("utf8");
|
|
assert.match(sourceOffer, /blender-web-corresponding-source\.tar\.gz/u);
|
|
assert.match(sourceOffer, /SHA256SUMS\.txt/u);
|
|
const embeddedPackage = archiveFile(sourceArchive, "web/package.json");
|
|
const embeddedLock = archiveFile(sourceArchive, "web/package-lock.json");
|
|
assert.equal(sha256(embeddedPackage), fileSha256(packagePath));
|
|
assert.equal(sha256(embeddedLock), lockHash);
|
|
const embeddedSbom = archiveFile(archive, "blender-web-offline/sbom.spdx.json");
|
|
assert.deepEqual(JSON.parse(embeddedSbom), sbom);
|
|
const commit = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
assert.match(commit, /^[a-f0-9]{40}$/u);
|
|
const report = {
|
|
schemaVersion: 1, task: "M13-05E", operation: "SUPPLY_CHAIN_BINDING", commit,
|
|
inputs: { packageSha256: fileSha256(packagePath), lockfileSha256: lockHash, sbomSha256: fileSha256(sbomPath), noticesSha256: noticesHash },
|
|
archives: { binary: { path: path.relative(root, archive), sha256: fileSha256(archive), entries: binaryEntries.length }, source: { path: path.relative(root, sourceArchive), sha256: fileSha256(sourceArchive), entries: sourceEntries.length } },
|
|
sourceOffer: "BOUND_TO_CORRESPONDING_SOURCE_ARCHIVE_AND_SHA256SUMS",
|
|
checks: { spdx23: true, lockfileBound: true, noticesBound: true, sourceOfferBound: true, archiveChecksumsBound: true, sourcePackageBound: true },
|
|
execution: "DISABLED", nextTask: "M13-05F",
|
|
};
|
|
if (process.env.UPDATE_M13_05E_REPORT === "1") { fs.mkdirSync(path.dirname(reportPath), { recursive: true }); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); }
|
|
assert.deepEqual(JSON.parse(fs.readFileSync(reportPath, "utf8")), report);
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
assert.deepEqual({ schemaVersion: manifest.schemaVersion, task: manifest.task, parentTask: manifest.parentTask, nextTask: manifest.nextTask }, { schemaVersion: 1, task: "M13-05E", parentTask: "M13-05D", nextTask: "M13-05F" });
|
|
for (const artifact of Object.values(manifest.artifacts)) assert.equal(fileSha256(path.join(root, artifact.path)), artifact.sha256, artifact.path);
|
|
process.stdout.write(`supply-chain-binding-ok sbom=SPDX-2.3 lockfile=BOUND notices=BOUND sourceOffer=BOUND binarySha256=${report.archives.binary.sha256} sourceSha256=${report.archives.source.sha256} execution=DISABLED next=M13-05F\n`);
|