fix: keep G14 generated state on gitops branch

This commit is contained in:
Codex
2026-05-27 10:14:24 +08:00
parent 278bbe1c6a
commit 336a9b2c2c
10 changed files with 188 additions and 154 deletions
+6 -1
View File
@@ -24,6 +24,11 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
- 当前发布只走 G14 k3s Tekton + `G14-gitops` + Argo CD;镜像构建 helper 是 `scripts/g14-artifact-publish.mjs`desired state 由 `scripts/g14-gitops-render.mjs` 生成,细则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
- 发现旧脚本、旧文档或旧 `hwlab-cli cicd` 再次进入 G14 Pipeline、AGENTS 或长期参考时,优先删除旧入口并把调用方改到 G14 GitOps 路径,不再做兼容保留。
## P0 门禁最小化纪律
- 架构迁移时,过时的自检、预检、guard、gate 优先拆除;不要在旧门禁上继续加例外、加复杂度或制造噪声。
- 新增自检、预检、guard、gate 必须遵循最小原则,只覆盖明确高价值风险;禁止乱加门禁导致系统僵化、难迁移、难调试或产生大量误报和摩擦。
## 工作区
- G14 分支固定 source workspace 是 G14 节点上的 `/root/hwlab`,固定使用 `G14` 分支和 `origin git@github.com:pikasTech/HWLAB.git`。在 G14 上进行代码、文档、GitOps render、Tekton/poller/Argo CD 修复或 CI/CD 验证前,必须先确认 `pwd``/root/hwlab``git status --short --branch``G14...origin/G14`;不满足时先停止并修正 workspace。
@@ -77,7 +82,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
- Cloud Workbench 布局/遮挡 smoke`npm run web:layout`local-build 用 `npm run web:layout:build`DEV deploy 后用 `npm run web:layout:live`
- G14 artifact build helper`node scripts/g14-artifact-publish.mjs --publish ...`,只能由 G14 Tekton task 携带 CI artifact identity 调用;人工发布走 G14 poller/GitOps,不走 legacy CLI CD。
- G14 monorepo 组件计划:`node scripts/g14-ci-plan.mjs --base-ref <ref> --target-ref <ref> --pretty`,只读分析 affected/reused services,基于内建 service-path component model 与 `deploy/deploy.json`;细则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
- G14 GitOps 渲染:`npm run g14:gitops:render`检查已生成 Tekton/Argo CD manifests`npm run g14:gitops:check`
- G14 GitOps 渲染:`npm run g14:gitops:render`source 分支不再运行生成物 drift check,发布态由 Tekton 写入 `G14-gitops`
- DEV 依赖 runtime base 构建:`npm run dev-runtime-base:build`
- Legacy D601 DEV CD:旧脚本入口已删除;事故回放只读历史 issue/commit,不恢复旧命令。
- CI/CD job 查询:`node tools/hwlab-cli/bin/hwlab-cli.mjs cicd status|logs|report <jobId>`
+8 -6
View File
@@ -70,17 +70,19 @@ node scripts/deploy-desired-state-plan.mjs --promotion-commit <sha> --check
The desired-state plan reads `deploy/deploy.json`,
`deploy/artifact-catalog.dev.json`, `deploy/k8s/base/workloads.yaml`, and the
optional artifact report snapshot. `deploy/deploy.json` is the human-authored
runtime config truth source. Generated image identity lives in
`deploy/artifact-catalog.dev.json`; GitOps render overlays catalog image fields
and generated env mirrors such as `HWLAB_COMMIT_ID`, `HWLAB_IMAGE`,
runtime config truth source. Published image identity lives in
`G14-gitops:deploy/artifact-catalog.dev.json`; the source branch catalog is only
a seed contract for local checks. GitOps render overlays catalog image fields and
generated env mirrors such as `HWLAB_COMMIT_ID`, `HWLAB_IMAGE`,
`HWLAB_IMAGE_TAG`, and `HWLAB_SKILLS_COMMIT_ID` into runtime manifests.
The plan blocks if generated artifact identity leaks back into `deploy.json`,
or if `hwlab-cloud-api` loses the Code Agent provider Secret ref
`hwlab-code-agent-provider/openai-api-key`, the `OPENAI_API_KEY` env name, or
the DEV egress proxy base-url env. Promotion updates must refresh only
`deploy/artifact-catalog.dev.json` from a publish report before render/check;
report files are contextual evidence only and must not override source truth.
the DEV egress proxy base-url env. Promotion updates must refresh only the
workspace catalog from a publish report before render; Tekton writes the
refreshed catalog to `G14-gitops`, not back to the source branch. Report files
are contextual evidence only and must not override human-authored source truth.
This is source/dry-run support only. It does not prove a registry image exists,
does not build, pull, push, apply, restart, or touch PROD, and must not be used
+97 -97
View File
@@ -5,13 +5,13 @@
"profile": "dev",
"namespace": "hwlab-dev",
"endpoint": "http://74.48.78.17:16667",
"commitId": "8bdcff5",
"artifactState": "published",
"commitId": "278bbe1",
"artifactState": "contract-skeleton",
"publish": {
"ciPublished": true,
"registryVerified": true,
"provenance": "g14-ci-artifact:hwlab-g14-ci-poll-8bdcff56c580-collect",
"note": "Digest fields were copied from a successful DEV artifact publish report for this source commit."
"ciPublished": false,
"registryVerified": false,
"provenance": "not_available_until_publish",
"note": "Artifact identity was refreshed to this source commit, but no publish report proved registry digests."
},
"healthContract": {
"method": "GET",
@@ -66,11 +66,11 @@
"services": [
{
"serviceId": "hwlab-cloud-api",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:af46386",
"imageTag": "af46386",
"digest": "sha256:fe724e72e7cea3c93abe17085c4d885c78d34d18eb2a3c01f987d1971e2f4425",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -78,8 +78,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:43.480Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "8d533cb736c3bf30fe97ef0ddf3d24047cad1b78",
"componentInputHash": "7498160c36db86db76d3c1b103e397f2b2304248136d3b79eafe1ab69d4abeff",
@@ -101,11 +101,11 @@
},
{
"serviceId": "hwlab-cloud-web",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:af46386",
"imageTag": "af46386",
"digest": "sha256:3f5d4dcd1df4f736293d4223acb8104fd857e847a3a9a0acec4ef031ea333913",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -113,8 +113,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:45.158Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "8d533cb736c3bf30fe97ef0ddf3d24047cad1b78",
"componentInputHash": "fdd27830a0cef173fc07a2cab8cd469d67aaff9da12bb0195f188e323854f93a",
@@ -136,11 +136,11 @@
},
{
"serviceId": "hwlab-agent-mgr",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:af46386",
"imageTag": "af46386",
"digest": "sha256:5a55d6199b5bf2db4afa55a0be8cc6eae38f2dcac8a7b374ebbec2af2f36d71c",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -148,8 +148,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:44.484Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "ce4aa5c0b74c873d3270a71d9481a8255571b574f024bd5980f18547b4361860",
@@ -171,11 +171,11 @@
},
{
"serviceId": "hwlab-agent-worker",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:af46386",
"imageTag": "af46386",
"digest": "sha256:03f09333657bb37ad059004aabb4484503f79866ba19272d5af9bf611bbb9c5d",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -183,8 +183,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:45.112Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "7b426c226ec6f6d4de516f58055f3bf26d911477a88ebeef320066b1903ef40f",
@@ -206,11 +206,11 @@
},
{
"serviceId": "hwlab-gateway",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:af46386",
"imageTag": "af46386",
"digest": "sha256:1f44666c01f11416fee24b2d671c2ddcea3b5a6c575fe16ad0cf5307315d35e6",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -218,8 +218,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:45.140Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "6c97b4f8e49ba46148dde9126a21f475356bfdd4d6826326799d03d68212002e",
@@ -241,11 +241,11 @@
},
{
"serviceId": "hwlab-gateway-simu",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:af46386",
"imageTag": "af46386",
"digest": "sha256:9b65bfff46f5fa206e7887340b9e7df2647eb4d0c186f9ad9796636551cd121b",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -253,8 +253,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:45.127Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "184706b960dc00c23d6aacf35bde93558d422329635752cfb85796c6fe5e4820",
@@ -276,11 +276,11 @@
},
{
"serviceId": "hwlab-box-simu",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:af46386",
"imageTag": "af46386",
"digest": "sha256:909fd838bf6fa7dc2b2eb7b2a868bde4d110aab1640272ee63dbcf6ae488b330",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -288,8 +288,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:45.148Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "1dfe5c35338d049b996a27f628d8d7b64ddacfa51de07e4c1cbd4a0f19c289bf",
@@ -311,11 +311,11 @@
},
{
"serviceId": "hwlab-patch-panel",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:af46386",
"imageTag": "af46386",
"digest": "sha256:5d49ba34c8dc1e71eb5fedc769ea0759b4ab66d7665086fc773c2020fcd11a2b",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -323,8 +323,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:48.434Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "4b84d74569d7d847c886695014aa84236c678b19ceeaa7df1f80ff7aa06c00ed",
@@ -346,11 +346,11 @@
},
{
"serviceId": "hwlab-router",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-router:af46386",
"imageTag": "af46386",
"digest": "sha256:074539cf5a9aef2330c6dc8df96d0be76f5fbb940f8d0ad20f1d67cb1f299666",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-router:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -358,8 +358,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:48.571Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "a2e72fca1d0acb7b5db4ecf77ff5ffacae7e897e8d50920cae8b17fcb6e1e6fb",
@@ -381,11 +381,11 @@
},
{
"serviceId": "hwlab-tunnel-client",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:af46386",
"imageTag": "af46386",
"digest": "sha256:f1f200009731ba832dc26258b01c3abb4de711237728a95124822e0d056c70fe",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -393,8 +393,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:48.544Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "58f4fc6f1b56c6074cdeae1d33bfe22f54072ef0d45351d3186cdcf15a3f1573",
@@ -416,11 +416,11 @@
},
{
"serviceId": "hwlab-edge-proxy",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:af46386",
"imageTag": "af46386",
"digest": "sha256:b9dbf6f08bfea10ec9ca9c15cd864e96eb37e483fcd8716513dd74533d8b3cf0",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -428,8 +428,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:48.521Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "497d88f5f34075f2ba064cdc10d3a80a236e96e4eab4ca71ae3138a751201315",
@@ -451,11 +451,11 @@
},
{
"serviceId": "hwlab-cli",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:af46386",
"imageTag": "af46386",
"digest": "sha256:3908855f68043119452bd40109668c7a163c33bf048ddcf0cc9c1097ef741c5a",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-cli:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -463,8 +463,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:48.479Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "bff91d0ad2c23aceacc494cd4ad84e93d163c9bcae8815ac4018f2a0f37a0617",
@@ -486,11 +486,11 @@
},
{
"serviceId": "hwlab-agent-skills",
"commitId": "af46386",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:af46386",
"imageTag": "af46386",
"digest": "sha256:8cdf6922fda03703288a796b107f9185c3635f2768bc0bcf9b8272bf33ae7547",
"publishState": "reused",
"commitId": "278bbe1",
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:278bbe1",
"imageTag": "278bbe1",
"digest": "not_published",
"publishState": "skeleton-only",
"profile": "dev",
"namespace": "hwlab-dev",
"healthPath": "/health/live",
@@ -498,8 +498,8 @@
"publishEnabled": true,
"artifactRequired": true,
"artifactScope": "required",
"notPublishedReason": null,
"buildCreatedAt": "2026-05-27T00:14:48.439Z",
"notPublishedReason": "publish_not_run",
"buildCreatedAt": null,
"buildSource": null,
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
"componentInputHash": "33cc71d1216f9f523499c950c445b6d6d4ac604e8048c1a2c3a9561b3bdce9e8",
+19 -17
View File
@@ -6,8 +6,8 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
- Source of truth:业务版本以 Git source commit 为唯一身份;镜像 tag、OCI labels、runtime annotation 和 Argo CD desired state 都必须记录同一个 source commit。
- CITekton 在 G14 k3s 内运行 `hwlab-g14-ci-image-publish` Pipeline,由 `scripts/g14-gitops-render.mjs` 直接生成原生 task;最小校验固定为 `repo-reports-guard``g14-contract-check``codex-api-forwarder-check`,随后按 component plan 做 per-service BuildKit publish 与 GitOps promote;没有 `CI.json` runner、DIND 单任务发布或 Docker fallback。
- Artifact:镜像使用 commit tag,例如 `127.0.0.1:5000/hwlab/hwlab-cloud-api:<shortCommit>`digest 由 registry 返回,CI report 只作为审计证据,不作为 CD 真相。
- Branch split`G14` 是源码监控分支;`G14-gitops` 是 Tekton promotion 写入的生成分支,保存 `deploy/gitops/g14/**` desired state
- Artifact:镜像使用 commit tag,例如 `127.0.0.1:5000/hwlab/hwlab-cloud-api:<shortCommit>`digest 由 registry 返回,CI report 只作为审计证据,不作为 CD 真相。发布态 artifact catalog 由 Tekton 写入 `G14-gitops:deploy/artifact-catalog.dev.json`
- Branch split`G14` 是源码监控分支,只保存人写源码、声明和 seed contract`G14-gitops` 是 Tekton promotion 写入的生成分支,保存 `deploy/artifact-catalog.dev.json``deploy/gitops/g14/**` desired state。CI/CD 不再把 catalog promotion commit 写回 `G14`
- CDArgo CD 只消费 `G14-gitops:deploy/gitops/g14/runtime-dev``deploy/gitops/g14/runtime-prod` 的 Git desired state,不重新构建镜像,不读取 D601 状态,不获取 legacy DEV CD Lease。
- FRPG14 DEV 通过 `hwlab-dev/hwlab-g14-frpc` 暴露 `17666/17667`G14 PROD 通过 `hwlab-prod/hwlab-g14-prod-frpc` 暴露 `18666/18667`master frps 的 `deploy/frp/frps.dev.toml` 与实际 `/etc/frp/frps.toml` 必须同时放行这四个 G14 端口。
- 并行性:不同 source commit 的 CI build 不共享发布锁;并行安全由 immutable commit tag/digest 和 Git desired state 保证。最终运行版本由 Argo CD 当前同步的 Git revision 决定。
@@ -16,6 +16,8 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
`scripts/g14-gitops-render.mjs` 是 G14 专用转换器:
- 架构迁移时,过时的自检、预检、guard、gate 优先删除,不在旧门禁上叠加例外或复杂度;新门禁只允许覆盖明确高价值风险,必须保持最小、低噪声、易迁移。
- 直接声明 Tekton Pipeline、最小原语校验 task 和 PipelineRun 样板;不再读取 `CI.json` 或生成 `ci-json` step。
- 读取 `deploy/deploy.json``deploy/k8s/*`,生成 Argo CD 可消费的 G14 runtime Kustomize path。
- G14 Tekton 的镜像构建发布入口必须是 `scripts/g14-artifact-publish.mjs`;它只是集群内 Task 的 build/push helper,不做 rollout、不写 D601、不获取 legacy DEV CD Lease。旧 `scripts/dev-artifact-publish.mjs` 入口已删除;`dev-cd-apply``ci-publish` 和旧 `main` JS CD 入口禁止出现在 G14 Pipeline 生成脚本和 G14 验收证据中。
@@ -23,7 +25,7 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
- 生成 `hwlab-g14-branch-poller` CronJob:它使用 G14 集群内的 Git SSH Secret 轮询 `G14` 分支,按 source commit 创建确定命名的 Tekton PipelineRun。
- 生成 `hwlab-g14-control-plane-reconciler` CronJob:它使用同一个 Git SSH Secret 轮询 `G14`,运行 repo 内 `scripts/g14-gitops-render.mjs`,并 server-side apply 生成的 Tekton RBAC、Pipeline、Poller 和 Reconciler manifests;因此 CI 控制面变化应自动进入 G14 k3s,不需要人工长期执行 render/apply。
- 默认输出到 `deploy/gitops/g14/`
- 默认 GitOps 生成分支是 `G14-gitops`Pipeline 成功后把本次 source commit 对应的 `deploy/gitops/g14/**` 推送到该分支,避免把生成提交继续写回 `G14`
- 默认 GitOps 生成分支是 `G14-gitops`Pipeline 成功后把本次 source commit 对应的 `deploy/artifact-catalog.dev.json``deploy/gitops/g14/**` 推送到该分支,避免把生成提交继续写回 `G14`
- 默认 registry prefix 是 `127.0.0.1:5000/hwlab`,用于 G14 单节点 k3s 的 node-local registry。
- 默认 CI/CD proxy 是 G14 本机 `http://127.0.0.1:10808` / `socks5h://127.0.0.1:10808`。Tekton CI step、BuildKit sidecar 和 publish step 都注入 proxy/no_proxy;服务镜像构建只允许通过 Pod 内 BuildKit Unix socket 直接 push 到 G14 本地 registry,不能回退到 Docker daemon、DIND 或 host Docker。
- `prepare-source` 和最小原语校验 task 不允许每次运行时重新 `apk add``apt-get install` 或临时下载 browser/runtime 依赖。当前固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-v1`,包含 Node 22、npm、Git、OpenSSH、curl 和 Python3。脚本启动时必须输出工具与 proxy preflight 结构化日志;缺少工具时应先重建并推送工具镜像,再修改 `HWLAB_G14_CI_TOOLS_IMAGE`/render 默认值,不能回退到 runtime 安装。
@@ -36,9 +38,10 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
```sh
npm run g14:gitops:render -- --source-revision <sourceCommit>
npm run g14:gitops:check
```
人工 source workspace 不再对 `deploy/gitops/g14/**` 做生成物对比;这些文件在 source 分支下是忽略的生成物,旧文件和 `source.json` 不是真相。需要看渲染计划时使用 `npm run g14:gitops:render -- --no-write`;正式发布态只看 `G14-gitops`、Argo Application 和 live runtime。
## 原生 k8s Tekton + Argo 配置面 vs 已废弃的 CI.json runner
当前 G14 已完全删除 `CI.json``ci-json` step。下面的对比只保留为迁移理由:Tekton + Argo 是唯一发布控制面;repo-local 校验已经收敛为 render 内建的少量原语 task,而不是再保留一条 shell runner 路线。
@@ -62,13 +65,13 @@ npm run g14:gitops:check
HWLAB 是 monorepoG14 CI/CD 加速必须按组件输入判断构建和滚动,并直接依赖内建 component model、`deploy/deploy.json` 与 per-service artifact catalog`CI.json` 已删除,不再作为任何 planner 输入。
- `scripts/g14-ci-plan.mjs` 是只读 planner,默认读取 `deploy/deploy.json``deploy/artifact-catalog.dev.json``scripts/src/g14-ci-plan-lib.mjs` 内建 component model,输出 `affectedServices``reusedServices``componentCommitId``componentInputHash``dockerfileHash``baseImageDigest``buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。
- `scripts/g14-ci-plan.mjs` 是只读 planner,默认读取当前 workspace 的 `deploy/deploy.json``deploy/artifact-catalog.dev.json``scripts/src/g14-ci-plan-lib.mjs` 内建 component model,输出 `affectedServices``reusedServices``componentCommitId``componentInputHash``dockerfileHash``baseImageDigest``buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。Tekton `prepare-source` 会先从 `G14-gitops` 注入上一轮发布态 catalogsource 分支里的 catalog 只作为 seed contract。
- 服务清单兼容顺序固定为:显式 `--services``deploy.services[]``deploy.k3s.serviceMappings[]``internal/protocol.SERVICE_IDS`。因此旧 `deploy/deploy.json` 形态和当前完整 `deploy.services[]` 形态都必须能被 planner 识别。
- 组件边界固定由 `scripts/src/g14-ci-plan-lib.mjs` 的内建 service-path model 定义;如需新增或调整 `componentPaths``sharedPaths``runtimeDeps``buildSystemPaths`,直接修改 planner 库和对应测试,不再额外维护 repo-local commands/forbidden skeleton。
- `scripts/g14-artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices``reusedServices` 必须`deploy/artifact-catalog.dev.json` 复用已有 sha256 digest;如果 catalog 没有可验证 digest必须结构化阻塞并修复 catalog 或显式扩大 changed service 范围,不能退回 Docker 或 legacy full-build 路线。
- `scripts/g14-artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 只把生成的 `commitId``image``imageTag``digest``publishState` 和 component provenance 字段复制进 `deploy/artifact-catalog.dev.json``deploy/deploy.json` 是人写的 runtime config 真相源,不得被 promotion 回写镜像身份字段。
- `scripts/g14-artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices``reusedServices``deploy/artifact-catalog.dev.json` 复用已有 sha256 digest;如果 catalog 没有可验证 digestplanner 直接把该服务列为 affected 并重新发布,不能用旧 guard 阻塞,也不能退回 Docker 或 legacy full-build 路线。
- `scripts/g14-artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 只把生成的 `commitId``image``imageTag``digest``publishState` 和 component provenance 字段复制进当前 workspace 的 `deploy/artifact-catalog.dev.json`Tekton promotion 只把刷新后的 catalog 写入 `G14-gitops``deploy/deploy.json` 是人写的 runtime config 真相源,不得被 promotion 回写镜像身份字段。
- `scripts/g14-gitops-render.mjs` 只支持混合 desired stateworkload 的 container image、`HWLAB_IMAGE``HWLAB_IMAGE_TAG` 和 pod template `source-commit` 来自 `deploy/artifact-catalog.dev.json` 的 per-service artifact identity;普通 env、replica、healthPath、profile 等配置来自 `deploy/deploy.json`。全局 GitOps metadata 仍记录本次 source commit。`--legacy-source-images``HWLAB_G14_USE_DEPLOY_IMAGES=0` 已废弃,不得把所有 workload image 回退渲染为同一个 source commit tag。
- G14 Tekton promotion 在推送 `G14-gitops` 前,必须用 publish report 刷新 workspace 内的 `deploy/artifact-catalog.dev.json`并且只在 catalog 变化时以 `chore: promote G14 GitOps source <short>` 提交回 `G14` source branchpoller 必须跳过这类生成提交,避免自触发循环。promotion 不得自动修改 `deploy/deploy.json``deploy/k8s/base/workloads.yaml`,这样人写配置不会和 CI 生成身份反复冲突。
- G14 Tekton promotion 在推送 `G14-gitops` 前,必须用 publish report 刷新 workspace 内的 `deploy/artifact-catalog.dev.json`然后把刷新后的 catalog 和 rendered GitOps desired state 一起提交到 `G14-gitops`。promotion 不得自动修改或推送 `G14` source branch,也不得自动修改 `deploy/deploy.json``deploy/k8s/base/workloads.yaml`,这样人写配置不会和 CI 生成身份反复冲突。
- Tekton 并发化只能以 planner 输出作为输入;每个 service 都有独立 TaskRunchanged service 启动 BuildKitunchanged service 只写 reuse result 并复用 catalog digest,且不能改 pod template,避免无意义 rollout。没有完整 per-service desired state 证据时,必须修复 planner/catalog 证据,不能使用 `--full-build``--legacy-source-images`、DIND 或 Docker fallback 回退。
## 加速判定与当前瓶颈
@@ -85,7 +88,7 @@ G14 CI/CD 加速的第一判定标准不是 PipelineRun 总耗时单点变短,
当前仍然慢的主要位置:
- `prepare-source` 与最小原语校验 task 仍是前段主要开销。它们包含 Git clone、checkout、必要的 `npm ci``repo-reports-guard``g14-contract-check``codex-api-forwarder-check`。继续加速应优先减少 workspace cold start 与依赖安装成本,而不是重新塞回一条 shell runner。
- `gitops-promote` 仍是大头,当前量级约 100 秒。它包含 source branch freshness check、artifact catalog refresh、render/check、clone `G14-gitops`、复制 generated desired state、commit 和 push。继续加速应优先减少 git clone/push 成本,例如使用浅 clone、持久 workspace、server-side patch 或把 GitOps repo 操作单独缓存。
- `gitops-promote` 仍是大头,当前量级约 100 秒。它包含 source branch freshness check、artifact catalog refresh、render、clone `G14-gitops`、复制 catalog 与 generated desired state、commit 和 push。继续加速应优先减少 git clone/push 成本,例如使用浅 clone、持久 workspace、server-side patch 或把 GitOps repo 操作单独缓存。
- per-service TaskRun 数量增加后会有 k8s 调度和 sidecar 生命周期开销。reuse-only 任务很快,但如果每个 unchanged service 都启动完整 buildkit sidecar,调度开销会抵消部分收益;长期目标是让 unchanged service 走更轻量的 reuse Taskchanged service 才启动 BuildKit。
- per-service BuildKit 必须使用 Pod 内 Unix socket 或其他 Pod-scoped 端点,禁止在 hostNetwork 下绑定固定 TCP 端口。多个 service TaskRun 并发时,如果 sidecar 统一监听 `0.0.0.0:1234`,会抢同一个宿主端口,导致主 step 结果成功但 Pod phase 显示 Failed,严重污染 CI 观测。
- Argo sync/health 不是 instant。`Sync=Synced` 后 health 仍可能因为 replicas=0 的模板 Deployment、未排除 health 的模板 Job 或 StatefulSet 旧 revision 显示 `Suspended`/`Progressing`。这时应先看实际长驻 workload ready 与具体 unhealthy resource,不要只看 Application 总 health;如果 suspended Job 只是模板资源,应修 render 注解而不是把 DEV rollout 判成失败。
@@ -117,9 +120,9 @@ DeepSeek proxy manifest 是 GitOps desired state 的一部分,DEV/PROD 分别
G14 不要求 GitHub webhook 或 GitHub Actions 配置。`hwlab-ci/hwlab-g14-branch-poller` CronJob 每 1 分钟通过 Git SSH 拉取 `G14` HEAD,并用 source commit 的前 12 位生成 PipelineRun 名称 `hwlab-g14-ci-poll-<short12>`
如果同名 PipelineRun 已存在,poller 直接跳过;如果不存在,则创建新的 PipelineRun。这样可以用 Kubernetes 原生 CronJob、ServiceAccount、RBAC 和 Tekton API 实现无 GitHub webhook 的分支监控,同时避免同一个 commit 被反复派单。历史上的 `chore: promote G14 GitOps source ...` 生成提交会被 poller 跳过
如果同名 PipelineRun 已存在,poller 直接跳过;如果不存在,则创建新的 PipelineRun。这样可以用 Kubernetes 原生 CronJob、ServiceAccount、RBAC 和 Tekton API 实现无 GitHub webhook 的分支监控,同时避免同一个 commit 被反复派单。历史上的 `chore: promote G14 GitOps source ...` source 分支生成提交已经废弃;poller 只应该看到人写 source commit
Pipeline 的标准路径是:`G14` source commit -> Tekton 原语校验 task -> commit-tagged image push 到 G14 registry -> render `deploy/gitops/g14/**` -> push `G14-gitops` -> Argo CD 同步 runtime。CD 只消费已经构建好的镜像和 Git desired state,不在 Argo CD 内构建镜像。
Pipeline 的标准路径是:`G14` source commit -> Tekton 原语校验 task -> commit-tagged image push 到 G14 registry -> refresh artifact catalog -> render `deploy/gitops/g14/**` -> push catalog 与 GitOps desired state 到 `G14-gitops` -> Argo CD 同步 runtime。CD 只消费已经构建好的镜像和 Git desired state,不在 Argo CD 内构建镜像。
GitOps promotion 成功只证明 `G14-gitops` 分支已经写入新 desired state;如果 render 改变了 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步检查并应用 `deploy/gitops/g14/argocd/project.yaml``application-dev.yaml``application-prod.yaml``hwlab-g14-dev` 必须指向 `deploy/gitops/g14/runtime-dev``hwlab-g14-prod` 必须指向 `deploy/gitops/g14/runtime-prod`;如果集群里 Application 仍指向旧 `deploy/gitops/g14/runtime`Argo 会停在旧 revision,即使 CI/publish/promote 全部成功也不会滚动新镜像。
@@ -169,14 +172,14 @@ G14 PR、CI、CD 的判断应按以下顺序收敛真相,越靠前越接近最
1. live runtime:目标 namespace 的 Deployment/StatefulSet template、Pod ready、`describe`、容器日志和公网 health。
2. Argo desired state`hwlab-g14-dev` / `hwlab-g14-prod` 的 Application revision、sync、health 和实际 runtime path。
3. Tekton 执行证据:branch-poller 日志、PipelineRun、TaskRun results、`gitops-promote` 终态。
4. 干净 source workspace`origin/G14` 当前内容,以及 `npm run g14:gitops:render``npm run g14:gitops:check` 和 planner 输出。
4. 干净 source workspace`origin/G14` 当前内容,以及 `npm run g14:gitops:render -- --no-write` 和 planner 输出。
5. 对照线索:旧 commit 记忆、坏 worktree、D601 legacy 路径、脚本旧默认值,只能当线索,不能当真相。
常见误判与纠正:
- 不要把 `/root/hwlab` 当前 checkout、任意 `/tmp` worktree 或带 conflict marker 的目录当 source truth;只有跟踪 `origin/G14` 的干净 worktree 才能作为当前发布依据。
- 不要按“某个预期 commit 是否出现在 ancestry 中”判断功能是否已经合入;当前 `origin/G14` 实际内容比历史 commit 轨迹更重要。
- 改了 source 但没先 render/check 时,generated mismatch 只说明 source 与 generated 没对齐,不等于 Tekton 或 Argo 本身坏掉。
- 改了 source 但没有触发 Tekton promotion 时,`G14-gitops` 仍停在旧 generated state 是正常现象,不等于 Tekton 或 Argo 本身坏掉。
- 不要把 `build-*` TaskRun 名称直接当作镜像重建证据;reuse-only 变更也会扇出同名 TaskRun,必须看 Tekton result 的 `status=reused``build-backend=reused-catalog`
- 不要把 `G14-gitops` 分支已更新误判成 DEV 或 PROD 已滚动;只有 Argo Application revision、目标 workload ready 和 live manifest 生效,才算 CD 真实通过。
- 不要把 health payload 中的镜像 commit 当成 runtime manifest commitruntime-only 修复可能复用旧镜像,只改变 probe、env、annotation 或 sidecar 行为。
@@ -200,14 +203,13 @@ bun scripts/cli.ts ssh G14:/root/hwlab shell 'git status --short --branch && git
2. Source / PR 预检
```sh
npm run g14:gitops:render
npm run g14:gitops:check
npm run g14:gitops:render -- --no-write
node scripts/g14-ci-plan.mjs --base-ref origin/G14 --target-ref HEAD --pretty
```
- 先看当前内容和长期参考,不按旧 commit 记忆判断“功能是否已合入”。
- GitOps、manifest、poller/reconciler、provider profile 相关改动必须先跑 render/checkplanner 用于判断本轮是 docs-only、reuse-only 还是需要真实构建。
- provider/profile 变更在进入 PR 或 CI 之前,先按 `code-agent-chat-readiness.md` 做目标 Pod 最小环;不要把完整 CI/CD 当成 transport 试错工具。
- GitOps、manifest、poller/reconciler、provider profile 相关改动先跑 render no-writeplanner 用于判断本轮是 docs-only、reuse-only 还是需要真实构建。不要在 source 分支对 ignored generated output 跑 drift check。
- provider/profile 变更在进入 PR 或 CI 之前,先按 `code-agent-chat-readiness.md` 做目标 Pod 最小环;不要把完整 CI/CD 当成 transport 试错工具。
3. 合并或推送到 `G14`
-1
View File
@@ -30,7 +30,6 @@
"web:build": "node web/hwlab-cloud-web/scripts/build.mjs",
"artifact-catalog:refresh-blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked",
"g14:gitops:render": "node scripts/g14-gitops-render.mjs",
"g14:gitops:check": "node scripts/g14-gitops-render.mjs --check",
"cicd:jobs": "node tools/hwlab-cli/bin/hwlab-cli.mjs cicd jobs",
"docs:validate:m3-rollout": "node scripts/validate-m3-rollout-runbook.mjs",
"d601:k3s:readonly": "node scripts/d601-k3s-readonly-observability.mjs",
+30
View File
@@ -42,6 +42,19 @@ test("G14 CI planner treats docs-only changes as no image build", async () => {
assert.equal(plan.changedPathSummary.docsOnly, true);
});
test("planner rebuilds when catalog digest is missing instead of blocking reuse", async () => {
const repo = await createFixtureRepo({ catalog: "missing-digest" });
await mkdir(path.join(repo, "docs/reference"), { recursive: true });
await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs v2\n");
await git(repo, ["add", "."]);
await git(repo, ["commit", "-m", "change docs"]);
const plan = await createG14CiPlan({ repoRoot: repo, baseRef: "HEAD~1", targetRef: "HEAD" });
assert.deepEqual(plan.affectedServices, ["hwlab-cloud-api", "hwlab-cloud-web"]);
assert.equal(plan.imageBuildRequired, true);
assert.equal(plan.services[0].reason.includes("catalog-digest-missing"), true);
});
test("component model uses built-in service paths", () => {
const models = componentModelsForServices(["hwlab-cloud-api"]);
assert.deepEqual(models[0].componentPaths, [
@@ -84,6 +97,10 @@ async function createFixtureRepo(options = {}) {
await mkdir(path.join(repo, "web/hwlab-cloud-web"), { recursive: true });
await mkdir(path.join(repo, "deploy"), { recursive: true });
await writeFile(path.join(repo, "deploy/deploy.json"), JSON.stringify(createDeployFixture({ deployServices, k3sServiceMappings }), null, 2));
const catalogMode = options.catalog ?? "ready";
if (catalogMode !== false) {
await writeFile(path.join(repo, "deploy/artifact-catalog.dev.json"), JSON.stringify(createCatalogFixture(catalogMode), null, 2));
}
await writeFile(path.join(repo, "package.json"), JSON.stringify({ type: "module" }, null, 2));
await writeFile(path.join(repo, "package-lock.json"), JSON.stringify({ lockfileVersion: 3 }, null, 2));
await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.mjs"), "console.log('api');\n");
@@ -115,6 +132,19 @@ function createDeployFixture({ deployServices, k3sServiceMappings }) {
return deploy;
}
function createCatalogFixture(mode) {
const digest = mode === "ready" ? `sha256:${"1".repeat(64)}` : "not_published";
return {
services: ["hwlab-cloud-api", "hwlab-cloud-web"].map((serviceId) => ({
serviceId,
commitId: "abc1234",
image: `127.0.0.1:5000/hwlab/${serviceId}:abc1234`,
imageTag: "abc1234",
digest
}))
};
}
async function git(cwd, args) {
return execFileAsync("git", ["-c", "user.name=HWLAB Test", "-c", "user.email=hwlab-test@example.invalid", ...args], { cwd });
}
+16 -24
View File
@@ -239,20 +239,6 @@ async function resolveSourceRevision(value) {
return { full, short: full.slice(0, 7) };
}
async function defaultCheckSourceRevision(args) {
if (!args.check || args.sourceRevision) return args;
try {
const raw = await readFile(generatedPath(path.join(args.outDir, "source.json")), "utf8");
const parsed = JSON.parse(raw);
if (typeof parsed.sourceCommit === "string" && sourceCommitPattern.test(parsed.sourceCommit)) {
return { ...args, sourceRevision: parsed.sourceCommit };
}
} catch (error) {
if (error?.code !== "ENOENT") throw error;
}
return args;
}
function jsonManifest(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
@@ -729,6 +715,17 @@ function prepareSourceScript() {
"git config --global --add safe.directory /workspace/source/repo",
"git checkout \"$(params.revision)\"",
"test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"",
"if git ls-remote --exit-code --heads origin \"$(params.gitops-branch)\" >/dev/null 2>&1; then",
" git fetch --depth 1 origin \"$(params.gitops-branch)\" >/dev/null 2>&1 || true",
" if git cat-file -e FETCH_HEAD:deploy/artifact-catalog.dev.json 2>/dev/null; then",
" git show FETCH_HEAD:deploy/artifact-catalog.dev.json > deploy/artifact-catalog.dev.json",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"loaded\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'",
" else",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"missing-on-gitops-branch\"}'",
" fi",
"else",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"gitops-branch-missing\"}'",
"fi",
"npm ci --ignore-scripts",
`echo ${shellSingleQuote(`G14 prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}`
];
@@ -918,17 +915,10 @@ git config --global user.name "HWLAB G14 GitOps Bot"
git config --global user.email "hwlab-g14-gitops-bot@users.noreply.github.com"
if [ -s /workspace/source/dev-artifacts.json ]; then
node scripts/refresh-artifact-catalog.mjs --target-ref "$(params.revision)" --publish-report /workspace/source/dev-artifacts.json
if ! git diff --quiet -- deploy/artifact-catalog.dev.json; then
short="$(printf '%.7s' "$(params.revision)")"
git add deploy/artifact-catalog.dev.json
git commit -m "chore: promote G14 GitOps source $short"
git push origin "HEAD:$(params.source-branch)"
fi
fi
source_state_revision="$(git rev-parse HEAD)"
node scripts/g14-gitops-render.mjs --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images
node scripts/g14-gitops-render.mjs --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images --check
check_source_head before-push "$source_state_revision"
check_source_head before-push
workdir="$(mktemp -d)"
git clone --no-checkout "$(params.git-url)" "$workdir/gitops"
cd "$workdir/gitops"
@@ -942,7 +932,8 @@ else
fi
mkdir -p deploy/gitops
cp -a /workspace/source/repo/deploy/gitops/g14 deploy/gitops/g14
git add deploy/gitops/g14
cp /workspace/source/repo/deploy/artifact-catalog.dev.json deploy/artifact-catalog.dev.json
git add deploy/artifact-catalog.dev.json deploy/gitops/g14
if git diff --cached --quiet; then
echo '{"status":"unchanged","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
exit 0
@@ -1492,6 +1483,7 @@ function tektonPipeline() {
params: [
{ name: "git-url" },
{ name: "source-branch" },
{ name: "gitops-branch" },
{ name: "revision" }
],
workspaces: [{ name: "source" }, { name: "git-ssh" }],
@@ -1500,6 +1492,7 @@ function tektonPipeline() {
params: [
{ name: "git-url", value: "$(params.git-url)" },
{ name: "source-branch", value: "$(params.source-branch)" },
{ name: "gitops-branch", value: "$(params.gitops-branch)" },
{ name: "revision", value: "$(params.revision)" }
]
},
@@ -2373,7 +2366,6 @@ async function main() {
console.log(usage());
return;
}
args = await defaultCheckSourceRevision(args);
ensureObject(args, "args");
const { files, source } = await plannedFiles(args);
if (args.check) {
@@ -1186,7 +1186,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
return [
"node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty",
"node scripts/deploy-desired-state-plan.mjs --promotion-commit <origin-main-sha> --check",
"node scripts/g14-gitops-render.mjs --check",
"node scripts/g14-gitops-render.mjs --no-write",
guardCommand
];
}
@@ -1199,7 +1199,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
];
}
return [
"node scripts/g14-gitops-render.mjs --check",
"node scripts/g14-gitops-render.mjs --no-write",
"node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --live --no-report"
];
}
+9 -4
View File
@@ -101,7 +101,9 @@ export async function createG14CiPlan(options = {}) {
...runtimeDepMatches,
...buildSystemMatches
].filter((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item)));
const affected = imageRelevantChangedPaths.length > 0;
const catalogRecord = catalogByServiceId.get(model.serviceId) ?? null;
const catalogReuse = reuseCandidate(catalogRecord, imageRelevantChangedPaths.length > 0);
const affected = imageRelevantChangedPaths.length > 0 || catalogReuse.status === "candidate-no-catalog" || catalogReuse.status === "candidate-unverified-digest";
const componentInputPaths = uniqueSorted([
...model.componentPaths,
...model.sharedPaths,
@@ -119,7 +121,6 @@ export async function createG14CiPlan(options = {}) {
runtimeKind: model.runtimeKind,
entrypoint: model.entrypoint
});
const catalogRecord = catalogByServiceId.get(model.serviceId) ?? null;
services.push({
serviceId: model.serviceId,
runtimeKind: model.runtimeKind,
@@ -136,7 +137,9 @@ export async function createG14CiPlan(options = {}) {
buildArgsHash,
changedPaths: imageRelevantChangedPaths,
affected,
reason: affected ? reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches }) : reasonForUnchanged(globalChange),
reason: affected
? reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches, catalogReuse })
: reasonForUnchanged(globalChange),
reuse: reuseCandidate(catalogRecord, affected)
});
}
@@ -315,12 +318,14 @@ async function gitValue(repoRoot, args) {
return result.stdout.trim();
}
function reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches }) {
function reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches, catalogReuse = null }) {
const reasons = [];
if (directMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("component-path-changed");
if (sharedMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("shared-runtime-changed");
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 === "candidate-no-catalog") reasons.push("catalog-record-missing");
if (catalogReuse?.status === "candidate-unverified-digest") reasons.push("catalog-digest-missing");
return reasons.length ? reasons : ["affected"];
}
+1 -2
View File
@@ -196,7 +196,6 @@ function assertCatalogServices(catalog, deployManifest, catalogMode) {
assert.ok(deployService, `${context} missing from deploy manifest`);
assertCommitId(service.commitId, `${context} commitId`);
assert.equal(service.commitId, catalog.commitId, `${context} commitId must match catalog`);
assert.equal(service.imageTag, service.commitId.slice(0, 7), `${context} imageTag must be the short commit`);
assertNoMutableTag(service.imageTag, `${context} imageTag`);
@@ -233,7 +232,7 @@ function assertCatalogServices(catalog, deployManifest, catalogMode) {
assert.equal(service.digest, "not_published", `${context} digest`);
} else {
if (service.artifactRequired) {
assert.equal(service.publishState, "published", `${context} publishState`);
assert.ok(["published", "reused"].includes(service.publishState), `${context} publishState`);
assert.match(service.digest, digestPattern, `${context} digest`);
} else {
assert.equal(service.publishState, "skeleton-only", `${context} publishState`);