refactor: make v0.3 runtime node-scoped
This commit is contained in:
+2
-2
@@ -39,8 +39,8 @@ yarn-error.log*
|
||||
.worktree/
|
||||
.preserved-untracked/
|
||||
|
||||
# G14 generated GitOps desired state is stored on the G14-gitops branch.
|
||||
/deploy/gitops/g14/
|
||||
# Generated GitOps desired state is stored on the GitOps branch.
|
||||
/deploy/gitops/node/
|
||||
|
||||
# v0.2 generated artifact catalog is stored only on the v0.2-gitops branch.
|
||||
/deploy/artifact-catalog.v02.json
|
||||
|
||||
@@ -30,11 +30,11 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
|
||||
## P0 G14 原生 k8s/GitOps 运行面归一
|
||||
|
||||
- G14 是当前 HWLAB DEV/PROD 原生 k8s 与 GitOps 运行面真相;`hwlab-dev` 与 `hwlab-prod` 均由 G14 k3s、Tekton、`G14-gitops` 和 Argo CD 管理,详见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
|
||||
- G14 是当前 HWLAB DEV/PROD 原生 k8s 与 GitOps 运行面真相;`hwlab-dev` 与 `hwlab-prod` 均由 G14 k3s、Tekton、`G14-gitops` 和 Argo CD 管理,详见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。
|
||||
- `v0.2` 是 G14 上的加法扩容线:固定分支 `v0.2`、固定开发 workspace `G14:/root/hwlab-v02`、固定 CI/CD source repo `G14:/root/hwlab-v02-cicd.git`、固定 namespace `hwlab-v02`、FRP 入口 `74.48.78.17:19666/19667`;创建和集成 `v0.2` 不得删除、重命名、复用或改义现有 `G14`/`G14-gitops` 分支、`hwlab-dev`/`hwlab-prod` namespace 或 17666/17667、18666/18667 入口。
|
||||
- `v0.2` 的人写 deploy/runtime 配置单一出处是 `deploy/deploy.yaml`;`deploy/deploy.json` 不再是 v0.2 兼容源。所有脚本、renderer、planner 和 CLI 必须通过 `scripts/src/structured-config.mjs` / `scripts/src/deploy-config.mjs` 这类格式无关读写层消费配置,禁止把 YAML parser import 或 ad hoc YAML 解析散落到业务脚本里。
|
||||
- G14 GitOps render 若改变 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步应用 `deploy/gitops/g14/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`;只推 `G14-gitops` 分支不等于 Argo 已切到新 path。
|
||||
- D601 HWLAB DEV、D601 `dev-cd-apply`、`ci-publish` 和旧 `main` JS 脚本式 CI/CD 已退出 G14 发布入口;G14 Tekton 只能通过 `scripts/g14-artifact-publish.mjs` 作为集群内 build/push helper,再由 GitOps/Argo CD rollout,新开发、发布、验收、文档和运行面实验不得把 D601 或 legacy 脚本 CD 当作当前 HWLAB runtime source-of-truth。
|
||||
- G14 GitOps render 若改变 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步应用 `deploy/gitops/node/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`;只推 `G14-gitops` 分支不等于 Argo 已切到新 path。
|
||||
- D601 HWLAB DEV、D601 `dev-cd-apply`、`ci-publish` 和旧 `main` JS 脚本式 CI/CD 已退出 G14 发布入口;G14 Tekton 只能通过 `scripts/artifact-publish.mjs` 作为集群内 build/push helper,再由 GitOps/Argo CD rollout,新开发、发布、验收、文档和运行面实验不得把 D601 或 legacy 脚本 CD 当作当前 HWLAB runtime source-of-truth。
|
||||
- G14 k3s 操作必须通过 UniDesk route `G14:k3s` 执行;不得用 D601 kubeconfig、Docker Desktop Kubernetes、master server 本地 check/build 或旧 JS CD 结果作为 G14 DEV/PROD 通过证据。
|
||||
|
||||
## P0 HyueAPI Direct NO_PROXY 规则
|
||||
@@ -49,7 +49,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
## P0 Legacy CD 删除纪律
|
||||
|
||||
- 旧 D601 `ci-publish`、`dev-cd-apply`、`dev-deploy-apply` 和 `dev-artifact-publish` 脚本入口已经从当前发布面删除;不要恢复这些文件、CLI 子命令或文档入口。
|
||||
- 当前发布只走 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)。
|
||||
- 当前发布只走 G14 k3s Tekton + `G14-gitops` + Argo CD;镜像构建 helper 是 `scripts/artifact-publish.mjs`,desired state 由 `scripts/gitops-render.mjs` 生成,细则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。
|
||||
- 发现旧脚本、旧文档或旧 `hwlab-cli cicd` 再次进入 G14 Pipeline、AGENTS 或长期参考时,优先删除旧入口并把调用方改到 G14 GitOps 路径,不再做兼容保留。
|
||||
|
||||
## P0 门禁最小化纪律
|
||||
@@ -73,7 +73,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
- Runner 和指挥常用工作区是 `/workspace/hwlab`;进入仓库先检查分支与工作树状态,详见 [docs/reference/commander-collaboration.md](docs/reference/commander-collaboration.md)。
|
||||
- G14 DEV/PROD CI/CD 由 `G14` source branch、G14 k3s Tekton 和 `G14-gitops` branch 驱动;`v0.2` CI/CD 由 UniDesk 手动 trigger、`/root/hwlab-v02-cicd.git`、`devops-infra` git mirror/relay、G14 k3s Tekton 和 `v0.2-gitops` 驱动;需要构建、Playwright、check、发布预检或运行面验证时放到 G14 k3s/runner/CI/CD,不在 master server 跑重型验证。
|
||||
- G14/v0.2 远端验证必须用短连接触发后台 job、PipelineRun 或脚本任务,再用短连接轮询 status/tail/exit code;不要用 UniDesk SSH/tran 长连接等待 check、layout、Playwright、Tekton/Argo 或发布动作完整结束,细则见 [docs/reference/spec-v02-cicd.md](docs/reference/spec-v02-cicd.md)。
|
||||
- D601 发布/构建 worktree 纪律只适用于 legacy 路径回溯,不再作为当前 HWLAB 发布默认入口;当前入口见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
|
||||
- D601 发布/构建 worktree 纪律只适用于 legacy 路径回溯,不再作为当前 HWLAB 发布默认入口;当前入口见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。
|
||||
- 交付路径按变更风险选择:单纯文档、CLI/helper 轻量变更直接提交并 push 到当前工作线的 source branch,G14 默认 `origin/G14`,`v0.2` 默认 `origin/v0.2`;业务代码、运行面、发布链路、Secret、权限、数据迁移、PROD 或其他高风险变更走 PR 工作流,PR base 必须匹配当前工作线,不能默认投向 `main`;默认不要合并自己的 PR,用户或指挥官明确授权且满足门禁时可按长期参考自合并,不要改 PROD、不要重启服务。
|
||||
- `DC-DCSN-P0-2026-003` / [pikasTech/HWLAB#78](https://github.com/pikasTech/HWLAB/issues/78) 是当前 M3 虚拟硬件可信闭环的上位约束;其他任务不得把 SOURCE、LOCAL、DRY-RUN、fixture 或前端状态误报为 M3 DEV-LIVE。
|
||||
- 仓库禁止创建或提交 repo report 目录;验收、进展和结论只承载在 #7、专题 issue、每日简报或 PR/issue 评论。临时 JSON 只能写入 `/tmp`、`.state` 或 CI artifact,不能进入源码仓库。
|
||||
@@ -82,8 +82,8 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
|
||||
- G14 DEV Cloud Web:`http://74.48.78.17:17666/`,规则见 [docs/reference/dev-runtime-boundary.md](docs/reference/dev-runtime-boundary.md)。
|
||||
- G14 DEV API/edge/live:`http://74.48.78.17:17667/health/live`,规则见 [docs/reference/dev-runtime-boundary.md](docs/reference/dev-runtime-boundary.md)。
|
||||
- G14 PROD 预留入口:`http://74.48.78.17:18666/` 与 `http://74.48.78.17:18667/health/live`,规则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
|
||||
- G14 `v0.2` 规划入口:`http://74.48.78.17:19666/` 与 `http://74.48.78.17:19667/health/live`,只能指向 `hwlab-v02` namespace,规则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
|
||||
- G14 PROD 预留入口:`http://74.48.78.17:18666/` 与 `http://74.48.78.17:18667/health/live`,规则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。
|
||||
- G14 `v0.2` 规划入口:`http://74.48.78.17:19666/` 与 `http://74.48.78.17:19667/health/live`,只能指向 `hwlab-v02` namespace,规则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。
|
||||
- Cloud Workbench 默认首页与 UX 约束见 [docs/reference/cloud-workbench.md](docs/reference/cloud-workbench.md)。
|
||||
|
||||
## 规格
|
||||
@@ -121,9 +121,9 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
- v0.2 文档治理规格、过程文档迁 issue、`docs/` 根目录清理和 D601/G14 旧口径收敛:[docs/reference/spec-v02-documentation-governance.md](docs/reference/spec-v02-documentation-governance.md)
|
||||
- 架构和 M3 主线:[docs/reference/architecture.md](docs/reference/architecture.md)
|
||||
- DEV 运行态、端口、k3s 和 DB DNS 边界:[docs/reference/dev-runtime-boundary.md](docs/reference/dev-runtime-boundary.md)
|
||||
- G14 GitOps 发布、SecretRef preflight、runner/host 边界、镜像发布和单纯文档/CLI 直推规则:[docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)、[docs/reference/commander-collaboration.md](docs/reference/commander-collaboration.md)
|
||||
- G14 GitOps CI/CD、Tekton/Argo CD、集群内 registry 和无锁镜像化发布:[docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)
|
||||
- G14 CI/CD 性能基线、根因分析和加速收益估算:[docs/reference/g14-cicd-performance.md](docs/reference/g14-cicd-performance.md)
|
||||
- G14 GitOps 发布、SecretRef preflight、runner/host 边界、镜像发布和单纯文档/CLI 直推规则:[docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)、[docs/reference/commander-collaboration.md](docs/reference/commander-collaboration.md)
|
||||
- G14 GitOps CI/CD、Tekton/Argo CD、集群内 registry 和无锁镜像化发布:[docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)
|
||||
- G14 CI/CD 性能基线、根因分析和加速收益估算:[docs/reference/node-cicd-performance.md](docs/reference/node-cicd-performance.md)
|
||||
- Code Agent 对话就绪与真实回复判定:[docs/reference/code-agent-chat-readiness.md](docs/reference/code-agent-chat-readiness.md)
|
||||
- AgentRun 手动调度装配、UniDesk SSH passthrough 与 GitHub tool credential 边界:[docs/reference/agentrun-code-agent-dispatch.md](docs/reference/agentrun-code-agent-dispatch.md)
|
||||
- DEV runtime hotfix runbook 与只读审计:[docs/reference/dev-runtime-hotfix-runbook.md](docs/reference/dev-runtime-hotfix-runbook.md)
|
||||
@@ -146,9 +146,9 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
- Cloud Web 静态检查:`npm run web:check`
|
||||
- Cloud Web 构建:`npm run web:build`
|
||||
- Cloud Web M3 只读护栏:`npm run web:m3-readonly`
|
||||
- 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.yaml`;细则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
|
||||
- G14 GitOps 渲染:`npm run g14:gitops:render`;source 分支不再运行生成物 drift check,发布态由 Tekton 写入 `G14-gitops`。
|
||||
- G14 artifact build helper:`node scripts/artifact-publish.mjs --publish ...`,只能由 G14 Tekton task 携带 CI artifact identity 调用;人工发布走 G14 poller/GitOps,不走 legacy CLI CD。
|
||||
- G14 monorepo 组件计划:`node scripts/ci-plan.mjs --base-ref <ref> --target-ref <ref> --pretty`,只读分析 affected/reused services,基于内建 service-path component model 与 `deploy/deploy.yaml`;细则见 [docs/reference/node-gitops-cicd.md](docs/reference/node-gitops-cicd.md)。
|
||||
- G14 GitOps 渲染:`npm run gitops:render`;source 分支不再运行生成物 drift check,发布态由 Tekton 写入 `G14-gitops`。
|
||||
- 平行 lane 配置扩容:`npm run lane:expand -- configure --lane v03 --from v02 --write`,只写 `deploy/deploy.yaml` 的新 lane 声明,后续 `v04`/`v05`/`v06` 复用同一入口。
|
||||
- GitOps lane 严格 TS 检查:`npm run gitops:ts:check`;新拆 GitOps 模块直接进入 `scripts/src/*.ts` 和 `tsconfig.gitops.json`,不要继续堆进单个 `.mjs`。
|
||||
- DEV 依赖 runtime base 构建:`npm run dev-runtime-base:build`
|
||||
|
||||
+1
-5
@@ -81,7 +81,7 @@ profiles:
|
||||
notes: Reserved placeholder only. PROD deployment and acceptance are explicitly out of scope.
|
||||
nodes:
|
||||
G14:
|
||||
gitopsRoot: deploy/gitops/g14
|
||||
gitopsRoot: deploy/gitops/node
|
||||
sourceRepo: git@github.com:pikasTech/HWLAB.git
|
||||
publicHost: 74.48.78.17
|
||||
lanes:
|
||||
@@ -203,7 +203,6 @@ lanes:
|
||||
additionalEnvPaths:
|
||||
- internal/dev-entrypoint/artifact-runtime.mjs
|
||||
- scripts/artifact-publish.mjs
|
||||
- scripts/g14-artifact-publish.mjs
|
||||
- scripts/src/dev-artifact-services.mjs
|
||||
hwpodAliases:
|
||||
- command: hwpod
|
||||
@@ -232,7 +231,6 @@ lanes:
|
||||
HWLAB_KEYCLOAK_CLIENT_SECRET: secretRef:hwlab-cloud-web-client/client-secret
|
||||
HWLAB_CODE_AGENT_ADAPTER: agentrun-v01
|
||||
AGENTRUN_MGR_URL: http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080
|
||||
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: G14
|
||||
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: agentrun-v01
|
||||
HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: agentrun-v01
|
||||
HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git
|
||||
@@ -354,7 +352,6 @@ lanes:
|
||||
additionalEnvPaths:
|
||||
- internal/dev-entrypoint/artifact-runtime.mjs
|
||||
- scripts/artifact-publish.mjs
|
||||
- scripts/g14-artifact-publish.mjs
|
||||
- scripts/src/dev-artifact-services.mjs
|
||||
hwpodAliases:
|
||||
- command: hwpod
|
||||
@@ -383,7 +380,6 @@ lanes:
|
||||
HWLAB_KEYCLOAK_CLIENT_SECRET: secretRef:hwlab-cloud-web-client/client-secret
|
||||
HWLAB_CODE_AGENT_ADAPTER: agentrun-v01
|
||||
AGENTRUN_MGR_URL: http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080
|
||||
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: G14
|
||||
HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE: agentrun-v01
|
||||
HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE: agentrun-v01
|
||||
HWLAB_CODE_AGENT_AGENTRUN_REPO_URL: http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git
|
||||
|
||||
@@ -23,15 +23,15 @@ G14 uses separate preview port ranges and must not reuse D601's `16666` / `16667
|
||||
|
||||
- G14 DEV browser endpoint: `http://74.48.78.17:17666`
|
||||
- G14 DEV API/edge endpoint: `http://74.48.78.17:17667`
|
||||
- G14 DEV frpc proxy names: `hwlab-g14-cloud-web` and `hwlab-g14-edge-proxy`
|
||||
- G14 DEV k3s Deployment: `hwlab-dev/hwlab-g14-frpc`, generated by `scripts/g14-gitops-render.mjs`
|
||||
- G14 DEV frpc proxy names: `hwlab-node-cloud-web` and `hwlab-node-edge-proxy`
|
||||
- G14 DEV k3s Deployment: `hwlab-dev/hwlab-node-frpc`, generated by `scripts/gitops-render.mjs`
|
||||
- G14 PROD browser endpoint: `http://74.48.78.17:18666`
|
||||
- G14 PROD API/edge endpoint: `http://74.48.78.17:18667`
|
||||
- G14 PROD frpc proxy names: `hwlab-g14-prod-cloud-web` and `hwlab-g14-prod-edge-proxy`
|
||||
- G14 PROD k3s Deployment: `hwlab-prod/hwlab-g14-prod-frpc`, generated by `scripts/g14-gitops-render.mjs`
|
||||
- G14 PROD frpc proxy names: `hwlab-node-prod-cloud-web` and `hwlab-node-prod-edge-proxy`
|
||||
- G14 PROD k3s Deployment: `hwlab-prod/hwlab-node-prod-frpc`, generated by `scripts/gitops-render.mjs`
|
||||
- Master frps allow-list template: `deploy/frp/frps.dev.toml`
|
||||
- G14 DEV frpc local template: `deploy/frp/frpc.g14.toml`
|
||||
- G14 PROD frpc local template: `deploy/frp/frpc.g14-prod.toml`
|
||||
- G14 DEV frpc local template: `deploy/frp/frpc.node-dev.toml`
|
||||
- G14 PROD frpc local template: `deploy/frp/frpc.node-prod.toml`
|
||||
|
||||
Only `hwlab-cloud-web:8080` and `hwlab-edge-proxy:6667` are exposed through FRP.
|
||||
Internal services, Tekton, Argo CD, registry, and database stay ClusterIP-only.
|
||||
|
||||
@@ -3,14 +3,14 @@ serverPort = 7000
|
||||
loginFailExit = true
|
||||
|
||||
[[proxies]]
|
||||
name = "hwlab-g14-cloud-web"
|
||||
name = "hwlab-node-cloud-web"
|
||||
type = "tcp"
|
||||
localIP = "hwlab-cloud-web.hwlab-dev.svc.cluster.local"
|
||||
localPort = 8080
|
||||
remotePort = 17666
|
||||
|
||||
[[proxies]]
|
||||
name = "hwlab-g14-edge-proxy"
|
||||
name = "hwlab-node-edge-proxy"
|
||||
type = "tcp"
|
||||
localIP = "hwlab-edge-proxy.hwlab-dev.svc.cluster.local"
|
||||
localPort = 6667
|
||||
@@ -3,14 +3,14 @@ serverPort = 7000
|
||||
loginFailExit = true
|
||||
|
||||
[[proxies]]
|
||||
name = "hwlab-g14-prod-cloud-web"
|
||||
name = "hwlab-node-prod-cloud-web"
|
||||
type = "tcp"
|
||||
localIP = "hwlab-cloud-web.hwlab-prod.svc.cluster.local"
|
||||
localPort = 8080
|
||||
remotePort = 18666
|
||||
|
||||
[[proxies]]
|
||||
name = "hwlab-g14-prod-edge-proxy"
|
||||
name = "hwlab-node-prod-edge-proxy"
|
||||
type = "tcp"
|
||||
localIP = "hwlab-edge-proxy.hwlab-prod.svc.cluster.local"
|
||||
localPort = 6667
|
||||
@@ -291,5 +291,5 @@ blockers/failures:`viewport`、`selector`、`failureType`、summary 和 artifa
|
||||
chat 同源 readiness 和 provider credential blocker 边界。
|
||||
- [spec-v02-services.md](spec-v02-services.md): Cloud Web runtime wrapper and browser-side TypeScript migration boundary.
|
||||
- [pikasTech/HWLAB#532](https://github.com/pikasTech/HWLAB/issues/532): archived historical `docs/cloud-web-workbench.md` content.
|
||||
- [g14-gitops-cicd.md](g14-gitops-cicd.md): Cloud Web rollout through G14 Tekton, GitOps and Argo CD
|
||||
- [node-gitops-cicd.md](node-gitops-cicd.md): Cloud Web rollout through node-scoped Tekton, GitOps and Argo CD
|
||||
path.
|
||||
|
||||
@@ -19,12 +19,12 @@ The current G14 DEV route is:
|
||||
|
||||
```text
|
||||
master hwlab-frps-dev :17666/:17667
|
||||
-> G14 hwlab-g14-frpc in namespace hwlab-dev
|
||||
-> G14 hwlab-node-frpc in namespace hwlab-dev
|
||||
-> hwlab-cloud-web on internal :8080 for browser traffic
|
||||
-> hwlab-edge-proxy / hwlab-cloud-api on internal :6667 for API and health
|
||||
|
||||
master hwlab-frps-dev :18666/:18667
|
||||
-> G14 hwlab-g14-prod-frpc in namespace hwlab-prod
|
||||
-> G14 hwlab-node-prod-frpc in namespace hwlab-prod
|
||||
-> hwlab-cloud-web on internal :8080 for browser traffic
|
||||
-> hwlab-edge-proxy / hwlab-cloud-api on internal :6667 for API and health
|
||||
|
||||
@@ -34,7 +34,7 @@ master hwlab-frps-dev :19666/:19667
|
||||
-> hwlab-edge-proxy / hwlab-cloud-api on internal :6667 for API and health
|
||||
```
|
||||
|
||||
G14 PROD is rendered by GitOps as namespace `hwlab-prod` and Argo CD application `hwlab-g14-prod`; it uses the `:18666/:18667` public endpoints when enabled. G14 desired state is generated into `deploy/gitops/g14/runtime-dev` and `deploy/gitops/g14/runtime-prod` and promoted through the `G14-gitops` branch.
|
||||
G14 PROD is rendered by GitOps as namespace `hwlab-prod` and Argo CD application `hwlab-node-prod`; it uses the `:18666/:18667` public endpoints when enabled. G14 desired state is generated into `deploy/gitops/node/runtime-dev` and `deploy/gitops/node/runtime-prod` and promoted through the `G14-gitops` branch.
|
||||
|
||||
G14 `v0.2` is an additive lane, not a replacement for DEV or PROD. It uses source branch `v0.2`, fixed development workspace `G14:/root/hwlab-v02`, dedicated CI/CD source repo `G14:/root/hwlab-v02-cicd.git`, namespace `hwlab-v02`, and public ports `19666/19667`. The `v0.2` GitOps branch, runtime path and Argo Application must be distinct from DEV/PROD before any automated rollout is enabled.
|
||||
|
||||
@@ -44,7 +44,7 @@ G14 native k3s is the HWLAB DEV/PROD runtime source of truth. Commands that obse
|
||||
|
||||
```sh
|
||||
tran G14:k3s kubectl -n hwlab-dev get deploy,svc,pod -o wide
|
||||
tran G14:k3s kubectl -n argocd get application hwlab-g14-dev hwlab-g14-prod
|
||||
tran G14:k3s kubectl -n argocd get application hwlab-node-dev hwlab-node-prod
|
||||
tran G14:k3s kubectl -n hwlab-v02 get deploy,svc,pod -o wide
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ tran G14:k3s kubectl get nodes -o wide
|
||||
tran G14:k3s kubectl get ns hwlab-dev hwlab-prod
|
||||
```
|
||||
|
||||
结果必须显示 G14 原生 k3s 中存在 `hwlab-dev`;需要验证 PROD 发布面时也必须显示 `hwlab-prod`。出现 D601 kubeconfig、Docker Desktop Kubernetes、`desktop-control-plane`、`127.0.0.1:11700` 或第二套 `hwlab-dev` 控制面时,停止 hotfix、回滚和验收判断。此边界与 [dev-runtime-boundary.md](dev-runtime-boundary.md) 和 [g14-gitops-cicd.md](g14-gitops-cicd.md) 保持一致。
|
||||
结果必须显示 G14 原生 k3s 中存在 `hwlab-dev`;需要验证 PROD 发布面时也必须显示 `hwlab-prod`。出现 D601 kubeconfig、Docker Desktop Kubernetes、`desktop-control-plane`、`127.0.0.1:11700` 或第二套 `hwlab-dev` 控制面时,停止 hotfix、回滚和验收判断。此边界与 [dev-runtime-boundary.md](dev-runtime-boundary.md) 和 [node-gitops-cicd.md](node-gitops-cicd.md) 保持一致。
|
||||
|
||||
## kubectl 热修命令形状
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
G14 CI/CD 耗时必须按阶段测量,不能只看一个 PipelineRun 总时长:
|
||||
|
||||
1. Source trigger:`G14` source commit 被 `hwlab-g14-branch-poller` 发现。
|
||||
1. Source trigger:`G14` source commit 被 `hwlab-node-branch-poller` 发现。
|
||||
2. Source preparation:`prepare-source` clone source、checkout 精确 revision、导入最新 GitOps artifact catalog,并安装必要 npm 依赖。
|
||||
3. Primitive validation:`repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check` 从准备好的 workspace 并行运行。
|
||||
3. Primitive validation:`repo-reports-guard`、`node-contract-check`、`codex-api-forwarder-check` 从准备好的 workspace 并行运行。
|
||||
4. Artifact planning:`plan-artifacts` 根据 component input hash 和上一轮 catalog 判断 affected/reused services。
|
||||
5. Per-service fan-out:affected services 通过 BuildKit 发布镜像;reused services 复用 catalog identity,不能改变 workload pod template。
|
||||
6. Artifact collection:`collect-artifacts` 汇总 per-service results 并验证 publish report。
|
||||
@@ -24,7 +24,7 @@ G14 CI/CD 耗时必须按阶段测量,不能只看一个 PipelineRun 总时长
|
||||
|
||||
## Timing 输出合同
|
||||
|
||||
G14 Tekton 日志中的分段耗时统一使用 JSON 行事件:`event="g14-cicd-timing"`、`schemaVersion="v1"`、`stage`、`status`、`durationMs`,并尽量携带 `pipelineRun`、`taskRun`、`task`、`revision` 和 `serviceId`。后续性能对比只消费这些稳定字段,不从自然语言日志里反推耗时。
|
||||
G14 Tekton 日志中的分段耗时统一使用 JSON 行事件:`event="node-cicd-timing"`、`schemaVersion="v1"`、`stage`、`status`、`durationMs`,并尽量携带 `pipelineRun`、`taskRun`、`task`、`revision` 和 `serviceId`。后续性能对比只消费这些稳定字段,不从自然语言日志里反推耗时。
|
||||
|
||||
当前稳定 stage 名称:`source-clone`、`catalog-fetch`、`npm-ci`、`buildkit-context-transfer`、`dependency-install`、`cache-import`、`cache-export`、`gitops-render`、`gitops-clone`、`gitops-commit`、`gitops-push`、`argo-refresh`、`workload-ready`。
|
||||
|
||||
@@ -5,12 +5,12 @@ 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。
|
||||
- CI:Tekton 在 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。
|
||||
- CI:Tekton 在 G14 k3s 内运行 `hwlab-node-ci-image-publish` Pipeline,由 `scripts/gitops-render.mjs` 直接生成原生 task;最小校验固定为 `repo-reports-guard`、`node-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 真相。发布态 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`。
|
||||
- Branch split:`G14` 是源码监控分支,只保存人写源码、声明和 seed contract;`G14-gitops` 是 Tekton promotion 写入的生成分支,保存 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/node/**` desired state。CI/CD 不再把 catalog promotion commit 写回 `G14`。
|
||||
- v0.2 扩容线:`v0.2` 必须从当前 `G14` fork 出来,并以 `G14:/root/hwlab-v02` 作为固定开发 workspace、`G14:/root/hwlab-v02-cicd.git` 作为固定 CI/CD source repo、`hwlab-v02` 作为固定 runtime namespace。`v0.2` CI/CD 只能作为新增 lane 接入现有 G14 Tekton/Argo 体系,不得改写、删除、暂停或重定向现有 `G14` poller、`G14-gitops` DEV/PROD desired state、`hwlab-dev` 或 `hwlab-prod`;详细规格见 [spec-v02-cicd.md](spec-v02-cicd.md)。
|
||||
- CD:Argo CD 只消费 `G14-gitops:deploy/gitops/g14/runtime-dev` 与 `deploy/gitops/g14/runtime-prod` 的 Git desired state,不重新构建镜像,不读取 D601 状态,不获取 legacy DEV CD Lease。
|
||||
- FRP:G14 DEV 通过 `hwlab-dev/hwlab-g14-frpc` 暴露 `17666/17667`,G14 PROD 通过 `hwlab-prod/hwlab-g14-prod-frpc` 暴露 `18666/18667`;`v0.2` 规划通过 `hwlab-v02` 内独立 frpc 暴露 `19666/19667`。master frps 的 `deploy/frp/frps.dev.toml` 与实际 `/etc/frp/frps.toml` 必须放行对应端口,但 `v0.2` 放行只能新增 19xxx 入口,不能复用或覆盖 DEV/PROD 入口。
|
||||
- CD:Argo CD 只消费 `G14-gitops:deploy/gitops/node/runtime-dev` 与 `deploy/gitops/node/runtime-prod` 的 Git desired state,不重新构建镜像,不读取 D601 状态,不获取 legacy DEV CD Lease。
|
||||
- FRP:G14 DEV 通过 `hwlab-dev/hwlab-node-frpc` 暴露 `17666/17667`,G14 PROD 通过 `hwlab-prod/hwlab-node-prod-frpc` 暴露 `18666/18667`;`v0.2` 规划通过 `hwlab-v02` 内独立 frpc 暴露 `19666/19667`。master frps 的 `deploy/frp/frps.dev.toml` 与实际 `/etc/frp/frps.toml` 必须放行对应端口,但 `v0.2` 放行只能新增 19xxx 入口,不能复用或覆盖 DEV/PROD 入口。
|
||||
- 并行性:不同 source commit 的 CI build 不共享发布锁;并行安全由 immutable commit tag/digest 和 Git desired state 保证。最终运行版本由 Argo CD 当前同步的 Git revision 决定。
|
||||
|
||||
## 门禁最小化与扩容治理
|
||||
@@ -19,38 +19,38 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
|
||||
|
||||
旧 DEV/D601/main 门禁如果阻碍当前 G14 或 `v0.2` 路径,默认处理是从当前调用链删除,而不是做兼容性迁移、fallback、legacy mode、双路径绕行或在旧门禁上叠加例外。新增门禁只能覆盖明确高价值风险,且必须最小、低噪声、容易删除;资源配额、RBAC 命名、清理策略、回滚顺序、人工同步策略等默认是设计约定或 runbook,不是 CI/CD 通过条件。
|
||||
|
||||
`v0.2` 的硬边界只包括:source branch 必须是 `v0.2`,CI/CD source repo 必须是 `/root/hwlab-v02-cicd.git`,GitOps branch 必须是 `v0.2-gitops`,Git mirror/relay 必须来自 `devops-infra`,runtime namespace 必须是 `hwlab-v02`,runtime path 必须是 `deploy/gitops/g14/runtime-v02`,Argo Application 必须指向 `v0.2` GitOps lane,公网入口只能是 `19666/19667`,`v0.2` source branch 不跟踪生成物,旧 DEV/D601/main 门禁不进入 `v0.2` 调用链。其他事项先写成决策表或 runbook;只有被证明无法靠上述边界和标准入口自然收敛时,才允许新增最小检查。
|
||||
`v0.2` 的硬边界只包括:source branch 必须是 `v0.2`,CI/CD source repo 必须是 `/root/hwlab-v02-cicd.git`,GitOps branch 必须是 `v0.2-gitops`,Git mirror/relay 必须来自 `devops-infra`,runtime namespace 必须是 `hwlab-v02`,runtime path 必须是 `deploy/gitops/node/runtime-v02`,Argo Application 必须指向 `v0.2` GitOps lane,公网入口只能是 `19666/19667`,`v0.2` source branch 不跟踪生成物,旧 DEV/D601/main 门禁不进入 `v0.2` 调用链。其他事项先写成决策表或 runbook;只有被证明无法靠上述边界和标准入口自然收敛时,才允许新增最小检查。
|
||||
|
||||
## 生成入口
|
||||
|
||||
`scripts/g14-gitops-render.mjs` 是 G14 专用转换器:
|
||||
`scripts/gitops-render.mjs` 是 G14 专用转换器:
|
||||
|
||||
- 架构迁移时,过时的自检、预检、guard、gate 优先删除,不在旧门禁上叠加例外或复杂度;新门禁只允许覆盖明确高价值风险,必须保持最小、低噪声、易迁移。
|
||||
|
||||
- 直接声明 Tekton Pipeline、最小原语校验 task 和 PipelineRun 样板;不再读取 `CI.json` 或生成 `ci-json` step。
|
||||
- 读取 `deploy/deploy.yaml` 与 `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 验收证据中。
|
||||
- `g14-contract-check` 在 fresh source clone 内取当前 `HEAD`,把 GitOps 产物渲染到 `mktemp -d` 临时目录,并立刻用同一个 `--source-revision` 执行 `--check`;它校验 render 代码、原生 Tekton 产物生成合同和 forbidden-fragment 护栏,不依赖 source branch 预先存在 `deploy/gitops/g14/source.json`。面向已发布 runtime 的 `source.json` 只存在于 `G14-gitops` 生成分支,作为 promotion evidence。
|
||||
- 生成 `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/artifact-catalog.dev.json` 与 `deploy/gitops/g14/**` 推送到该分支,避免把生成提交继续写回 `G14`。
|
||||
- `v0.2` GitOps lane 必须使用独立生成身份,例如 `v0.2-gitops`、`deploy/artifact-catalog.v02.json` 和 `deploy/gitops/g14/runtime-v02`,并由独立 Argo CD Application 指向 `hwlab-v02`。若后续实现选择复用某个脚本入口,也必须通过显式参数区分 source branch、catalog、runtime path、Application 和 namespace,不能靠修改默认值让 `G14` DEV/PROD 行为漂移。
|
||||
- G14 Tekton 的镜像构建发布入口必须是 `scripts/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 验收证据中。
|
||||
- `node-contract-check` 在 fresh source clone 内取当前 `HEAD`,把 GitOps 产物渲染到 `mktemp -d` 临时目录,并立刻用同一个 `--source-revision` 执行 `--check`;它校验 render 代码、原生 Tekton 产物生成合同和 forbidden-fragment 护栏,不依赖 source branch 预先存在 `deploy/gitops/node/source.json`。面向已发布 runtime 的 `source.json` 只存在于 `G14-gitops` 生成分支,作为 promotion evidence。
|
||||
- 生成 `hwlab-node-branch-poller` CronJob:它使用 G14 集群内的 Git SSH Secret 轮询 `G14` 分支,按 source commit 创建确定命名的 Tekton PipelineRun。
|
||||
- 生成 `hwlab-node-control-plane-reconciler` CronJob:它使用同一个 Git SSH Secret 轮询 `G14`,运行 repo 内 `scripts/gitops-render.mjs`,并 server-side apply 生成的 Tekton RBAC、Pipeline、Poller 和 Reconciler manifests;因此 CI 控制面变化应自动进入 G14 k3s,不需要人工长期执行 render/apply。
|
||||
- 默认输出到 `deploy/gitops/node/`。
|
||||
- 默认 GitOps 生成分支是 `G14-gitops`;Pipeline 成功后把本次 source commit 对应的 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/node/**` 推送到该分支,避免把生成提交继续写回 `G14`。
|
||||
- `v0.2` GitOps lane 必须使用独立生成身份,例如 `v0.2-gitops`、`deploy/artifact-catalog.v02.json` 和 `deploy/gitops/node/runtime-v02`,并由独立 Argo CD Application 指向 `hwlab-v02`。若后续实现选择复用某个脚本入口,也必须通过显式参数区分 source branch、catalog、runtime path、Application 和 namespace,不能靠修改默认值让 `G14` DEV/PROD 行为漂移。
|
||||
- 默认 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-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI。镜像内容由 `deploy/ci/hwlab-ci-node-tools.Dockerfile` 声明;脚本启动时必须输出工具与 proxy preflight 结构化日志。缺少工具时应先在 G14 构建并推送新的工具镜像,再修改 `HWLAB_G14_CI_TOOLS_IMAGE`/render 默认值,不能回退到 runtime 安装。
|
||||
- `prepare-source` 和最小原语校验 task 不允许每次运行时重新 `apk add`、`apt-get install` 或临时下载 browser/runtime 依赖。当前固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI。镜像内容由 `deploy/ci/hwlab-ci-node-tools.Dockerfile` 声明;脚本启动时必须输出工具与 proxy preflight 结构化日志。缺少工具时应先在 G14 构建并推送新的工具镜像,再修改 `HWLAB_NODE_CI_TOOLS_IMAGE`/render 默认值,不能回退到 runtime 安装。
|
||||
- G14 host 只用于 source workspace、GitOps render、k3s 控制和轻量语法/静态合同检查;不要把 host 当成浏览器执行面。低频 browser smoke 已不再属于默认 primitive CI。若确实需要一次性布局、移动端或交互验证,必须显式在 G14 k3s/Tekton 的专用 Playwright 镜像内运行,不能回退成 host 上强装 browser 的长期方案。
|
||||
- Poller、control-plane reconciler、image publish 和 GitOps promote step 都不允许每次运行时 `apk add` / `apt-get install`。当前 G14 registry 固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI;生成的脚本只做 proxy preflight 与工具存在性检查。若需要升级工具,先在 G14 构建/推送新的工具镜像,再修改 `HWLAB_G14_CI_TOOLS_IMAGE`/render 默认值并由 reconciler apply。
|
||||
- 服务镜像构建的默认 parent/base image 不得从 Docker Hub 反复拉取。当前 `node:20-bookworm-slim` 已镜像到 G14 registry 的 allowlist 名称:`127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim`,render 默认 `base-image` 和 poller `BASE_IMAGE` 都指向这个本地镜像。需要升级 parent image 时,先通过 G14 proxy 拉取并推送到 G14 registry,再修改 `HWLAB_G14_DEV_BASE_IMAGE`/render 默认值;image publish step 只允许从本地 registry pull base image,且 tag 必须符合 publish gate 的 `hwlab-node20-base`/`hwlab-dev-base`/`hwlab-node-runtime-base` allowlist。
|
||||
- Poller、control-plane reconciler、image publish 和 GitOps promote step 都不允许每次运行时 `apk add` / `apt-get install`。当前 G14 registry 固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1`,包含 Node 22、npm、Bun、Git、OpenSSH、curl、Python3 和 Docker CLI;生成的脚本只做 proxy preflight 与工具存在性检查。若需要升级工具,先在 G14 构建/推送新的工具镜像,再修改 `HWLAB_NODE_CI_TOOLS_IMAGE`/render 默认值并由 reconciler apply。
|
||||
- 服务镜像构建的默认 parent/base image 不得从 Docker Hub 反复拉取。当前 `node:20-bookworm-slim` 已镜像到 G14 registry 的 allowlist 名称:`127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim`,render 默认 `base-image` 和 poller `BASE_IMAGE` 都指向这个本地镜像。需要升级 parent image 时,先通过 G14 proxy 拉取并推送到 G14 registry,再修改 `HWLAB_NODE_DEV_BASE_IMAGE`/render 默认值;image publish step 只允许从本地 registry pull base image,且 tag 必须符合 publish gate 的 `hwlab-node20-base`/`hwlab-dev-base`/`hwlab-node-runtime-base` allowlist。
|
||||
- 任何依赖下载阶段都必须有可观测诊断。生成的 Tekton 脚本在 npm、BuildKit base image/local registry probe 和 GitOps promote 之前输出结构化 `dependency-proxy-probe`、`dependency-curl-probe` 或 `dependency-download-*` 日志,至少包含 phase、目标 URL/镜像、脱敏 proxy、首包耗时、总耗时、下载字节数和速度。CI/CD 卡在下载时,先用这些日志判断是 proxy 不可达、目标源慢、DNS/首包慢还是下载吞吐低,再决定是否切换 G14 代理节点或预热镜像;不能只凭 PipelineRun Running 时长判断业务测试失败。
|
||||
|
||||
常用命令:
|
||||
|
||||
```sh
|
||||
npm run g14:gitops:render -- --source-revision <sourceCommit>
|
||||
npm run gitops:render -- --source-revision <sourceCommit>
|
||||
```
|
||||
|
||||
人工 source workspace 不再对 `deploy/gitops/g14/**` 做生成物对比;这些文件在 source 分支下是忽略的生成物,旧文件和 `source.json` 不是真相。需要看渲染计划时使用 `npm run g14:gitops:render -- --no-write`;正式发布态只看 `G14-gitops`、Argo Application 和 live runtime。
|
||||
人工 source workspace 不再对 `deploy/gitops/node/**` 做生成物对比;这些文件在 source 分支下是忽略的生成物,旧文件和 `source.json` 不是真相。需要看渲染计划时使用 `npm run gitops:render -- --no-write`;正式发布态只看 `G14-gitops`、Argo Application 和 live runtime。
|
||||
|
||||
## 原生 k8s Tekton + Argo 配置面 vs 已废弃的 CI.json runner
|
||||
|
||||
@@ -76,14 +76,14 @@ npm run g14:gitops:render -- --source-revision <sourceCommit>
|
||||
HWLAB 是 monorepo,G14 CI/CD 加速必须按组件输入判断构建和滚动;v0.2 env-reuse 的组件边界与环境配方直接来自 `deploy/deploy.yaml`,运行态镜像身份来自 per-service artifact catalog;`CI.json` 已删除,不再作为任何 planner 输入。
|
||||
|
||||
- `deploy/deploy.yaml` 是当前 v0.2 人写 deploy/runtime 配置单一出处;`deploy/deploy.json` 不得作为 v0.2 兼容源恢复。配置读写由 `scripts/src/structured-config.mjs` 和 `scripts/src/deploy-config.mjs` 统一承载,只有该格式无关层直接处理 YAML/JSON 解析;planner、renderer、smoke、artifact helper 和 CLI 只调用读写 helper。
|
||||
- `scripts/g14-ci-plan.mjs` 是只读 planner,默认读取当前 workspace 的 `deploy/deploy.yaml`、`deploy/artifact-catalog.dev.json`,以及 `deploy/deploy.yaml` 内的 lane service declarations/env recipe,输出 `affectedServices`、`reusedServices`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImageDigest`、`buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。Tekton `prepare-source` 会先从 `G14-gitops` 注入上一轮发布态 catalog,source 分支里的 catalog 只作为 seed contract。
|
||||
- `scripts/ci-plan.mjs` 是只读 planner,默认读取当前 workspace 的 `deploy/deploy.yaml`、`deploy/artifact-catalog.dev.json`,以及 `deploy/deploy.yaml` 内的 lane service declarations/env recipe,输出 `affectedServices`、`reusedServices`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImageDigest`、`buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。Tekton `prepare-source` 会先从 `G14-gitops` 注入上一轮发布态 catalog,source 分支里的 catalog 只作为 seed contract。
|
||||
- v0.2 服务清单来自显式 `--services` 或 `deploy.lanes.v02.envReuseServices`;旧 `deploy.services[]`、`deploy.k3s.serviceMappings[]` 和 `internal/protocol.SERVICE_IDS` 推导路径不再作为 planner 输入。
|
||||
- v0.2 env-reuse 组件边界和环境镜像配方以 `deploy/deploy.yaml` 的 `lanes.v02.serviceDeclarations`、`lanes.v02.envRecipe` 和 `lanes.v02.bootConfig` 为单一配置点;新增服务或调整 `componentPaths`、`runtimeKind`、`entrypoint`、Bun 版本、系统包、launcher 路径和 HWPOD alias 时先改 deploy 配置与 schema/测试,不再把这些属性硬编码进 planner 或 artifact publish helper。
|
||||
- `hwpod` 是 runner 内的稳定 HWPOD task 入口,由 `tools/hwpod-cli.ts`、`tools/hwpod-compiler-cli.ts`、`tools/hwpod-ctl.ts`、`tools/hwpod-node.ts` 和 `skills/hwpod-cli/`、`skills/hwpod-ctl/` 组成。修改这些路径时,G14/v0.2 CI 必须至少触发携带 `/usr/local/bin/hwpod` 的 runtime skills/env-reuse 重新装配或 rollout,不能全量复用旧 artifact 后只报告 PipelineRun 成功。
|
||||
- `scripts/g14-artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices`,`reusedServices` 从 `deploy/artifact-catalog.dev.json` 或 lane catalog 复用已有 sha256 digest。非 env-reuse 服务复用前必须满足 artifact provenance 自证:catalog 有可验证 digest、catalog 的 `sourceCommitId` 在当前 repo 可解析、用该 `sourceCommitId` 的 source tree 重新计算出的 `componentInputHash` 等于 catalog 记录、该 hash 再等于本轮 planner 计算值,且 `dockerfileHash`/`buildArgsHash` 没有不一致。catalog 缺 digest、缺 provenance、source tree 无法解析、catalog hash 与 catalog source tree 不一致、或 catalog hash 与本轮 input 不一致时,planner 必须把该服务列为 affected 并重新发布,不能用旧 guard 阻塞,也不能退回 Docker 或 legacy full-build 路线。
|
||||
- `scripts/artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices`,`reusedServices` 从 `deploy/artifact-catalog.dev.json` 或 lane catalog 复用已有 sha256 digest。非 env-reuse 服务复用前必须满足 artifact provenance 自证:catalog 有可验证 digest、catalog 的 `sourceCommitId` 在当前 repo 可解析、用该 `sourceCommitId` 的 source tree 重新计算出的 `componentInputHash` 等于 catalog 记录、该 hash 再等于本轮 planner 计算值,且 `dockerfileHash`/`buildArgsHash` 没有不一致。catalog 缺 digest、缺 provenance、source tree 无法解析、catalog hash 与 catalog source tree 不一致、或 catalog hash 与本轮 input 不一致时,planner 必须把该服务列为 affected 并重新发布,不能用旧 guard 阻塞,也不能退回 Docker 或 legacy full-build 路线。
|
||||
- Artifact catalog 的 per-service provenance 是镜像 digest 的身份证明,不是本轮 planner 状态缓存。reuse 路径只能保留旧 artifact 自身的 `sourceCommitId`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImage*` 和 `buildArgsHash`;禁止把当前 planner 的 component/build 元数据写入复用的旧 digest,否则下一轮 planner 会把旧镜像误判成已包含新输入,造成 CI/CD false-green。
|
||||
- `scripts/g14-artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 默认只预览,只有 G14 Tekton promotion 显式传 `--write` 时,才把生成的 `commitId`、`image`、`imageTag`、`digest`、`publishState` 和 component provenance 字段写进当前 workspace 的 `deploy/artifact-catalog.dev.json`,随后只提交到 `G14-gitops`。`deploy/deploy.yaml` 是人写的 runtime config 真相源,不得被 promotion、refresh 脚本或人工发布流程回写镜像身份字段。
|
||||
- `scripts/g14-gitops-render.mjs` 只支持混合 desired state:workload 的 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.yaml`。全局 GitOps metadata 仍记录本次 source commit。`--legacy-source-images` 与 `HWLAB_G14_USE_DEPLOY_IMAGES=0` 已废弃,不得把所有 workload image 回退渲染为同一个 source commit tag。
|
||||
- `scripts/artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 默认只预览,只有 G14 Tekton promotion 显式传 `--write` 时,才把生成的 `commitId`、`image`、`imageTag`、`digest`、`publishState` 和 component provenance 字段写进当前 workspace 的 `deploy/artifact-catalog.dev.json`,随后只提交到 `G14-gitops`。`deploy/deploy.yaml` 是人写的 runtime config 真相源,不得被 promotion、refresh 脚本或人工发布流程回写镜像身份字段。
|
||||
- `scripts/gitops-render.mjs` 只支持混合 desired state:workload 的 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.yaml`。全局 GitOps metadata 仍记录本次 source commit。`--legacy-source-images` 与 `HWLAB_NODE_USE_DEPLOY_IMAGES=0` 已废弃,不得把所有 workload image 回退渲染为同一个 source commit tag。
|
||||
- G14 Tekton promotion 在推送 `G14-gitops` 前,必须用 publish report 显式 `--write` 刷新 workspace 内的 `deploy/artifact-catalog.dev.json`,然后把刷新后的 catalog 和 rendered GitOps desired state 一起提交到 `G14-gitops`。promotion 不得自动修改或推送 `G14` source branch,也不得自动修改 `deploy/deploy.yaml` 或 `deploy/k8s/base/workloads.yaml`,这样人写配置不会和 CI 生成身份反复冲突。
|
||||
- Tekton 并发化只能以 planner 输出作为输入;每个 service 都有独立 TaskRun,changed service 启动 BuildKit,unchanged service 只写 reuse result 并复用 catalog digest,且不能改 pod template,避免无意义 rollout。没有完整 per-service desired state 证据时,必须修复 planner/catalog 证据,不能使用 `--full-build`、`--legacy-source-images`、DIND 或 Docker fallback 回退。
|
||||
|
||||
@@ -91,7 +91,7 @@ HWLAB 是 monorepo,G14 CI/CD 加速必须按组件输入判断构建和滚动
|
||||
|
||||
G14 CI/CD 加速的第一判定标准不是 PipelineRun 总耗时单点变短,而是 monorepo 组件粒度的“少构建、少滚动、可并发”是否成立。
|
||||
|
||||
- 触发延迟:`hwlab-g14-branch-poller` 固定每 1 分钟轮询 `G14`。相对 5 分钟轮询,平均触发等待应从约 2.5 分钟降到约 0.5 分钟,最坏等待从 5 分钟降到 1 分钟。发现 `kubectl -n hwlab-ci get cronjob hwlab-g14-branch-poller -o jsonpath='{.spec.schedule}'` 不是 `* * * * *` 时,应先修 poller/reconciler,而不是手工长期创建 PipelineRun。
|
||||
- 触发延迟:`hwlab-node-branch-poller` 固定每 1 分钟轮询 `G14`。相对 5 分钟轮询,平均触发等待应从约 2.5 分钟降到约 0.5 分钟,最坏等待从 5 分钟降到 1 分钟。发现 `kubectl -n hwlab-ci get cronjob hwlab-node-branch-poller -o jsonpath='{.spec.schedule}'` 不是 `* * * * *` 时,应先修 poller/reconciler,而不是手工长期创建 PipelineRun。
|
||||
- 组件懒构建:docs-only、GitOps-only、poller/reconciler manifest-only 等不影响服务输入的变更,planner 应输出 `affectedServices=[]`、`buildSkippedCount=<全部服务数>`,各 service TaskRun 应写出 `status=reused` 和 `build-backend=reused-catalog`。这类变更不应重新构建任何服务镜像,也不应把 workload image 改成当前 source commit tag。
|
||||
- 并发 fan-out:per-service TaskRun 应由 Tekton/k8s scheduler 同时调度,多个 service 的 build/reuse 窗口应接近“最慢单个服务任务耗时”,而不是所有服务串行相加。reuse-only 场景的 fan-out 窗口当前基线约为十几秒;真实组件构建场景仍需按 changed service 单独记录 BuildKit 耗时和 cache hit 情况。
|
||||
- CD rollout:unchanged service 必须复用 `deploy/artifact-catalog.dev.json` 的 image/digest,pod template 不应因全局 source commit 改变而无意义滚动。手写 manifest(例如 `deepseek-proxy`、device-agent 类辅助 workload)只要复用某个已发布服务镜像,也必须走同一个 catalog image 选择逻辑,不能直接用当前 source commit tag。
|
||||
@@ -100,14 +100,14 @@ 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。
|
||||
- `prepare-source` 与最小原语校验 task 仍是前段主要开销。它们包含 Git clone、checkout、必要的 `npm ci`、`repo-reports-guard`、`node-contract-check` 和 `codex-api-forwarder-check`。继续加速应优先减少 workspace cold start 与依赖安装成本,而不是重新塞回一条 shell runner。
|
||||
- `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 Task,changed 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 判成失败。
|
||||
- StatefulSet 旧 ordinal pod 可能长期卡在历史缺失镜像上,即使 StatefulSet 当前 template 已经修正。DEV 可按运行面热修流程删除旧 Pending pod 让 StatefulSet 按当前 template 重建;PROD namespace 需要明确 maintenance 授权后再做同类删除。
|
||||
|
||||
每次优化 CI/CD 都必须保留可比测量:记录 PipelineRun 总耗时、`prepare-source`、`repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out 起止窗口、`collect-artifacts`、`gitops-promote`、Argo sync 到 workload ready 的时间,并标明本轮是 reuse-only、单组件 build 还是多组件 build。没有这些分段数据,不要只用“感觉变快/变慢”判断优化成败。
|
||||
每次优化 CI/CD 都必须保留可比测量:记录 PipelineRun 总耗时、`prepare-source`、`repo-reports-guard`、`node-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out 起止窗口、`collect-artifacts`、`gitops-promote`、Argo sync 到 workload ready 的时间,并标明本轮是 reuse-only、单组件 build 还是多组件 build。没有这些分段数据,不要只用“感觉变快/变慢”判断优化成败。
|
||||
|
||||
|
||||
## Code Agent Provider Profiles
|
||||
@@ -126,15 +126,15 @@ DeepSeek proxy manifest 是 GitOps desired state 的一部分,DEV/PROD 分别
|
||||
|
||||
## Polling 触发
|
||||
|
||||
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>`。
|
||||
G14 不要求 GitHub webhook 或 GitHub Actions 配置。`hwlab-ci/hwlab-node-branch-poller` CronJob 每 1 分钟通过 Git SSH 拉取 `G14` HEAD,并用 source commit 的前 12 位生成 PipelineRun 名称 `hwlab-node-ci-poll-<short12>`。
|
||||
|
||||
`v0.2` 接入不再新增 HWLAB 发布触发 CronJob;标准入口是 UniDesk CLI `bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm`,由该入口自动 fetch `/root/hwlab-v02-cicd.git`、解析当前 `origin/v0.2` HEAD,并直接创建可区分的 commit-pinned PipelineRun,例如 `hwlab-v02-ci-poll-<short12>`。该 lane 的 GitOps promotion 只允许写入 `v0.2` 专属 catalog、runtime path 和 GitOps branch;不得向 `G14` 或现有 DEV/PROD runtime path 写入生成物,也不得从 `/root/hwlab-v02` 工作树状态选择待发布 commit。
|
||||
|
||||
G14 lane 中如果同名 PipelineRun 已存在,poller 直接跳过;如果不存在,则创建新的 PipelineRun。这样可以用 Kubernetes 原生 CronJob、ServiceAccount、RBAC 和 Tekton API 实现无 GitHub webhook 的分支监控,同时避免同一个 commit 被反复派单。历史上的 `chore: promote G14 GitOps source ...` source 分支生成提交已经废弃;G14 poller 只应该看到人写 source commit。
|
||||
|
||||
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 内构建镜像。
|
||||
Pipeline 的标准路径是:`G14` source commit -> Tekton 原语校验 task -> commit-tagged image push 到 G14 registry -> refresh artifact catalog -> render `deploy/gitops/node/**` -> 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 全部成功也不会滚动新镜像。
|
||||
GitOps promotion 成功只证明 `G14-gitops` 分支已经写入新 desired state;如果 render 改变了 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步检查并应用 `deploy/gitops/node/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`。`hwlab-node-dev` 必须指向 `deploy/gitops/node/runtime-dev`,`hwlab-node-prod` 必须指向 `deploy/gitops/node/runtime-prod`;如果集群里 Application 仍指向旧 `deploy/gitops/node/runtime`,Argo 会停在旧 revision,即使 CI/publish/promote 全部成功也不会滚动新镜像。
|
||||
|
||||
观察 PipelineRun、Argo 和 rollout 时使用短连接轮询:一次 `kubectl get` 或有限 `logs --tail` 后返回,由指挥侧间隔重试。不要用长时间 `kubectl wait --timeout=900s` 占住 `tran G14:k3s` 透传锁;如果误用长 wait,只能杀掉本地等待客户端并清理 stale tran lock,不能把这个等待过程当成 CD 失败。
|
||||
|
||||
@@ -147,17 +147,17 @@ KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n hwlab-ci logs pod/<pipelinerun-t
|
||||
|
||||
如果 probe 显示 `proxy-env-missing`、`proxy-connect-failed`、`timeout` 或速度长期接近 0,应先按 `/root/docs/vpn-proxy-ops.md` 检查 v2rayN/Hysteria,再重跑 polling。若 probe 正常但后续校验失败,按对应 Tekton task 日志和业务检查排障。
|
||||
|
||||
手动 render/apply 只允许作为 bootstrap 或 reconciler 故障抢修;正常路径必须由 `hwlab-g14-control-plane-reconciler` 自动完成 CI 控制面 manifest 更新。
|
||||
手动 render/apply 只允许作为 bootstrap 或 reconciler 故障抢修;正常路径必须由 `hwlab-node-control-plane-reconciler` 自动完成 CI 控制面 manifest 更新。
|
||||
|
||||
修改 `scripts/g14-gitops-render.mjs`、Tekton Pipeline、RBAC、poller 或 reconciler 这类 CI 控制面后,不能立刻用旧集群 PipelineRun 作为新模板验证。必须先确认 `hwlab-g14-control-plane-reconciler` 已完成并且集群内 Pipeline manifest 包含新字段,再触发手动 poller 或等待下一轮 source commit;否则 PipelineRun 会在旧 Pipeline 下创建,容易把已经修复的 sidecar、proxy 或 TaskRun 行为误判为仍然失败。
|
||||
修改 `scripts/gitops-render.mjs`、Tekton Pipeline、RBAC、poller 或 reconciler 这类 CI 控制面后,不能立刻用旧集群 PipelineRun 作为新模板验证。必须先确认 `hwlab-node-control-plane-reconciler` 已完成并且集群内 Pipeline manifest 包含新字段,再触发手动 poller 或等待下一轮 source commit;否则 PipelineRun 会在旧 Pipeline 下创建,容易把已经修复的 sidecar、proxy 或 TaskRun 行为误判为仍然失败。
|
||||
|
||||
## 集群资源
|
||||
|
||||
- `hwlab-ci`:registry、Tekton runner RBAC、Polling CronJob、Pipeline/PipelineRun 所在 namespace。
|
||||
- `hwlab-ci/hwlab-g14-control-plane-reconciler`:自动 render/apply G14 CI 控制面 manifest 的 CronJob。
|
||||
- `argocd`:Argo CD 控制面和 `hwlab-g14-dev` / `hwlab-g14-prod` Application 所在 namespace。
|
||||
- `hwlab-dev`:HWLAB DEV runtime namespace,由 Argo CD 应用 `deploy/gitops/g14/runtime-dev`。
|
||||
- `hwlab-prod`:HWLAB PROD runtime namespace,由 Argo CD 应用 `deploy/gitops/g14/runtime-prod`;通过 `hwlab-g14-prod-frpc` 映射到 `18666/18667`,正式验收必须同时检查 Argo sync、Deployment ready、FRP public health 与 source commit。
|
||||
- `hwlab-ci/hwlab-node-control-plane-reconciler`:自动 render/apply G14 CI 控制面 manifest 的 CronJob。
|
||||
- `argocd`:Argo CD 控制面和 `hwlab-node-dev` / `hwlab-node-prod` Application 所在 namespace。
|
||||
- `hwlab-dev`:HWLAB DEV runtime namespace,由 Argo CD 应用 `deploy/gitops/node/runtime-dev`。
|
||||
- `hwlab-prod`:HWLAB PROD runtime namespace,由 Argo CD 应用 `deploy/gitops/node/runtime-prod`;通过 `hwlab-node-prod-frpc` 映射到 `18666/18667`,正式验收必须同时检查 Argo sync、Deployment ready、FRP public health 与 source commit。
|
||||
- `hwlab-v02`:HWLAB `v0.2` runtime namespace,只能由 `v0.2` 专属 Argo CD Application 和 GitOps runtime path 管理;规划通过独立 frpc 映射到 `19666/19667`。创建 `hwlab-v02` 不得删除、重命名或改写 `hwlab-dev`/`hwlab-prod`。
|
||||
|
||||
G14 registry 由 Kubernetes Deployment `hwlab-ci/hwlab-registry` 承载,使用 host network 暴露 `127.0.0.1:5000` 给 k3s/containerd 和 host-network Tekton build pod。旧 host Docker registry 不能与该 Deployment 同时占用 5000 端口。
|
||||
@@ -181,9 +181,9 @@ G14 GitOps manifests 不包含 D601 kubeconfig、D601 node guard、D601 FRP 公
|
||||
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;`v0.2` 接入后还要看 `hwlab-g14-v02` 或等价专属 Application 是否指向 `v0.2` GitOps lane 和 `hwlab-v02`。
|
||||
2. Argo desired state:`hwlab-node-dev` / `hwlab-node-prod` 的 Application revision、sync、health 和实际 runtime path;`v0.2` 接入后还要看 `hwlab-node-v02` 或等价专属 Application 是否指向 `v0.2` GitOps lane 和 `hwlab-v02`。
|
||||
3. Tekton 执行证据:branch-poller 日志、PipelineRun、TaskRun results、`gitops-promote` 终态。
|
||||
4. 干净 source workspace:`origin/G14` 当前内容,以及 `npm run g14:gitops:render -- --no-write` 和 planner 输出。
|
||||
4. 干净 source workspace:`origin/G14` 当前内容,以及 `npm run gitops:render -- --no-write` 和 planner 输出。
|
||||
5. 对照线索:旧 commit 记忆、坏 worktree、D601 legacy 路径、脚本旧默认值,只能当线索,不能当真相。
|
||||
|
||||
常见误判与纠正:
|
||||
@@ -214,8 +214,8 @@ bun scripts/cli.ts ssh G14:/root/hwlab shell 'git status --short --branch && git
|
||||
2. Source / PR 预检
|
||||
|
||||
```sh
|
||||
npm run g14:gitops:render -- --no-write
|
||||
node scripts/g14-ci-plan.mjs --base-ref origin/G14 --target-ref HEAD --pretty
|
||||
npm run gitops:render -- --no-write
|
||||
node scripts/ci-plan.mjs --base-ref origin/G14 --target-ref HEAD --pretty
|
||||
```
|
||||
|
||||
- 先看当前内容和长期参考,不按旧 commit 记忆判断“功能是否已合入”。
|
||||
@@ -230,12 +230,12 @@ node scripts/g14-ci-plan.mjs --base-ref origin/G14 --target-ref HEAD --pretty
|
||||
4. 确认 CI 已接单
|
||||
|
||||
- 先看最新 branch-poller job 或日志,确认它是否识别到新的 `G14` HEAD。
|
||||
- 再看 `hwlab-ci` 里的 PipelineRun 是否出现 `hwlab-g14-ci-poll-<short12>`。poller 还没创建 PipelineRun 时,不要先把问题归类为 Tekton 故障。
|
||||
- 如果本轮改的是 poller、Pipeline、RBAC、reconciler 或 render 模板,先确认 `hwlab-g14-control-plane-reconciler` 已经把新控制面 apply 进去,再用新 commit 验证。
|
||||
- 再看 `hwlab-ci` 里的 PipelineRun 是否出现 `hwlab-node-ci-poll-<short12>`。poller 还没创建 PipelineRun 时,不要先把问题归类为 Tekton 故障。
|
||||
- 如果本轮改的是 poller、Pipeline、RBAC、reconciler 或 render 模板,先确认 `hwlab-node-control-plane-reconciler` 已经把新控制面 apply 进去,再用新 commit 验证。
|
||||
|
||||
5. 确认 CI 真实通过
|
||||
|
||||
- 最低通过条件是:`prepare-source`、`repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out、`collect-artifacts`、`gitops-promote` 全部成功。
|
||||
- 最低通过条件是:`prepare-source`、`repo-reports-guard`、`node-contract-check`、`codex-api-forwarder-check`、`plan-artifacts`、per-service fan-out、`collect-artifacts`、`gitops-promote` 全部成功。
|
||||
- `build-*` fan-out 要按 result 判断 reused 还是 rebuilt;不要只看 TaskRun 名称。
|
||||
- reuse-only 变更的目标是 `affectedServices=[]`、全部 service `status=reused`,且不触发无意义 rollout。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# HWLAB 平行 Runtime Lane 扩容规格
|
||||
|
||||
本文是 HWLAB `v0.x` 平行 runtime lane 的长期扩容规格。`v0.3` 之后的 `v0.4`、`v0.5`、`v0.6` 等扩容必须沿用同一模式:人工只维护 `deploy/deploy.yaml` 中的 lane 声明,然后通过 repo-owned CLI 生成或应用扩容动作;不得再为每个新 lane 复制多处代码、手写 GitOps YAML、手动拼 namespace/Secret/FRP/Tekton/Argo 对象,或恢复 D601/G14 旧兼容路径。
|
||||
本文是 HWLAB `v0.x` 平行 runtime lane 的长期扩容规格。`v0.3` 之后的 `v0.4`、`v0.5`、`v0.6` 等扩容必须沿用同一模式:人工只维护 `deploy/deploy.yaml` 中的 lane 声明,然后通过 repo-owned CLI 生成或应用扩容动作;不得再为每个新 lane 复制多处代码、手写 GitOps YAML、手动拼 namespace/Secret/FRP/Tekton/Argo 对象,或恢复 D601/旧节点兼容路径。
|
||||
|
||||
## 标准入口
|
||||
|
||||
@@ -17,9 +17,9 @@ node scripts/hwlab-lane-expand.mjs configure --lane v03 --from v02 --write
|
||||
| `v03` | source branch `v0.3` |
|
||||
| `v03` | GitOps branch `v0.3-gitops` |
|
||||
| `v03` | namespace `hwlab-v03` |
|
||||
| `v03` | runtime path `deploy/gitops/g14/runtime-v03` |
|
||||
| `v03` | runtime path `deploy/gitops/node/runtime-v03` |
|
||||
| `v03` | artifact catalog `deploy/artifact-catalog.v03.json` |
|
||||
| `v03` | Argo Application `hwlab-g14-v03` |
|
||||
| `v03` | Argo Application `hwlab-node-v03` |
|
||||
| `v03` | Tekton Pipeline `hwlab-v03-ci-image-publish` |
|
||||
| `v03` | default legacy FRP ports `19766/19767` |
|
||||
|
||||
@@ -37,32 +37,32 @@ node scripts/hwlab-lane-expand.mjs configure --lane v03 --from v02 --write
|
||||
|
||||
## GitOps 模块化与严格 TS
|
||||
|
||||
`v0.3` 起,GitOps 扩容逻辑不得继续堆进单个 `scripts/g14-gitops-render.mjs`。该文件只保留历史入口兼容、参数解析和高层编排;新增或抽出的 GitOps 逻辑必须进入 `scripts/src/` 下的领域模块,并优先使用 `.ts`。
|
||||
`v0.3` 起,GitOps 扩容逻辑不得继续堆进单个 `scripts/gitops-render.mjs`。该文件只保留历史入口兼容、参数解析和高层编排;新增或抽出的 GitOps 逻辑必须进入 `scripts/src/` 下的领域模块,并优先使用 `.ts`。
|
||||
|
||||
第一批严格 TS 模块是 `scripts/src/g14-gitops-lane.ts`,负责 runtime lane 的命名、端口、endpoint、branch、catalog 和 `deploy.lanes.<lane>` 回填。该模块由 `tsconfig.gitops.json` 以 `strict`、`exactOptionalPropertyTypes`、`noUncheckedIndexedAccess` 等选项检查;验证入口是:
|
||||
第一批严格 TS 模块是 `scripts/src/runtime-lane.ts`,负责 runtime lane 的命名、端口、endpoint、branch、catalog 和 `deploy.lanes.<lane>` 回填。该模块由 `tsconfig.gitops.json` 以 `strict`、`exactOptionalPropertyTypes`、`noUncheckedIndexedAccess` 等选项检查;验证入口是:
|
||||
|
||||
```bash
|
||||
npm run gitops:ts:check
|
||||
```
|
||||
|
||||
后续从 renderer、Tekton、Argo、FRP、Git mirror 或 artifact helper 继续拆出的模块,必须直接纳入该严格 TS 检查范围;不要先拆成 `.mjs` 再另开长期迁移债。`scripts/g14-gitops-render.mjs` 因为导入 TS 模块,实际执行必须走 Bun 入口:
|
||||
后续从 renderer、Tekton、Argo、FRP、Git mirror 或 artifact helper 继续拆出的模块,必须直接纳入该严格 TS 检查范围;不要先拆成 `.mjs` 再另开长期迁移债。`scripts/gitops-render.mjs` 因为导入 TS 模块,实际执行必须走 Bun 入口:
|
||||
|
||||
```bash
|
||||
npm run g14:gitops:render -- --lane v03
|
||||
npm run gitops:render -- --lane v03
|
||||
```
|
||||
|
||||
生成的 Tekton 任务和 control-plane reconciler 也必须使用 `node scripts/run-bun.mjs scripts/g14-gitops-render.mjs ...`,不得再用裸 `node scripts/g14-gitops-render.mjs ...`。
|
||||
生成的 Tekton 任务和 control-plane reconciler 也必须使用 `node scripts/run-bun.mjs scripts/gitops-render.mjs ...`,不得再用裸 `node scripts/gitops-render.mjs ...`。
|
||||
|
||||
## 废弃路径
|
||||
|
||||
`G14` 和 D601 旧分支/runtime 只保留归档或事故对照价值,不再是新 HWLAB runtime lane 的 CI/CD 支持对象。`G14` 的归档分支名是 `v0.1`;CI/CD 不再为了 `v0.1` 保持 namespace、Pipeline、GitOps branch、poller、reconciler 或 FRP 兼容。若旧对象仍存在,应通过受控 CLI 分阶段退役,不能作为 v0.3+ 发布证据。
|
||||
旧节点和 D601 旧分支/runtime 只保留归档或事故对照价值,不再是新 HWLAB runtime lane 的 CI/CD 支持对象。旧节点归档分支名是 `v0.1`;CI/CD 不再为了 `v0.1` 保持 namespace、Pipeline、GitOps branch、poller、reconciler 或 FRP 兼容。若旧对象仍存在,应通过受控 CLI 分阶段退役,不能作为 v0.3+ 发布证据。
|
||||
|
||||
## 验收边界
|
||||
|
||||
一个新 lane 只有同时满足以下条件才算扩容完成:
|
||||
|
||||
- 远端 source branch 和 GitOps branch 存在,并与 `deploy.lanes.<lane>` 声明一致。
|
||||
- G14 k3s 中存在独立 namespace、Tekton Pipeline/RBAC、Argo AppProject/Application 和 runtime desired state。
|
||||
- 目标 node k3s 中存在独立 namespace、Tekton Pipeline/RBAC、Argo AppProject/Application 和 runtime desired state。
|
||||
- runtime namespace、SecretRef、PVC、ServiceAccount、FRP proxy、artifact catalog 和 observability 对象均使用 lane 独立命名。
|
||||
- `control-plane trigger-current --lane <lane>` 能创建 commit-pinned PipelineRun,promotion 只写入对应 GitOps branch/path。
|
||||
- 公网 Web/API health 命中该 lane 的 namespace 和 revision;不得用 DEV/PROD、D601 或 `v0.1` health 作为通过证据。
|
||||
|
||||
@@ -20,11 +20,11 @@ CI/CD 内部由 UniDesk 受控触发入口、CI/CD 专用 source repo、devops-i
|
||||
|
||||
| 接口 | 说明 |
|
||||
| --- | --- |
|
||||
| `scripts/g14-gitops-render.mjs --lane v02` | v02 GitOps render 入口,负责 namespace、runtime path、catalog 和 endpoint 固定。 |
|
||||
| `scripts/gitops-render.mjs --lane v02` | v02 GitOps render 入口,负责 namespace、runtime path、catalog 和 endpoint 固定。 |
|
||||
| UniDesk v02 PR auto-CD monitor | 观察并合并 base=`v0.2` 的 ready PR,merge 后调用现有 `trigger-current`、定点 `status` 和 `git-mirror flush`。 |
|
||||
| Tekton `hwlab-v02-ci-image-publish` | 构建 affected images、复用 unchanged digest,并 promotion 到 `v0.2-gitops`。 |
|
||||
| `devops-infra` git mirror/relay | 读写 `v0.2` allowlist refs;CI 读 source/catalog、写 `v0.2-gitops`,并通过手动 CLI flush 到 GitHub。 |
|
||||
| Argo `argocd/hwlab-g14-v02` | 从 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/g14/runtime-v02` 同步到 `hwlab-v02`。 |
|
||||
| Argo `argocd/hwlab-node-v02` | 从 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/node/runtime-v02` 同步到 `hwlab-v02`。 |
|
||||
| `http://74.48.78.17:19666/` | v02 Cloud Web 公网入口。 |
|
||||
| `http://74.48.78.17:19667/health/live` | v02 API/live 公网验收入口。 |
|
||||
|
||||
@@ -45,7 +45,7 @@ CI/CD 内部由 UniDesk 受控触发入口、CI/CD 专用 source repo、devops-i
|
||||
| CI/CD source repo | `G14:/root/hwlab-v02-cicd.git` bare repo,由 UniDesk control-plane 自动 fetch `origin/v0.2`,再从 commit-pinned detached worktree render/apply 控制面 |
|
||||
| GitOps branch | `v0.2-gitops` |
|
||||
| Artifact catalog | `v0.2-gitops:deploy/artifact-catalog.v02.json` |
|
||||
| Runtime path | `v0.2-gitops:deploy/gitops/g14/runtime-v02` |
|
||||
| Runtime path | `v0.2-gitops:deploy/gitops/node/runtime-v02` |
|
||||
| Runtime namespace | `hwlab-v02` |
|
||||
| Tekton Pipeline | `hwlab-ci/hwlab-v02-ci-image-publish` |
|
||||
| PR merge auto-CD | UniDesk CLI v02 PR monitor 合并 base=`v0.2` PR 后自动调用 `trigger-current`,并定点观察 merge head |
|
||||
@@ -55,36 +55,36 @@ CI/CD 内部由 UniDesk 受控触发入口、CI/CD 专用 source repo、devops-i
|
||||
| Tekton ServiceAccount | `hwlab-ci/hwlab-v02-tekton-runner` |
|
||||
| PipelineRun prefix | `hwlab-v02-ci-poll-<short12>` |
|
||||
| Argo CD AppProject | `argocd/hwlab-v02` |
|
||||
| Argo CD Application | `argocd/hwlab-g14-v02` |
|
||||
| Argo CD Application | `argocd/hwlab-node-v02` |
|
||||
| FRP Deployment | `hwlab-v02/hwlab-v02-frpc` |
|
||||
| Web entry | `http://74.48.78.17:19666/` |
|
||||
| API/live entry | `http://74.48.78.17:19667/health/live` |
|
||||
| Git mirror/relay | 独立 `devops-infra` 集群读写服务;不设 CronJob;读 source/catalog,写 `v0.2-gitops`,按需由 UniDesk CLI 手动 sync/flush |
|
||||
| Registry | 维持当前 G14 `hwlab-ci/hwlab-registry` |
|
||||
|
||||
`scripts/g14-gitops-render.mjs --lane v02` 是当前 `v0.2` GitOps render 入口。该 lane 必须把默认 source branch 改为 `v0.2`、GitOps branch 改为 `v0.2-gitops`、catalog 改为 `deploy/artifact-catalog.v02.json`、runtime endpoint 改为 `19667`、web endpoint 改为 `19666`,并使用完整 source commit 作为 image tag。
|
||||
`scripts/gitops-render.mjs --lane v02` 是当前 `v0.2` GitOps render 入口。该 lane 必须把默认 source branch 改为 `v0.2`、GitOps branch 改为 `v0.2-gitops`、catalog 改为 `deploy/artifact-catalog.v02.json`、runtime endpoint 改为 `19667`、web endpoint 改为 `19666`,并使用完整 source commit 作为 image tag。
|
||||
|
||||
## 真相源
|
||||
|
||||
`v0.2` 的发布真相按以下顺序判断:
|
||||
|
||||
1. live runtime:`hwlab-v02` namespace 中 Deployment/StatefulSet template、Pod ready、事件、日志和 `19666/19667` 公网 health。
|
||||
2. Argo desired state:`argocd/hwlab-g14-v02` 的 revision、sync、health、source branch 和 runtime path。
|
||||
2. Argo desired state:`argocd/hwlab-node-v02` 的 revision、sync、health、source branch 和 runtime path。
|
||||
3. CI/CD source refs:UniDesk control-plane 专用 bare repo `refs/remotes/origin/v0.2`、devops-infra mirror/relay 的 `refs/heads/v0.2` 与 `refs/mirror-stage/heads/v0.2` 必须共同指向最新 source commit。
|
||||
4. devops-infra mirror/relay 中的 GitOps branch:`v0.2-gitops` 中的 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/g14/runtime-v02/**`。
|
||||
4. devops-infra mirror/relay 中的 GitOps branch:`v0.2-gitops` 中的 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/node/runtime-v02/**`。
|
||||
5. Tekton 执行证据:UniDesk `trigger-current` 返回的 PipelineRun、TaskRun result、`gitops-promote` 终态。
|
||||
6. GitHub 上游归档状态:mirror/relay flush 后的 `origin/v0.2-gitops` 与 `origin/v0.2`。
|
||||
7. 固定开发 workspace:`/root/hwlab-v02` 的 `HEAD`、dirty 状态和 `origin/v0.2` 只作为人工开发对照线索;即使 workspace 脏或落后,也不得影响 CI/CD source commit 选择。
|
||||
|
||||
旧 commit 记忆、`G14`/`G14-gitops` DEV/PROD 产物、D601 legacy 路径、GitHub 上游尚未 flush 的短暂落后、source branch 中历史 generated 文件、固定开发 workspace 脏状态和临时 worktree 只能作为线索,不能作为 `v0.2` 发布通过证据。
|
||||
|
||||
source workspace 中被 `.gitignore` 忽略的 `deploy/gitops/g14/runtime-v02/**` 文件只可能是本地生成缓存。若这类文件与 `v0.2-gitops` 或 live runtime 不一致,应删除本地缓存并以 `v0.2-gitops`、Argo 和 live ConfigMap/Deployment 为准;不要在 source branch 修补 ignored generated 文件,也不要把它们的内容写入 issue closeout 作为发布证据。
|
||||
source workspace 中被 `.gitignore` 忽略的 `deploy/gitops/node/runtime-v02/**` 文件只可能是本地生成缓存。若这类文件与 `v0.2-gitops` 或 live runtime 不一致,应删除本地缓存并以 `v0.2-gitops`、Argo 和 live ConfigMap/Deployment 为准;不要在 source branch 修补 ignored generated 文件,也不要把它们的内容写入 issue closeout 作为发布证据。
|
||||
|
||||
## Workspace 与 CI/CD 分离
|
||||
|
||||
`v0.2` 开发 workspace 和 CI/CD repo 必须分离。`/root/hwlab-v02` 是人工开发、短连接 `hwlab-cli` 和问题复现的固定 workspace;它允许出现并行任务产生的 untracked `.worktree/`、本地 dirty 文件或临时落后状态。CI/CD 不从该 checkout 的 `HEAD`、工作树 clean 状态或本地 branch 读取待发布 commit,也不得因为该 workspace 脏而跳过、误判或复用旧 PipelineRun。
|
||||
|
||||
UniDesk control-plane 必须使用独立 bare repo `/root/hwlab-v02-cicd.git` 作为 CI/CD source repo:每次 `status`、`apply`、`git-mirror apply` 和 `trigger-current` 先自动 fetch `origin/v0.2` 到 `refs/remotes/origin/v0.2`,再用目标 commit 创建 detached temp worktree 运行 `scripts/g14-gitops-render.mjs --lane v02`。该 repo 没有业务工作树,因此不会被人工开发 dirty 状态污染;如果它不可用,应以 `v02-head-unresolved` 或等价结构化错误失败,而不是回退到固定 workspace HEAD。
|
||||
UniDesk control-plane 必须使用独立 bare repo `/root/hwlab-v02-cicd.git` 作为 CI/CD source repo:每次 `status`、`apply`、`git-mirror apply` 和 `trigger-current` 先自动 fetch `origin/v0.2` 到 `refs/remotes/origin/v0.2`,再用目标 commit 创建 detached temp worktree 运行 `scripts/gitops-render.mjs --lane v02`。该 repo 没有业务工作树,因此不会被人工开发 dirty 状态污染;如果它不可用,应以 `v02-head-unresolved` 或等价结构化错误失败,而不是回退到固定 workspace HEAD。
|
||||
|
||||
devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。`trigger-current --lane v02 --confirm` 在创建 PipelineRun 前必须比较 `expectedSourceHead` 与 mirror `localV02`,不一致时自动执行 bounded `git-mirror sync` Job。`git-mirror status` 必须分别暴露 `localV02`、`githubV02`、`localGitops`、`githubGitops`、`sourceInSync`、`gitopsInSync` 和 `pendingFlush`;`githubInSync` 不得再被理解为只代表 GitOps branch。
|
||||
|
||||
@@ -99,7 +99,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。`
|
||||
`v0.2` source branch 不得跟踪:
|
||||
|
||||
- `deploy/artifact-catalog.v02.json`。
|
||||
- `deploy/gitops/g14/runtime-v02/**`。
|
||||
- `deploy/gitops/node/runtime-v02/**`。
|
||||
- Tekton/Argo 的 rendered runtime desired state。
|
||||
- image digest、publish state、reuse evidence 或 CI 生成的 rollout metadata。
|
||||
|
||||
@@ -108,7 +108,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。`
|
||||
`v0.2-gitops` branch 必须包含:
|
||||
|
||||
- `deploy/artifact-catalog.v02.json`,记录 image tag、digest、source commit、component identity、publish/reuse 状态。
|
||||
- `deploy/gitops/g14/runtime-v02/**`,作为 Argo CD 实际消费的 desired state。
|
||||
- `deploy/gitops/node/runtime-v02/**`,作为 Argo CD 实际消费的 desired state。
|
||||
- 必要的 generated metadata,但不得包含 Secret 值。
|
||||
|
||||
首次初始化时,如果 `v0.2-gitops:deploy/artifact-catalog.v02.json` 尚不存在,只允许由 `v0.2` lane 的正式初始化步骤创建第一版 catalog。不得 fallback 到 `G14` source catalog、`G14-gitops` catalog、DEV runtime path 或 source branch 生成物。
|
||||
@@ -131,8 +131,8 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。`
|
||||
service source 或 boot script 的 code-only 变化只更新三变量和 GitOps desired state,不再重新构建业务镜像。
|
||||
`hwlab-codex-api-forwarder` sidecar 和 `hwlab-deepseek-proxy` 的 `responses-bridge` 是 `hwlab-cloud-api` 的运行面关联容器,复用 `hwlab-cloud-api-env` 并通过各自 boot script 启动;它们不是独立 service matrix 成员,也不得增加第六个 env-reuse service。
|
||||
CLI、CaseRun、HWPOD workspace 工具、文档、测试和其他非 service 资产不进入 env-reuse service 触发面。
|
||||
8. promotion 刷新 `deploy/artifact-catalog.v02.json`,render `deploy/gitops/g14/runtime-v02/**`,只在本 PipelineRun 的 source commit 仍是当前 `origin/v0.2` head 时推送到 `devops-infra` mirror/relay 的 `v0.2-gitops`;若 source branch 已推进,本轮输出 superseded/no-op,写出 `runtime-ready-required=false`,不得回写旧 GitOps revision。
|
||||
9. `hwlab-g14-v02` 从本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/g14/runtime-v02` 同步到 `hwlab-v02`。
|
||||
8. promotion 刷新 `deploy/artifact-catalog.v02.json`,render `deploy/gitops/node/runtime-v02/**`,只在本 PipelineRun 的 source commit 仍是当前 `origin/v0.2` head 时推送到 `devops-infra` mirror/relay 的 `v0.2-gitops`;若 source branch 已推进,本轮输出 superseded/no-op,写出 `runtime-ready-required=false`,不得回写旧 GitOps revision。
|
||||
9. `hwlab-node-v02` 从本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/node/runtime-v02` 同步到 `hwlab-v02`。
|
||||
10. UniDesk CLI auto-CD 观察定点 `control-plane status --lane v02 --source-commit <merge-head>` 或 `--pipeline-run <name>`,确认 PipelineRun、Argo、runtime workload、public probes 和 planArtifacts build/reuse 摘要。历史 merge head 若因后续 `origin/v0.2` 推进而被 superseded,PR 评论以 superseded 收口;最新 head 仍必须继续收敛到 runtime。
|
||||
11. UniDesk CLI 或 mirror/relay flush 操作把本地 `v0.2-gitops` 推送到 GitHub canonical remote;flush 不在 CI runtime-ready 的关键路径内,但自动 CD 收口必须查询 pending、lastFlushed 和 failure,必要时执行 `git-mirror flush --confirm` 并等待 `pendingFlush=false`。
|
||||
12. 验收只观察 `hwlab-v02` runtime 和 `19666/19667`,并把 source commit、PipelineRun、GitOps revision、build/reuse 摘要、Argo/runtime/public probe 状态写回 PR 或关联 issue。
|
||||
@@ -143,7 +143,7 @@ devops-infra git mirror 仍是 PipelineRun 和 Argo CD 的集群内读写源。`
|
||||
|
||||
GitOps promotion 成功后,本地 mirror/relay 的 `v0.2-gitops` 可立即被 Argo 消费;GitHub 上游 flush 是归档与跨节点同步步骤,不在 runtime-ready 关键路径内。`git-mirror status.pendingFlush=true` 时自动 CD 和人工收口都应运行 `git-mirror flush --confirm`,再用该 flush job 的 `job status` 或 `git-mirror status` 确认 `pendingFlush=false`、`localGitops=githubGitops`。不要把 pending flush 误判为 runtime 未发布,也不要在 flush job 仍运行时反复做全量 status。
|
||||
|
||||
`v0.2` 可以复用 G14 的 registry、proxy、BuildKit、工具镜像和脚本库;不得复用 `hwlab-g14-ci-image-publish`、`hwlab-g14-branch-poller`、`hwlab-g14-control-plane-reconciler`、`G14-gitops` runtime path 或 DEV/PROD Argo Application 作为 `v0.2` 发布入口。运行时不得为每个版本硬编码 namespace、catalog、runtime path 或健康判断;版本差异只通过 `deploy.yaml.lanes[profile]`、GitOps render 输入和实际 runtime 对象表达,新增版本不得新增运行时代码分支。
|
||||
`v0.2` 可以复用 G14 的 registry、proxy、BuildKit、工具镜像和脚本库;不得复用 `hwlab-node-ci-image-publish`、`hwlab-node-branch-poller`、`hwlab-node-control-plane-reconciler`、`G14-gitops` runtime path 或 DEV/PROD Argo Application 作为 `v0.2` 发布入口。运行时不得为每个版本硬编码 namespace、catalog、runtime path 或健康判断;版本差异只通过 `deploy.yaml.lanes[profile]`、GitOps render 输入和实际 runtime 对象表达,新增版本不得新增运行时代码分支。
|
||||
|
||||
`v0.2` 不设 k8s 自动轮询发布。历史上若存在 `hwlab-v02-branch-poller`、`hwlab-v02-control-plane-reconciler` 或等价 CronJob,均视为迁移残留,应由 UniDesk control-plane apply 清理。PR 合并后的自动 CD 只能由 UniDesk CLI monitor 或一次性 reconcile job 驱动,并且必须复用 `trigger-current`、定点 `status` 和 `git-mirror flush`。暂停发布时停止 UniDesk monitor 或停止创建新的 PipelineRun;不要通过新增 CronJob 维持重试。
|
||||
|
||||
@@ -280,7 +280,7 @@ P1 no-op runtime skip 只跳过无实际 runtime 变化的等待。planner 输
|
||||
|
||||
Pipeline 内部的 no-op fast lane 必须形成完整证据链。
|
||||
`prepare-source` 输出 `source-clone` 与 `catalog-fetch` timing。
|
||||
`plan-artifacts` 输出 `g14-ci-plan`,其中 `buildServices=[]`、`rolloutServices=[]` 且 runtime service 全部 reuse。
|
||||
`plan-artifacts` 输出 `ci-plan`,其中 `buildServices=[]`、`rolloutServices=[]` 且 runtime service 全部 reuse。
|
||||
`gitops-promote` 输出 `skipped-runtime-unchanged`、`runtime-identity-only`,并把 `gitops-commit` 与 `gitops-push` 标为 `skipped`。
|
||||
PipelineRun 的 skipped tasks 中必须包含 `runtime-ready`。
|
||||
这条证据链缺一项时,不能只看总耗时通过,应先确认是日志缺失、planner 误判还是 GitOps runtime 比对退化。
|
||||
@@ -340,7 +340,7 @@ for task in prepare-source plan-artifacts collect-artifacts gitops-promote runti
|
||||
-o jsonpath="{.items[0].metadata.name}")
|
||||
echo "--- $task $pod selected logs ---"
|
||||
pattern="skipped-runtime-unchanged|runtime-ready-required|runtime-identity-only"
|
||||
pattern="$pattern|g14-cicd-timing|git-operation|g14-ci-plan|artifact_reuse|buildSkippedCount"
|
||||
pattern="$pattern|node-cicd-timing|git-operation|ci-plan|artifact_reuse|buildSkippedCount"
|
||||
pattern="$pattern|gitops-commit|gitops-push|runtime-ready|progress|argo-refresh"
|
||||
pattern="$pattern|argo-sync-health|workload-ready|observedCount"
|
||||
kubectl logs -n "$ns" "$pod" --all-containers=true | \
|
||||
@@ -356,7 +356,7 @@ done
|
||||
| `prepare-source` 回到 20s 以上 | 日志是否出现 `npm ci`、npm registry probe、GitHub URL、SSH setup 或长时间 catalog fetch | 拆掉无效依赖安装和重复探针;读路径必须回到 mirror。 |
|
||||
| `source-clone` 超过 5s | PipelineRun 参数 `git-read-url`、mirror service、PVC I/O、`git-mirror status` | 不要改回 GitHub 直连;先修 mirror/read service。 |
|
||||
| catalog 缺失导致全量 build | `prepare-source` 是否从 `v0.2-gitops` 取到 `deploy/artifact-catalog.v02.json` | 先 `git-mirror sync --confirm`;必要时查 mirror refs/object closure。 |
|
||||
| `buildSkippedCount` 偏低或突然变成 0 | `g14-ci-plan` 的 `affectedServices`、`buildServices`、`rolloutServices`、catalog 加载状态、`package.json` 字段 diff 和 service component path | 真实业务变更可以 build;脚本清理、CLI/测试/文档、host asset 或 code-only env-reuse 变更不应误触发全量 build。 |
|
||||
| `buildSkippedCount` 偏低或突然变成 0 | `ci-plan` 的 `affectedServices`、`buildServices`、`rolloutServices`、catalog 加载状态、`package.json` 字段 diff 和 service component path | 真实业务变更可以 build;脚本清理、CLI/测试/文档、host asset 或 code-only env-reuse 变更不应误触发全量 build。 |
|
||||
| 多 service rebuild 仍串行执行 | `tekton-v02/pipeline.yaml` 中所有 `build-*` task 的 `runAfter`,以及 TaskRun startTime 是否都紧跟 `plan-artifacts` | build task 只能依赖 `plan-artifacts`;发现 `build-B` 依赖 `build-A` 时直接修 render 脚本和生成物,不保留串行兼容路径。 |
|
||||
| 全并行 rebuild 变慢或不稳定 | TaskRun duration、Pod Pending/Unschedulable、node pressure、PVC I/O、BuildKit sidecar 日志、本地 registry push latency | 优先定位资源容量或 registry/BuildKit 瓶颈;不要先把 Pipeline 拓扑改回串行或固定 8 并发。 |
|
||||
| no-op 仍执行 `runtime-ready` | `gitops-promote` 是否输出 `runtime-ready-required=false` 和 `skipped-runtime-unchanged` | 查 runtime 归一化比较;不要直接删除 `runtime-ready`,只修 no-op 判定。 |
|
||||
@@ -488,7 +488,7 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持
|
||||
|
||||
- `hwlab-v02` namespace 只能由 `v0.2` GitOps lane 管理。
|
||||
- `argocd/hwlab-v02` AppProject destination 只能包含 `hwlab-v02`。
|
||||
- `argocd/hwlab-g14-v02` source 必须指向 `devops-infra` 本地 mirror/relay 中的 `v0.2-gitops:deploy/gitops/g14/runtime-v02`,destination 必须是 `hwlab-v02`。
|
||||
- `argocd/hwlab-node-v02` source 必须指向 `devops-infra` 本地 mirror/relay 中的 `v0.2-gitops:deploy/gitops/node/runtime-v02`,destination 必须是 `hwlab-v02`。
|
||||
- `hwlab-v02-frpc` 只能暴露 `19666/19667`,不能复用 `17666/17667` 或 `18666/18667`。
|
||||
- `v0.2` Secret、DB 凭据、PVC、ServiceAccount 和 runtime config 必须独立命名或独立 namespace scope;文档、issue、trace 和 report 只记录 SecretRef 名称与 key,不记录值。
|
||||
- `v0.2` 初期不新增自动 registry GC;后续如启用清理,selector 必须带 lane/profile 标签,registry digest 保护集必须同时覆盖 `G14-gitops` 与 `v0.2-gitops`。
|
||||
@@ -502,11 +502,11 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持
|
||||
- GitOps branch 必须是 `v0.2-gitops`。
|
||||
- runtime namespace 必须是 `hwlab-v02`。
|
||||
- artifact catalog 必须是 `deploy/artifact-catalog.v02.json`。
|
||||
- runtime path 必须是 `deploy/gitops/g14/runtime-v02`。
|
||||
- Argo Application 必须是 `hwlab-g14-v02`,且只能部署到 `hwlab-v02`。
|
||||
- source branch publish 后不得出现 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/g14/runtime-v02/**` 变更。
|
||||
- source workspace 下若残留 ignored `deploy/gitops/g14/runtime-v02/**` 生成物,不能作为 source diff、PR 内容、回归证据或人工修补目标;发现和当前 `v0.2-gitops` 不一致时删除本地缓存。
|
||||
- GitOps promotion 的 changed paths 只能落在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/g14/runtime-v02/**` 及必要的 v02 Argo/GitOps 元数据。
|
||||
- runtime path 必须是 `deploy/gitops/node/runtime-v02`。
|
||||
- Argo Application 必须是 `hwlab-node-v02`,且只能部署到 `hwlab-v02`。
|
||||
- source branch publish 后不得出现 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/node/runtime-v02/**` 变更。
|
||||
- source workspace 下若残留 ignored `deploy/gitops/node/runtime-v02/**` 生成物,不能作为 source diff、PR 内容、回归证据或人工修补目标;发现和当前 `v0.2-gitops` 不一致时删除本地缓存。
|
||||
- GitOps promotion 的 changed paths 只能落在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/node/runtime-v02/**` 及必要的 v02 Argo/GitOps 元数据。
|
||||
- 公网验收只能使用 `19666/19667`。
|
||||
- `v0.2` 不得创建或依赖 k8s CronJob;`hwlab-v02-branch-poller`、`hwlab-v02-control-plane-reconciler` 或同类调度器出现时应清理,而不是接入发布链路。
|
||||
- GitHub webhook 不是 v02 首期自动 CD 的必需入口;若未来接入,只能作为唤醒信号,不能直接决定发布 commit 或创建 PipelineRun。
|
||||
@@ -527,11 +527,11 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持
|
||||
- `G14` source branch 的 poller/reconciler 语义。
|
||||
- `G14-gitops` DEV/PROD catalog 与 runtime desired state。
|
||||
- `hwlab-dev` 与 `hwlab-prod` namespace。
|
||||
- `hwlab-g14-dev` 与 `hwlab-g14-prod` Argo Application。
|
||||
- `hwlab-node-dev` 与 `hwlab-node-prod` Argo Application。
|
||||
- DEV `17666/17667` 与 PROD `18666/18667` FRP 入口。
|
||||
- D601 legacy 回溯路径和旧运行面边界。
|
||||
|
||||
如果 `v0.2` 接入失败,回滚或暂停只能作用于 `hwlab-v02` lane:停止 UniDesk v02 monitor、停止手动触发新的 v02 PipelineRun、暂停或删除 `hwlab-g14-v02`、回滚 `v0.2-gitops` runtime path、关闭 `hwlab-v02-frpc` 或清理 `hwlab-v02` namespace 资源;不得通过新增 CronJob 维持重试,也不得重启、删除或回滚 DEV/PROD 运行面。
|
||||
如果 `v0.2` 接入失败,回滚或暂停只能作用于 `hwlab-v02` lane:停止 UniDesk v02 monitor、停止手动触发新的 v02 PipelineRun、暂停或删除 `hwlab-node-v02`、回滚 `v0.2-gitops` runtime path、关闭 `hwlab-v02-frpc` 或清理 `hwlab-v02` namespace 资源;不得通过新增 CronJob 维持重试,也不得重启、删除或回滚 DEV/PROD 运行面。
|
||||
|
||||
## 验收标准
|
||||
|
||||
@@ -542,10 +542,10 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持
|
||||
- 同一个 source commit 已有 PipelineRun 时不重复创建,也不删除重建;失败 run 的人工重试必须走后续显式 retry 策略,不恢复默认 delete/create。
|
||||
- 自动 CD 在旧 run 运行期间遇到新 `origin/v0.2` head 时,新 head 可直接触发自己的 PipelineRun;旧 run 继续完成 CI,但 promotion 必须在写 GitOps 前发现 stale head 并 superseded/no-op,不能回写旧 `v0.2-gitops` 或回滚 runtime。
|
||||
- 最新 `v0.2` source commit 对应的 PipelineRun 完成,且 promotion 写入 `v0.2-gitops`。
|
||||
- `v0.2-gitops` 中存在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/g14/runtime-v02/**`。
|
||||
- `argocd/hwlab-g14-v02` 指向 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/g14/runtime-v02`,sync revision 与目标 GitOps revision 对齐。
|
||||
- `v0.2-gitops` 中存在 `deploy/artifact-catalog.v02.json` 与 `deploy/gitops/node/runtime-v02/**`。
|
||||
- `argocd/hwlab-node-v02` 指向 `devops-infra` 本地 mirror/relay 的 `v0.2-gitops:deploy/gitops/node/runtime-v02`,sync revision 与目标 GitOps revision 对齐。
|
||||
- `hwlab-v02` 中长驻 workload ready,没有把 DEV/PROD namespace 当成 `v0.2` 通过证据。
|
||||
- `gitops-promote` 推送本地 mirror/relay 的 `v0.2-gitops` 后应触发 `argocd/hwlab-g14-v02` hard refresh,减少 GitOps push 与 Argo 仓库发现之间的漂移窗口;refresh 触发失败只作为低噪声事件输出,最终通过仍由 `runtime-ready` 判定。
|
||||
- `gitops-promote` 推送本地 mirror/relay 的 `v0.2-gitops` 后应触发 `argocd/hwlab-node-v02` hard refresh,减少 GitOps push 与 Argo 仓库发现之间的漂移窗口;refresh 触发失败只作为低噪声事件输出,最终通过仍由 `runtime-ready` 判定。
|
||||
- mirror/relay status 必须能显示本地 `v0.2-gitops` revision、GitHub 已 flush revision、pending flush revision 和最近一次 flush 错误;CI 通过不要求 GitHub flush 已完成,但 auto-CD 收口必须在 `pendingFlush=true` 时触发 flush 并最终观察到 `pendingFlush=false`。
|
||||
- `runtime-ready` 必须以 workload Pod template 的 source commit 判断 Argo 刷新,并在 Argo 刷新超时、observer RBAC 不足或 workload 未就绪时失败;不得把 CrashLoop、未刷新或公网不可用的 runtime 标成绿色。
|
||||
- 启用 env 容器复用的 runtime service 必须在 catalog、Pod template annotation/env 和 `/health/live` 或等价 runtime identity 中同时暴露 env image digest 与 `HWLAB_BOOT_REPO`/`HWLAB_BOOT_COMMIT`/`HWLAB_BOOT_SH`;只暴露镜像 digest 不能证明 service code-only rollout 已生效。
|
||||
@@ -557,7 +557,7 @@ registry 与 git mirror/relay 分属不同基础设施边界。registry 保持
|
||||
- `trigger-current` 的异步 job 或 `--wait` 输出必须能显示 trigger 阶段进度;若 control-plane refresh、mirror pre-sync 或 delete/create PipelineRun 卡住,日志必须能定位阶段名和状态。
|
||||
- 自动 CD 回写 PR 或关联 issue 的 rollout 评论必须包含 source commit、PipelineRun、GitOps revision、planArtifacts build/reuse 摘要、Argo/runtime/public probe 状态和耗时;该评论不能替代需要用户原入口验收的问题关闭证据。
|
||||
- 自动 CD 过程中 source branch 的 `deploy/deploy.yaml` 不得出现 `commitId`、service `image`、`HWLAB_COMMIT_ID`、`HWLAB_IMAGE` 或 `HWLAB_IMAGE_TAG` 回写。
|
||||
- no-op env-reuse fast lane 必须输出完整证据链:`g14-ci-plan` 全复用、`skipped-runtime-unchanged`、`gitops-commit`/`gitops-push` skipped、`runtime-ready` skipped。
|
||||
- no-op env-reuse fast lane 必须输出完整证据链:`ci-plan` 全复用、`skipped-runtime-unchanged`、`gitops-commit`/`gitops-push` skipped、`runtime-ready` skipped。
|
||||
不能只用 PipelineRun `Completed` 或总耗时证明没有退化。
|
||||
- 真实 rollout 的 `runtime-ready` 必须输出 `started`、周期性 `progress` 和最终 `argo-refresh`/`argo-sync-health`/`workload-ready` 类事件;缺少这些事件时应按可见性回归处理。
|
||||
- Bun 语法检查和测试入口必须分开:`bun --check <file.ts>` 只用于非测试 TypeScript 源文件语法/转译检查;`*.test.ts`、`*.test.mts` 或其他使用 `test(...)` 的文件必须用 `bun test <file>` 或 repo-owned package script 执行。不得再用 `bun --check` 直接跑 `.test.ts`,该命令会在测试运行器外求值并报 `Cannot use test outside of the test runner`,这不是业务回归证据。
|
||||
@@ -568,11 +568,11 @@ GitOps branch 已更新、source branch render 通过、PipelineRun 名称存在
|
||||
|
||||
## T1
|
||||
|
||||
阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:确认 `origin/v0.2` 最新 commit 对应的 PipelineRun 完成,promotion 只写入 `v0.2-gitops`,且 source branch 没有跟踪 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/g14/runtime-v02/**` 生成物。
|
||||
阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:确认 `origin/v0.2` 最新 commit 对应的 PipelineRun 完成,promotion 只写入 `v0.2-gitops`,且 source branch 没有跟踪 `deploy/artifact-catalog.v02.json` 或 `deploy/gitops/node/runtime-v02/**` 生成物。
|
||||
|
||||
## T2
|
||||
|
||||
阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:查询 Argo `hwlab-g14-v02`,确认 source repo 为 `devops-infra` 本地 mirror/relay,branch/path 为 `v0.2-gitops:deploy/gitops/g14/runtime-v02`,destination namespace 为 `hwlab-v02`,sync revision 与目标 GitOps revision 对齐。
|
||||
阅读 docs/reference/spec-v02-cicd.md,然后用 cli 手动测试以下内容:查询 Argo `hwlab-node-v02`,确认 source repo 为 `devops-infra` 本地 mirror/relay,branch/path 为 `v0.2-gitops:deploy/gitops/node/runtime-v02`,destination namespace 为 `hwlab-v02`,sync revision 与目标 GitOps revision 对齐。
|
||||
|
||||
## T3
|
||||
|
||||
@@ -615,7 +615,7 @@ GitOps branch 已更新、source branch render 通过、PipelineRun 名称存在
|
||||
| PR merge 后自动 CD | 待迁移 | 目标见 #848:UniDesk CLI v02 PR monitor 合并 base=`v0.2` PR 后复用 `trigger-current`、定点 `status` 和 `git-mirror flush`;首期不要求 GitHub webhook,也不创建 k8s CronJob。 |
|
||||
| v02 runtime readiness fail-closed | 已实现 | `gitops-promote` 推送后触发 Argo hard refresh;`runtime-ready` 读取 workload Pod template source commit,timeout、observer RBAC 不足或未就绪会让 PipelineRun 失败。 |
|
||||
| v02 裁撤服务不进入发布面 | 已实现 | v02 Tekton build service set、artifact catalog 和 runtime render 只包含保留服务;v02 Argo 开启 prune 清理旧 live 对象;裁撤服务仍可保留在 DEV legacy 源码中。 |
|
||||
| Argo v02 Application | 已实现 | `hwlab-g14-v02` 指向 v02 GitOps path 和 namespace。 |
|
||||
| Argo v02 Application | 已实现 | `hwlab-node-v02` 指向 v02 GitOps path 和 namespace。 |
|
||||
| FRP `19666/19667` 入口 | 已实现 | 由 `hwlab-v02-frpc` 与 master frps allowlist 共同提供。 |
|
||||
| SecretRef 独立与 provider 验收 | 已实现/持续约束 | SecretRef 已独立;验收必须做真实短连接聊天。 |
|
||||
| env 容器复用三变量启动 | 已实现/持续约束 | 五个 v0.2 runtime service 均启用 env-reuse fast lane,并由 CI/CD 自动推导 `HWLAB_BOOT_REPO`、`HWLAB_BOOT_COMMIT`、`HWLAB_BOOT_SH`;service code-only rollout 复用 env image digest,只更新代码身份。 |
|
||||
@@ -653,7 +653,7 @@ Code Agent 的 Secret 修复不是只改一个 Secret 对象就结束。`hwlab-c
|
||||
|
||||
### GitOps 与 runtime 收敛
|
||||
|
||||
GitOps promotion 成功不等于 runtime 已经运行新版本。验收必须等待 `argocd/hwlab-g14-v02` 的 sync revision 对齐最新 `v0.2-gitops` revision,并确认 `19667/health/live` 的 `environment`、`endpoint`、关键 SecretRef 和 service revision 与目标 lane 对齐。
|
||||
GitOps promotion 成功不等于 runtime 已经运行新版本。验收必须等待 `argocd/hwlab-node-v02` 的 sync revision 对齐最新 `v0.2-gitops` revision,并确认 `19667/health/live` 的 `environment`、`endpoint`、关键 SecretRef 和 service revision 与目标 lane 对齐。
|
||||
|
||||
Argo `Synced/Healthy` 也不能单独替代公网验证。FRP server 侧 `allowPorts` 缺失时,`hwlab-v02-frpc` 会反复报告 `port not allowed`;这种问题应修 master 侧 `frps` allowlist 并只重启 `hwlab-frps-dev`,不要改 DEV/PROD GitOps、Service 或 v02 runtime path。修复后必须同时验证新增 `19666/19667` 和既有 DEV `17666/17667`。
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
- G14 共享监控控制面运行在 `devops-infra` 平台基础设施 namespace,由 UniDesk/G14 基础设施规格管理。它承载 Prometheus Operator、Prometheus 实例、可选 Alertmanager/Grafana 以及跨 namespace scrape 选择策略。
|
||||
- `hwlab-v02` 只承载 HWLAB 业务服务、`/metrics` endpoint、Service labels/ports、`ServiceMonitor` / `PodMonitor` 和 `PrometheusRule`。不得把 Prometheus Operator、Prometheus、Grafana 或 Alertmanager 部署进 `hwlab-v02`。
|
||||
- `hwlab-g14-v02` Argo Application 仍只同步 `deploy/gitops/g14/runtime-v02` 到 `hwlab-v02`。监控控制面不能挂到这个 Application;它只消费业务 namespace 中的监控声明。
|
||||
- `hwlab-node-v02` Argo Application 仍只同步 `deploy/gitops/node/runtime-v02` 到 `hwlab-v02`。监控控制面不能挂到这个 Application;它只消费业务 namespace 中的监控声明。
|
||||
- `hwlab-cli` 或 UniDesk 受控 CLI 可以作为查询入口,但必须通过 runtime namespace/lane 自动解析目标,不得手动暴露 Prometheus 公网地址。
|
||||
|
||||
## 内部架构
|
||||
@@ -91,7 +91,7 @@ sidecar 必须至少暴露以下基础指标:
|
||||
|
||||
### `ServiceMonitor`
|
||||
|
||||
`deploy/gitops/g14/runtime-v02` 应生成或包含 v0.2 业务服务的 `ServiceMonitor`。Selector 必须同时约束:
|
||||
`deploy/gitops/node/runtime-v02` 应生成或包含 v0.2 业务服务的 `ServiceMonitor`。Selector 必须同时约束:
|
||||
|
||||
- namespace 为 `hwlab-v02`
|
||||
- `hwlab.pikastech.local/gitops-target=v02`
|
||||
@@ -139,7 +139,7 @@ HWLAB v0.2 可声明 `PrometheusRule`,但规则只表达当前 v0.2 目标行
|
||||
|
||||
## T3
|
||||
|
||||
阅读本文,然后用 CLI 手动测试以下内容:读取 `deploy/gitops/g14/runtime-v02` 或 live `hwlab-v02` 对象,确认 `ServiceMonitor` selector 限定 `hwlab-v02`、`gitops-target=v02`、`monitoring=enabled` 和具体 service id;确认不存在把 Prometheus、Grafana 或 Alertmanager 部署到 `hwlab-v02` 的对象。
|
||||
阅读本文,然后用 CLI 手动测试以下内容:读取 `deploy/gitops/node/runtime-v02` 或 live `hwlab-v02` 对象,确认 `ServiceMonitor` selector 限定 `hwlab-v02`、`gitops-target=v02`、`monitoring=enabled` 和具体 service id;确认不存在把 Prometheus、Grafana 或 Alertmanager 部署到 `hwlab-v02` 的对象。
|
||||
|
||||
## T4
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ origin/v0.2
|
||||
| `frpc` / `frps` | 外部稳定服务 | 保留 | [spec-v02-frpc.md](spec-v02-frpc.md) |
|
||||
| Postgres | 外部稳定服务 | 保留 | [spec-v02-postgres.md](spec-v02-postgres.md) |
|
||||
| OpenFGA | 外部稳定服务 / 内部授权 PDP | 新增并保留,ClusterIP-only | [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md) |
|
||||
| Argo CD / Tekton / BuildKit / registry | 外部稳定服务 | 保留 | [spec-v02-cicd.md](spec-v02-cicd.md)、[g14-gitops-cicd.md](g14-gitops-cicd.md) |
|
||||
| Argo CD / Tekton / BuildKit / registry | 外部稳定服务 | 保留 | [spec-v02-cicd.md](spec-v02-cicd.md)、[node-gitops-cicd.md](node-gitops-cicd.md) |
|
||||
| Codex CLI | 外部工具/runtime | 保留 | [code-agent-chat-readiness.md](code-agent-chat-readiness.md) |
|
||||
| Keil / pyOCD / UART 工具 | 外部或主机侧工具 | 由 HWPOD spec 和 node-ops 绑定使用 | [spec-hwpod-harness.md](spec-hwpod-harness.md) |
|
||||
|
||||
@@ -121,7 +121,7 @@ hwlab-agent-worker
|
||||
|
||||
## T1
|
||||
|
||||
阅读 docs/reference/spec-v02-services.md,然后用 CLI 手动测试以下内容:列出 `deploy/deploy.yaml`、`deploy/deploy.schema.json`、`deploy/artifact-catalog.dev.json` 和 `deploy/gitops/g14/runtime-v02`,确认 runtime service set 只包含 `hwlab-cloud-api`、`hwlab-cloud-web`、`hwlab-gateway`、`hwlab-edge-proxy` 和 `hwlab-agent-skills`。
|
||||
阅读 docs/reference/spec-v02-services.md,然后用 CLI 手动测试以下内容:列出 `deploy/deploy.yaml`、`deploy/deploy.schema.json`、`deploy/artifact-catalog.dev.json` 和 `deploy/gitops/node/runtime-v02`,确认 runtime service set 只包含 `hwlab-cloud-api`、`hwlab-cloud-web`、`hwlab-gateway`、`hwlab-edge-proxy` 和 `hwlab-agent-skills`。
|
||||
|
||||
## T2
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export const DEFAULT_AGENTRUN_NAMESPACE = "agentrun-v01";
|
||||
export const DEFAULT_AGENTRUN_MANAGER_URL = "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080";
|
||||
export const DEFAULT_HWLAB_AGENTRUN_PROJECT_ID = "pikasTech/HWLAB";
|
||||
export const DEFAULT_HWLAB_AGENTRUN_PROVIDER_ID = "G14";
|
||||
export const HWLAB_AGENTRUN_PROVIDER_ID_ENV = "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID";
|
||||
export const LEGACY_AGENTRUN_PROVIDER_ID_ENV = "AGENTRUN_PROVIDER_ID";
|
||||
export const DEFAULT_HWLAB_AGENTRUN_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
||||
export const DEFAULT_HWLAB_AGENTRUN_BRANCH = "v0.2";
|
||||
export const DEFAULT_HWLAB_AGENTRUN_BACKEND_PROFILE = "deepseek";
|
||||
@@ -148,7 +149,7 @@ export function createHwlabAgentRunDispatchAssembly(options = {}) {
|
||||
const namespace = nonEmptyString(options.namespace ?? DEFAULT_AGENTRUN_NAMESPACE, "namespace");
|
||||
const managerUrl = trimTrailingSlash(nonEmptyString(options.managerUrl ?? DEFAULT_AGENTRUN_MANAGER_URL, "managerUrl"));
|
||||
const projectId = DEFAULT_HWLAB_AGENTRUN_PROJECT_ID;
|
||||
const providerId = DEFAULT_HWLAB_AGENTRUN_PROVIDER_ID;
|
||||
const providerId = resolveHwlabAgentRunProviderId(env);
|
||||
const repoUrl = nonEmptyString(options.repoUrl ?? DEFAULT_HWLAB_AGENTRUN_REPO_URL, "repoUrl");
|
||||
const branch = nonEmptyString(options.branch ?? DEFAULT_HWLAB_AGENTRUN_BRANCH, "branch");
|
||||
const timeoutMs = positiveInteger(options.timeoutMs ?? 600000, "timeoutMs");
|
||||
@@ -397,6 +398,13 @@ function positiveInteger(value, fieldName) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function resolveHwlabAgentRunProviderId(env = process.env) {
|
||||
const providerId = optionalString(env[HWLAB_AGENTRUN_PROVIDER_ID_ENV] ?? env[LEGACY_AGENTRUN_PROVIDER_ID_ENV]);
|
||||
if (!providerId) throw new Error(`${HWLAB_AGENTRUN_PROVIDER_ID_ENV} is required and must be injected from the runtime lane node id`);
|
||||
if (!/^[A-Za-z0-9._-]+$/u.test(providerId)) throw new Error(`${HWLAB_AGENTRUN_PROVIDER_ID_ENV} must be a simple node id`);
|
||||
return providerId;
|
||||
}
|
||||
|
||||
function nonEmptyString(value, fieldName) {
|
||||
const text = optionalString(value);
|
||||
if (!text) throw new Error(`${fieldName} is required`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import test, { after, before } from "node:test";
|
||||
|
||||
import {
|
||||
DEFAULT_UNIDESK_MAIN_SERVER_ENV,
|
||||
@@ -8,6 +8,17 @@ import {
|
||||
} from "./agentrun-dispatch.mjs";
|
||||
|
||||
const commitId = "8da27c83fa56d87e78e488a819afe898cf97c62b";
|
||||
let previousProviderId;
|
||||
|
||||
before(() => {
|
||||
previousProviderId = process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID;
|
||||
process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID = "G14";
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (previousProviderId === undefined) delete process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID;
|
||||
else process.env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID = previousProviderId;
|
||||
});
|
||||
|
||||
test("HWLAB AgentRun assembly grants GitHub and UniDesk SSH through toolCredentials", () => {
|
||||
const assembly = createHwlabAgentRunDispatchAssembly({
|
||||
|
||||
@@ -16,7 +16,6 @@ const ADAPTER_ID = "agentrun-v01";
|
||||
const DEFAULT_AGENTRUN_MGR_URL = "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080";
|
||||
const DEFAULT_TENANT_ID = "hwlab";
|
||||
const DEFAULT_PROJECT_ID = "pikasTech/HWLAB";
|
||||
const DEFAULT_PROVIDER_ID = "G14";
|
||||
const DEFAULT_REPO_URL = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
||||
const DEFAULT_RUNNER_NAMESPACE = "agentrun-v01";
|
||||
const DEFAULT_TIMEOUT_MS = 1_200_000;
|
||||
@@ -71,8 +70,13 @@ export function describeAgentRunAdapterAvailability(env = process.env, options =
|
||||
} catch (error) {
|
||||
blockers.push(agentRunAvailabilityBlocker(error, "resource-bundle-source-commit"));
|
||||
}
|
||||
let providerId = null;
|
||||
try {
|
||||
providerId = resolveAgentRunProviderId(env);
|
||||
} catch (error) {
|
||||
blockers.push(agentRunAvailabilityBlocker(error, "provider-id"));
|
||||
}
|
||||
|
||||
const providerId = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID);
|
||||
const runnerNamespace = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE, env.AGENTRUN_RUNTIME_NAMESPACE, DEFAULT_RUNNER_NAMESPACE);
|
||||
const ready = blockers.length === 0 && Boolean(providerId && runnerNamespace && backendProfile && managerUrl && repoUrl);
|
||||
const managerHost = hostForUrl(managerUrl);
|
||||
@@ -233,7 +237,7 @@ export function initialAgentRunChatResult({ params = {}, options = {}, traceId }
|
||||
adapter: ADAPTER_ID,
|
||||
status: "pending-dispatch",
|
||||
backendProfile,
|
||||
providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
||||
providerId: resolveAgentRunProviderId(env),
|
||||
managerUrl: resolveAgentRunManagerUrl(env),
|
||||
repoUrl: resolveAgentRunRepoUrl(env),
|
||||
resourceBundleSourceCommit: requireAgentRunSourceCommit(env),
|
||||
@@ -729,7 +733,7 @@ export function agentRunSessionEvidence(payload = {}) {
|
||||
terminalStatus: payload.agentRun.terminalStatus ?? null,
|
||||
traceId: payload.agentRun.traceId ?? payload.traceId ?? payload.providerTrace?.traceId ?? null,
|
||||
lastSeq: payload.agentRun.lastSeq ?? 0,
|
||||
providerId: payload.agentRun.providerId ?? DEFAULT_PROVIDER_ID,
|
||||
providerId: payload.agentRun.providerId ?? agentRunProviderIdOrNull(),
|
||||
reuseEligible: payload.agentRun.reuseEligible ?? false,
|
||||
sessionId: payload.agentRun.sessionId ?? payload.sessionId ?? null,
|
||||
conversationId: payload.conversationId ?? payload.agentRun.conversationId ?? null,
|
||||
@@ -828,7 +832,7 @@ function buildAgentRunCreateRunInput({ params, env, traceId, backendProfile, ses
|
||||
}
|
||||
},
|
||||
resourceBundleRef,
|
||||
providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
||||
providerId: resolveAgentRunProviderId(env),
|
||||
backendProfile,
|
||||
executionPolicy: {
|
||||
sandbox: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_SANDBOX, env.HWLAB_CODE_AGENT_CODEX_SANDBOX, "danger-full-access"),
|
||||
@@ -1695,7 +1699,7 @@ function agentRunMapping({ env, managerUrl, backendProfile, run, command, runner
|
||||
adapter: ADAPTER_ID,
|
||||
managerUrl,
|
||||
backendProfile,
|
||||
providerId: firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
||||
providerId: resolveAgentRunProviderId(env),
|
||||
runId: run.id,
|
||||
commandId: command.id,
|
||||
attemptId: runnerJob?.attemptId ?? runnerJob?.runner?.attemptId ?? null,
|
||||
@@ -1730,7 +1734,7 @@ function agentRunReusedMapping({ previous = {}, run = {}, command = {}, runnerJo
|
||||
adapter: ADAPTER_ID,
|
||||
managerUrl,
|
||||
backendProfile,
|
||||
providerId: previous.providerId ?? firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID, DEFAULT_PROVIDER_ID),
|
||||
providerId: previous.providerId ?? resolveAgentRunProviderId(env),
|
||||
runId: previous.runId ?? run?.id,
|
||||
commandId: command.id,
|
||||
attemptId: runnerJob?.attemptId ?? runnerJob?.runner?.attemptId ?? previous.attemptId ?? null,
|
||||
@@ -1903,6 +1907,31 @@ function requireAgentRunSourceCommit(env) {
|
||||
});
|
||||
}
|
||||
|
||||
function resolveAgentRunProviderId(env = process.env) {
|
||||
const providerId = firstNonEmpty(env.HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID, env.AGENTRUN_PROVIDER_ID);
|
||||
if (!providerId) {
|
||||
throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID must be configured from the runtime lane node id before AgentRun dispatch"), {
|
||||
code: "agentrun_provider_id_required",
|
||||
statusCode: 500
|
||||
});
|
||||
}
|
||||
if (!/^[A-Za-z0-9._-]+$/u.test(providerId)) {
|
||||
throw Object.assign(new Error("HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID must be a simple node id"), {
|
||||
code: "agentrun_provider_id_invalid",
|
||||
statusCode: 500
|
||||
});
|
||||
}
|
||||
return providerId;
|
||||
}
|
||||
|
||||
function agentRunProviderIdOrNull(env = process.env) {
|
||||
try {
|
||||
return resolveAgentRunProviderId(env);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function modelForBackendProfile(profile, env = process.env) {
|
||||
const resolved = resolveAgentRunBackendProfile({}, { providerProfile: profile });
|
||||
if (resolved === "codex") return firstNonEmpty(env.HWLAB_CODE_AGENT_CODEX_API_MODEL, env.HWLAB_CODE_AGENT_MODEL, "gpt-5.5");
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"web:build": "(cd web/hwlab-cloud-web && bun run build)",
|
||||
"artifact-catalog:preview-blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref HEAD --blocked --no-write",
|
||||
"lane:expand": "node scripts/hwlab-lane-expand.mjs",
|
||||
"g14:gitops:render": "node scripts/run-bun.mjs scripts/g14-gitops-render.mjs",
|
||||
"gitops:render": "node scripts/run-bun.mjs scripts/gitops-render.mjs",
|
||||
"gitops:ts:check": "tsc --noEmit -p tsconfig.gitops.json",
|
||||
"docs:validate:m3-rollout": "node scripts/validate-m3-rollout-runbook.mjs",
|
||||
"d601:k3s:readonly": "node scripts/d601-k3s-readonly-observability.mjs",
|
||||
|
||||
@@ -14,7 +14,7 @@ import { fileURLToPath } from "node:url";
|
||||
|
||||
import { activeReportLifecycle } from "../internal/dev-report-lifecycle.mjs";
|
||||
import { ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";
|
||||
import { createG14CiPlan } from "./src/g14-ci-plan-lib.mjs";
|
||||
import { createCiPlan } from "./src/ci-plan-lib.mjs";
|
||||
import {
|
||||
BUILDABLE_IMPLEMENTATION_STATES,
|
||||
resolveDevArtifactServices,
|
||||
@@ -26,7 +26,7 @@ import { readStructuredFile } from "./src/structured-config.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const cliEntrypoint = process.env.HWLAB_ARTIFACT_PUBLISH_ENTRYPOINT || "scripts/artifact-publish.mjs";
|
||||
const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "g14";
|
||||
const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "node";
|
||||
const defaultRegistryPrefix =
|
||||
process.env.HWLAB_DEV_REGISTRY_PREFIX || process.env.HWLAB_DEV_REGISTRY || "127.0.0.1:5000/hwlab";
|
||||
const defaultBaseImage = process.env.HWLAB_DEV_BASE_IMAGE || null;
|
||||
@@ -121,7 +121,7 @@ function parseArgs(argv) {
|
||||
else throw new Error(`unknown argument ${arg}`);
|
||||
}
|
||||
|
||||
assert.ok(args.lane === "g14" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`);
|
||||
assert.ok(args.lane === "node" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`);
|
||||
assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`);
|
||||
|
||||
return args;
|
||||
@@ -146,12 +146,12 @@ function artifactIdentityValue(env, name) {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
function g14ArtifactIdentityName(env = process.env) {
|
||||
function artifactIdentityName(env = process.env) {
|
||||
return ciArtifactIdentityEnvNames.find((name) => artifactIdentityValue(env, name));
|
||||
}
|
||||
|
||||
function g14ArtifactProducer(env = process.env) {
|
||||
const idName = g14ArtifactIdentityName(env);
|
||||
function artifactProducer(env = process.env) {
|
||||
const idName = artifactIdentityName(env);
|
||||
if (!idName) {
|
||||
return {
|
||||
kind: "missing",
|
||||
@@ -160,24 +160,24 @@ function g14ArtifactProducer(env = process.env) {
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "g14-ci-artifact",
|
||||
kind: "node-ci-artifact",
|
||||
runId: artifactIdentityValue(env, idName),
|
||||
owner: artifactIdentityValue(env, "HWLAB_CI_ARTIFACT_RUN_OWNER") ?? idName
|
||||
};
|
||||
}
|
||||
|
||||
function g14ArtifactPublishIdentityGuardFailure({ script, mode }) {
|
||||
function artifactPublishIdentityGuardFailure({ script, mode }) {
|
||||
return {
|
||||
ok: false,
|
||||
status: "blocked",
|
||||
error: "g14-artifact-publish-identity-required",
|
||||
code: "g14-artifact-publish-identity-required",
|
||||
error: "artifact-publish-identity-required",
|
||||
code: "artifact-publish-identity-required",
|
||||
script,
|
||||
mode,
|
||||
requiredEnv: "HWLAB_CI_ARTIFACT_RUN_ID or standard CI run identity",
|
||||
acceptedEnv: ciArtifactIdentityEnvNames,
|
||||
entrypoint: `HWLAB_CI_ARTIFACT_RUN_ID=<run-id> HWLAB_CI_ARTIFACT_RUN_OWNER=<owner> node scripts/g14-artifact-publish.mjs --publish --report ${tempReportPath("dev-artifacts.json")}`,
|
||||
summary: `${script} ${mode} is a G14 artifact publish side-effect step and must run under CI artifact identity. Legacy D601 CD transaction identity is not accepted.`,
|
||||
entrypoint: `HWLAB_CI_ARTIFACT_RUN_ID=<run-id> HWLAB_CI_ARTIFACT_RUN_OWNER=<owner> node scripts/artifact-publish.mjs --publish --report ${tempReportPath("dev-artifacts.json")}`,
|
||||
summary: `${script} ${mode} is a node artifact publish side-effect step and must run under CI artifact identity. Legacy D601 CD transaction identity is not accepted.`,
|
||||
devOnly: true,
|
||||
prodTouched: false,
|
||||
cdLockRequired: false,
|
||||
@@ -186,8 +186,8 @@ function g14ArtifactPublishIdentityGuardFailure({ script, mode }) {
|
||||
};
|
||||
}
|
||||
|
||||
function requireG14ArtifactPublishIdentity({ env = process.env, script, mode }) {
|
||||
return g14ArtifactIdentityName(env) ? null : g14ArtifactPublishIdentityGuardFailure({ script, mode });
|
||||
function requireArtifactPublishIdentity({ env = process.env, script, mode }) {
|
||||
return artifactIdentityName(env) ? null : artifactPublishIdentityGuardFailure({ script, mode });
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
@@ -197,7 +197,7 @@ function printHelp() {
|
||||
"Commit-pinned artifact build/publish helper for controlled CI execution.",
|
||||
"",
|
||||
"options:",
|
||||
" --lane LANE g14 or runtime lane vNN; default: g14",
|
||||
" --lane LANE node or runtime lane vNN; default: node",
|
||||
` --catalog-path PATH default: ${defaultCatalogPath}`,
|
||||
` --deploy-config PATH default: ${defaultDeployPath}`,
|
||||
" --image-tag-mode MODE short or full; v02 uses full source commit IDs",
|
||||
@@ -235,11 +235,11 @@ function emit(event, payload = {}) {
|
||||
}
|
||||
|
||||
function ciTimingEnabled(env = process.env) {
|
||||
return env.HWLAB_G14_CICD_TIMING === "1" || env.HWLAB_CI_TIMING === "1";
|
||||
return env.HWLAB_NODE_CICD_TIMING === "1" || env.HWLAB_CI_TIMING === "1";
|
||||
}
|
||||
|
||||
function emitCiTiming(payload) {
|
||||
emit("g14-cicd-timing", {
|
||||
emit("node-cicd-timing", {
|
||||
schemaVersion: "v1",
|
||||
source: "scripts/artifact-publish.mjs",
|
||||
pipelineRun: process.env.HWLAB_TEKTON_PIPELINERUN ?? null,
|
||||
@@ -305,7 +305,7 @@ function parseDockerfileStepTimings(text) {
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (parsed?.event !== "g14-cicd-build-step-timing") continue;
|
||||
if (parsed?.event !== "node-cicd-build-step-timing") continue;
|
||||
timings.push({
|
||||
stage: parsed.stage,
|
||||
status: parsed.status ?? "unknown",
|
||||
@@ -369,7 +369,7 @@ function buildkitBaseImagePreflight(baseImage) {
|
||||
blockers: [],
|
||||
nextSteps: [],
|
||||
recommendation: {
|
||||
source: "G14 k8s registry image used by rootless BuildKit.",
|
||||
source: "node k8s registry image used by rootless BuildKit.",
|
||||
envVar: "HWLAB_DEV_BASE_IMAGE",
|
||||
recommendedEnvValue: requestedImage
|
||||
},
|
||||
@@ -638,7 +638,7 @@ function artifactCatalogSkeleton({ args, deployManifest, commitId }) {
|
||||
ciPublished: false,
|
||||
registryVerified: false,
|
||||
provenance: "not_available_until_publish",
|
||||
note: `${artifactEnvironment(args)} artifact catalog skeleton initialized without DEV/G14 fallback.`
|
||||
note: `${artifactEnvironment(args)} artifact catalog skeleton initialized without DEV/node fallback.`
|
||||
},
|
||||
allowedProfiles: [artifactEnvironment(args)],
|
||||
forbiddenProfiles: isRuntimeArtifactLane(args.lane) ? ["dev", "prod"] : ["prod"],
|
||||
@@ -808,7 +808,7 @@ async function resolveServices(serviceIds, catalog) {
|
||||
}
|
||||
|
||||
function dockerfile(baseImage, port, labels = []) {
|
||||
const dependencyTimingScript = `started_at=$(node -e "console.log(Date.now())"); status=succeeded; if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then bun_pkg=""; case "$(uname -m)" in x86_64|amd64) bun_pkg="@oven/bun-linux-x64@1.3.13" ;; aarch64|arm64) bun_pkg="@oven/bun-linux-aarch64@1.3.13" ;; *) echo "unsupported bun-command architecture: $(uname -m)" >&2; false ;; esac && npm install --no-save --omit=dev --include=optional --ignore-scripts "$bun_pkg" && test -x "node_modules/\${bun_pkg%@*}/bin/bun" && ln -sf "/app/node_modules/\${bun_pkg%@*}/bin/bun" /usr/local/bin/bun; fi && codex_version="$(node -p "require('./node_modules/@openai/codex/package.json').version")" && case "$(uname -m)" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-x64@npm:@openai/codex@\${codex_version}-linux-x64" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-arm64@npm:@openai/codex@\${codex_version}-linux-arm64" ;; esac || status=failed; finished_at=$(node -e "console.log(Date.now())"); node -e "console.log(JSON.stringify({event:'g14-cicd-build-step-timing',stage:'dependency-install',status:process.argv[1],durationMs:Number(process.argv[3])-Number(process.argv[2]),source:'dockerfile-run'}))" "$status" "$started_at" "$finished_at"; test "$status" = succeeded`;
|
||||
const dependencyTimingScript = `started_at=$(node -e "console.log(Date.now())"); status=succeeded; if [ -d /opt/hwlab-node-runtime-base/node_modules ]; then cp -a /opt/hwlab-node-runtime-base/node_modules ./node_modules; fi && if [ -f package-lock.json ]; then npm install --omit=dev --include=optional --ignore-scripts; else npm install --omit=dev --include=optional --ignore-scripts; fi && if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then bun_pkg=""; case "$(uname -m)" in x86_64|amd64) bun_pkg="@oven/bun-linux-x64@1.3.13" ;; aarch64|arm64) bun_pkg="@oven/bun-linux-aarch64@1.3.13" ;; *) echo "unsupported bun-command architecture: $(uname -m)" >&2; false ;; esac && npm install --no-save --omit=dev --include=optional --ignore-scripts "$bun_pkg" && test -x "node_modules/\${bun_pkg%@*}/bin/bun" && ln -sf "/app/node_modules/\${bun_pkg%@*}/bin/bun" /usr/local/bin/bun; fi && codex_version="$(node -p "require('./node_modules/@openai/codex/package.json').version")" && case "$(uname -m)" in x86_64|amd64) test -e node_modules/@openai/codex-linux-x64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-x64@npm:@openai/codex@\${codex_version}-linux-x64" ;; aarch64|arm64) test -e node_modules/@openai/codex-linux-arm64 || npm install --omit=dev --include=optional --ignore-scripts "@openai/codex-linux-arm64@npm:@openai/codex@\${codex_version}-linux-arm64" ;; esac || status=failed; finished_at=$(node -e "console.log(Date.now())"); node -e "console.log(JSON.stringify({event:'node-cicd-build-step-timing',stage:'dependency-install',status:process.argv[1],durationMs:Number(process.argv[3])-Number(process.argv[2]),source:'dockerfile-run'}))" "$status" "$started_at" "$finished_at"; test "$status" = succeeded`;
|
||||
const runtimeReadinessScript = `set -eu; fail() { echo "hwlab-artifact-runtime-check-failed:$1" >&2; exit 1; }; mkdir -p /workspace /codex-home; rm -rf /workspace/hwlab; ln -s /app /workspace/hwlab; if [ "\${HWLAB_ARTIFACT_KIND:-}" = "bun-command" ]; then test -x /usr/local/bin/bun || fail bun-missing; /usr/local/bin/bun --version >/tmp/hwlab-bun-version.txt || fail bun-version; fi; chmod -R a+rwX /app /workspace /codex-home || fail chmod; test -x /app/node_modules/.bin/codex || fail codex-bin-missing; /app/node_modules/.bin/codex --version >/tmp/hwlab-codex-version.txt || fail codex-version; test -x /app/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex/codex || test -x /app/node_modules/@openai/codex-linux-arm64/vendor/aarch64-unknown-linux-musl/codex/codex || fail codex-native-missing`;
|
||||
return [
|
||||
`FROM ${baseImage}`,
|
||||
@@ -1032,7 +1032,7 @@ async function preflight({ args, services, catalog, deployManifest, baseImagePre
|
||||
type: "safety_blocker",
|
||||
scope: "registry",
|
||||
summary: error.message,
|
||||
next: "Use the G14 local/internal registry prefix, for example 127.0.0.1:5000/hwlab."
|
||||
next: "Use the node local/internal registry prefix, for example 127.0.0.1:5000/hwlab."
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -1072,7 +1072,7 @@ async function preflight({ args, services, catalog, deployManifest, baseImagePre
|
||||
type: "environment_blocker",
|
||||
scope: "buildkit-command",
|
||||
summary: `BuildKit command ${args.buildkitCommand} is not available in the publish task image.`,
|
||||
next: "Use the G14 Tekton BuildKit path that prepares buildctl/buildkitd before running --publish."
|
||||
next: "Use the node Tekton BuildKit path that prepares buildctl/buildkitd before running --publish."
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -2038,14 +2038,14 @@ function createReport({ args, repo, commitId, shortCommit, mode, services, artif
|
||||
documents: [
|
||||
"docs/reference/MVP-e2e-acceptance.md",
|
||||
"docs/reference/spec-v02-documentation-governance.md",
|
||||
"docs/reference/g14-gitops-cicd.md",
|
||||
"docs/reference/node-gitops-cicd.md",
|
||||
"docs/reference/spec-v02-cicd.md"
|
||||
],
|
||||
summary: `${environment} artifact publish evidence; runtime acceptance stays separate from artifact publication.`
|
||||
},
|
||||
validationCommands: [
|
||||
"node --check scripts/artifact-publish.mjs",
|
||||
"node --check scripts/g14-artifact-publish.mjs",
|
||||
"node --check scripts/artifact-publish.mjs",
|
||||
"node --check scripts/src/dev-artifact-services.mjs",
|
||||
"node --check scripts/src/registry-capabilities.mjs",
|
||||
"node --check scripts/preflight-dev-base-image.mjs",
|
||||
@@ -2053,7 +2053,7 @@ function createReport({ args, repo, commitId, shortCommit, mode, services, artif
|
||||
"node --check scripts/dev-runtime-base-image.mjs",
|
||||
"node --test scripts/refresh-artifact-catalog.test.mjs",
|
||||
"node scripts/preflight-dev-base-image.mjs",
|
||||
"node scripts/g14-artifact-publish.mjs --preflight --no-report",
|
||||
"node scripts/artifact-publish.mjs --preflight --no-report",
|
||||
"node --check scripts/validate-dev-gate-report.mjs",
|
||||
"node scripts/validate-dev-gate-report.mjs"
|
||||
],
|
||||
@@ -2197,7 +2197,7 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
if (args.mode === "publish") {
|
||||
const identityGuard = requireG14ArtifactPublishIdentity({
|
||||
const identityGuard = requireArtifactPublishIdentity({
|
||||
script: cliEntrypoint,
|
||||
mode: "--publish"
|
||||
});
|
||||
@@ -2237,8 +2237,8 @@ async function main() {
|
||||
}
|
||||
const buildCreatedAt = new Date().toISOString();
|
||||
const repo = repoLabelFromRemote(remoteUrl);
|
||||
const producer = g14ArtifactProducer(process.env);
|
||||
const ciPlan = await createG14CiPlan({
|
||||
const producer = artifactProducer(process.env);
|
||||
const ciPlan = await createCiPlan({
|
||||
repoRoot,
|
||||
lane: args.lane,
|
||||
targetRef: "HEAD",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
import { createG14CiPlan } from "./src/g14-ci-plan-lib.mjs";
|
||||
import { createCiPlan } from "./src/ci-plan-lib.mjs";
|
||||
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
|
||||
@@ -8,7 +8,7 @@ if (args.help) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const plan = await createG14CiPlan(args);
|
||||
const plan = await createCiPlan(args);
|
||||
process.stdout.write(args.pretty ? `${JSON.stringify(plan, null, 2)}\n` : `${JSON.stringify(plan)}\n`);
|
||||
|
||||
function parseArgs(argv) {
|
||||
@@ -38,14 +38,14 @@ function readOption(argv, index, name) {
|
||||
|
||||
function usage() {
|
||||
return [
|
||||
"usage: node scripts/g14-ci-plan.mjs [--lane v02|v03] [--base-ref REF] [--target-ref REF] [--pretty]",
|
||||
"usage: node scripts/ci-plan.mjs [--lane v02|v03] [--base-ref REF] [--target-ref REF] [--pretty]",
|
||||
"",
|
||||
"Plan G14 monorepo image builds without mutating CI/CD state.",
|
||||
"Plan node monorepo image builds without mutating CI/CD state.",
|
||||
"For runtime lane env reuse, service component boundaries and env recipe must come from deploy/deploy.yaml.",
|
||||
"",
|
||||
"examples:",
|
||||
" node scripts/g14-ci-plan.mjs --lane v02 --base-ref HEAD~1 --target-ref HEAD --pretty",
|
||||
" node scripts/g14-ci-plan.mjs --lane v03 --base-ref HEAD~1 --target-ref HEAD --pretty",
|
||||
" node scripts/g14-ci-plan.mjs --services hwlab-cloud-api,hwlab-cloud-web --pretty"
|
||||
" node scripts/ci-plan.mjs --lane v02 --base-ref HEAD~1 --target-ref HEAD --pretty",
|
||||
" node scripts/ci-plan.mjs --lane v03 --base-ref HEAD~1 --target-ref HEAD --pretty",
|
||||
" node scripts/ci-plan.mjs --services hwlab-cloud-api,hwlab-cloud-web --pretty"
|
||||
].join("\n");
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import { promisify } from "node:util";
|
||||
import {
|
||||
classifyGlobalChange,
|
||||
componentModelsForServices,
|
||||
createG14CiPlan,
|
||||
createCiPlan,
|
||||
envReuseRecipeForLane,
|
||||
matchingPaths,
|
||||
packageRuntimeFieldsChanged
|
||||
} from "./src/g14-ci-plan-lib.mjs";
|
||||
} from "./src/ci-plan-lib.mjs";
|
||||
import { readStructuredFile, writeStructuredFile } from "./src/structured-config.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
@@ -26,7 +26,7 @@ const V02_RUNTIME_SERVICE_IDS = Object.freeze([
|
||||
"hwlab-agent-skills"
|
||||
]);
|
||||
|
||||
test("G14 CI planner maps bridge changes to cloud-api only", async () => {
|
||||
test("node CI planner maps bridge changes to cloud-api only", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.ts"), "console.log('bridge v2');\n");
|
||||
await git(repo, ["add", "."]);
|
||||
@@ -40,10 +40,10 @@ test("G14 CI planner maps bridge changes to cloud-api only", async () => {
|
||||
assert.deepEqual(cloudApi.reason, ["code-input-changed", "component-path-changed"]);
|
||||
});
|
||||
|
||||
test("G14 CI planner treats docs-only changes as no image build", async () => {
|
||||
test("node CI planner treats docs-only changes as no image build", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
await mkdir(path.join(repo, "docs/reference"), { recursive: true });
|
||||
await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs v2\n");
|
||||
await writeFile(path.join(repo, "docs/reference/node-gitops-cicd.md"), "docs v2\n");
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change docs"]);
|
||||
|
||||
@@ -56,7 +56,7 @@ test("G14 CI planner treats docs-only changes as no image build", async () => {
|
||||
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 writeFile(path.join(repo, "docs/reference/node-gitops-cicd.md"), "docs v2\n");
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change docs"]);
|
||||
|
||||
@@ -69,7 +69,7 @@ test("planner rebuilds when catalog digest is missing instead of blocking reuse"
|
||||
|
||||
test("v02 planner rolls cloud-api service code changes without rebuilding service image", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -91,7 +91,7 @@ test("v02 planner rolls cloud-api service code changes without rebuilding servic
|
||||
await git(repo, ["add", "cmd/hwlab-cloud-api/main.ts"]);
|
||||
await git(repo, ["commit", "-m", "change cloud-api service code"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -112,11 +112,11 @@ test("v02 planner rolls cloud-api service code changes without rebuilding servic
|
||||
test("v02 planner rebuilds env image when an env reuse catalog digest is missing", 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 only with missing env digest\n");
|
||||
await git(repo, ["add", "docs/reference/g14-gitops-cicd.md"]);
|
||||
await writeFile(path.join(repo, "docs/reference/node-gitops-cicd.md"), "docs only with missing env digest\n");
|
||||
await git(repo, ["add", "docs/reference/node-gitops-cicd.md"]);
|
||||
await git(repo, ["commit", "-m", "docs after missing env digest"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -143,7 +143,7 @@ test("artifact reuse paths preserve catalog provenance instead of current planne
|
||||
assert.match(artifactPublish, /envReuseRecipe: servicePlan\.envReuseRecipe \?\? null/u);
|
||||
assert.match(artifactPublish, /componentInputHash: serviceResultValue\(env, service\.serviceId, "COMPONENT_INPUT_HASH"\) \?\? \(reusedServiceImage \? null : service\.componentInputHash \?\? null\)/u);
|
||||
|
||||
const gitopsRender = await readFile("scripts/g14-gitops-render.mjs", "utf8");
|
||||
const gitopsRender = await readFile("scripts/gitops-render.mjs", "utf8");
|
||||
assert.match(gitopsRender, /const componentInputHash = envReuse \? \(planned\.componentInputHash \|\| service\.componentInputHash \|\| ""\) : \(service\.componentInputHash \|\| ""\);/u);
|
||||
assert.doesNotMatch(gitopsRender, /"component-input-hash": planned\.componentInputHash \|\| service\.componentInputHash \|\| ""/u);
|
||||
});
|
||||
@@ -178,7 +178,7 @@ test("v02 planner rejects missing service declarations instead of falling back t
|
||||
await git(repo, ["commit", "-m", "remove v02 declarations"]);
|
||||
|
||||
await assert.rejects(
|
||||
() => createG14CiPlan({
|
||||
() => createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -200,7 +200,7 @@ test("v02 planner rejects missing boot script declarations instead of guessing p
|
||||
await git(repo, ["commit", "-m", "remove v02 cloud-api boot script"]);
|
||||
|
||||
await assert.rejects(
|
||||
() => createG14CiPlan({
|
||||
() => createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -218,7 +218,7 @@ test("v02 planner keeps hwpod-spec changes out of runtime service env reuse", as
|
||||
await git(repo, ["add", ".hwlab/hwpod-spec.yaml"]);
|
||||
await git(repo, ["commit", "-m", "change preinstalled hwpod spec"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -240,7 +240,7 @@ test("v02 planner treats AgentRun runtime prompt changes as cloud-api inputs", a
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "seed agentrun prompt"]);
|
||||
const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -261,7 +261,7 @@ test("v02 planner treats AgentRun runtime prompt changes as cloud-api inputs", a
|
||||
await git(repo, ["add", "internal/agent/prompts/hwlab-v02-runtime.md"]);
|
||||
await git(repo, ["commit", "-m", "change agentrun prompt"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -295,7 +295,7 @@ test("planner scopes cloud-web runtime changes to cloud-web", async () => {
|
||||
test("v02 planner treats shared trace renderer changes as cloud-web code rollout inputs", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -317,7 +317,7 @@ test("v02 planner treats shared trace renderer changes as cloud-web code rollout
|
||||
await git(repo, ["add", "tools/src/hwlab-cli/trace-renderer.ts"]);
|
||||
await git(repo, ["commit", "-m", "change shared trace renderer"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -399,14 +399,14 @@ test("planner treats dependency changes as runtime image inputs", async () => {
|
||||
});
|
||||
|
||||
test("global change classifier distinguishes gitops-only and test-only", () => {
|
||||
assert.equal(classifyGlobalChange(["deploy/gitops/g14/runtime-dev/workloads.yaml"]).gitopsOnly, true);
|
||||
assert.equal(classifyGlobalChange(["deploy/gitops/node/runtime-dev/workloads.yaml"]).gitopsOnly, true);
|
||||
assert.equal(classifyGlobalChange(["cmd/hwlab-cloud-api/runtime-options.test.ts"]).testOnly, true);
|
||||
});
|
||||
|
||||
test("v02 planner marks cloud-web code-only change as env reuse rollout", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -427,7 +427,7 @@ test("v02 planner marks cloud-web code-only change as env reuse rollout", async
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change cloud web code"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -451,7 +451,7 @@ test("v02 planner marks cloud-web code-only change as env reuse rollout", async
|
||||
test("v02 planner enables env reuse for all retained runtime services", async () => {
|
||||
const repo = await createFixtureRepo({ serviceIds: V02_RUNTIME_SERVICE_IDS });
|
||||
const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -480,7 +480,7 @@ test("v02 planner enables env reuse for all retained runtime services", async ()
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change all v02 service code"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -506,7 +506,7 @@ test("v02 planner enables env reuse for all retained runtime services", async ()
|
||||
test("v02 planner rejects removed HWLAB-owned agent worker service selections", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
await assert.rejects(
|
||||
() => createG14CiPlan({
|
||||
() => createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -514,14 +514,14 @@ test("v02 planner rejects removed HWLAB-owned agent worker service selections",
|
||||
artifactCatalogPath: "deploy/artifact-catalog.v02.json",
|
||||
services: ["hwlab-agent-worker"]
|
||||
}),
|
||||
/unknown service IDs for G14 CI plan: hwlab-agent-worker/u
|
||||
/unknown service IDs for node CI plan: hwlab-agent-worker/u
|
||||
);
|
||||
});
|
||||
|
||||
test("v02 planner keeps hwlab-cli-only changes out of runtime service builds", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -544,7 +544,7 @@ test("v02 planner keeps hwlab-cli-only changes out of runtime service builds", a
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change cli and host asset"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -561,7 +561,7 @@ test("v02 planner keeps hwlab-cli-only changes out of runtime service builds", a
|
||||
test("v02 planner scopes hwpod skill bundle changes to the skills service", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -578,7 +578,7 @@ test("v02 planner scopes hwpod skill bundle changes to the skills service", asyn
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change hwpod runtime cli"]);
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD~1",
|
||||
@@ -619,7 +619,7 @@ test("v02 planner reads env reuse declarations and recipe from deploy config", a
|
||||
test("v03 planner reads env reuse declarations and catalog from v03 lane config", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v03",
|
||||
baseRef: "HEAD",
|
||||
@@ -645,7 +645,7 @@ test("v03 planner reads env reuse declarations and catalog from v03 lane config"
|
||||
async function createFixtureRepo(options = {}) {
|
||||
const serviceIds = options.serviceIds ?? ["hwlab-cloud-api", "hwlab-cloud-web"];
|
||||
const laneServiceIds = options.laneServiceIds ?? V02_RUNTIME_SERVICE_IDS;
|
||||
const repo = await mkdtemp(path.join(os.tmpdir(), "hwlab-g14-ci-plan-"));
|
||||
const repo = await mkdtemp(path.join(os.tmpdir(), "hwlab-ci-plan-"));
|
||||
await mkdir(path.join(repo, "cmd/hwlab-cloud-api"), { recursive: true });
|
||||
await mkdir(path.join(repo, "cmd/hwlab-codex-api-responses-forwarder"), { recursive: true });
|
||||
await mkdir(path.join(repo, "cmd/hwlab-deepseek-responses-bridge"), { recursive: true });
|
||||
@@ -705,7 +705,7 @@ async function createFixtureRepo(options = {}) {
|
||||
await git(repo, ["commit", "-m", "initial"]);
|
||||
if (catalogMode !== false) {
|
||||
const initialSha = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const initialPlan = await createG14CiPlan({
|
||||
const initialPlan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -726,7 +726,7 @@ async function createFixtureRepo(options = {}) {
|
||||
async function refreshFixtureCatalogForHead(repo) {
|
||||
const sourceCommitId = (await git(repo, ["rev-parse", "HEAD"])).stdout.trim();
|
||||
const services = ["hwlab-cloud-api", "hwlab-cloud-web"];
|
||||
const plan = await createG14CiPlan({
|
||||
const plan = await createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
baseRef: "HEAD",
|
||||
@@ -743,7 +743,7 @@ async function refreshFixtureCatalogForHead(repo) {
|
||||
}
|
||||
|
||||
function planV02CoreServices(repo, options) {
|
||||
return createG14CiPlan({
|
||||
return createCiPlan({
|
||||
repoRoot: repo,
|
||||
lane: "v02",
|
||||
artifactCatalogPath: "deploy/artifact-catalog.v02.json",
|
||||
@@ -853,7 +853,6 @@ function createDeployFixture({ serviceIds = V02_RUNTIME_SERVICE_IDS } = {}) {
|
||||
additionalEnvPaths: [
|
||||
"internal/dev-entrypoint/artifact-runtime.mjs",
|
||||
"scripts/artifact-publish.mjs",
|
||||
"scripts/g14-artifact-publish.mjs",
|
||||
"scripts/src/dev-artifact-services.mjs"
|
||||
],
|
||||
hwpodAliases: [
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
process.env.HWLAB_ARTIFACT_PUBLISH_ENTRYPOINT ||= "scripts/g14-artifact-publish.mjs";
|
||||
process.env.HWLAB_ARTIFACT_PUBLISH_PROFILE ||= "g14";
|
||||
await import("./artifact-publish.mjs");
|
||||
@@ -20,29 +20,29 @@ import { readStructuredFile } from "./src/structured-config.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const defaultOutDir = "deploy/gitops/g14";
|
||||
const defaultRegistryPrefix = process.env.HWLAB_G14_REGISTRY_PREFIX || "127.0.0.1:5000/hwlab";
|
||||
const defaultSourceRepo = process.env.HWLAB_G14_GIT_URL || "git@github.com:pikasTech/HWLAB.git";
|
||||
const defaultGitReadUrl = process.env.HWLAB_G14_GIT_READ_URL || null;
|
||||
const defaultOutDir = "deploy/gitops/node";
|
||||
const defaultRegistryPrefix = process.env.HWLAB_NODE_REGISTRY_PREFIX || "127.0.0.1:5000/hwlab";
|
||||
const defaultSourceRepo = process.env.HWLAB_NODE_GIT_URL || "git@github.com:pikasTech/HWLAB.git";
|
||||
const defaultGitReadUrl = process.env.HWLAB_NODE_GIT_READ_URL || null;
|
||||
const defaultV02GitReadUrl = process.env.HWLAB_V02_GIT_READ_URL || "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
||||
const defaultV02GitWriteUrl = process.env.HWLAB_V02_GIT_WRITE_URL || "http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
|
||||
const defaultRuntimeLaneGitReadUrl = process.env.HWLAB_RUNTIME_LANE_GIT_READ_URL || defaultV02GitReadUrl;
|
||||
const defaultRuntimeLaneGitWriteUrl = process.env.HWLAB_RUNTIME_LANE_GIT_WRITE_URL || defaultV02GitWriteUrl;
|
||||
const defaultBranch = process.env.HWLAB_G14_BRANCH || "G14";
|
||||
const defaultGitopsBranch = process.env.HWLAB_G14_GITOPS_BRANCH || "G14-gitops";
|
||||
const defaultBranch = process.env.HWLAB_NODE_BRANCH || "node";
|
||||
const defaultGitopsBranch = process.env.HWLAB_NODE_GITOPS_BRANCH || "node-gitops";
|
||||
const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json";
|
||||
const defaultRuntimeEndpoint = process.env.HWLAB_G14_RUNTIME_ENDPOINT || "http://74.48.78.17:17667";
|
||||
const defaultWebEndpoint = process.env.HWLAB_G14_WEB_ENDPOINT || "http://74.48.78.17:17666";
|
||||
const defaultProdRuntimeEndpoint = process.env.HWLAB_G14_PROD_RUNTIME_ENDPOINT || "http://74.48.78.17:18667";
|
||||
const defaultProdWebEndpoint = process.env.HWLAB_G14_PROD_WEB_ENDPOINT || "http://74.48.78.17:18666";
|
||||
const defaultRuntimeEndpoint = process.env.HWLAB_NODE_RUNTIME_ENDPOINT || "http://74.48.78.17:17667";
|
||||
const defaultWebEndpoint = process.env.HWLAB_NODE_WEB_ENDPOINT || "http://74.48.78.17:17666";
|
||||
const defaultProdRuntimeEndpoint = process.env.HWLAB_NODE_PROD_RUNTIME_ENDPOINT || "http://74.48.78.17:18667";
|
||||
const defaultProdWebEndpoint = process.env.HWLAB_NODE_PROD_WEB_ENDPOINT || "http://74.48.78.17:18666";
|
||||
const defaultV02RuntimeEndpoint = process.env.HWLAB_V02_RUNTIME_ENDPOINT || "https://hwlab.74-48-78-17.nip.io";
|
||||
const defaultV02WebEndpoint = process.env.HWLAB_V02_WEB_ENDPOINT || "https://hwlab.74-48-78-17.nip.io";
|
||||
const defaultProxyUrl = process.env.HWLAB_G14_PROXY_URL || "http://127.0.0.1:10808";
|
||||
const defaultAllProxyUrl = process.env.HWLAB_G14_ALL_PROXY_URL || "socks5h://127.0.0.1:10808";
|
||||
const defaultNoProxy = process.env.HWLAB_G14_NO_PROXY || "hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc,git-mirror-http,git-mirror-http.devops-infra,git-mirror-http.devops-infra.svc,git-mirror-http.devops-infra.svc.cluster.local,git-mirror-write,git-mirror-write.devops-infra,git-mirror-write.devops-infra.svc,git-mirror-write.devops-infra.svc.cluster.local";
|
||||
const ciToolsRunnerImage = process.env.HWLAB_G14_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1";
|
||||
const buildkitRunnerImage = process.env.HWLAB_G14_BUILDKIT_IMAGE || "moby/buildkit:rootless";
|
||||
const defaultDevBaseImage = process.env.HWLAB_G14_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim";
|
||||
const defaultProxyUrl = process.env.HWLAB_NODE_PROXY_URL || "http://127.0.0.1:10808";
|
||||
const defaultAllProxyUrl = process.env.HWLAB_NODE_ALL_PROXY_URL || "socks5h://127.0.0.1:10808";
|
||||
const defaultNoProxy = process.env.HWLAB_NODE_NO_PROXY || "hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc,git-mirror-http,git-mirror-http.devops-infra,git-mirror-http.devops-infra.svc,git-mirror-http.devops-infra.svc.cluster.local,git-mirror-write,git-mirror-write.devops-infra,git-mirror-write.devops-infra.svc,git-mirror-write.devops-infra.svc.cluster.local";
|
||||
const ciToolsRunnerImage = process.env.HWLAB_NODE_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1";
|
||||
const buildkitRunnerImage = process.env.HWLAB_NODE_BUILDKIT_IMAGE || "moby/buildkit:rootless";
|
||||
const defaultDevBaseImage = process.env.HWLAB_NODE_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim";
|
||||
const defaultServiceIds = Object.freeze([
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-cloud-web",
|
||||
@@ -86,13 +86,13 @@ const v02RemovedServiceEnvNames = new Set([
|
||||
"HWLAB_ROUTER_URL",
|
||||
"HWLAB_TUNNEL_CLIENT_URL"
|
||||
]);
|
||||
const moonBridgeSourceRef = process.env.HWLAB_G14_MOONBRIDGE_REF || "1b99888d3dae889b79ee602cb875c7907f7e76f2";
|
||||
const moonBridgeImage = process.env.HWLAB_G14_MOONBRIDGE_IMAGE || `${defaultRegistryPrefix}/moonbridge:${moonBridgeSourceRef.slice(0, 12)}`;
|
||||
const deepSeekProfileModel = process.env.HWLAB_G14_DEEPSEEK_PROFILE_MODEL || "deepseek-chat";
|
||||
const codexApiProfileModel = process.env.HWLAB_G14_CODEX_API_PROFILE_MODEL || "gpt-5.5";
|
||||
const codexApiProfileBaseUrl = process.env.HWLAB_G14_CODEX_API_PROFILE_BASE_URL || "http://127.0.0.1:49280/responses";
|
||||
const codexApiForwarderPort = process.env.HWLAB_G14_CODEX_API_FORWARDER_PORT || "49280";
|
||||
const codexApiForwarderUpstreamBaseUrl = process.env.HWLAB_G14_CODEX_API_UPSTREAM_BASE_URL || "https://hyueapi.com";
|
||||
const moonBridgeSourceRef = process.env.HWLAB_NODE_MOONBRIDGE_REF || "1b99888d3dae889b79ee602cb875c7907f7e76f2";
|
||||
const moonBridgeImage = process.env.HWLAB_NODE_MOONBRIDGE_IMAGE || `${defaultRegistryPrefix}/moonbridge:${moonBridgeSourceRef.slice(0, 12)}`;
|
||||
const deepSeekProfileModel = process.env.HWLAB_NODE_DEEPSEEK_PROFILE_MODEL || "deepseek-chat";
|
||||
const codexApiProfileModel = process.env.HWLAB_NODE_CODEX_API_PROFILE_MODEL || "gpt-5.5";
|
||||
const codexApiProfileBaseUrl = process.env.HWLAB_NODE_CODEX_API_PROFILE_BASE_URL || "http://127.0.0.1:49280/responses";
|
||||
const codexApiForwarderPort = process.env.HWLAB_NODE_CODEX_API_FORWARDER_PORT || "49280";
|
||||
const codexApiForwarderUpstreamBaseUrl = process.env.HWLAB_NODE_CODEX_API_UPSTREAM_BASE_URL || "https://hyueapi.com";
|
||||
const sourceCommitPattern = /^[a-f0-9]{7,40}$/u;
|
||||
const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout";
|
||||
|
||||
@@ -106,14 +106,14 @@ const primitiveValidationTasks = Object.freeze([
|
||||
commands: ["node scripts/repo-reports-guard.mjs"]
|
||||
},
|
||||
{
|
||||
name: "g14-contract-check",
|
||||
name: "node-contract-check",
|
||||
commands: [
|
||||
"npm run gitops:ts:check",
|
||||
"node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --help >/dev/null",
|
||||
"node scripts/run-bun.mjs scripts/gitops-render.mjs --help >/dev/null",
|
||||
"render_check_revision=$(git rev-parse HEAD)",
|
||||
"render_check_dir=$(mktemp -d)",
|
||||
"node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --source-revision \"$render_check_revision\"",
|
||||
"node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --check --source-revision \"$render_check_revision\"",
|
||||
"node scripts/run-bun.mjs scripts/gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --source-revision \"$render_check_revision\"",
|
||||
"node scripts/run-bun.mjs scripts/gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --check --source-revision \"$render_check_revision\"",
|
||||
`node --input-type=module <<'NODE'
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -122,7 +122,7 @@ const root = process.cwd();
|
||||
const ciFileName = "CI" + ".json";
|
||||
const forbiddenCiFragments = [
|
||||
"ci" + "-json",
|
||||
"HWLAB_G14_TEKTON" + "_SINGLE_IMAGE_PUBLISH",
|
||||
"HWLAB_NODE_TEKTON" + "_SINGLE_IMAGE_PUBLISH",
|
||||
"single" + "-dind",
|
||||
"docker" + ":29",
|
||||
"DOCKER" + "_HOST",
|
||||
@@ -132,15 +132,14 @@ const forbiddenCiFragments = [
|
||||
"--build" + "-backend",
|
||||
"HWLAB_ARTIFACT" + "_BUILD_BACKEND",
|
||||
"--legacy" + "-source-images",
|
||||
"HWLAB_G14_USE_DEPLOY_IMAGES" + "=0"
|
||||
"HWLAB_NODE_USE_DEPLOY_IMAGES" + "=0"
|
||||
];
|
||||
const scanTargets = [
|
||||
"scripts/g14-gitops-render.mjs",
|
||||
"scripts/gitops-render.mjs",
|
||||
"scripts/src/runtime-lane.ts",
|
||||
"scripts/artifact-publish.mjs",
|
||||
"scripts/g14-artifact-publish.mjs",
|
||||
"scripts/src/g14-ci-plan-lib.mjs",
|
||||
"deploy/gitops/g14/tekton"
|
||||
"scripts/src/ci-plan-lib.mjs",
|
||||
"deploy/gitops/node/tekton"
|
||||
];
|
||||
const skipDirs = new Set([".git", "node_modules", ".worktree"]);
|
||||
|
||||
@@ -175,8 +174,7 @@ if (ciFiles.length || violations.length) {
|
||||
process.exit(1);
|
||||
}
|
||||
NODE`,
|
||||
"node --check scripts/artifact-publish.mjs",
|
||||
"node --check scripts/g14-artifact-publish.mjs"
|
||||
"node --check scripts/artifact-publish.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -202,8 +200,8 @@ function proxyEnv() {
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
lane: process.env.HWLAB_GITOPS_LANE || "g14",
|
||||
nodeId: process.env.HWLAB_NODE_ID || "G14",
|
||||
lane: process.env.HWLAB_GITOPS_LANE || "node",
|
||||
nodeId: process.env.HWLAB_NODE_ID || "node",
|
||||
outDir: defaultOutDir,
|
||||
gitopsRoot: defaultOutDir,
|
||||
catalogPath: defaultCatalogPath,
|
||||
@@ -265,7 +263,7 @@ function parseArgs(argv) {
|
||||
}
|
||||
if (!args.gitReadUrl) args.gitReadUrl = args.sourceRepo;
|
||||
if (!args.gitWriteUrl) args.gitWriteUrl = args.sourceRepo;
|
||||
assert.ok(args.lane === "g14" || isRuntimeLane(args.lane), `unknown lane ${args.lane}`);
|
||||
assert.ok(args.lane === "node" || isRuntimeLane(args.lane), `unknown lane ${args.lane}`);
|
||||
assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`);
|
||||
if (isRuntimeLane(args.lane)) {
|
||||
const versionName = versionNameForRuntimeLane(args.lane);
|
||||
@@ -283,13 +281,13 @@ function readOption(argv, index, name) {
|
||||
|
||||
function usage() {
|
||||
return [
|
||||
"usage: node scripts/run-bun.mjs scripts/g14-gitops-render.mjs [options]",
|
||||
"usage: node scripts/run-bun.mjs scripts/gitops-render.mjs [options]",
|
||||
"",
|
||||
"Render G14-only Kubernetes-native CI/CD manifests from built-in Tekton CI primitives, deploy/deploy.yaml, and deploy/k8s/*.",
|
||||
"Render node-scoped Kubernetes-native CI/CD manifests from built-in Tekton CI primitives, deploy/deploy.yaml, and deploy/k8s/*.",
|
||||
"",
|
||||
"options:",
|
||||
" --lane LANE g14 or runtime lane such as v02/v03/v04; default: g14",
|
||||
` --node NODE deployment node id from deploy.nodes; default: ${process.env.HWLAB_NODE_ID || "G14"}`,
|
||||
" --lane LANE node or runtime lane such as v02/v03/v04; default: node",
|
||||
` --node NODE deployment node id from deploy.nodes; runtime lanes default from deploy.lanes.<lane>.node`,
|
||||
` --out DIR default: ${defaultOutDir}`,
|
||||
` --gitops-root DIR path inside GitOps repo; default comes from deploy.nodes.<node>.gitopsRoot`,
|
||||
` --catalog-path PATH default: ${defaultCatalogPath}`,
|
||||
@@ -453,7 +451,7 @@ function v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds = null) {
|
||||
}
|
||||
|
||||
function bootShForService(deployService, serviceId) {
|
||||
assert.ok(deployService?.bootSh, `deploy.lanes.v02.bootScripts.${serviceId} is required`);
|
||||
assert.ok(deployService?.bootSh, `deploy lane bootScripts.${serviceId} is required`);
|
||||
const value = deployService.bootSh;
|
||||
const text = String(value).replaceAll("\\", "/").replace(/^\.\//u, "");
|
||||
assert.ok(text && !text.startsWith("/") && !text.split("/").includes(".."), `${serviceId} boot script must be repo-relative`);
|
||||
@@ -461,16 +459,16 @@ function bootShForService(deployService, serviceId) {
|
||||
}
|
||||
|
||||
function envReuseServiceIdsForLane(deploy, lane) {
|
||||
if (lane !== "v02") return new Set();
|
||||
const configured = deploy?.lanes?.v02?.envReuseServices;
|
||||
if (!isRuntimeLane(lane)) return new Set();
|
||||
const configured = deploy?.lanes?.[lane]?.envReuseServices;
|
||||
const allowed = new Set(serviceIdsForLane(lane, deploy));
|
||||
return new Set((Array.isArray(configured) ? configured : []).filter((serviceId) => allowed.has(serviceId)));
|
||||
}
|
||||
|
||||
function deployServiceForBoot(deploy, serviceId) {
|
||||
function deployServiceForBoot(deploy, serviceId, lane = "v02") {
|
||||
return {
|
||||
serviceId,
|
||||
bootSh: deploy?.lanes?.v02?.bootScripts?.[serviceId]
|
||||
bootSh: deploy?.lanes?.[lane]?.bootScripts?.[serviceId]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -697,7 +695,7 @@ function artifactCatalogSkeletonService({ args, deploy, source, serviceId, envir
|
||||
};
|
||||
const envReuseServiceIds = envReuseServiceIdsForLane(deploy, args.lane);
|
||||
if (!envReuseServiceIds.has(serviceId)) return base;
|
||||
const bootSh = bootShForService(deployServiceForBoot(deploy, serviceId), serviceId);
|
||||
const bootSh = bootShForService(deployServiceForBoot(deploy, serviceId, args.lane), serviceId);
|
||||
const environmentImage = imageFor(args.registryPrefix, `${serviceId}-env`, `env-${String(source.full).slice(0, 12)}`);
|
||||
return {
|
||||
...base,
|
||||
@@ -738,7 +736,7 @@ function runtimeLabelForProfile(profile) {
|
||||
}
|
||||
|
||||
function gitopsTargetForProfile(profile) {
|
||||
return isRuntimeLane(profile) ? profile : "g14";
|
||||
return isRuntimeLane(profile) ? profile : "node";
|
||||
}
|
||||
|
||||
function gitopsRootForArgs(args) {
|
||||
@@ -776,24 +774,24 @@ function laneNames(args) {
|
||||
pipelineRunPrefix: `${namespace}-ci-poll-`,
|
||||
runtimeNamespace: namespace,
|
||||
runtimePath: gitopsPathForProfile(args, args.lane),
|
||||
argoApplications: [`argocd/hwlab-g14-${args.lane}`]
|
||||
argoApplications: [`argocd/hwlab-node-${args.lane}`]
|
||||
};
|
||||
}
|
||||
return {
|
||||
gitopsTarget: "g14",
|
||||
pipeline: "hwlab-g14-ci-image-publish",
|
||||
poller: "hwlab-g14-branch-poller",
|
||||
reconciler: "hwlab-g14-control-plane-reconciler",
|
||||
gitopsTarget: "node",
|
||||
pipeline: "hwlab-node-ci-image-publish",
|
||||
poller: "hwlab-node-branch-poller",
|
||||
reconciler: "hwlab-node-control-plane-reconciler",
|
||||
serviceAccount: "hwlab-tekton-runner",
|
||||
pipelineRunPrefix: "hwlab-g14-ci-poll-",
|
||||
pipelineRunPrefix: "hwlab-node-ci-poll-",
|
||||
runtimeNamespace: "hwlab-dev",
|
||||
runtimePath: gitopsPathForProfile(args, "dev"),
|
||||
argoApplications: ["argocd/hwlab-g14-dev", "argocd/hwlab-g14-prod"]
|
||||
argoApplications: ["argocd/hwlab-node-dev", "argocd/hwlab-node-prod"]
|
||||
};
|
||||
}
|
||||
|
||||
function argoApplicationName(profile) {
|
||||
return `hwlab-g14-${profile}`;
|
||||
return `hwlab-node-${profile}`;
|
||||
}
|
||||
|
||||
function imageTagForSource(args, source) {
|
||||
@@ -830,16 +828,16 @@ function namespaceScopedHost(value, namespace) {
|
||||
}
|
||||
|
||||
function secretNameForProfile(secretName, profile) {
|
||||
if (profile !== "v02") return secretName;
|
||||
if (secretName === "hwlab-cloud-api-dev-db") return "hwlab-cloud-api-v02-db";
|
||||
if (secretName === "hwlab-code-agent-provider") return "hwlab-v02-code-agent-provider";
|
||||
if (secretName === "hwlab-code-agent-codex-auth") return "hwlab-v02-code-agent-codex-auth";
|
||||
if (secretName === "hwlab-bootstrap-admin") return "hwlab-v02-bootstrap-admin";
|
||||
if (!isRuntimeLane(profile)) return secretName;
|
||||
if (secretName === "hwlab-cloud-api-dev-db") return `hwlab-cloud-api-${profile}-db`;
|
||||
if (secretName === "hwlab-code-agent-provider") return `hwlab-${profile}-code-agent-provider`;
|
||||
if (secretName === "hwlab-code-agent-codex-auth") return `hwlab-${profile}-code-agent-codex-auth`;
|
||||
if (secretName === "hwlab-bootstrap-admin") return `hwlab-${profile}-bootstrap-admin`;
|
||||
return secretName;
|
||||
}
|
||||
|
||||
function rewritePodSecretRefs(podSpec, profile) {
|
||||
if (!podSpec || profile !== "v02") return;
|
||||
if (!podSpec || !isRuntimeLane(profile)) return;
|
||||
for (const volume of podSpec.volumes ?? []) {
|
||||
if (volume?.secret?.secretName) volume.secret.secretName = secretNameForProfile(volume.secret.secretName, profile);
|
||||
}
|
||||
@@ -897,8 +895,8 @@ function deployServicesForProfile(deploy, profile) {
|
||||
const services = new Map((deploy.services ?? [])
|
||||
.filter((service) => allowed.has(service.serviceId))
|
||||
.map((service) => [service.serviceId, cloneJson(service)]));
|
||||
const laneServices = profile === "v02" && Array.isArray(deploy.lanes?.v02?.services)
|
||||
? deploy.lanes.v02.services
|
||||
const laneServices = isRuntimeLane(profile) && Array.isArray(deploy.lanes?.[profile]?.services)
|
||||
? deploy.lanes[profile].services
|
||||
: [];
|
||||
for (const override of laneServices) {
|
||||
const existing = services.get(override.serviceId) ?? { serviceId: override.serviceId };
|
||||
@@ -911,6 +909,12 @@ function deployServicesForProfile(deploy, profile) {
|
||||
return services;
|
||||
}
|
||||
|
||||
function runtimeNodeIdForProfile(deploy, profile, fallback = "node") {
|
||||
if (!isRuntimeLane(profile)) return fallback;
|
||||
const nodeId = deploy?.lanes?.[profile]?.node;
|
||||
return typeof nodeId === "string" && nodeId.length > 0 ? nodeId : fallback;
|
||||
}
|
||||
|
||||
function v02MetricsLabels(serviceId) {
|
||||
return {
|
||||
"hwlab.pikastech.local/monitoring": "enabled",
|
||||
@@ -978,25 +982,26 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const profileLabel = runtimeLabelForProfile(profile);
|
||||
const gitopsTarget = gitopsTargetForProfile(profile);
|
||||
const digestPin = profile === "v02";
|
||||
const runtimeLane = isRuntimeLane(profile);
|
||||
const digestPin = runtimeLane;
|
||||
const envReuseServiceIds = envReuseServiceIdsForLane(deploy, profile);
|
||||
const v02ObservableServiceIds = profile === "v02" ? v02ObservableServiceIdsForDeploy(deploy) : new Set();
|
||||
const runtimeObservableServiceIds = runtimeLane ? runtimeLaneObservableServiceIdsForDeploy(deploy, profile) : new Set();
|
||||
const stableRuntimeLabels = {
|
||||
"app.kubernetes.io/part-of": "hwlab",
|
||||
"hwlab.pikastech.local/environment": profileLabel,
|
||||
"hwlab.pikastech.local/profile": profileLabel
|
||||
};
|
||||
result.items = asItems(result, "workloads").filter((item) => !(profile === "v02" && isV02RemovedServiceId(serviceIdForWorkload(item, null))));
|
||||
result.items = asItems(result, "workloads").filter((item) => !(runtimeLane && isV02RemovedServiceId(serviceIdForWorkload(item, null))));
|
||||
for (const item of asItems(result, "workloads")) {
|
||||
item.metadata.namespace = namespace;
|
||||
label(item.metadata, {
|
||||
...stableRuntimeLabels,
|
||||
"hwlab.pikastech.local/gitops-target": gitopsTarget,
|
||||
"hwlab.pikastech.local/source-commit": source.full,
|
||||
"unidesk.ai/g14-staging": profile === "dev" ? "true" : "false"
|
||||
"unidesk.ai/node-staging": profile === "dev" ? "true" : "false"
|
||||
});
|
||||
annotate(item.metadata, {
|
||||
"hwlab.pikastech.local/gitops-note": profile === "v02" ? "HWLAB v0.2 GitOps target." : profile === "prod" ? "G14 PROD GitOps target." : "G14 DEV GitOps target.",
|
||||
"hwlab.pikastech.local/gitops-note": profile === "v02" ? "HWLAB v0.2 GitOps target." : profile === "prod" ? "node PROD GitOps target." : "node DEV GitOps target.",
|
||||
"hwlab.pikastech.local/source-commit": source.full
|
||||
});
|
||||
if (item.kind === "Job") {
|
||||
@@ -1013,7 +1018,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
if (!podTemplate?.spec?.containers) continue;
|
||||
const templateServiceId = serviceIdForWorkload(item, podTemplate.spec.containers[0]);
|
||||
const templateArtifact = runtimeArtifactForService({ catalog, deploy, serviceId: templateServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
||||
const templateSourceCommit = profile === "v02" ? (templateArtifact.commit ?? source.full) : source.full;
|
||||
const templateSourceCommit = runtimeLane ? (templateArtifact.commit ?? source.full) : source.full;
|
||||
const templateArtifactCommit = templateArtifact.commit;
|
||||
label(podTemplate.metadata ??= {}, {
|
||||
...stableRuntimeLabels,
|
||||
@@ -1021,7 +1026,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
"hwlab.pikastech.local/source-commit": templateSourceCommit,
|
||||
"hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit
|
||||
});
|
||||
if (profile === "v02" && v02ObservableServiceIds.has(templateServiceId)) {
|
||||
if (runtimeLane && runtimeObservableServiceIds.has(templateServiceId)) {
|
||||
label(item.metadata, v02MetricsLabels(templateServiceId));
|
||||
label(podTemplate.metadata, v02MetricsLabels(templateServiceId));
|
||||
annotate(podTemplate.metadata, v02MetricsSidecarAnnotations(metricsSidecarSha256));
|
||||
@@ -1036,7 +1041,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
annotate(podTemplate.metadata, {
|
||||
"hwlab.pikastech.local/source-commit": templateSourceCommit,
|
||||
"hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit,
|
||||
"hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs"
|
||||
"hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs"
|
||||
});
|
||||
for (const container of podTemplate.spec.containers) {
|
||||
if (container.name === "hwlab-metrics") continue;
|
||||
@@ -1044,8 +1049,8 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
const deployService = deployServices.get(serviceId);
|
||||
if (!deployService) continue;
|
||||
const artifact = runtimeArtifactForService({ catalog, deploy, serviceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
||||
const envReuse = profile === "v02" && v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds);
|
||||
const bootMetadata = envReuse ? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.v02?.sourceRepo || defaultSourceRepo, registryPrefix }, catalog, deployService, serviceId, source }) : null;
|
||||
const envReuse = runtimeLane && v02EnvReuseEnabled(catalog, serviceId, envReuseServiceIds);
|
||||
const bootMetadata = envReuse ? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.[profile]?.sourceRepo || defaultSourceRepo, registryPrefix }, catalog, deployService, serviceId, source }) : null;
|
||||
const image = artifact.image;
|
||||
const runtimeCommit = artifact.commit;
|
||||
const runtimeImageTag = artifact.imageTag;
|
||||
@@ -1056,7 +1061,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
if (Object.hasOwn(entry, "value")) entry.value = namespaceScopedHost(entry.value, namespace);
|
||||
}
|
||||
applyDeployEnv(container.env, deployService, namespace, profile);
|
||||
if (profile === "v02") container.env = removeV02LegacySimulatorEnv(container.env);
|
||||
if (runtimeLane) container.env = removeV02LegacySimulatorEnv(container.env);
|
||||
upsertEnv(container.env, "HWLAB_ENVIRONMENT", profileLabel);
|
||||
upsertEnv(container.env, "HWLAB_COMMIT_ID", runtimeCommit);
|
||||
upsertEnv(container.env, "HWLAB_IMAGE", image);
|
||||
@@ -1066,8 +1071,8 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
}
|
||||
upsertEnv(container.env, "HWLAB_GITOPS_TARGET", gitopsTarget);
|
||||
upsertEnv(container.env, "HWLAB_GITOPS_PROFILE", profileLabel);
|
||||
upsertEnv(container.env, "HWLAB_GITOPS_SOURCE_COMMIT", profile === "v02" ? runtimeCommit : source.full);
|
||||
if (profile === "v02" && serviceId === "hwlab-cloud-api") {
|
||||
upsertEnv(container.env, "HWLAB_GITOPS_SOURCE_COMMIT", runtimeLane ? runtimeCommit : source.full);
|
||||
if (runtimeLane && serviceId === "hwlab-cloud-api") {
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT", runtimeCommit);
|
||||
}
|
||||
if (bootMetadata) {
|
||||
@@ -1080,32 +1085,17 @@ function transformWorkloads({ workloads, deploy, catalog, source, sourceBranch =
|
||||
if (serviceId === "hwlab-cloud-api" || serviceId === "hwlab-edge-proxy") {
|
||||
upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", runtimeEndpoint);
|
||||
}
|
||||
if (profile === "v02" && serviceId === "hwlab-edge-proxy") {
|
||||
if (runtimeLane && serviceId === "hwlab-edge-proxy") {
|
||||
useLocalHealthProbe(container, "/health");
|
||||
}
|
||||
if (serviceId === "hwlab-cloud-api") {
|
||||
if (profile === "v02") {
|
||||
if (runtimeLane) {
|
||||
removeV02RepoOwnedCodeAgentPodConfig(podTemplate.spec);
|
||||
upsertEnv(container.env, "HWLAB_M3_IO_CONTROL_ENABLED", "false");
|
||||
upsertEnv(container.env, "HWLAB_ACCESS_CONTROL_REQUIRED", "1");
|
||||
upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_ID", "usr_v02_admin");
|
||||
upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_USERNAME", "admin");
|
||||
upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_DISPLAY_NAME", "HWLAB v0.2 Admin");
|
||||
upsertEnvEntry(container.env, deployEnvEntry("HWLAB_BOOTSTRAP_ADMIN_PASSWORD_HASH", "secretRef:hwlab-v02-bootstrap-admin/password-hash", namespace, profile));
|
||||
upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_API_KEY_ID", "key_master_server_admin");
|
||||
upsertEnvEntry(container.env, deployEnvEntry("HWLAB_BOOTSTRAP_ADMIN_API_KEY", "secretRef:hwlab-v02-master-server-admin-api-key/api-key", namespace, profile));
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID", runtimeNodeIdForProfile(deploy, profile));
|
||||
upsertEnv(container.env, "HWLAB_OPENFGA_MODE", "enforce");
|
||||
upsertEnv(container.env, "HWLAB_OPENFGA_API_URL", "http://hwlab-openfga.hwlab-v02.svc.cluster.local:8080");
|
||||
upsertEnvEntry(container.env, deployEnvEntry("HWLAB_OPENFGA_AUTHN_TOKEN", "secretRef:hwlab-v02-openfga/authn-preshared-key", namespace, profile));
|
||||
upsertEnv(container.env, "HWLAB_KEYCLOAK_ISSUER", "https://auth.74-48-78-17.nip.io/realms/hwlab");
|
||||
upsertEnv(container.env, "HWLAB_KEYCLOAK_CLIENT_ID", "hwlab-cloud-web");
|
||||
upsertEnvEntry(container.env, deployEnvEntry("HWLAB_KEYCLOAK_CLIENT_SECRET", "secretRef:hwlab-cloud-web-client/client-secret", namespace, profile));
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_ADAPTER", "agentrun-v01");
|
||||
upsertEnv(container.env, "AGENTRUN_MGR_URL", "http://agentrun-mgr.agentrun-v01.svc.cluster.local:8080");
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID", "G14");
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_RUNNER_NAMESPACE", "agentrun-v01");
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_SECRET_NAMESPACE", "agentrun-v01");
|
||||
upsertEnv(container.env, "HWLAB_CODE_AGENT_AGENTRUN_REPO_URL", "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git");
|
||||
upsertEnv(container.env, "HWLAB_OPENFGA_API_URL", `http://hwlab-openfga.${namespace}.svc.cluster.local:8080`);
|
||||
upsertEnvEntry(container.env, deployEnvEntry("HWLAB_OPENFGA_AUTHN_TOKEN", `secretRef:hwlab-${profile}-openfga/authn-preshared-key`, namespace, profile));
|
||||
upsertEnv(container.env, "UNIDESK_MAIN_SERVER_IP", "http://74.48.78.17:18081");
|
||||
} else {
|
||||
syncCodeAgentWorkspaceInitContainer(podTemplate.spec.initContainers, { image, runtimeCommit, runtimeImageTag });
|
||||
@@ -1150,7 +1140,7 @@ function transformHealthContract(value, namespace, labels, annotations, runtimeE
|
||||
result.data["cloud-api"] = `GET /health/live through ${runtimeEndpoint}`;
|
||||
}
|
||||
if (Object.hasOwn(result.data, "tunnel-client")) {
|
||||
result.data["tunnel-client"] = "disabled for G14 GitOps; no FRP or production traffic tunnel is required";
|
||||
result.data["tunnel-client"] = "disabled for node GitOps; no FRP or production traffic tunnel is required";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -1278,7 +1268,7 @@ ci_timing_emit() {
|
||||
finished_ms="$(ci_now_ms)"
|
||||
duration_ms="$((finished_ms - started_ms))"
|
||||
service_id="\${HWLAB_TIMING_SERVICE_ID:-}"
|
||||
node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:"g14-cicd-timing",schemaVersion:"v1",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:"scripts/g14-gitops-render.mjs",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' "$stage" "$status" "$duration_ms" "$service_id"
|
||||
node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:"node-cicd-timing",schemaVersion:"v1",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:"scripts/gitops-render.mjs",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' "$stage" "$status" "$duration_ms" "$service_id"
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -1344,7 +1334,7 @@ const timeout = setTimeout(() => {
|
||||
}, 15000);
|
||||
|
||||
socket.on("connect", () => {
|
||||
socket.write("GET " + target.href + " HTTP/1.1\\r\\nHost: " + target.host + "\\r\\nUser-Agent: hwlab-g14-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n");
|
||||
socket.write("GET " + target.href + " HTTP/1.1\\r\\nHost: " + target.host + "\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n");
|
||||
});
|
||||
|
||||
socket.on("data", (chunk) => {
|
||||
@@ -1601,7 +1591,7 @@ function prepareSourceScript() {
|
||||
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seeded-v02-skeleton\"}'",
|
||||
"fi",
|
||||
"ci_timing_emit catalog-fetch succeeded \"$catalog_fetch_started_ms\"",
|
||||
`echo ${shellSingleQuote(`G14 prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}`
|
||||
`echo ${shellSingleQuote(`node prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}`
|
||||
];
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
@@ -1656,7 +1646,7 @@ echo '{"event":"ci-base-image","phase":"plan-artifacts","image":"${ciToolsRunner
|
||||
for tool in node git; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"plan-artifacts","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
|
||||
cd /workspace/source/repo
|
||||
test "$(git rev-parse HEAD)" = "$(params.revision)"
|
||||
node scripts/g14-ci-plan.mjs --lane "$(params.lane)" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog "$(params.catalog-path)" --registry-prefix "$(params.registry-prefix)" --services "$(params.services)" > /workspace/source/ci-plan.json
|
||||
node scripts/ci-plan.mjs --lane "$(params.lane)" --target-ref HEAD --deploy-config deploy/deploy.yaml --artifact-catalog "$(params.catalog-path)" --registry-prefix "$(params.registry-prefix)" --services "$(params.services)" > /workspace/source/ci-plan.json
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const plan = JSON.parse(fs.readFileSync("/workspace/source/ci-plan.json", "utf8"));
|
||||
@@ -1698,7 +1688,7 @@ fs.writeFileSync("/workspace/source/affected-services.json", JSON.stringify({
|
||||
services: plan.services || [],
|
||||
entries
|
||||
}, null, 2) + String.fromCharCode(10));
|
||||
console.log(JSON.stringify({ event: "g14-ci-plan", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], reusedServices: plan.reusedServices || [], buildSkippedCount: plan.buildSkippedCount || 0 }));
|
||||
console.log(JSON.stringify({ event: "ci-plan", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], reusedServices: plan.reusedServices || [], buildSkippedCount: plan.buildSkippedCount || 0 }));
|
||||
NODE
|
||||
`;
|
||||
}
|
||||
@@ -1772,7 +1762,7 @@ export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)-$(params.service-id
|
||||
export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton"
|
||||
export HWLAB_DEV_REGISTRY_PREFIX="$(params.registry-prefix)"
|
||||
export HWLAB_DEV_REGISTRY_K8S_NATIVE=1
|
||||
export HWLAB_G14_CICD_TIMING=1
|
||||
export HWLAB_NODE_CICD_TIMING=1
|
||||
export HWLAB_TEKTON_PIPELINERUN
|
||||
export HWLAB_TEKTON_TASKRUN
|
||||
export HWLAB_TEKTON_TASK
|
||||
@@ -1799,7 +1789,7 @@ if [ "$buildkit_ready" != "1" ]; then
|
||||
echo '{"event":"buildkit-sidecar-not-ready","serviceId":"'"$(params.service-id)"'","addr":"'"$HWLAB_BUILDKIT_ADDR"'"}' >&2
|
||||
exit 1
|
||||
fi
|
||||
node scripts/g14-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/service-results/$(params.service-id).json" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR"
|
||||
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/service-results/$(params.service-id).json" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR"
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1819,7 +1809,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)"
|
||||
node scripts/g14-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
|
||||
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
|
||||
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
|
||||
`;
|
||||
}
|
||||
@@ -1883,21 +1873,21 @@ check_source_head() {
|
||||
}
|
||||
|
||||
check_source_head before-render
|
||||
git config --global user.name "HWLAB G14 GitOps Bot"
|
||||
git config --global user.email "hwlab-g14-gitops-bot@users.noreply.github.com"
|
||||
git config --global user.name "HWLAB node GitOps Bot"
|
||||
git config --global user.email "hwlab-node-gitops-bot@users.noreply.github.com"
|
||||
catalog_path="$(params.catalog-path)"
|
||||
runtime_path="$(params.runtime-path)"
|
||||
|
||||
argo_hard_refresh_runtime_lane() {
|
||||
if [ "$runtime_lane" != "true" ]; then return 0; fi
|
||||
ARGO_APPLICATION="hwlab-g14-$lane" ARGO_FIELD_MANAGER="hwlab-$lane-gitops-promote" node <<'NODE'
|
||||
ARGO_APPLICATION="hwlab-node-$lane" ARGO_FIELD_MANAGER="hwlab-$lane-gitops-promote" node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const https = require("node:https");
|
||||
|
||||
const host = process.env.KUBERNETES_SERVICE_HOST;
|
||||
const port = process.env.KUBERNETES_SERVICE_PORT || "443";
|
||||
const startedAt = Date.now();
|
||||
const application = process.env.ARGO_APPLICATION || "hwlab-g14-v02";
|
||||
const application = process.env.ARGO_APPLICATION || "hwlab-node-v02";
|
||||
const fieldManager = process.env.ARGO_FIELD_MANAGER || "hwlab-v02-gitops-promote";
|
||||
const pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;
|
||||
const taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;
|
||||
@@ -1906,7 +1896,7 @@ const revision = process.env.HWLAB_SOURCE_REVISION || null;
|
||||
|
||||
function emit(payload) {
|
||||
console.log(JSON.stringify({
|
||||
event: "g14-cicd-timing",
|
||||
event: "node-cicd-timing",
|
||||
schemaVersion: "v1",
|
||||
stage: "argo-hard-refresh",
|
||||
pipelineRun,
|
||||
@@ -1914,7 +1904,7 @@ function emit(payload) {
|
||||
task,
|
||||
revision,
|
||||
application,
|
||||
source: "scripts/g14-gitops-render.mjs",
|
||||
source: "scripts/gitops-render.mjs",
|
||||
at: new Date().toISOString(),
|
||||
...payload
|
||||
}));
|
||||
@@ -1975,8 +1965,8 @@ if [ -s /workspace/source/dev-artifacts.json ]; then
|
||||
node scripts/refresh-artifact-catalog.mjs --lane "$lane" --catalog-path "$catalog_path" --deploy-config deploy/deploy.yaml --image-tag-mode "$(params.image-tag-mode)" --target-ref "$(params.revision)" --publish-report /workspace/source/dev-artifacts.json --write
|
||||
fi
|
||||
gitops_render_started_ms="$(ci_now_ms)"
|
||||
node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --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/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --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
|
||||
node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --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/run-bun.mjs scripts/gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --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
|
||||
ci_timing_emit gitops-render succeeded "$gitops_render_started_ms"
|
||||
check_source_head before-push
|
||||
workdir="$(mktemp -d)"
|
||||
@@ -1997,7 +1987,7 @@ if git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin "$(par
|
||||
mkdir -p "$old_runtime_snapshot"
|
||||
cp -a "$runtime_path"/. "$old_runtime_snapshot"/
|
||||
fi
|
||||
if [ "$runtime_lane" = "true" ]; then rm -rf "$runtime_path" "$catalog_path"; else rm -rf deploy/gitops/g14 "$catalog_path"; fi
|
||||
if [ "$runtime_lane" = "true" ]; then rm -rf "$runtime_path" "$catalog_path"; else rm -rf deploy/gitops/node "$catalog_path"; fi
|
||||
else
|
||||
git checkout --orphan "$(params.gitops-branch)"
|
||||
git rm -rf . >/dev/null 2>&1 || true
|
||||
@@ -2010,9 +2000,9 @@ if [ "$runtime_lane" = "true" ]; then
|
||||
git add "$catalog_path" "$runtime_path"
|
||||
else
|
||||
mkdir -p deploy/gitops
|
||||
cp -a /workspace/source/repo/deploy/gitops/g14 deploy/gitops/g14
|
||||
cp -a /workspace/source/repo/deploy/gitops/node deploy/gitops/node
|
||||
cp "/workspace/source/repo/$catalog_path" "$catalog_path"
|
||||
git add "$catalog_path" deploy/gitops/g14
|
||||
git add "$catalog_path" deploy/gitops/node
|
||||
fi
|
||||
if [ "$runtime_lane" = "true" ] && [ -s /workspace/source/affected-services.json ]; then
|
||||
runtime_noop_decision="$(node - "$runtime_path" "$old_runtime_snapshot" /workspace/source/affected-services.json <<'NODE'
|
||||
@@ -2116,7 +2106,7 @@ if git diff --cached --quiet; then
|
||||
fi
|
||||
short="$(printf '%.7s' "$(params.revision)")"
|
||||
gitops_commit_started_ms="$(ci_now_ms)"
|
||||
git commit -m "chore: promote G14 GitOps source $short"
|
||||
git commit -m "chore: promote node GitOps source $short"
|
||||
ci_timing_emit gitops-commit succeeded "$gitops_commit_started_ms"
|
||||
gitops_push_started_ms="$(ci_now_ms)"
|
||||
git_timed gitops-push 120 git push origin "HEAD:$(params.gitops-branch)"
|
||||
@@ -2142,8 +2132,8 @@ revision="$(git rev-parse HEAD)"
|
||||
: "\${LANE:?LANE is required}"
|
||||
: "\${CATALOG_PATH:?CATALOG_PATH is required}"
|
||||
: "\${IMAGE_TAG_MODE:?IMAGE_TAG_MODE is required}"
|
||||
node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX"
|
||||
node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" --check
|
||||
node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX"
|
||||
node scripts/run-bun.mjs scripts/gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" --check
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const https = require("node:https");
|
||||
@@ -2166,11 +2156,11 @@ const argoFiles = String(process.env.ARGO_FILES || "").split(",").map((item) =>
|
||||
const fieldManager = requiredEnv("FIELD_MANAGER");
|
||||
const eventName = requiredEnv("RECONCILE_EVENT");
|
||||
const manifestFiles = [
|
||||
"deploy/gitops/g14/" + tektonDir + "/rbac.yaml",
|
||||
"deploy/gitops/g14/" + tektonDir + "/pipeline.yaml",
|
||||
"deploy/gitops/node/" + tektonDir + "/rbac.yaml",
|
||||
"deploy/gitops/node/" + tektonDir + "/pipeline.yaml",
|
||||
...(process.env.LANE === "v02" ? [] : [
|
||||
"deploy/gitops/g14/" + tektonDir + "/poller.yaml",
|
||||
"deploy/gitops/g14/" + tektonDir + "/control-plane-reconciler.yaml"
|
||||
"deploy/gitops/node/" + tektonDir + "/poller.yaml",
|
||||
"deploy/gitops/node/" + tektonDir + "/control-plane-reconciler.yaml"
|
||||
]),
|
||||
...argoFiles
|
||||
];
|
||||
@@ -2280,7 +2270,7 @@ git remote set-url origin "$GIT_READ_URL"
|
||||
revision="$(git rev-parse HEAD)"
|
||||
subject="$(git log -1 --pretty=%s)"
|
||||
case "$subject" in
|
||||
"chore: promote G14 GitOps source "*)
|
||||
"chore: promote node GitOps source "*)
|
||||
echo "Skipping generated GitOps promotion commit $revision"
|
||||
exit 0
|
||||
;;
|
||||
@@ -2410,7 +2400,7 @@ NODE
|
||||
`;
|
||||
}
|
||||
|
||||
function ciLaneSettings(argsOrLane = "g14") {
|
||||
function ciLaneSettings(argsOrLane = "node") {
|
||||
const lane = typeof argsOrLane === "string" ? argsOrLane : argsOrLane.lane;
|
||||
if (isRuntimeLane(lane)) {
|
||||
const namespace = namespaceNameForProfile(lane);
|
||||
@@ -2430,14 +2420,14 @@ function ciLaneSettings(argsOrLane = "g14") {
|
||||
};
|
||||
}
|
||||
return {
|
||||
lane: "g14",
|
||||
lane: "node",
|
||||
profile: "dev",
|
||||
gitopsTarget: "g14",
|
||||
pipelineName: "hwlab-g14-ci-image-publish",
|
||||
pipelineRunPrefix: "hwlab-g14-ci-poll",
|
||||
gitopsTarget: "node",
|
||||
pipelineName: "hwlab-node-ci-image-publish",
|
||||
pipelineRunPrefix: "hwlab-node-ci-poll",
|
||||
serviceAccountName: "hwlab-tekton-runner",
|
||||
pollerName: "hwlab-g14-branch-poller",
|
||||
controlPlaneReconcilerName: "hwlab-g14-control-plane-reconciler",
|
||||
pollerName: "hwlab-node-branch-poller",
|
||||
controlPlaneReconcilerName: "hwlab-node-control-plane-reconciler",
|
||||
tektonDir: "tekton",
|
||||
catalogPath: "deploy/artifact-catalog.dev.json",
|
||||
imageTagMode: "short",
|
||||
@@ -2445,7 +2435,7 @@ function ciLaneSettings(argsOrLane = "g14") {
|
||||
};
|
||||
}
|
||||
|
||||
function tektonRbac(args = { lane: "g14" }) {
|
||||
function tektonRbac(args = { lane: "node" }) {
|
||||
const settings = ciLaneSettings(args);
|
||||
if (isRuntimeLane(settings.lane)) {
|
||||
const runtimeObserverName = `${settings.runtimeNamespace}-runtime-observer`;
|
||||
@@ -2521,7 +2511,7 @@ function tektonRbac(args = { lane: "g14" }) {
|
||||
{
|
||||
apiVersion: "rbac.authorization.k8s.io/v1",
|
||||
kind: "Role",
|
||||
metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" },
|
||||
metadata: { name: "hwlab-node-control-plane-reconciler", namespace: "hwlab-dev" },
|
||||
rules: [
|
||||
{ apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] }
|
||||
]
|
||||
@@ -2529,9 +2519,9 @@ function tektonRbac(args = { lane: "g14" }) {
|
||||
{
|
||||
apiVersion: "rbac.authorization.k8s.io/v1",
|
||||
kind: "RoleBinding",
|
||||
metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" },
|
||||
metadata: { name: "hwlab-node-control-plane-reconciler", namespace: "hwlab-dev" },
|
||||
subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }],
|
||||
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-g14-control-plane-reconciler" }
|
||||
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-node-control-plane-reconciler" }
|
||||
},
|
||||
{
|
||||
apiVersion: "rbac.authorization.k8s.io/v1",
|
||||
@@ -2683,7 +2673,7 @@ function planArtifactsTask({ serviceIds = defaultServiceIds } = {}) {
|
||||
{ name: "registry-prefix" },
|
||||
{ name: "services" }
|
||||
],
|
||||
results: serviceIds.map((serviceId) => ({ name: affectedResultName(serviceId), description: `${serviceId} changed according to g14-ci-plan` })),
|
||||
results: serviceIds.map((serviceId) => ({ name: affectedResultName(serviceId), description: `${serviceId} changed according to ci-plan` })),
|
||||
workspaces: [{ name: "source" }],
|
||||
steps: [{
|
||||
name: "plan",
|
||||
@@ -2808,13 +2798,13 @@ const progressEveryMs = 10000;
|
||||
|
||||
function emit(payload) {
|
||||
console.log(JSON.stringify({
|
||||
event: "g14-cicd-timing",
|
||||
event: "node-cicd-timing",
|
||||
schemaVersion: "v1",
|
||||
pipelineRun,
|
||||
taskRun,
|
||||
task,
|
||||
revision,
|
||||
source: "scripts/g14-gitops-render.mjs",
|
||||
source: "scripts/gitops-render.mjs",
|
||||
at: new Date().toISOString(),
|
||||
...payload
|
||||
}));
|
||||
@@ -3075,7 +3065,7 @@ function runtimeReadyTask({ profile = "dev" } = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function imagePublishTaskSet(args = { lane: "g14" }, deploy = null) {
|
||||
function imagePublishTaskSet(args = { lane: "node" }, deploy = null) {
|
||||
const serviceIds = serviceIdsForLane(args.lane, deploy);
|
||||
const buildTasks = serviceIds.map((serviceId) => perServiceBuildTask(serviceId, {
|
||||
runAfter: ["plan-artifacts"]
|
||||
@@ -3087,7 +3077,7 @@ function imagePublishTaskSet(args = { lane: "g14" }, deploy = null) {
|
||||
];
|
||||
}
|
||||
|
||||
function tektonPipeline(args = { lane: "g14" }, deploy = null) {
|
||||
function tektonPipeline(args = { lane: "node" }, deploy = null) {
|
||||
const settings = ciLaneSettings(args);
|
||||
const runtimePath = gitopsPathForProfile(args, settings.profile);
|
||||
return {
|
||||
@@ -3101,7 +3091,7 @@ function tektonPipeline(args = { lane: "g14" }, deploy = null) {
|
||||
"hwlab.pikastech.local/gitops-target": settings.gitopsTarget
|
||||
},
|
||||
annotations: {
|
||||
"hwlab.pikastech.local/source-config": "scripts/g14-gitops-render.mjs#tekton-native-primitive-ci",
|
||||
"hwlab.pikastech.local/source-config": "scripts/gitops-render.mjs#tekton-native-primitive-ci",
|
||||
"hwlab.pikastech.local/ci-contract": "tekton-native-primitive-tasks",
|
||||
"hwlab.pikastech.local/policy": "native-per-service-taskrun-image-publish"
|
||||
}
|
||||
@@ -3349,7 +3339,7 @@ function tektonControlPlaneReconcilerCronJob(args) {
|
||||
annotations: {
|
||||
"hwlab.pikastech.local/source-branch": args.sourceBranch,
|
||||
"hwlab.pikastech.local/gitops-branch": args.gitopsBranch,
|
||||
"hwlab.pikastech.local/purpose": "render-and-apply-g14-ci-control-plane"
|
||||
"hwlab.pikastech.local/purpose": "render-and-apply-node-ci-control-plane"
|
||||
}
|
||||
},
|
||||
spec: {
|
||||
@@ -4115,7 +4105,7 @@ function contentType(filePath) {
|
||||
};
|
||||
}
|
||||
|
||||
function argoProject(args = { lane: "g14" }) {
|
||||
function argoProject(args = { lane: "node" }) {
|
||||
if (isRuntimeLane(args.lane)) {
|
||||
const namespace = namespaceNameForProfile(args.lane);
|
||||
return {
|
||||
@@ -4123,7 +4113,7 @@ function argoProject(args = { lane: "g14" }) {
|
||||
kind: "AppProject",
|
||||
metadata: { name: namespace, namespace: "argocd" },
|
||||
spec: {
|
||||
description: `HWLAB ${versionNameForRuntimeLane(args.lane)} additive GitOps project on G14; DEV/PROD remain outside this lane.`,
|
||||
description: `HWLAB ${versionNameForRuntimeLane(args.lane)} additive GitOps project on target node; DEV/PROD remain outside this lane.`,
|
||||
sourceRepos: [args.gitReadUrl],
|
||||
destinations: [{ server: "https://kubernetes.default.svc", namespace }],
|
||||
clusterResourceWhitelist: [{ group: "", kind: "Namespace" }],
|
||||
@@ -4134,9 +4124,9 @@ function argoProject(args = { lane: "g14" }) {
|
||||
return {
|
||||
apiVersion: "argoproj.io/v1alpha1",
|
||||
kind: "AppProject",
|
||||
metadata: { name: "hwlab-g14", namespace: "argocd" },
|
||||
metadata: { name: "hwlab-node", namespace: "argocd" },
|
||||
spec: {
|
||||
description: "HWLAB G14 GitOps project; D601 remains outside this project.",
|
||||
description: "HWLAB node GitOps project; D601 remains outside this project.",
|
||||
sourceRepos: [defaultSourceRepo],
|
||||
destinations: [
|
||||
{ server: "https://kubernetes.default.svc", namespace: "hwlab-dev" },
|
||||
@@ -4152,7 +4142,7 @@ function argoApplication(args, profile = "dev") {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const runtimePath = runtimePathForProfile(profile);
|
||||
const gitopsTarget = gitopsTargetForProfile(profile);
|
||||
const project = isRuntimeLane(profile) ? namespace : "hwlab-g14";
|
||||
const project = isRuntimeLane(profile) ? namespace : "hwlab-node";
|
||||
const repoURL = isRuntimeLane(profile) ? args.gitReadUrl : args.sourceRepo;
|
||||
return {
|
||||
apiVersion: "argoproj.io/v1alpha1",
|
||||
@@ -4171,12 +4161,12 @@ function argoApplication(args, profile = "dev") {
|
||||
};
|
||||
}
|
||||
|
||||
function g14FrpcManifest({ profile = "dev", source = null } = {}) {
|
||||
function nodeFrpcManifest({ profile = "dev", source = null } = {}) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const profileLabel = runtimeLabelForProfile(profile);
|
||||
const deploymentName = isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc";
|
||||
const deploymentName = isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc";
|
||||
const configName = `${deploymentName}-config`;
|
||||
const proxyPrefix = isRuntimeLane(profile) ? `hwlab-${profile}` : profile === "prod" ? "hwlab-g14-prod" : "hwlab-g14";
|
||||
const proxyPrefix = isRuntimeLane(profile) ? `hwlab-${profile}` : profile === "prod" ? "hwlab-node-prod" : "hwlab-node";
|
||||
const webRemotePort = isRuntimeLane(profile) ? defaultPortForRuntimeLane(profile) : profile === "prod" ? 18666 : 17666;
|
||||
const edgeRemotePort = isRuntimeLane(profile) ? webRemotePort + 1 : profile === "prod" ? 18667 : 17667;
|
||||
const labels = {
|
||||
@@ -4260,16 +4250,17 @@ remotePort = ${edgeRemotePort}
|
||||
};
|
||||
}
|
||||
|
||||
function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14", sourceRepo = defaultSourceRepo, deploy = null, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) {
|
||||
function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = defaultBranch, sourceRepo = defaultSourceRepo, deploy = null, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) {
|
||||
const namespace = namespaceNameForProfile(profile);
|
||||
const bridgeServiceId = "hwlab-cloud-api";
|
||||
const digestPin = profile === "v02";
|
||||
const envReuseServiceIds = profile === "v02" ? new Set([bridgeServiceId]) : null;
|
||||
const runtimeLane = isRuntimeLane(profile);
|
||||
const digestPin = runtimeLane;
|
||||
const envReuseServiceIds = runtimeLane ? envReuseServiceIdsForLane(deploy, profile) : null;
|
||||
const bridgeImage = runtimeImageForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
||||
const bridgeCommit = runtimeCommitForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
||||
const bridgeImageTag = runtimeImageTagForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
||||
const bridgeBootMetadata = profile === "v02" && v02EnvReuseEnabled(catalog, bridgeServiceId, envReuseServiceIds)
|
||||
? bootMetadataForService({ args: { sourceRepo, registryPrefix }, catalog, deployService: deployServiceForBoot(deploy, bridgeServiceId), serviceId: bridgeServiceId, source })
|
||||
const bridgeBootMetadata = runtimeLane && v02EnvReuseEnabled(catalog, bridgeServiceId, envReuseServiceIds)
|
||||
? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.[profile]?.sourceRepo || sourceRepo, registryPrefix }, catalog, deployService: deployServiceForBoot(deploy, bridgeServiceId, profile), serviceId: bridgeServiceId, source })
|
||||
: null;
|
||||
const labels = {
|
||||
"app.kubernetes.io/name": "hwlab-deepseek-proxy",
|
||||
@@ -4279,7 +4270,7 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14",
|
||||
"hwlab.pikastech.local/service-id": "hwlab-deepseek-proxy",
|
||||
"hwlab.pikastech.local/source-commit": source.full
|
||||
};
|
||||
if (profile === "v02") Object.assign(labels, v02MetricsLabels("hwlab-deepseek-proxy"));
|
||||
if (runtimeLane) Object.assign(labels, v02MetricsLabels("hwlab-deepseek-proxy"));
|
||||
const templateLabels = {
|
||||
...labels,
|
||||
"hwlab.pikastech.local/source-commit": bridgeCommit,
|
||||
@@ -4291,7 +4282,7 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14",
|
||||
"hwlab.pikastech.local/bridge-source-commit": bridgeCommit,
|
||||
"hwlab.pikastech.local/bridge-image-tag": bridgeImageTag,
|
||||
"hwlab.pikastech.local/bridge-image": bridgeImage,
|
||||
...v02MetricsSidecarAnnotations(profile === "v02" ? metricsSidecarSha256 : null)
|
||||
...v02MetricsSidecarAnnotations(runtimeLane ? metricsSidecarSha256 : null)
|
||||
};
|
||||
const responsesBridgeContainer = {
|
||||
name: "responses-bridge",
|
||||
@@ -4372,12 +4363,12 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14",
|
||||
{ name: "moonbridge-config", mountPath: "/config", readOnly: true },
|
||||
{ name: "moonbridge-data", mountPath: "/data" }
|
||||
]
|
||||
}, ...(profile === "v02" ? [v02MetricsSidecarContainer({ deploy, serviceId: "hwlab-deepseek-proxy", namespace, gitopsTarget: gitopsTargetForProfile(profile) })] : [])],
|
||||
}, ...(runtimeLane ? [v02MetricsSidecarContainer({ deploy, serviceId: "hwlab-deepseek-proxy", namespace, gitopsTarget: gitopsTargetForProfile(profile) })] : [])],
|
||||
volumes: [
|
||||
{ name: "moonbridge-scripts", configMap: { name: "hwlab-deepseek-proxy-config" } },
|
||||
{ name: "moonbridge-config", emptyDir: {} },
|
||||
{ name: "moonbridge-data", emptyDir: {} },
|
||||
...(profile === "v02" ? [v02MetricsSidecarVolume()] : [])
|
||||
...(runtimeLane ? [v02MetricsSidecarVolume()] : [])
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4387,7 +4378,7 @@ function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = "G14",
|
||||
apiVersion: "v1",
|
||||
kind: "Service",
|
||||
metadata: { name: "hwlab-deepseek-proxy", namespace, labels },
|
||||
spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }, ...(profile === "v02" ? [{ name: "metrics", port: 9100, targetPort: "metrics" }] : [])] }
|
||||
spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }, ...(runtimeLane ? [{ name: "metrics", port: 9100, targetPort: "metrics" }] : [])] }
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -4620,12 +4611,12 @@ function deviceAgent71FreqManifest({ profile = "dev", source, registryPrefix, ca
|
||||
"hwlab.pikastech.local/component": "device-agent",
|
||||
"hwlab.pikastech.local/device-id": "71-freq",
|
||||
"hwlab.pikastech.local/environment": runtimeLabelForProfile(profile),
|
||||
"hwlab.pikastech.local/gitops-target": "g14",
|
||||
"hwlab.pikastech.local/gitops-target": "node",
|
||||
"hwlab.pikastech.local/profile": runtimeLabelForProfile(profile),
|
||||
"hwlab.pikastech.local/service-id": name,
|
||||
"hwlab.pikastech.local/source-commit": source.full
|
||||
};
|
||||
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" };
|
||||
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
|
||||
const selector = { "app.kubernetes.io/name": name };
|
||||
const script = deviceAgent71FreqServerScript();
|
||||
const scriptSha256 = createHash("sha256").update(script).digest("hex");
|
||||
@@ -4795,7 +4786,7 @@ function v02OpenFgaManifest({ profile = "v02", source }) {
|
||||
"hwlab.pikastech.local/service-id": name,
|
||||
"hwlab.pikastech.local/source-commit": source.full
|
||||
};
|
||||
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" };
|
||||
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
|
||||
const selector = { "app.kubernetes.io/name": name };
|
||||
const env = [
|
||||
{ name: "OPENFGA_DATASTORE_ENGINE", value: "postgres" },
|
||||
@@ -4877,7 +4868,7 @@ function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile ==
|
||||
if (!isRuntimeLane(profile)) resources.splice(1, 0, "code-agent-codex-config.yaml");
|
||||
if (isRuntimeLane(profile)) resources.push("postgres.yaml", "openfga.yaml", "observability.yaml");
|
||||
if (includeDeviceAgent) resources.push("device-agent-71-freq.yaml");
|
||||
resources.push("g14-frpc.yaml");
|
||||
resources.push("node-frpc.yaml");
|
||||
return {
|
||||
apiVersion: "kustomize.config.k8s.io/v1beta1",
|
||||
kind: "Kustomization",
|
||||
@@ -4890,33 +4881,33 @@ function readme({ source, args }) {
|
||||
if (args.lane === "v02") {
|
||||
return `# HWLAB v0.2 GitOps CI/CD
|
||||
|
||||
This directory is generated by \`scripts/g14-gitops-render.mjs --lane v02\` from source branch \`${args.sourceBranch}\`.
|
||||
This directory is generated by \`scripts/gitops-render.mjs --lane v02\` from source branch \`${args.sourceBranch}\`.
|
||||
|
||||
- Target: additive G14 v0.2 lane only.
|
||||
- Target: additive node-scoped v0.2 lane only.
|
||||
- CI: Tekton Pipeline \`hwlab-ci/hwlab-v02-ci-image-publish\`.
|
||||
- Git mirror/relay: v0.2 CI reads source/catalog from \`${args.gitReadUrl}\` and writes GitOps promotion to \`${args.gitWriteUrl}\`; canonical repo \`${args.sourceRepo}\` remains the source identity and manual flush upstream.
|
||||
- Artifact contract: runtime images come from \`${args.catalogPath}\`; source branch \`v0.2\` does not track this generated catalog.
|
||||
- Manual trigger: run \`bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm\` from UniDesk to create commit-pinned PipelineRun \`hwlab-v02-ci-poll-<short12>\` for the current \`origin/v0.2\` commit.
|
||||
- Manual trigger: run \`bun scripts/cli.ts hwlab node control-plane trigger-current --lane v02 --confirm\` from UniDesk to create commit-pinned PipelineRun \`hwlab-v02-ci-poll-<short12>\` for the current \`origin/v0.2\` commit.
|
||||
- Branch split: source branch \`${args.sourceBranch}\` remains human-authored source; generated desired state is promoted to local mirror branch \`${args.gitopsBranch}\`.
|
||||
- CD: Argo CD Application \`argocd/hwlab-g14-v02\` consumes \`${args.gitReadUrl}:${args.gitopsBranch}:deploy/gitops/g14/runtime-v02\` and deploys only to namespace \`hwlab-v02\`.
|
||||
- CD: Argo CD Application \`argocd/hwlab-node-v02\` consumes \`${args.gitReadUrl}:${args.gitopsBranch}:deploy/gitops/node/runtime-v02\` and deploys only to namespace \`hwlab-v02\`.
|
||||
- Public preview: FRP exposes v0.2 web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-v02/hwlab-v02-frpc\`.
|
||||
- G14 DEV/PROD boundary: this lane does not modify \`G14\`, \`G14-gitops\`, \`hwlab-dev\`, \`hwlab-prod\`, 17666/17667 or 18666/18667.
|
||||
- node DEV/PROD boundary: this lane does not modify archived DEV/PROD branches, \`hwlab-dev\`, \`hwlab-prod\`, 17666/17667 or 18666/18667.
|
||||
- CronJob policy: v0.2 does not generate \`hwlab-v02-branch-poller\` or \`hwlab-v02-control-plane-reconciler\`; obsolete live CronJobs should be deleted by the UniDesk control-plane apply command.
|
||||
- Gate policy: old DEV/D601/main gates do not enter this lane; new checks stay limited to the fixed branch, namespace, catalog, runtime path and Argo boundaries.
|
||||
`;
|
||||
}
|
||||
return `# HWLAB G14 GitOps CI/CD
|
||||
return `# HWLAB node GitOps CI/CD
|
||||
|
||||
This directory is generated from built-in Tekton-native CI primitive declarations plus human-authored \`deploy/deploy.yaml\`, CI-authored \`deploy/artifact-catalog.dev.json\`, and \`deploy/k8s/*\` by \`scripts/g14-gitops-render.mjs\`.
|
||||
This directory is generated from built-in Tekton-native CI primitive declarations plus human-authored \`deploy/deploy.yaml\`, CI-authored \`deploy/artifact-catalog.dev.json\`, and \`deploy/k8s/*\` by \`scripts/gitops-render.mjs\`.
|
||||
|
||||
- Target: G14 k3s only.
|
||||
- CI: Tekton Pipeline \`hwlab-ci/hwlab-g14-ci-image-publish\`.
|
||||
- Target: node k3s only.
|
||||
- CI: Tekton Pipeline \`hwlab-ci/hwlab-node-ci-image-publish\`.
|
||||
- Artifact contract: runtime images come from \`deploy/artifact-catalog.dev.json\`; \`deploy/deploy.yaml\` only carries human-authored config such as env and topology.
|
||||
- Branch split: source branch \`${args.sourceBranch}\` remains the watched code branch; generated desired state is promoted to \`${args.gitopsBranch}\`.
|
||||
- CD: Argo CD Applications \`argocd/hwlab-g14-dev\` and \`argocd/hwlab-g14-prod\` consume \`${args.gitopsBranch}:deploy/gitops/g14/runtime-dev\` and \`runtime-prod\`.
|
||||
- Public preview: FRP exposes G14 DEV web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-dev/hwlab-g14-frpc\`, and G14 PROD web \`${args.prodWebEndpoint}\` / edge \`${args.prodRuntimeEndpoint}\` through \`hwlab-prod/hwlab-g14-prod-frpc\`; D601 keeps \`:16666/:16667\`.
|
||||
- Manual polling: run \`kubectl -n hwlab-ci create job --from=cronjob/hwlab-g14-branch-poller hwlab-g14-branch-poller-manual-$(date -u +%Y%m%d%H%M%S)\`; this reuses the same poller path and creates a commit-pinned Tekton PipelineRun only when needed.
|
||||
- Control-plane reconcile: CronJob \`hwlab-ci/hwlab-g14-control-plane-reconciler\` polls \`${args.sourceBranch}\`, runs \`scripts/g14-gitops-render.mjs\`, and server-side-applies the generated Tekton RBAC/Pipeline/Poller/Reconciler manifests so CI control-plane changes do not require a manual render/apply step.
|
||||
- CD: Argo CD Applications \`argocd/hwlab-node-dev\` and \`argocd/hwlab-node-prod\` consume \`${args.gitopsBranch}:deploy/gitops/node/runtime-dev\` and \`runtime-prod\`.
|
||||
- Public preview: FRP exposes node DEV web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-dev/hwlab-node-frpc\`, and node PROD web \`${args.prodWebEndpoint}\` / edge \`${args.prodRuntimeEndpoint}\` through \`hwlab-prod/hwlab-node-prod-frpc\`; D601 keeps \`:16666/:16667\`.
|
||||
- Manual polling: run \`kubectl -n hwlab-ci create job --from=cronjob/hwlab-node-branch-poller hwlab-node-branch-poller-manual-$(date -u +%Y%m%d%H%M%S)\`; this reuses the same poller path and creates a commit-pinned Tekton PipelineRun only when needed.
|
||||
- Control-plane reconcile: CronJob \`hwlab-ci/hwlab-node-control-plane-reconciler\` polls \`${args.sourceBranch}\`, runs \`scripts/gitops-render.mjs\`, and server-side-applies the generated Tekton RBAC/Pipeline/Poller/Reconciler manifests so CI control-plane changes do not require a manual render/apply step.
|
||||
- D601 boundary: this path does not target D601, does not use UniDesk Code Queue, and does not change D601 production traffic.
|
||||
- Concurrency: CI builds are per source commit and do not acquire the legacy DEV CD Lease; Argo CD reconciles the Git desired state.
|
||||
`;
|
||||
@@ -4956,7 +4947,7 @@ async function plannedFiles(args) {
|
||||
: null;
|
||||
const metricsSidecarSha256 = metricsSidecarScript ? createHash("sha256").update(metricsSidecarScript).digest("hex") : null;
|
||||
const baseLabels = { "hwlab.pikastech.local/gitops-target": settings.gitopsTarget, "hwlab.pikastech.local/source-commit": source.full };
|
||||
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" };
|
||||
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
|
||||
const files = new Map();
|
||||
const putJson = (relative, value) => files.set(path.join(args.outDir, relative), jsonManifest(value));
|
||||
const putText = (relative, value) => files.set(path.join(args.outDir, relative), textFile(value));
|
||||
@@ -4966,7 +4957,7 @@ async function plannedFiles(args) {
|
||||
}));
|
||||
|
||||
const sourceDescriptor = {
|
||||
kind: isRuntimeLane(args.lane) ? `hwlab-${args.lane}-gitops-source` : "hwlab-g14-gitops-source",
|
||||
kind: isRuntimeLane(args.lane) ? `hwlab-${args.lane}-gitops-source` : "hwlab-node-gitops-source",
|
||||
sourceCommit: source.full,
|
||||
sourceShortCommit: source.short,
|
||||
sourceImageTag: source.imageTag,
|
||||
@@ -4979,7 +4970,7 @@ async function plannedFiles(args) {
|
||||
registryPrefix: args.registryPrefix,
|
||||
runtimePaths: profiles.map((profile) => gitopsPathForProfile(args, profile)),
|
||||
publicEndpoints,
|
||||
frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile)}/${isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc"}`])),
|
||||
frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile)}/${isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc"}`])),
|
||||
tektonPipeline: `hwlab-ci/${settings.pipelineName}`,
|
||||
argoApplications: profiles.map((profile) => `argocd/${argoApplicationName(profile)}`)
|
||||
};
|
||||
@@ -5025,7 +5016,7 @@ async function plannedFiles(args) {
|
||||
if (isRuntimeLane(profile)) putJson(`${runtimePath}/openfga.yaml`, v02OpenFgaManifest({ profile, source }));
|
||||
if (isRuntimeLane(profile)) putJson(`${runtimePath}/observability.yaml`, observabilityManifest({ deploy, profile, namespace, labels: profileLabels, annotations, metricsSidecarScript }));
|
||||
if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages }));
|
||||
putJson(`${runtimePath}/g14-frpc.yaml`, g14FrpcManifest({ profile, source }));
|
||||
putJson(`${runtimePath}/node-frpc.yaml`, nodeFrpcManifest({ profile, source }));
|
||||
}
|
||||
return { files, source };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
@@ -11,6 +11,28 @@ function taskByName(pipeline, name) {
|
||||
return task;
|
||||
}
|
||||
|
||||
async function collectGeneratedFiles(root) {
|
||||
const files = [];
|
||||
async function walk(dir) {
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) await walk(fullPath);
|
||||
else if (entry.isFile()) files.push(fullPath);
|
||||
}
|
||||
}
|
||||
await walk(root);
|
||||
return files.sort();
|
||||
}
|
||||
|
||||
function collectContainersFromItem(item) {
|
||||
const podSpec = item?.spec?.template?.spec ?? item?.spec?.jobTemplate?.spec?.template?.spec ?? item?.spec?.template?.spec;
|
||||
return [
|
||||
...(podSpec?.initContainers ?? []),
|
||||
...(podSpec?.containers ?? [])
|
||||
];
|
||||
}
|
||||
|
||||
function extractRuntimeNoopDecisionScript(gitopsPromoteScript) {
|
||||
const marker = `runtime_noop_decision="$(node - "$runtime_path" "$old_runtime_snapshot" /workspace/source/affected-services.json <<'NODE'\n`;
|
||||
const start = gitopsPromoteScript.indexOf(marker);
|
||||
@@ -91,7 +113,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
|
||||
|
||||
const render = spawnSync(process.execPath, [
|
||||
"scripts/run-bun.mjs",
|
||||
"scripts/g14-gitops-render.mjs",
|
||||
"scripts/gitops-render.mjs",
|
||||
"--lane", "v02",
|
||||
"--catalog-path", "deploy/artifact-catalog.v02.json",
|
||||
"--image-tag-mode", "full",
|
||||
@@ -130,7 +152,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
|
||||
assert.match(runtimeReadyScript, /if \(!observedServiceIds\) \{/u);
|
||||
assert.match(runtimeReadyScript, /const refresh = await waitForArgoRefresh\(\);\n if \(!refresh\.observed\) return;/u);
|
||||
assert.ok(runtimeReady.taskSpec.params.some((param) => param.name === "argo-application"));
|
||||
assert.ok(runtimeReady.params.some((param) => param.name === "argo-application" && param.value === "hwlab-g14-v02"));
|
||||
assert.ok(runtimeReady.params.some((param) => param.name === "argo-application" && param.value === "hwlab-node-v02"));
|
||||
assert.ok(runtimeReady.workspaces?.some((workspace) => workspace.name === "source" && workspace.workspace === "source"));
|
||||
assert.ok(runtimeReady.taskSpec.workspaces?.some((workspace) => workspace.name === "source"));
|
||||
|
||||
@@ -152,7 +174,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
|
||||
}
|
||||
}), "runtime-identity-only");
|
||||
assert.match(gitopsPromoteScript, /argo_hard_refresh_runtime_lane\(\) \{/u);
|
||||
assert.match(gitopsPromoteScript, /ARGO_APPLICATION="hwlab-g14-\$lane" ARGO_FIELD_MANAGER="hwlab-\$lane-gitops-promote"/u);
|
||||
assert.match(gitopsPromoteScript, /ARGO_APPLICATION="hwlab-node-\$lane" ARGO_FIELD_MANAGER="hwlab-\$lane-gitops-promote"/u);
|
||||
assert.match(gitopsPromoteScript, /argocd\.argoproj\.io\/refresh": "hard"/u);
|
||||
assert.match(gitopsPromoteScript, /applications\/" \+ encodeURIComponent\(application\) \+ "\?fieldManager=" \+ encodeURIComponent\(fieldManager\)/u);
|
||||
assert.match(gitopsPromoteScript, /BatchMode=yes/u);
|
||||
@@ -231,7 +253,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
|
||||
assert.match(gitMirrorScript, /refs\/mirror-stage\/heads\/v0\.3/u);
|
||||
assert.match(gitMirrorScript, /for gitops_branch in 'v0\.2-gitops' 'v0\.3-gitops'/u);
|
||||
assert.match(gitMirrorScript, /keeps local \$name ahead of GitHub/u);
|
||||
assert.match(gitMirror, /deploy\/artifact-catalog\.v03\.json\|deploy\/gitops\/g14\/runtime-v03\/\*/u);
|
||||
assert.match(gitMirror, /deploy\/artifact-catalog\.v03\.json\|deploy\/gitops\/node\/runtime-v03\/\*/u);
|
||||
assert.ok((gitMirrorJson.items || []).every((item) => item.kind !== "Secret"));
|
||||
assert.ok((gitMirrorJson.items || []).every((item) => item.kind !== "CronJob"));
|
||||
assert.ok((gitMirrorJson.items || []).every((item) => !item.data?.["ssh-privatekey"]));
|
||||
@@ -281,7 +303,7 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
|
||||
const collectScript = collectArtifacts.taskSpec.steps[0].script;
|
||||
assert.match(collectScript, /--external-service-report-dir \/workspace\/source\/service-results/u);
|
||||
const cloudWebBuildScript = taskByName(pipelineJson, "build-hwlab-cloud-web").taskSpec.steps.find((step) => step.name === "publish")?.script ?? "";
|
||||
assert.match(cloudWebBuildScript, /node scripts\/g14-artifact-publish\.mjs --publish/u);
|
||||
assert.match(cloudWebBuildScript, /node scripts\/artifact-publish\.mjs --publish/u);
|
||||
|
||||
const artifactPublishSource = await readFile("scripts/artifact-publish.mjs", "utf8");
|
||||
assert.match(artifactPublishSource, /\["run", "--cwd", "web\/hwlab-cloud-web", "check"\]/u);
|
||||
@@ -426,11 +448,11 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
|
||||
assert.match(application, /git-mirror-http\.devops-infra\.svc\.cluster\.local/u);
|
||||
assert.doesNotMatch(application, /git@github\.com:pikasTech\/HWLAB\.git/u);
|
||||
|
||||
for (const generatedFile of ["deepseek-proxy.yaml", "postgres.yaml", "openfga.yaml", "g14-frpc.yaml"]) {
|
||||
for (const generatedFile of ["deepseek-proxy.yaml", "postgres.yaml", "openfga.yaml", "node-frpc.yaml"]) {
|
||||
const content = await readFile(path.join(outDir, "runtime-v02", generatedFile), "utf8");
|
||||
assert.match(content, /"hwlab\.pikastech\.local\/source-commit"/u);
|
||||
}
|
||||
const frpc = JSON.parse(await readFile(path.join(outDir, "runtime-v02", "g14-frpc.yaml"), "utf8"));
|
||||
const frpc = JSON.parse(await readFile(path.join(outDir, "runtime-v02", "node-frpc.yaml"), "utf8"));
|
||||
const frpcDeployment = frpc.items.find((item) => item.kind === "Deployment");
|
||||
const frpcConfigLabel = frpcDeployment.spec.template.metadata.labels["hwlab.pikastech.local/config-sha256"];
|
||||
const frpcConfigAnnotation = frpcDeployment.spec.template.metadata.annotations["hwlab.pikastech.local/config-sha256"];
|
||||
@@ -485,3 +507,49 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
|
||||
await rm(outDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("v03 render keeps node identity as data instead of generated structure", async () => {
|
||||
const outDir = await mkdtemp(path.join(os.tmpdir(), "hwlab-v03-render-test-"));
|
||||
try {
|
||||
const render = spawnSync(process.execPath, [
|
||||
"scripts/run-bun.mjs",
|
||||
"scripts/gitops-render.mjs",
|
||||
"--lane", "v03",
|
||||
"--node", "G14",
|
||||
"--catalog-path", "deploy/artifact-catalog.v03.json",
|
||||
"--image-tag-mode", "full",
|
||||
"--source-branch", "v0.3",
|
||||
"--gitops-branch", "v0.3-gitops",
|
||||
"--out", outDir,
|
||||
"--source-revision", "HEAD"
|
||||
], { cwd: process.cwd(), encoding: "utf8" });
|
||||
assert.equal(render.status, 0, render.stderr || render.stdout);
|
||||
|
||||
const generatedFiles = await collectGeneratedFiles(outDir);
|
||||
const generatedPaths = generatedFiles.map((filePath) => path.relative(outDir, filePath));
|
||||
assert.ok(generatedPaths.includes("runtime-v03/node-frpc.yaml"));
|
||||
assert.ok(generatedPaths.includes("argocd/application-v03.yaml"));
|
||||
assert.ok(generatedPaths.includes("tekton-v03/pipeline.yaml"));
|
||||
for (const relativePath of generatedPaths) {
|
||||
assert.doesNotMatch(relativePath, /g14/i, `legacy node token leaked into generated path ${relativePath}`);
|
||||
}
|
||||
|
||||
const workloads = await readFile(path.join(outDir, "runtime-v03", "workloads.yaml"), "utf8");
|
||||
const workloadsJson = JSON.parse(workloads);
|
||||
const agentRunNodeEnv = (workloadsJson.items ?? [])
|
||||
.flatMap((item) => collectContainersFromItem(item))
|
||||
.flatMap((container) => container.env ?? [])
|
||||
.filter((entry) => entry.name === "HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID");
|
||||
assert.ok(agentRunNodeEnv.length > 0, "expected AgentRun provider id env to carry configured node id");
|
||||
assert.deepEqual([...new Set(agentRunNodeEnv.map((entry) => entry.value))], ["G14"]);
|
||||
|
||||
const allowedNodeValue = /"name":\s*"HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID"[\s\S]{0,120}"value":\s*"G14"/gu;
|
||||
for (const filePath of generatedFiles) {
|
||||
const text = await readFile(filePath, "utf8");
|
||||
const sanitized = text.replace(allowedNodeValue, "__CONFIGURED_NODE_ID__");
|
||||
assert.doesNotMatch(sanitized, /g14|G14/u, `legacy node token leaked outside node data in ${path.relative(outDir, filePath)}`);
|
||||
}
|
||||
} finally {
|
||||
await rm(outDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -38,9 +38,9 @@ process.stdout.write(`${JSON.stringify({
|
||||
copiedFiles: result.copiedFiles.map((file) => ({ from: file.from, to: file.to })),
|
||||
config: laneSummary(result.config),
|
||||
next: {
|
||||
render: `node scripts/run-bun.mjs scripts/g14-gitops-render.mjs --lane ${result.lane} --source-branch ${result.config.sourceBranch} --gitops-branch ${result.config.gitopsBranch} --catalog-path ${result.config.artifactCatalog} --image-tag-mode ${result.config.imageTagMode ?? "full"}`,
|
||||
controlPlane: `bun scripts/cli.ts hwlab g14 control-plane apply --lane ${result.lane} --confirm`,
|
||||
trigger: `bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane ${result.lane} --confirm`
|
||||
render: `node scripts/run-bun.mjs scripts/gitops-render.mjs --lane ${result.lane} --source-branch ${result.config.sourceBranch} --gitops-branch ${result.config.gitopsBranch} --catalog-path ${result.config.artifactCatalog} --image-tag-mode ${result.config.imageTagMode ?? "full"}`,
|
||||
controlPlane: `bun scripts/cli.ts hwlab node control-plane apply --lane ${result.lane} --confirm`,
|
||||
trigger: `bun scripts/cli.ts hwlab node control-plane trigger-current --lane ${result.lane} --confirm`
|
||||
}
|
||||
}, null, 2)}\n`);
|
||||
|
||||
@@ -136,7 +136,8 @@ async function configureLane(deploy, options) {
|
||||
const target = deepTransform(clone(source), replacements);
|
||||
|
||||
target.name = targetInfo.versionName;
|
||||
target.node = target.node ?? source.node ?? "G14";
|
||||
target.node = target.node ?? source.node;
|
||||
assert.ok(target.node, "target lane node is required; set deploy.lanes.<lane>.node or source lane node");
|
||||
target.sourceBranch = targetInfo.sourceBranch;
|
||||
target.gitopsBranch = targetInfo.gitopsBranch;
|
||||
target.namespace = targetInfo.namespace;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { readStructuredFile } from "./src/structured-config.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "g14";
|
||||
const defaultLane = process.env.HWLAB_ARTIFACT_LANE || process.env.HWLAB_GITOPS_LANE || "node";
|
||||
const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json";
|
||||
const defaultDeployPath = process.env.HWLAB_DEPLOY_MANIFEST_PATH || "deploy/deploy.yaml";
|
||||
const defaultPublishReportPath = tempReportPath("dev-artifacts.json");
|
||||
@@ -99,7 +99,7 @@ function parseArgs(argv) {
|
||||
}
|
||||
|
||||
if (!args.help) {
|
||||
assert.ok(args.lane === "g14" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`);
|
||||
assert.ok(args.lane === "node" || isRuntimeArtifactLane(args.lane), `unknown artifact lane ${args.lane}`);
|
||||
assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`);
|
||||
assert.notEqual(args.blocked && Boolean(args.publishReportPath), true, "--blocked and --publish-report are mutually exclusive");
|
||||
assert.ok(args.blocked || args.publishReportPath, `choose --blocked or --publish-report ${defaultPublishReportPath}`);
|
||||
@@ -122,7 +122,7 @@ function usage() {
|
||||
"usage: node scripts/refresh-artifact-catalog.mjs --target-ref REF (--blocked|--publish-report PATH) [--write|--no-write]",
|
||||
"",
|
||||
"Preview or explicitly write DEV artifact catalog identity without faking digest evidence.",
|
||||
"Default mode is read-only. --write is reserved for G14 Tekton GitOps promotion and never writes deploy/deploy.yaml.",
|
||||
"Default mode is read-only. --write is reserved for node Tekton GitOps promotion and never writes deploy/deploy.yaml.",
|
||||
"",
|
||||
"examples:",
|
||||
" node scripts/refresh-artifact-catalog.mjs --target-ref HEAD --blocked --no-write",
|
||||
@@ -225,7 +225,7 @@ function artifactCatalogSkeleton({ args, deploy, target, registryPrefix = defaul
|
||||
ciPublished: false,
|
||||
registryVerified: false,
|
||||
provenance: "not_available_until_publish",
|
||||
note: `${environment} artifact catalog skeleton initialized without DEV/G14 fallback.`
|
||||
note: `${environment} artifact catalog skeleton initialized without DEV/node fallback.`
|
||||
},
|
||||
healthContract: {
|
||||
method: "GET",
|
||||
@@ -426,7 +426,7 @@ function publishRecordsFromReport(report, target, serviceIds = SERVICE_IDS) {
|
||||
return { records, requiredServiceIds, disabledServiceIds };
|
||||
}
|
||||
|
||||
function refreshDocuments({ deploy, catalog, target, publishRecords, serviceInventory, provenancePath, registryPrefix, lane = "g14", serviceIds = SERVICE_IDS }) {
|
||||
function refreshDocuments({ deploy, catalog, target, publishRecords, serviceInventory, provenancePath, registryPrefix, lane = "node", serviceIds = SERVICE_IDS }) {
|
||||
const published = Boolean(publishRecords);
|
||||
const records = publishRecords?.records ?? null;
|
||||
const requiredIds = new Set(publishRecords?.requiredServiceIds ?? serviceInventory.requiredServiceIds);
|
||||
|
||||
@@ -43,7 +43,7 @@ test("refresh accepts an absolute publish report path", async () => {
|
||||
const reportPath = path.join(tempDir, "dev-artifacts.json");
|
||||
await writeFile(reportPath, `${JSON.stringify({
|
||||
reportVersion: "v1",
|
||||
taskId: "g14-artifact-publish",
|
||||
taskId: "artifact-publish",
|
||||
commitId: shortCommitId,
|
||||
artifactPublish: {
|
||||
status: "published",
|
||||
@@ -112,7 +112,7 @@ test("refresh is read-only by default", async () => {
|
||||
const reportPath = path.join(tempDir, "dev-artifacts.json");
|
||||
await writeFile(reportPath, `${JSON.stringify({
|
||||
reportVersion: "v1",
|
||||
taskId: "g14-artifact-publish",
|
||||
taskId: "artifact-publish",
|
||||
commitId: shortCommitId,
|
||||
artifactPublish: {
|
||||
status: "published",
|
||||
@@ -170,7 +170,7 @@ test("refresh keeps reused service image tags from the publish report", async ()
|
||||
const reportPath = path.join(tempDir, "dev-artifacts.json");
|
||||
await writeFile(reportPath, `${JSON.stringify({
|
||||
reportVersion: "v1",
|
||||
taskId: "g14-artifact-publish",
|
||||
taskId: "artifact-publish",
|
||||
commitId: shortCommitId,
|
||||
artifactPublish: {
|
||||
status: "published",
|
||||
|
||||
@@ -141,7 +141,7 @@ export function evaluateArtifactRuntimeReadiness({
|
||||
pass: commitsMatch(catalog.commitId, target.commitId),
|
||||
type: "observability_blocker",
|
||||
summary: `Artifact catalog commit=${catalog.shortCommitId}; target=${target.shortCommitId}.`,
|
||||
nextTask: "Do not refresh source desired-state. Let G14 Tekton promotion write artifact catalog identity to G14-gitops, then verify Argo/runtime revision."
|
||||
nextTask: "Do not refresh source desired-state. Let node Tekton promotion write artifact catalog identity to the configured GitOps branch, then verify Argo/runtime revision."
|
||||
});
|
||||
addCheck(checks, {
|
||||
id: "artifact-report-catalog-match",
|
||||
@@ -162,7 +162,7 @@ export function evaluateArtifactRuntimeReadiness({
|
||||
pass: desiredState.matchesTarget,
|
||||
type: "observability_blocker",
|
||||
summary: `Desired-state deploy=${desiredState.deployCommitId}; target=${target.shortCommitId}; convergence=${desiredState.targetConvergence.state}; pending=${desiredState.targetConvergence.pendingTargetFields}.`,
|
||||
nextTask: `Run node scripts/deploy-desired-state-plan.mjs --promotion-commit ${target.shortCommitId} --check as a read-only review, then rely on G14 Tekton promotion for catalog writes.`
|
||||
nextTask: `Run node scripts/deploy-desired-state-plan.mjs --promotion-commit ${target.shortCommitId} --check as a read-only review, then rely on node Tekton promotion for catalog writes.`
|
||||
});
|
||||
addCheck(checks, {
|
||||
id: "rollout-read-access",
|
||||
@@ -303,7 +303,7 @@ export function buildArtifactRuntimeReadinessReport({
|
||||
sourceContract: {
|
||||
status: "pass",
|
||||
documents: [
|
||||
"docs/reference/g14-gitops-cicd.md"
|
||||
"docs/reference/node-gitops-cicd.md"
|
||||
],
|
||||
summary: "Target commit, artifact publish/catalog identity, desired-state convergence, API runtime identity, and Cloud Web served runtime identity are separate gates."
|
||||
},
|
||||
@@ -1220,7 +1220,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
|
||||
return [
|
||||
"bun run --cwd web/hwlab-cloud-web build",
|
||||
"bun run --cwd web/hwlab-cloud-web check",
|
||||
"node scripts/g14-artifact-publish.mjs --preflight --no-report",
|
||||
"node scripts/artifact-publish.mjs --preflight --no-report",
|
||||
"node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked --no-write",
|
||||
guardCommand
|
||||
];
|
||||
@@ -1229,7 +1229,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 --no-write",
|
||||
"node scripts/gitops-render.mjs --no-write",
|
||||
guardCommand
|
||||
];
|
||||
}
|
||||
@@ -1241,7 +1241,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
|
||||
];
|
||||
}
|
||||
return [
|
||||
"node scripts/g14-gitops-render.mjs --no-write",
|
||||
"node scripts/gitops-render.mjs --no-write",
|
||||
"node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --live --no-report"
|
||||
];
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@ export const checkProfiles = Object.freeze({
|
||||
{ id: "check-090-deploy-refresh-artifact-catalog", group: "deploy", command: ["node","--check","scripts/refresh-artifact-catalog.mjs"] },
|
||||
{ id: "check-091-deploy-refresh-artifact-catalog-test", group: "deploy", command: ["node","--check","scripts/refresh-artifact-catalog.test.mjs"] },
|
||||
{ id: "check-092-artifact-artifact-publish", group: "artifact", command: ["node","--check","scripts/artifact-publish.mjs"] },
|
||||
{ id: "check-093-artifact-g14-artifact-publish", group: "artifact", command: ["node","--check","scripts/g14-artifact-publish.mjs"] },
|
||||
{ id: "check-093a-artifact-g14-gitops-render-test", group: "artifact", command: ["node","scripts/run-bun.mjs","test","scripts/g14-gitops-render.test.ts"] },
|
||||
{ id: "check-093-artifact-artifact-publish", group: "artifact", command: ["node","--check","scripts/artifact-publish.mjs"] },
|
||||
{ id: "check-093a-artifact-gitops-render-test", group: "artifact", command: ["node","scripts/run-bun.mjs","test","scripts/gitops-render.test.ts"] },
|
||||
{ id: "check-094-artifact-dev-runtime-base-image", group: "artifact", command: ["node","--check","scripts/dev-runtime-base-image.mjs"] },
|
||||
{ id: "check-095-artifact-dev-artifact-services", group: "artifact", command: ["node","--check","scripts/src/dev-artifact-services.mjs"] },
|
||||
{ id: "check-096-artifact-registry-capabilities", group: "artifact", command: ["node","--check","scripts/src/registry-capabilities.mjs"] },
|
||||
|
||||
@@ -7,7 +7,7 @@ import { readStructuredFileIfPresent } from "./structured-config.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export const G14_CI_PLAN_VERSION = "v1";
|
||||
export const CI_PLAN_VERSION = "v1";
|
||||
export const ENV_REUSE_RUNTIME_MODE = "env-reuse-git-mirror-checkout";
|
||||
export const V02_ENV_REUSE_RUNTIME_MODE = ENV_REUSE_RUNTIME_MODE;
|
||||
|
||||
@@ -41,12 +41,12 @@ export const DEFAULT_DOCS_ONLY_PATHS = Object.freeze([
|
||||
export const DEFAULT_GITOPS_ONLY_PATHS = Object.freeze([
|
||||
"deploy/gitops/",
|
||||
"deploy/frp/",
|
||||
"scripts/g14-gitops-render.mjs",
|
||||
"scripts/gitops-render.mjs",
|
||||
"scripts/src/runtime-lane.ts",
|
||||
"tsconfig.gitops.json"
|
||||
]);
|
||||
|
||||
export async function createG14CiPlan(options = {}) {
|
||||
export async function createCiPlan(options = {}) {
|
||||
const repoRoot = options.repoRoot ?? process.cwd();
|
||||
const deployConfigPath = options.deployConfigPath ?? "deploy/deploy.yaml";
|
||||
const targetRef = options.targetRef ?? "HEAD";
|
||||
@@ -218,7 +218,7 @@ export async function createG14CiPlan(options = {}) {
|
||||
const buildServices = services.filter((service) => service.buildRequired).map((service) => service.serviceId);
|
||||
const reusedServices = services.filter((service) => !service.affected).map((service) => service.serviceId);
|
||||
return {
|
||||
planVersion: G14_CI_PLAN_VERSION,
|
||||
planVersion: CI_PLAN_VERSION,
|
||||
sourceCommitId,
|
||||
shortCommitId,
|
||||
baseRef,
|
||||
@@ -227,7 +227,7 @@ export async function createG14CiPlan(options = {}) {
|
||||
deployConfig: deployConfigPath,
|
||||
artifactCatalog: artifactCatalog ? artifactCatalogPath : null,
|
||||
lane,
|
||||
ciContractSource: "scripts/g14-gitops-render.mjs",
|
||||
ciContractSource: "scripts/gitops-render.mjs",
|
||||
mode: "advisory-read-only",
|
||||
currentRenderCompatible: true,
|
||||
plannerMutatesDeployJson: false,
|
||||
@@ -595,7 +595,7 @@ function resolveServiceIds({ options, laneConfig, lane }) {
|
||||
const serviceIds = uniqueSorted(options.services.map(String));
|
||||
const unknownServiceIds = serviceIds.filter((serviceId) => !allowedServiceIds.has(serviceId));
|
||||
if (unknownServiceIds.length > 0) {
|
||||
throw new Error(`unknown service IDs for G14 CI plan: ${unknownServiceIds.join(", ")}`);
|
||||
throw new Error(`unknown service IDs for node CI plan: ${unknownServiceIds.join(", ")}`);
|
||||
}
|
||||
return { source: "cli-services", serviceIds };
|
||||
}
|
||||
@@ -974,7 +974,7 @@ export async function buildDesiredStatePlan(options = {}) {
|
||||
humanAuthoredTruth: [deployPath, workloadsPath],
|
||||
artifactIdentityTruth: [catalogPath],
|
||||
nonAuthoritativeEvidence: [artifactReportPath],
|
||||
note: "This planner is read-only. deploy.yaml is human-authored config; artifact-catalog carries generated image identity and is written by G14 Tekton promotion only to G14-gitops. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence."
|
||||
note: "This planner is read-only. deploy.yaml is human-authored config; artifact-catalog carries generated image identity and is written by node Tekton promotion only to the configured GitOps branch. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence."
|
||||
},
|
||||
cloudApiDb: cloudApiDb ?? {
|
||||
status: "blocked",
|
||||
|
||||
@@ -67,7 +67,7 @@ function provisionPlan({ exactBlocker = null, envValue = recommendedNode20Image,
|
||||
`${envVarName}=${recommendedNode20Image} node scripts/preflight-dev-base-image.mjs`
|
||||
],
|
||||
artifactPublishDryRun: [
|
||||
`${envVarName}=${envValue} node scripts/g14-artifact-publish.mjs --preflight --no-report`
|
||||
`${envVarName}=${envValue} node scripts/artifact-publish.mjs --preflight --no-report`
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -168,7 +168,7 @@ async function createDevGateReport(edgeHealth) {
|
||||
documents: [
|
||||
"docs/reference/dev-runtime-boundary.md",
|
||||
"docs/reference/spec-v02-documentation-governance.md",
|
||||
"docs/reference/g14-gitops-cicd.md",
|
||||
"docs/reference/node-gitops-cicd.md",
|
||||
"deploy/frp/README.md",
|
||||
"deploy/master-edge/README.md"
|
||||
],
|
||||
|
||||
@@ -327,7 +327,7 @@ function buildEvidence() {
|
||||
category: "repository-governance",
|
||||
level: "SOURCE",
|
||||
status: "pass",
|
||||
sources: ["scripts/repo-reports-guard.mjs", "package.json", "scripts/g14-gitops-render.mjs"],
|
||||
sources: ["scripts/repo-reports-guard.mjs", "package.json", "scripts/gitops-render.mjs"],
|
||||
commands: ["node scripts/repo-reports-guard.mjs"],
|
||||
summary: "Repository report files are removed and guarded against recurrence."
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ const artifactBuildInputFiles = new Set([
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"npm-shrinkwrap.json",
|
||||
"scripts/g14-artifact-publish.mjs",
|
||||
"scripts/artifact-publish.mjs",
|
||||
"scripts/src/dev-artifact-services.mjs"
|
||||
]);
|
||||
|
||||
@@ -674,10 +674,10 @@ async function validateLocalContracts(reporter, contracts, optionalReports, targ
|
||||
|
||||
function validateArtifactPublishReport(reporter, artifactReport, artifactIdentity, targetShortCommit, targetCommit, targetRef) {
|
||||
if (!artifactReport) {
|
||||
reporter.check("g14-artifact-publish-report", "registry", "blocked", "No G14 artifact publish report is present.");
|
||||
reporter.check("artifact-publish-report", "registry", "blocked", "No node artifact publish report is present.");
|
||||
reporter.block({
|
||||
type: "runtime_blocker",
|
||||
scope: "g14-artifact-publish",
|
||||
scope: "artifact-publish",
|
||||
summary: `${defaultArtifactReportPath} is missing, so the preflight has no publish evidence for HWLAB runtime artifacts.`,
|
||||
nextTask: `Run the DEV artifact publish workflow and keep its temporary JSON at ${defaultArtifactReportPath}; do not commit report output.`
|
||||
});
|
||||
@@ -721,7 +721,7 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit
|
||||
const summary = sourceMatchesTarget
|
||||
? "DEV artifact publish report covers all required enabled service IDs with immutable digests for the target commit, and records disabled service reasons."
|
||||
: `DEV artifact publish report covers artifact source ${artifactIdentity.artifactSource.shortCommitId}; target ${targetRef} ${targetShortCommit} has no artifact build input changes since that source.`;
|
||||
reporter.check("g14-artifact-publish-report", "registry", "pass", summary, [artifactIdentity?.targetCoverage].filter(Boolean));
|
||||
reporter.check("artifact-publish-report", "registry", "pass", summary, [artifactIdentity?.targetCoverage].filter(Boolean));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -731,14 +731,14 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit
|
||||
? `source states resolved: ${sourcePresentCount} source-present, ${intentionallyDisabledCount} intentionally-disabled`
|
||||
: "source states are not fully resolved";
|
||||
reporter.check(
|
||||
"g14-artifact-publish-report",
|
||||
"artifact-publish-report",
|
||||
"registry",
|
||||
"blocked",
|
||||
`DEV artifact publish report status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published for source ${artifactPublish?.sourceCommitId ?? artifactReport.commitId ?? "unknown"}; ${sourceSummary}.`
|
||||
);
|
||||
reporter.block({
|
||||
type: "runtime_blocker",
|
||||
scope: "g14-artifact-publish",
|
||||
scope: "artifact-publish",
|
||||
summary: `${defaultArtifactReportPath} does not prove all required HWLAB service artifacts for ${targetRef} ${targetShortCommit}; current status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published and ${sourceSummary}.`,
|
||||
nextTask: "Complete DEV artifact publishing for every enabled required HWLAB service at the artifact source commit, or prove the target commit has no artifact build input changes; keep disabled services marked not_published with reasons."
|
||||
});
|
||||
|
||||
@@ -60,8 +60,8 @@ export type DeployConfig = {
|
||||
lanes?: Record<string, RuntimeLaneConfig | undefined>;
|
||||
};
|
||||
|
||||
const defaultNodeId = "G14";
|
||||
const defaultGitopsRoot = "deploy/gitops/g14";
|
||||
const defaultNodeId = "node";
|
||||
const defaultGitopsRoot = "deploy/gitops/node";
|
||||
const defaultPublicHost = "74.48.78.17";
|
||||
|
||||
export function isRuntimeLane(lane: unknown): lane is string {
|
||||
@@ -143,8 +143,8 @@ export function applyRuntimeLaneDeployConfig(args: GitOpsRenderArgs, deploy: Dep
|
||||
const configuredEndpoint = laneConfig.endpoint ?? defaultEndpoint;
|
||||
const configuredWebEndpoint = laneConfig.publicEndpoints?.frontend ?? configuredEndpoint;
|
||||
const configuredRuntimeEndpoint = laneConfig.publicEndpoints?.api ?? configuredEndpoint;
|
||||
const defaultBranch = defaults.defaultBranch ?? "G14";
|
||||
const defaultGitopsBranch = defaults.defaultGitopsBranch ?? "G14-gitops";
|
||||
const defaultBranch = defaults.defaultBranch ?? "node";
|
||||
const defaultGitopsBranch = defaults.defaultGitopsBranch ?? "node-gitops";
|
||||
const defaultCatalogPath = defaults.defaultCatalogPath ?? "deploy/artifact-catalog.dev.json";
|
||||
const defaultRuntimeEndpoint = defaults.defaultRuntimeEndpoint ?? "http://74.48.78.17:17667";
|
||||
const defaultWebEndpoint = defaults.defaultWebEndpoint ?? "http://74.48.78.17:17666";
|
||||
|
||||
@@ -249,10 +249,10 @@ function assertCloudApiImageRuntimeEntrypoints({ packageJson, artifactPublisher,
|
||||
assertIncludes(packageJson.scripts?.check, `bun build ${expected} --target=bun --packages=external`, `${label} package check`);
|
||||
assertIncludes(packageJson.scripts?.check, `bun ${expected} --check`, `${label} package runtime check`);
|
||||
}
|
||||
for (const expected of ["scripts/g14-artifact-publish.mjs", "runtime-dev", "runtime-prod"]) {
|
||||
assertIncludes(gitopsRenderSource, expected, `${label} G14 GitOps render source`);
|
||||
for (const expected of ["scripts/artifact-publish.mjs", "runtime-dev", "runtime-prod"]) {
|
||||
assertIncludes(gitopsRenderSource, expected, `${label} node GitOps render source`);
|
||||
}
|
||||
for (const expected of ["name: \"prepare-source\"", "name: \"repo-reports-guard\"", "name: \"g14-contract-check\"", "name: \"codex-api-forwarder-check\""]) {
|
||||
for (const expected of ["name: \"prepare-source\"", "name: \"repo-reports-guard\"", "name: \"node-contract-check\"", "name: \"codex-api-forwarder-check\""]) {
|
||||
assertIncludes(gitopsRenderSource, expected, `${label} Tekton primitive CI task`);
|
||||
}
|
||||
}
|
||||
@@ -446,7 +446,7 @@ assertDurableRuntimeRunbook(await readText("docs/reference/dev-runtime-boundary.
|
||||
assertCloudApiImageRuntimeEntrypoints({
|
||||
packageJson: await readJSON("package.json"),
|
||||
artifactPublisher: await readText("scripts/artifact-publish.mjs"),
|
||||
gitopsRenderSource: await readText("scripts/g14-gitops-render.mjs")
|
||||
gitopsRenderSource: await readText("scripts/gitops-render.mjs")
|
||||
});
|
||||
|
||||
console.log(
|
||||
|
||||
Reference in New Issue
Block a user