fix: ignore stale envreuse service reports (#2177)

This commit is contained in:
Lyon
2026-06-26 05:35:15 +08:00
committed by GitHub
parent b0f36a3789
commit a587493232
2 changed files with 119 additions and 5 deletions
+11 -5
View File
@@ -1846,7 +1846,8 @@ async function readExternalServiceArtifacts({ dir, services, commitId }) {
const artifacts = new Map();
const blockers = [];
for (const service of services) {
const buildRequired = service.buildRequired === true || service.ciBuildRequired === true;
const buildRequired = serviceRequiresCurrentBuildArtifact(service);
if (!buildRequired) continue;
const reportPath = externalServiceReportPath(dir, service.serviceId);
let report;
try {
@@ -1897,6 +1898,10 @@ async function readExternalServiceArtifacts({ dir, services, commitId }) {
return { artifacts, blockers };
}
function serviceRequiresCurrentBuildArtifact(service) {
return service?.buildRequired === true || service?.ciBuildRequired === true;
}
function artifactRecordFromServiceResultsEnv({ service, env }) {
const status = serviceResultValue(env, service.serviceId, "STATUS");
if (!status) return null;
@@ -2354,6 +2359,7 @@ async function main() {
const buildServiceIds = new Set(plannedBuildServices ?? services
.filter((service) => service.artifactRequired && (service.envReuse ? service.envChanged === true : affectedServiceIds.has(service.serviceId)))
.map((service) => service.serviceId));
const currentBuildArtifactServices = services.filter((service) => buildServiceIds.has(service.serviceId) && serviceRequiresCurrentBuildArtifact(service));
const preflightBuildServiceIds = args.externalServiceReportDir || args.externalServiceResultsEnv ? new Set() : buildServiceIds;
let blockers = await preflight({ args, services, catalog, deployManifest, baseImagePreflight, registryCapabilities, buildServiceIds: preflightBuildServiceIds });
@@ -2366,10 +2372,10 @@ async function main() {
}));
let externalArtifacts = new Map();
if (args.mode === "build" || args.mode === "publish") {
if (args.externalServiceReportDir && buildServiceIds.size > 0) {
if (args.externalServiceReportDir && currentBuildArtifactServices.length > 0) {
const externalResult = await readExternalServiceArtifacts({
dir: args.externalServiceReportDir,
services: services.filter((service) => buildServiceIds.has(service.serviceId)),
services: currentBuildArtifactServices,
commitId
});
externalArtifacts = externalResult.artifacts;
@@ -2387,7 +2393,7 @@ async function main() {
if (externalArtifacts.has(service.serviceId)) {
return externalArtifacts.get(service.serviceId);
}
if (!service.artifactRequired || buildServiceIds.has(service.serviceId)) {
if (!service.artifactRequired || (buildServiceIds.has(service.serviceId) && serviceRequiresCurrentBuildArtifact(service))) {
return artifactRecord({
args,
service,
@@ -2426,7 +2432,7 @@ async function main() {
});
} else if (args.mode === "build" || args.mode === "publish") {
const artifactByServiceId = new Map(artifacts.map((artifact) => [artifact.serviceId, artifact]));
await mapWithConcurrency(services.filter((item) => buildServiceIds.has(item.serviceId) && !externalArtifacts.has(item.serviceId)), args.concurrency, async (service) => {
await mapWithConcurrency(services.filter((item) => buildServiceIds.has(item.serviceId) && serviceRequiresCurrentBuildArtifact(item) && !externalArtifacts.has(item.serviceId)), args.concurrency, async (service) => {
emit("build_start", { serviceId: service.serviceId, image: imageRef(args.registryPrefix, service.serviceId, shortCommit) });
const artifact = await buildService({ args, repo, commitId, shortCommit, service, buildCreatedAt });
artifactByServiceId.set(service.serviceId, artifact);
+108
View File
@@ -281,6 +281,114 @@ test("artifact reuse paths preserve catalog provenance instead of current planne
assert.doesNotMatch(gitopsRender, /"component-input-hash": planned\.componentInputHash \|\| service\.componentInputHash \|\| ""/u);
});
test("artifact publish ignores stale external reports for envreuse services that do not need current build artifacts", async () => {
const currentCommit = (await git(process.cwd(), ["rev-parse", "HEAD"])).stdout.trim();
const oldCommit = "0".repeat(40);
const digest = `sha256:${"3".repeat(64)}`;
const tmp = await mkdtemp(path.join(os.tmpdir(), "hwlab-artifact-stale-report-"));
const reportDir = path.join(tmp, "service-results");
await mkdir(reportDir, { recursive: true });
const image = "127.0.0.1:5000/hwlab/hwlab-cloud-web-env:env-test";
const deployPath = path.join(tmp, "deploy.yaml");
const catalogPath = path.join(tmp, "artifact-catalog.v03.json");
const ciPlanPath = path.join(tmp, "ci-plan.json");
const deploy = createDeployFixture({ serviceIds: ["hwlab-cloud-web"] });
deploy.lanes.v03.namespace = "hwlab-v03";
deploy.lanes.v03.endpoint = "https://hwlab.pikapython.com";
await writeStructuredFile(process.cwd(), deployPath, deploy);
const catalog = createCatalogFixture("ready", {
serviceIds: ["hwlab-cloud-web"],
sourceCommitId: currentCommit,
bootCommits: { "hwlab-cloud-web": currentCommit },
environmentInputHashes: { "hwlab-cloud-web": "e".repeat(64) },
codeInputHashes: { "hwlab-cloud-web": "c".repeat(64) }
});
catalog.environment = "v03";
catalog.profile = "v03";
catalog.namespace = "hwlab-v03";
catalog.allowedProfiles = ["v03"];
catalog.forbiddenProfiles = ["dev", "prod"];
for (const service of catalog.services) {
service.profile = "v03";
service.namespace = "hwlab-v03";
}
await writeFile(catalogPath, JSON.stringify(catalog, null, 2));
await writeFile(ciPlanPath, JSON.stringify({
planVersion: "v1",
sourceCommitId: currentCommit,
shortCommitId: currentCommit.slice(0, 12),
baseRef: "HEAD~1",
targetRef: "HEAD",
compatibility: { lane: "v03" },
changedPathSummary: {},
imageBuildRequired: false,
affectedServices: [],
buildServices: ["hwlab-cloud-web"],
rolloutServices: [],
reusedServices: ["hwlab-cloud-web"],
buildSkippedCount: 1,
ciCdPlan: {},
services: [{
serviceId: "hwlab-cloud-web",
runtimeMode: "env-reuse-git-mirror-checkout",
envReuse: true,
affected: false,
buildRequired: false,
envChanged: false,
codeChanged: false,
environmentImage: image,
environmentDigest: digest,
environmentInputHash: "e".repeat(64),
codeInputHash: "c".repeat(64),
bootRepo: "git@github.com:pikasTech/HWLAB.git",
bootCommit: currentCommit,
bootSh: "deploy/runtime/boot/hwlab-cloud-web.sh",
reason: ["component-inputs-unchanged"],
reuse: { status: "ready", image, digest, reusedFrom: "e".repeat(64) }
}]
}, null, 2));
await writeFile(path.join(reportDir, "hwlab-cloud-web.json"), JSON.stringify({
artifactPublish: {
services: [{
serviceId: "hwlab-cloud-web",
status: "reused",
commitId: oldCommit,
sourceCommitId: oldCommit,
image: "127.0.0.1:5000/hwlab/hwlab-cloud-web-env:env-old",
imageTag: "env-old",
digest,
repositoryDigest: `127.0.0.1:5000/hwlab/hwlab-cloud-web-env@${digest}`,
runtimeMode: "env-reuse-git-mirror-checkout",
envReuse: true
}]
}
}, null, 2));
const { stdout } = await execFileAsync(process.execPath, [
"scripts/artifact-publish.mjs",
"--build",
"--lane", "v03",
"--services", "hwlab-cloud-web",
"--deploy-config", deployPath,
"--catalog-path", catalogPath,
"--ci-plan-path", ciPlanPath,
"--external-service-report-dir", reportDir,
"--registry-prefix", "127.0.0.1:5000/hwlab",
"--no-report"
], {
cwd: process.cwd(),
env: { ...process.env, HWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY: "false" }
});
const result = JSON.parse(stdout);
const blockerText = JSON.stringify(result.blockers ?? []);
assert.equal(blockerText.includes(oldCommit), false);
assert.equal(result.status, "built");
assert.equal(result.services[0].serviceId, "hwlab-cloud-web");
assert.equal(result.services[0].status, "reused");
assert.equal(result.services[0].sourceCommitId, currentCommit);
});
test("component model uses service paths declared in deploy config", () => {
const deploy = createDeployFixture();
const models = componentModelsForServices(["hwlab-cloud-api"], deploy.lanes.v02);