diff --git a/scripts/artifact-publish.mjs b/scripts/artifact-publish.mjs index dccaff24..07958429 100644 --- a/scripts/artifact-publish.mjs +++ b/scripts/artifact-publish.mjs @@ -88,6 +88,7 @@ function parseArgs(argv) { buildkitAddr: defaultBuildkitAddr, externalServiceReportDir: null, externalServiceResultsEnv: false, + ciPlanPath: null, tektonResultsDir: null, emitReport: true, quietBuild: false, @@ -115,6 +116,7 @@ function parseArgs(argv) { else if (arg === "--buildkit-addr") args.buildkitAddr = readOption(argv, ++index, arg); else if (arg === "--external-service-report-dir") args.externalServiceReportDir = readOption(argv, ++index, arg); else if (arg === "--external-service-results-env") args.externalServiceResultsEnv = true; + else if (arg === "--ci-plan-path") args.ciPlanPath = readOption(argv, ++index, arg); else if (arg === "--tekton-results-dir") args.tektonResultsDir = readOption(argv, ++index, arg); else if (arg === "--no-report") args.emitReport = false; else if (arg === "--quiet-build") args.quietBuild = true; @@ -211,6 +213,7 @@ function printHelp() { " --buildkit-addr ADDR optional buildctl --addr endpoint for a sidecar BuildKit daemon", " --external-service-report-dir DIR collect affected service artifacts from per-service publish reports", " --external-service-results-env collect service artifacts from HWLAB_SERVICE_RESULT_* env vars", + " --ci-plan-path PATH reuse plan-artifacts JSON to scope external service reports", " --tekton-results-dir DIR write single-service build result files for Tekton results", ` --report PATH default: ${defaultReportPath}`, " --no-report print JSON without updating the temporary artifact JSON", @@ -223,6 +226,13 @@ async function readConfig(relativePath) { return readStructuredFile(repoRoot, relativePath); } +async function readOptionalCiPlan(pathValue) { + const text = typeof pathValue === "string" ? pathValue.trim() : ""; + if (!text) return null; + const fullPath = path.isAbsolute(text) ? text : path.resolve(repoRoot, text); + return JSON.parse(await readFile(fullPath, "utf8")); +} + async function readConfigIfPresent(relativePath, fallback = null) { try { return await readConfig(relativePath); @@ -2324,7 +2334,7 @@ async function main() { const buildCreatedAt = new Date().toISOString(); const repo = repoLabelFromRemote(remoteUrl); const producer = artifactProducer(process.env); - const ciPlan = await createCiPlan({ + const computedCiPlan = await createCiPlan({ repoRoot, lane: args.lane, targetRef: "HEAD", @@ -2334,6 +2344,8 @@ async function main() { baseImage: args.baseImage ?? undefined, ...(args.servicesExplicit ? { services: args.services } : {}) }); + const ciPlanOverride = await readOptionalCiPlan(args.ciPlanPath); + const ciPlan = ciPlanOverride ?? computedCiPlan; const allowedServiceIds = isRuntimeArtifactLane(args.lane) ? runtimeLaneServiceIdsFromDeploy(deployManifest, args.lane) : SERVICE_IDS; const services = enrichServicesWithCiPlan(await resolveServices(args.services, catalog, allowedServiceIds), ciPlan); const catalogByServiceId = new Map((catalog?.services ?? []).map((service) => [service.serviceId, service])); diff --git a/scripts/gitops-render.mjs b/scripts/gitops-render.mjs index 15848b9e..03509f3f 100644 --- a/scripts/gitops-render.mjs +++ b/scripts/gitops-render.mjs @@ -2104,7 +2104,7 @@ export HWLAB_ARTIFACT_LANE="$(params.lane)" export HWLAB_ARTIFACT_CATALOG_PATH="$(params.catalog-path)" export HWLAB_DEPLOY_MANIFEST_PATH="deploy/deploy.yaml" export HWLAB_ARTIFACT_IMAGE_TAG_MODE="$(params.image-tag-mode)" -HWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report /workspace/source/dev-artifacts.json --quiet-build --concurrency 1 --services "$(params.services)" --external-service-report-dir /workspace/source/service-results +HWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node scripts/artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report /workspace/source/dev-artifacts.json --quiet-build --concurrency 1 --services "$(params.services)" --external-service-report-dir /workspace/source/service-results --ci-plan-path /workspace/source/affected-services.json node scripts/refresh-artifact-catalog.mjs --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --target-ref HEAD --publish-report /workspace/source/dev-artifacts.json --no-write `; } diff --git a/scripts/gitops-render.test.ts b/scripts/gitops-render.test.ts index 3894b4d3..36095bc2 100644 --- a/scripts/gitops-render.test.ts +++ b/scripts/gitops-render.test.ts @@ -532,6 +532,9 @@ test("v03 render keeps node identity as data instead of generated structure", as assert.ok(generatedPaths.includes("argocd/application-v03.yaml")); assert.ok(generatedPaths.includes("tekton-v03/pipeline.yaml")); assert.ok(generatedPaths.includes("runtime-v03/external-postgres.yaml")); + const pipeline = await readFile(path.join(outDir, "tekton-v03", "pipeline.yaml"), "utf8"); + assert.match(pipeline, /--external-service-report-dir \/workspace\/source\/service-results/u); + assert.match(pipeline, /--ci-plan-path \/workspace\/source\/affected-services\.json/u); assert.ok(!generatedPaths.includes("runtime-v03/postgres.yaml")); for (const relativePath of generatedPaths) { assert.doesNotMatch(relativePath, /g14/i, `legacy node token leaked into generated path ${relativePath}`);