From 609c78f4efe812347c7b3dc72db43ce5413cb245 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 15 Jul 2026 07:24:25 +0200 Subject: [PATCH 1/3] =?UTF-8?q?docs(pikaoa):=20=E8=B7=9F=E8=B8=AA=20PaC=20?= =?UTF-8?q?admission=20bootstrap=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/MDTODO/pikaoa-enterprise-platform.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/MDTODO/pikaoa-enterprise-platform.md b/docs/MDTODO/pikaoa-enterprise-platform.md index 4c013294..50c3991d 100644 --- a/docs/MDTODO/pikaoa-enterprise-platform.md +++ b/docs/MDTODO/pikaoa-enterprise-platform.md @@ -132,6 +132,9 @@ ##### R1.5.9.13 [in_progress] 接入仅跟随产品 master 的 pikaoa-test-nc01 独立测试 CI/CD,通过正常 source PR merge 自动交付 NC01 pikaoa-test,保持生产 consumer 不触发(pikasTech/unidesk#2105),完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.5.9.13_Task_Report.md)。 +###### R1.5.9.13.1 [in_progress] + +修复 PikaOA 首次 PaC bootstrap 未同步 admission provenance desired policy/binding 的缺口,使配置指纹漂移降级为非阻塞 warning 且正常产品 PR merge 产生的 PipelineRun 能继续执行,执行记录见 [pikasTech/unidesk#2127](https://github.com/pikasTech/unidesk/issues/2127),完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.5.9.13.1_Task_Report.md)。 ### R1.6 审核并合并双仓 PR,执行首次受控 PaC bootstrap,由正常 source PR merge 自动发布,依赖 R1.3-R1.5,完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.6_Task_Report.md)。 From e60dce625bb7644de463fe4852b0f97241e3abbc Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 15 Jul 2026 07:37:43 +0200 Subject: [PATCH 2/3] fix: bootstrap PaC admission provenance --- ...-infra-pipelines-as-code-bootstrap.test.ts | 10 ++++-- ...tform-infra-pipelines-as-code-bootstrap.ts | 2 ++ ...platform-infra-pipelines-as-code-remote.sh | 36 ++++++++++++++++++- .../src/platform-infra-pipelines-as-code.ts | 3 +- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts b/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts index e769fe11..bc0a0982 100644 --- a/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts +++ b/scripts/src/platform-infra-pipelines-as-code-bootstrap.test.ts @@ -166,10 +166,16 @@ test("PaC apply checks the Gitea repository before dry-run return and cluster mu const preflight = remote.indexOf("if ! repository_preflight", action); const dryRun = remote.indexOf('if [ "$UNIDESK_PAC_DRY_RUN" = "1" ]', action); const release = remote.indexOf(" apply_release", action); + const admission = remote.indexOf(" apply_admission_manifest", release); + const rbac = remote.indexOf(" rbac_tmp=$(mktemp)", admission); expect(action).toBeGreaterThanOrEqual(0); expect(preflight).toBeGreaterThan(action); expect(dryRun).toBeGreaterThan(preflight); expect(release).toBeGreaterThan(dryRun); + expect(admission).toBeGreaterThan(release); + expect(rbac).toBeGreaterThan(admission); + expect(remote).toContain('"admission":{"applied":%s,"ready":%s}'); + expect(remote).toContain('"admissionProvenance":%s'); expect(remote).toContain('"error":"gitea-repository-missing"'); }); @@ -197,7 +203,7 @@ test("PaC bootstrap projects typed stages and compact JSON", () => { const projection = projectPacBootstrapResult(result); expect(projection.ok).toBe(true); expect(JSON.stringify(projection)).not.toContain("pipelinesAsCodeApply"); - expect((projection.stages as Record[]).map((item) => item.id)).toEqual(["gitea-init", "pac-controller", "tekton-consumer", "gitops-argo"]); + expect((projection.stages as Record[]).map((item) => item.id)).toEqual(["gitea-init", "pac-controller", "pac-admission", "tekton-consumer", "gitops-argo"]); const rendered = renderPacBootstrap(result).renderedText; expect(rendered).toContain("PRECONDITIONS"); expect(rendered).toContain("yaml-repository-exact-match"); @@ -237,6 +243,6 @@ test("PaC bootstrap distinguishes GitHub access failure from Gitea and PaC stage }); expect((projection.blockers as Record[])).toEqual([{ code: "github-repository-not-found-or-no-access", stage: "github-upstream", reason: "repository unavailable" }]); expect((projection.preconditions as Record[])[2]).toMatchObject({ ok: null, code: "gitea-bootstrap-skipped" }); - expect((projection.stages as Record[]).map((item) => item.state)).toEqual(["skipped", "skipped", "skipped", "skipped"]); + expect((projection.stages as Record[]).map((item) => item.state)).toEqual(["skipped", "skipped", "skipped", "skipped", "skipped"]); expect((projection.next as Record).kind).toBe("read-only-status"); }); diff --git a/scripts/src/platform-infra-pipelines-as-code-bootstrap.ts b/scripts/src/platform-infra-pipelines-as-code-bootstrap.ts index 1bd3f6fa..3a8e2b84 100644 --- a/scripts/src/platform-infra-pipelines-as-code-bootstrap.ts +++ b/scripts/src/platform-infra-pipelines-as-code-bootstrap.ts @@ -69,6 +69,7 @@ export function projectPacBootstrapResult(result: Record): Reco const gitea = record(result.giteaBootstrap); const apply = record(result.pipelinesAsCodeApply); const remote = record(apply.remote); + const admission = record(remote.admission); const blockers = bootstrapBlockers(githubPreflight, gitea, apply, remote); const dryRun = result.mode === "dry-run"; const repositoryMissing = remote.error === "gitea-repository-missing"; @@ -101,6 +102,7 @@ export function projectPacBootstrapResult(result: Record): Reco stages: [ { id: "gitea-init", ok: giteaSkipped ? null : giteaReady, state: giteaSkipped ? "skipped" : giteaReady ? (dryRun ? "planned" : "ready") : "failed", mutation: gitea.mutation === true }, { id: "pac-controller", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true }, + { id: "pac-admission", ok: applySkipped ? null : dryRun ? true : admission.ready === true, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : dryRun ? "planned" : admission.ready === true ? "ready" : "failed", mutation: admission.applied === true }, { id: "tekton-consumer", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true }, { id: "gitops-argo", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true }, ], diff --git a/scripts/src/platform-infra-pipelines-as-code-remote.sh b/scripts/src/platform-infra-pipelines-as-code-remote.sh index 292b3bd1..e268cdcc 100644 --- a/scripts/src/platform-infra-pipelines-as-code-remote.sh +++ b/scripts/src/platform-infra-pipelines-as-code-remote.sh @@ -284,6 +284,13 @@ apply_release() { rm -f "$tmp" } +apply_admission_manifest() { + tmp=$(mktemp) + printf '%s' "$UNIDESK_PAC_ADMISSION_MANIFEST_B64" | base64 -d > "$tmp" + kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_PAC_FIELD_MANAGER-admission-bootstrap" -f "$tmp" >/dev/null + rm -f "$tmp" +} + wait_ready() { timeout="${UNIDESK_PAC_WAIT_TIMEOUT_SECONDS:-55}" end=$(( $(now_epoch) + timeout )) @@ -313,6 +320,11 @@ apply_action() { return fi apply_release + admission_applied=false + if [ "${UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED:-0}" = "1" ]; then + apply_admission_manifest + admission_applied=true + fi kubectl create ns "$UNIDESK_PAC_TARGET_NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - >/dev/null if [ "${UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED:-0}" = "1" ]; then rbac_tmp=$(mktemp) @@ -351,7 +363,16 @@ apply_action() { hook_id=$(ensure_webhook) ready=false if wait_ready; then ready=true; fi - printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"argoBootstrap":%s,"preflight":{"repositoryExists":true,"httpStatus":"2xx","repository":"%s/%s","valuesPrinted":false},"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ready" "$ready" "$argo_bootstrap" "$(json_string "$UNIDESK_PAC_GITEA_OWNER")" "$(json_string "$UNIDESK_PAC_GITEA_REPO")" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")" + admission_ready=true + if [ "${UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED:-0}" = "1" ]; then + admission_ready=false + if wait_admission_ready; then admission_ready=true; fi + else + prepare_admission_provenance_state + fi + ok=false + if [ "$ready" = true ] && [ "$admission_ready" = true ]; then ok=true; fi + printf '{"ok":%s,"mode":"confirmed","mutation":true,"controllerReady":%s,"admission":{"applied":%s,"ready":%s},"admissionProvenance":%s,"argoBootstrap":%s,"preflight":{"repositoryExists":true,"httpStatus":"2xx","repository":"%s/%s","valuesPrinted":false},"webhook":{"present":true,"id":"%s","url":"%s"},"repository":"%s","namespace":"%s","valuesPrinted":false}\n' "$ok" "$ready" "$admission_applied" "$admission_ready" "$UNIDESK_PAC_ADMISSION_STATE_JSON" "$argo_bootstrap" "$(json_string "$UNIDESK_PAC_GITEA_OWNER")" "$(json_string "$UNIDESK_PAC_GITEA_REPO")" "$(json_string "$hook_id")" "$(json_string "$UNIDESK_PAC_WEBHOOK_URL")" "$(json_string "$UNIDESK_PAC_REPOSITORY_NAME")" "$(json_string "$UNIDESK_PAC_TARGET_NAMESPACE")" } condition_status() { @@ -415,6 +436,19 @@ prepare_admission_provenance_state() { export UNIDESK_PAC_ADMISSION_STATE_JSON } +wait_admission_ready() { + timeout="${UNIDESK_PAC_WAIT_TIMEOUT_SECONDS:-55}" + end=$(( $(now_epoch) + timeout )) + while :; do + prepare_admission_provenance_state + if printf '%s' "$UNIDESK_PAC_ADMISSION_STATE_JSON" | node -e 'let input="";process.stdin.on("data",chunk=>input+=chunk);process.stdin.on("end",()=>process.exit(JSON.parse(input).ready===true?0:1));'; then + return 0 + fi + if [ "$(now_epoch)" -ge "$end" ]; then return 1; fi + sleep 2 + done +} + pipeline_rows() { payload_file=$(mktemp) kubectl -n "$UNIDESK_PAC_TARGET_NAMESPACE" get pipelinerun -o json >"$payload_file" 2>/dev/null || printf '{"items":[]}' >"$payload_file" diff --git a/scripts/src/platform-infra-pipelines-as-code.ts b/scripts/src/platform-infra-pipelines-as-code.ts index b4738c89..69f0f204 100644 --- a/scripts/src/platform-infra-pipelines-as-code.ts +++ b/scripts/src/platform-infra-pipelines-as-code.ts @@ -28,7 +28,7 @@ import { type PacSourceArtifactRuntimeObservation, type PacSourceArtifactSpec, } from "./platform-infra-pipelines-as-code-source-artifact"; -import { pacAdmissionDesiredIdentity } from "./platform-infra-pac-provenance"; +import { pacAdmissionDesiredIdentity, renderPacAdmissionDesiredFragment } from "./platform-infra-pac-provenance"; import { pacConsumerRbacDesiredIdentity, renderPacConsumerRbacDesiredFragment } from "./platform-infra-pac-consumer-rbac"; import { runGiteaMirrorBootstrapPreflight, runPlatformInfraGiteaCommand } from "./platform-infra-gitea"; import { readGiteaConfig } from "./platform-infra-gitea-config"; @@ -1754,6 +1754,7 @@ function remoteScript(action: "apply" | "status" | "history" | "debug-step", pac UNIDESK_PAC_PIPELINE_RUN_PREFIX: consumer.pipelineRunPrefix, UNIDESK_PAC_CONSUMER_CONFIG_B64: Buffer.from(JSON.stringify(consumerConfig), "utf8").toString("base64"), UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED: consumer.deliveryProvenance?.required === true ? "1" : "0", + UNIDESK_PAC_ADMISSION_MANIFEST_B64: Buffer.from(renderPacAdmissionDesiredFragment(target.id), "utf8").toString("base64"), UNIDESK_PAC_CONSUMER_RBAC_MANIFEST_B64: Buffer.from(renderPacConsumerRbacDesiredFragment(target.id), "utf8").toString("base64"), UNIDESK_PAC_RUNNER_SERVICE_ACCOUNT_MANIFEST_B64: Buffer.from(runnerServiceAccountManifest(consumer), "utf8").toString("base64"), UNIDESK_PAC_RUNNER_SERVICE_ACCOUNT_NAME: consumer.runnerServiceAccount?.name ?? "", From 3d2c84e46f84eafda42de8ab74c6467e8901caa9 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 15 Jul 2026 07:45:01 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix(pikaoa):=20=E4=BF=AE=E6=AD=A3=20PaC=20w?= =?UTF-8?q?orkspace=20=E6=8C=82=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/src/pikaoa-test-delivery-renderer.test.ts | 9 +++++++++ scripts/src/pikaoa-test-delivery-renderer.ts | 9 +++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/src/pikaoa-test-delivery-renderer.test.ts b/scripts/src/pikaoa-test-delivery-renderer.test.ts index 2585747e..6421cb61 100644 --- a/scripts/src/pikaoa-test-delivery-renderer.test.ts +++ b/scripts/src/pikaoa-test-delivery-renderer.test.ts @@ -48,6 +48,15 @@ describe("PikaOA test PaC renderer", () => { const rendered = renderPikaoaTestDesiredPipeline(rendererBinding(), source); const spec = rendered.pipeline.spec as Record; const tasks = spec.tasks as Array>; + for (const task of tasks) { + const taskSpec = task.taskSpec as Record; + expect(taskSpec.workspaces).toEqual([{ name: "workspace", mountPath: "/workspace" }]); + const steps = taskSpec.steps as Array>; + for (const step of steps) { + const mounts = (step.volumeMounts ?? []) as Array>; + expect(mounts.some((mount) => mount.name === "workspace")).toBe(false); + } + } for (const name of ["build-api", "build-worker", "build-web"]) { const task = tasks.find((item) => item.name === name); expect(task?.runAfter).toEqual(["source"]); diff --git a/scripts/src/pikaoa-test-delivery-renderer.ts b/scripts/src/pikaoa-test-delivery-renderer.ts index 9ff83392..4524142e 100644 --- a/scripts/src/pikaoa-test-delivery-renderer.ts +++ b/scripts/src/pikaoa-test-delivery-renderer.ts @@ -98,7 +98,7 @@ function sourceTask(target: TestTargetSpec): Record { { name: "gitops-username", type: "string" }, { name: "gitops-secret-name", type: "string" }, ], - workspaces: [{ name: "workspace" }], + workspaces: [{ name: "workspace", mountPath: "/workspace" }], steps: [{ name: "checkout", image: target.ci.toolsImage, @@ -132,7 +132,6 @@ printf '{"ok":true,"phase":"source","sourceCommit":"%s","valuesPrinted":false}\n `, securityContext: nonRootSecurity(), volumeMounts: [ - { name: "workspace", mountPath: "/workspace" }, { name: "source-token", mountPath: "/var/run/pikaoa-source", readOnly: true }, ], }], @@ -158,7 +157,7 @@ function imageBuildTask(target: TestTargetSpec, component: "api" | "worker" | "w runAfter: ["source"], taskSpec: { params: [{ name: "revision", type: "string" }], - workspaces: [{ name: "workspace" }], + workspaces: [{ name: "workspace", mountPath: "/workspace" }], results: [{ name: "digest", type: "string" }, { name: "image", type: "string" }], steps: [{ name: "build", @@ -186,7 +185,6 @@ printf '%s@%s' ${shellSingleQuote(imageRepository)} "$digest" > "$(results.image printf '{"ok":true,"phase":"${taskName}","sourceCommit":"%s","imageTag":"%s","digest":"%s","valuesPrinted":false}\n' "$revision" "$tag" "$digest" `, securityContext: { privileged: true, runAsUser: 1000, runAsGroup: 1000 }, - volumeMounts: [{ name: "workspace", mountPath: "/workspace" }], }], }, params: [{ name: "revision", value: "$(params.revision)" }], @@ -210,7 +208,7 @@ function gitOpsTask(target: TestTargetSpec, runtimeManifest: string): Record