fix: 防止 artifact 复用污染组件证明
This commit is contained in:
@@ -195,10 +195,32 @@ export async function createG14CiPlan(options = {}) {
|
||||
runtimeKind: model.runtimeKind,
|
||||
entrypoint: model.entrypoint
|
||||
});
|
||||
const componentInputChanged = !envReuse && typeof catalogRecord?.componentInputHash === "string" && catalogRecord.componentInputHash !== componentInputHash;
|
||||
const catalogSourceCommitId = !envReuse ? text(catalogRecord?.sourceCommitId) : "";
|
||||
const catalogComponentInputHash = !envReuse ? text(catalogRecord?.componentInputHash) : "";
|
||||
const catalogComponentInputHashAtSource = !envReuse && catalogSourceCommitId
|
||||
? await hashGitPathsIfCommitExists(repoRoot, catalogSourceCommitId, componentInputPaths, {
|
||||
skipPath: (filePath) => isTestOnlyPath(filePath) || isDocsOnlyPath(filePath),
|
||||
semanticPackageJson: true
|
||||
})
|
||||
: null;
|
||||
const catalogComponentProvenance = !envReuse ? catalogComponentProvenanceStatus({
|
||||
catalogRecord,
|
||||
catalogSourceCommitId,
|
||||
catalogComponentInputHash,
|
||||
catalogComponentInputHashAtSource,
|
||||
componentInputHash,
|
||||
dockerfileHash,
|
||||
buildArgsHash
|
||||
}) : null;
|
||||
const catalogRecordForReuse = catalogRecord && catalogComponentProvenance ? {
|
||||
...catalogRecord,
|
||||
componentProvenanceStatus: catalogComponentProvenance.status,
|
||||
componentProvenanceReason: catalogComponentProvenance.reason
|
||||
} : catalogRecord;
|
||||
const componentInputChanged = !envReuse && catalogComponentProvenance?.safeToReuse !== true;
|
||||
const catalogReuse = envReuse
|
||||
? envReuseCandidate(catalogRecord, envChanged)
|
||||
: reuseCandidate(catalogRecord, imageRelevantChangedPaths.length > 0, componentInputChanged);
|
||||
: reuseCandidate(catalogRecordForReuse, imageRelevantChangedPaths.length > 0, componentInputChanged);
|
||||
const affected = envReuse
|
||||
? Boolean(envChanged || codeChanged)
|
||||
: imageRelevantChangedPaths.length > 0 || componentInputChanged || catalogReuse.status === "candidate-no-catalog" || catalogReuse.status === "candidate-unverified-digest";
|
||||
@@ -213,6 +235,10 @@ export async function createG14CiPlan(options = {}) {
|
||||
buildSystemPaths: model.buildSystemPaths,
|
||||
componentCommitId,
|
||||
componentInputHash,
|
||||
catalogSourceCommitId: catalogSourceCommitId || null,
|
||||
catalogComponentInputHash: catalogComponentInputHash || null,
|
||||
catalogComponentInputHashAtSource,
|
||||
catalogComponentProvenance,
|
||||
dockerfileHash,
|
||||
baseImageReference: baseImage,
|
||||
baseImageDigest: digestFromImageReference(baseImage),
|
||||
@@ -501,6 +527,10 @@ function reasonForService({ directMatches, sharedMatches, runtimeDepMatches, bui
|
||||
if (runtimeDepMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("runtime-deps-changed");
|
||||
if (buildSystemMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("build-system-changed");
|
||||
if (catalogReuse?.status === "component-input-mismatch") reasons.push("component-input-mismatch");
|
||||
if (catalogReuse?.status === "component-source-mismatch") reasons.push("component-source-mismatch");
|
||||
if (catalogReuse?.status === "component-source-unavailable") reasons.push("component-source-unavailable");
|
||||
if (catalogReuse?.status === "component-provenance-missing") reasons.push("component-provenance-missing");
|
||||
if (catalogReuse?.status === "component-build-mismatch") reasons.push("component-build-mismatch");
|
||||
if (catalogReuse?.status === "candidate-no-catalog") reasons.push("catalog-record-missing");
|
||||
if (catalogReuse?.status === "candidate-unverified-digest") reasons.push("catalog-digest-missing");
|
||||
return reasons.length ? reasons : ["affected"];
|
||||
@@ -517,7 +547,16 @@ function reasonForUnchanged(globalChange) {
|
||||
function reuseCandidate(catalogRecord, affected, componentInputChanged = false) {
|
||||
if (affected) return { status: "not-reused", reason: "component-affected" };
|
||||
if (!catalogRecord) return { status: "candidate-no-catalog", reason: "no-previous-artifact-record" };
|
||||
if (componentInputChanged) {
|
||||
if (componentInputChanged && catalogRecord?.componentProvenanceStatus) {
|
||||
return {
|
||||
status: catalogRecord.componentProvenanceStatus,
|
||||
reason: catalogRecord.componentProvenanceReason,
|
||||
image: catalogRecord.image ?? null,
|
||||
digest: catalogRecord.digest ?? null,
|
||||
reusedFrom: catalogRecord.componentInputHash ?? catalogRecord.sourceCommitId ?? catalogRecord.commitId ?? catalogRecord.imageTag ?? null
|
||||
};
|
||||
}
|
||||
if (componentInputChanged && typeof catalogRecord?.componentInputHash === "string") {
|
||||
return {
|
||||
status: "component-input-mismatch",
|
||||
reason: "catalog-component-input-hash-differs-from-current-plan",
|
||||
@@ -526,6 +565,15 @@ function reuseCandidate(catalogRecord, affected, componentInputChanged = false)
|
||||
reusedFrom: catalogRecord.componentInputHash ?? catalogRecord.commitId ?? catalogRecord.imageTag ?? null
|
||||
};
|
||||
}
|
||||
if (componentInputChanged) {
|
||||
return {
|
||||
status: "component-source-mismatch",
|
||||
reason: "catalog-source-commit-does-not-contain-current-component-input",
|
||||
image: catalogRecord.image ?? null,
|
||||
digest: catalogRecord.digest ?? null,
|
||||
reusedFrom: catalogRecord.sourceCommitId ?? catalogRecord.commitId ?? catalogRecord.imageTag ?? null
|
||||
};
|
||||
}
|
||||
if (!/^sha256:[a-f0-9]{64}$/u.test(catalogRecord.digest ?? "")) {
|
||||
return { status: "candidate-unverified-digest", image: catalogRecord.image ?? null, digest: catalogRecord.digest ?? null };
|
||||
}
|
||||
@@ -537,6 +585,39 @@ function reuseCandidate(catalogRecord, affected, componentInputChanged = false)
|
||||
};
|
||||
}
|
||||
|
||||
function catalogComponentProvenanceStatus({ catalogRecord, catalogSourceCommitId, catalogComponentInputHash, catalogComponentInputHashAtSource, componentInputHash, dockerfileHash, buildArgsHash }) {
|
||||
if (!catalogRecord) return { safeToReuse: false, status: "candidate-no-catalog", reason: "no-previous-artifact-record" };
|
||||
if (!catalogSourceCommitId) return { safeToReuse: false, status: "component-provenance-missing", reason: "catalog-source-commit-missing" };
|
||||
if (!catalogComponentInputHash) return { safeToReuse: false, status: "component-provenance-missing", reason: "catalog-component-input-hash-missing" };
|
||||
if (!catalogComponentInputHashAtSource) return { safeToReuse: false, status: "component-source-unavailable", reason: "catalog-source-commit-unavailable" };
|
||||
if (catalogComponentInputHashAtSource !== catalogComponentInputHash) {
|
||||
return { safeToReuse: false, status: "component-source-mismatch", reason: "catalog-component-input-hash-does-not-match-catalog-source-tree" };
|
||||
}
|
||||
if (catalogComponentInputHash !== componentInputHash) {
|
||||
return { safeToReuse: false, status: "component-input-mismatch", reason: "catalog-component-input-hash-differs-from-current-plan" };
|
||||
}
|
||||
if (text(catalogRecord.dockerfileHash) && catalogRecord.dockerfileHash !== dockerfileHash) {
|
||||
return { safeToReuse: false, status: "component-build-mismatch", reason: "catalog-dockerfile-hash-differs-from-current-plan" };
|
||||
}
|
||||
if (text(catalogRecord.buildArgsHash) && catalogRecord.buildArgsHash !== buildArgsHash) {
|
||||
return { safeToReuse: false, status: "component-build-mismatch", reason: "catalog-build-args-hash-differs-from-current-plan" };
|
||||
}
|
||||
return { safeToReuse: true, status: "safe-reuse", reason: "catalog-source-tree-and-current-component-input-match" };
|
||||
}
|
||||
|
||||
async function hashGitPathsIfCommitExists(repoRoot, targetRef, paths, options = {}) {
|
||||
try {
|
||||
await gitValue(repoRoot, ["rev-parse", "--verify", `${targetRef}^{commit}`]);
|
||||
return await hashGitPaths(repoRoot, targetRef, paths, options);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function text(value) {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
function envReuseCandidate(catalogRecord, envChanged) {
|
||||
if (envChanged) return { status: "not-reused", reason: "environment-affected" };
|
||||
if (!catalogRecord) return { status: "candidate-no-catalog", reason: "no-previous-artifact-record" };
|
||||
|
||||
Reference in New Issue
Block a user