fix: YAML-first 治理 CI/CD target (#919)
* docs: specify cicd yaml target governance * fix: resolve cicd targets from yaml --------- Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
version: 1
|
||||
kind: UnideskArtifactRegistry
|
||||
metadata:
|
||||
name: unidesk-artifact-registry
|
||||
owner: unidesk
|
||||
|
||||
defaults:
|
||||
targetId: D601
|
||||
|
||||
targets:
|
||||
D601:
|
||||
providerId: D601
|
||||
mode: d601-host-managed
|
||||
registry:
|
||||
host: 127.0.0.1
|
||||
port: 5000
|
||||
endpoint: http://127.0.0.1:5000
|
||||
image: registry:2.8.3
|
||||
repositoryPrefix: 127.0.0.1:5000/unidesk
|
||||
runtime:
|
||||
baseDir: /home/ubuntu/.unidesk/artifact-registry
|
||||
storageDir: /home/ubuntu/.unidesk/registry-storage
|
||||
unitName: unidesk-artifact-registry.service
|
||||
composeProject: unidesk-artifact-registry
|
||||
serviceName: registry
|
||||
containerName: unidesk-artifact-registry
|
||||
timeoutMs: 30000
|
||||
source:
|
||||
repo: https://github.com/pikasTech/unidesk
|
||||
consumers:
|
||||
target:
|
||||
composePullMode: provider-gateway-image-stream
|
||||
k3sImportMode: d601-native-containerd
|
||||
defaultEnvironment: prod
|
||||
catalogRef: scripts/src/artifact-registry/catalog.ts
|
||||
note: catalogRef is a legacy adapter boundary until service manifests own each consumer.
|
||||
local:
|
||||
providerId: local
|
||||
mode: d601-host-managed-local
|
||||
registry:
|
||||
host: 127.0.0.1
|
||||
port: 5000
|
||||
endpoint: http://127.0.0.1:5000
|
||||
image: registry:2.8.3
|
||||
repositoryPrefix: 127.0.0.1:5000/unidesk
|
||||
runtime:
|
||||
baseDir: /home/ubuntu/.unidesk/artifact-registry
|
||||
storageDir: /home/ubuntu/.unidesk/registry-storage
|
||||
unitName: unidesk-artifact-registry.service
|
||||
composeProject: unidesk-artifact-registry
|
||||
serviceName: registry
|
||||
containerName: unidesk-artifact-registry
|
||||
timeoutMs: 30000
|
||||
source:
|
||||
repo: https://github.com/pikasTech/unidesk
|
||||
consumers:
|
||||
target:
|
||||
composePullMode: provider-gateway-image-stream
|
||||
k3sImportMode: d601-native-containerd
|
||||
defaultEnvironment: prod
|
||||
catalogRef: scripts/src/artifact-registry/catalog.ts
|
||||
note: local target reuses the D601 host registry facts when the CLI runs on the target host.
|
||||
@@ -0,0 +1,35 @@
|
||||
# SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
version: 1
|
||||
kind: UnideskCicdTargets
|
||||
metadata:
|
||||
name: unidesk-cicd-targets
|
||||
owner: unidesk
|
||||
|
||||
defaults:
|
||||
targetId: D601
|
||||
|
||||
targets:
|
||||
D601:
|
||||
providerId: D601
|
||||
kubeRoute: D601:k3s
|
||||
kubeconfig: /etc/rancher/k3s/k3s.yaml
|
||||
hostCwd: /home/ubuntu
|
||||
homeDir: /home/ubuntu
|
||||
pipelineManifest: src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.yaml
|
||||
codeQueueImage: unidesk-code-queue:dev
|
||||
guardName: d601_native_k3s_guard
|
||||
requiredNodeName: d601
|
||||
artifactRegistry:
|
||||
configRef: config/artifact-registry.yaml#targets.D601
|
||||
G14:
|
||||
providerId: G14
|
||||
kubeRoute: G14:k3s
|
||||
kubeconfig: /etc/rancher/k3s/k3s.yaml
|
||||
hostCwd: /root
|
||||
homeDir: /root
|
||||
pipelineManifest: src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.g14.yaml
|
||||
codeQueueImage: unidesk-code-queue:dev
|
||||
guardName: g14_native_k3s_guard
|
||||
requiredNodeLabel:
|
||||
key: unidesk.ai/node-id
|
||||
value: G14
|
||||
@@ -120,6 +120,7 @@ lanes:
|
||||
deployment: hwlab-cloud-api
|
||||
targets:
|
||||
D601:
|
||||
node: D601
|
||||
workspace: /home/ubuntu/workspace/hwlab-v03
|
||||
cicdRepo: /home/ubuntu/workspace/hwlab-v03-cicd.git
|
||||
cicdRepoLock: /tmp/hwlab-v03-cicd-repo.lock
|
||||
|
||||
@@ -64,9 +64,10 @@ instrumentation:
|
||||
serviceConnections:
|
||||
- serviceName: hwlab-cloud-api
|
||||
owningRepo: pikasTech/HWLAB
|
||||
targetNode: D601
|
||||
lane: v0.3
|
||||
namespace: hwlab-v03
|
||||
configRefs:
|
||||
targetNode: config/hwlab-node-lanes.yaml#lanes.v03.targets.D601.node
|
||||
lane: config/hwlab-node-lanes.yaml#lanes.v03.version
|
||||
namespace: config/hwlab-node-lanes.yaml#lanes.v03.targets.D601.runtime.namespace
|
||||
requiredSpans:
|
||||
- POST /v1/agent/chat
|
||||
- durable_admission
|
||||
@@ -77,16 +78,18 @@ instrumentation:
|
||||
- turn_status_read
|
||||
- serviceName: user-billing
|
||||
owningRepo: pikasTech/HWLAB
|
||||
targetNode: D601
|
||||
lane: v0.3
|
||||
namespace: hwlab-v03
|
||||
configRefs:
|
||||
targetNode: config/hwlab-node-lanes.yaml#lanes.v03.targets.D601.node
|
||||
lane: config/hwlab-node-lanes.yaml#lanes.v03.version
|
||||
namespace: config/hwlab-node-lanes.yaml#lanes.v03.targets.D601.runtime.namespace
|
||||
requiredSpans:
|
||||
- billing_preflight
|
||||
- serviceName: agentrun-manager
|
||||
owningRepo: pikasTech/agentrun
|
||||
targetNode: D601
|
||||
lane: v0.2
|
||||
namespace: agentrun-v02
|
||||
configRefs:
|
||||
targetNode: config/agentrun.yaml#controlPlane.lanes.v02.node
|
||||
lane: config/agentrun.yaml#controlPlane.lanes.v02.version
|
||||
namespace: config/agentrun.yaml#controlPlane.lanes.v02.runtime.namespace
|
||||
requiredSpans:
|
||||
- agentrun_dispatch
|
||||
- run_created
|
||||
|
||||
@@ -92,6 +92,7 @@ YAML运维负责 HWLAB/UniDesk 自有平台配置的真相源、解析、渲染
|
||||
| PJ2026-01060305 | 执行策略 | 本规格 6.5 | AgentRun control-plane default、session policy、provider profile 和 workspace 策略配置 | Agent编排语义、lane 配置 | Agent编排、平台发布 |
|
||||
| PJ2026-01060306 | 公共原语 | 本规格 6.6 | 字段解析、fingerprint、摘要输出、Secret 引用和 YAML path 捕获复用 | 各平台 CLI 实现 | 全部平台运维 CLI |
|
||||
| PJ2026-01060307 | 控制面模块化 | [PJ2026-01060307 控制面模块化](PJ2026-01060307-control-plane-modularity.md) | CI/CD、YAML-first 和平台运维 CLI 源码入口的职责拆分、兼容入口和模块边界 | 发布流水、源码同步、公共原语 | 全部平台运维 CLI |
|
||||
| PJ2026-01060308 | YAML目标治理 | [PJ2026-01060308 CI/CD YAML目标治理](PJ2026-01060308-cicd-yaml-first-target-governance.md) | CI/CD、HWLAB lane、AgentRun、platform-infra、Secret 和 public exposure 的 target/configRef/sourceRef 解析治理 | 控制面模块化、发布流水、源码同步 | CI、deploy、artifact-registry、HWLAB、AgentRun、platform-infra、secrets |
|
||||
|
||||
## 6. 原子需求
|
||||
|
||||
@@ -180,3 +181,15 @@ YAML运维应沉淀公共 ops primitive,使字段解析、YAML path 捕获、f
|
||||
YAML运维应约束 CI/CD、YAML-first 和平台基础设施 CLI 的源码入口,使超过 3000 行的控制面文件先按职责拆入领域子目录,再继续沉淀公共 ops primitive。
|
||||
|
||||
原 `scripts/src/*.ts` 同名入口应只保留兼容 re-export、命令路由或极薄 adapter。配置解析、manifest 渲染、远端脚本、Secret/public exposure、git-mirror、Tekton/Argo、status summary 和 bounded output 等职责应进入领域模块或共享 helper。新增平台运维逻辑不得继续追加到已超限入口文件。
|
||||
|
||||
### 6.8 OPS-YAML-REQ-008 CI/CD YAML目标治理
|
||||
|
||||
| 编号 | 短名 | 主责模块 | 关联模块 |
|
||||
| --- | --- | --- | --- |
|
||||
| OPS-YAML-REQ-008 | YAML目标治理 | [PJ2026-01060308 CI/CD YAML目标治理](PJ2026-01060308-cicd-yaml-first-target-governance.md) | [发布流水](PJ2026-010601-controlled-release.md)、[源码同步](PJ2026-010602-source-sync.md)、[控制面模块化](PJ2026-01060307-control-plane-modularity.md) |
|
||||
|
||||
YAML运维应约束 CI、deploy、artifact-registry、HWLAB node/lane、AgentRun、platform-infra、Secret 分发和 public exposure 的目标解析,使 node、lane、target、namespace、route、workspace、registry endpoint、public URL、SecretRef、gitops path 和 CI/CD 参数来自 owning YAML 或显式参数。
|
||||
|
||||
公共 option parser 不得内置 domain target 默认。未显式传入目标时,调用 domain 可以从自身 YAML default 解析,并必须输出默认来源路径;已显式传入 node/lane/target 时,解析器只校验和解释该目标,不得用全局 default 覆盖。
|
||||
|
||||
跨 YAML 的运行目标事实应使用 `path/to/file.yaml#object.path` configRef,CLI `plan/status --full` 应显示引用链、resolved target、presence、摘要 hash 和缺失字段,不打印 Secret 值。代码中保留的 `G14`、`D601`、`v02`、`v03` 等目标名必须被 hardcode inventory 分类为 legacy adapter、help example、test fixture 或真实领域特例;隐藏默认必须迁移到 YAML。
|
||||
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
# PJ2026-01060308 CI/CD YAML目标治理
|
||||
|
||||
## 修改历史
|
||||
|
||||
| 版本 | 对应 commit id | 更新日期 | 变更说明 |
|
||||
| --- | --- | --- | --- |
|
||||
|
||||
当前正文仍在规格治理草稿中;未定稿前不新增版本号,不为单次编辑追加 `待提交` 版本。
|
||||
|
||||
## 正文
|
||||
|
||||
## PJ2026-01060308 CI/CD YAML目标治理需求规格
|
||||
|
||||
## 1. 文档控制
|
||||
|
||||
| 字段 | 内容 |
|
||||
| --- | --- |
|
||||
| 编号 | PJ2026-01060308 |
|
||||
| 短名 | YAML目标治理 |
|
||||
| 层级 | L3 子课题 |
|
||||
| 状态 | 已生效 |
|
||||
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
|
||||
| 上级规格 | [PJ2026-010603 YAML运维](PJ2026-010603-yaml-first-ops.md) |
|
||||
| 关联规格 | [PJ2026-010601 发布流水](PJ2026-010601-controlled-release.md)、[PJ2026-010602 源码同步](PJ2026-010602-source-sync.md)、[PJ2026-01060307 控制面模块化](PJ2026-01060307-control-plane-modularity.md)、[PJ2026-010604 公开入口](PJ2026-010604-public-entry.md)、[PJ2026-010605 运维监控](PJ2026-010605-observability-monitoring.md) |
|
||||
| 实现引用版本 | draft-2026-06-25-cicd-yaml-targets |
|
||||
| 规格治理索引 | [规格治理](spec-governance.md) |
|
||||
|
||||
本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版:正文只保留 CI/CD、YAML-first 和平台运维控制面中 target、lane、Secret、public exposure、PipelineRun/Argo/git-mirror 与公共 ops primitive 的稳定治理口径。
|
||||
|
||||
## 2. 目的和范围
|
||||
|
||||
### 2.1 目的
|
||||
|
||||
YAML目标治理负责把 CI、deploy、artifact-registry、HWLAB node/lane、AgentRun、platform-infra、Secret 分发和 public exposure 中的运行目标事实收敛到 owning YAML 或显式参数,使新增 node/lane/target 时不需要发布 TypeScript 代码来改变默认目标、namespace、route、registry endpoint 或 SecretRef。
|
||||
|
||||
本规格建立在控制面模块化完成后的领域目录边界上。代码可以保留 legacy adapter、测试夹具、错误消息和帮助示例中的历史目标名,但主路径不得继续通过隐藏默认值把 `G14`、`D601`、`v02`、`v03`、namespace、route、registry endpoint 或工作目录写死在解析逻辑中。
|
||||
|
||||
### 2.2 范围内
|
||||
|
||||
- `config/hwlab-node-lanes.yaml`、`config/agentrun.yaml`、`config/platform-infra/*.yaml`、`config/secrets-distribution.yaml`、`config/cicd/targets.yaml` 和 `config/artifact-registry.yaml` 的 target/lane/configRef/sourceRef 职责。
|
||||
- `scripts/src/ops/` 中的 configRef、target resolver、Secret redaction、public exposure、K8s/CI-CD 和 bounded output 公共原语。
|
||||
- `ci`、`deploy`、`artifact-registry`、`hwlab g14`、`hwlab nodes`、`agentrun`、`platform-infra observability` 和 `secrets` CLI 对目标来源、配置引用图和脱敏状态的解释输出。
|
||||
- hardcode inventory 的分类:`remove-code-default`、`keep-domain-special`、`legacy-adapter`、`test-fixture` 和 `help-example`。
|
||||
|
||||
### 2.3 范围外
|
||||
|
||||
- 不把所有配置合并成单一超级 YAML。
|
||||
- 不删除必要的 legacy 安全阻断;legacy 入口必须隔离并在输出中标注。
|
||||
- 不把运行面 Secret、pod env、日志或数据库状态反向写成本地配置真相。
|
||||
- 不绕过 UniDesk 受控 CLI 使用原生 `kubectl`、`argo`、`tkn`、`curl` 或临时 shell 作为正式控制入口。
|
||||
- 不为业务策略数值新增合同测试、硬编码范围或 schema 数值限制。
|
||||
|
||||
## 3. 术语表
|
||||
|
||||
| 术语 | 定义 |
|
||||
| --- | --- |
|
||||
| target | YAML 中描述部署目标、运行节点、route、kubeRoute、namespace、workspace、registry、public URL 或 CI/CD 参数的对象。 |
|
||||
| lane | YAML 中描述一条 runtime 或 control-plane 运行线的对象,通常包含 node、source truth、runtime namespace、gitops、CI、Secret 和 publicExposure。 |
|
||||
| configRef | `path/to/file.yaml#object.path` 形式的跨 YAML 引用,解析器只校验引用存在性、类型和摘要,不把合并结果写成第二真相。 |
|
||||
| sourceRef | Secret 来源声明。输出只能显示来源标识、key 名、presence、fingerprint、字节数和 `valuesPrinted=false`。 |
|
||||
| hidden default | 代码在未显式参数或未读取 YAML 时自动选择固定目标、namespace、route、registry、lane 或工作目录。 |
|
||||
| legacy adapter | 为历史入口保留的隔离兼容路径。它可以拒绝 mutation 或提示替代命令,但不得污染主 runtime lane 解析。 |
|
||||
| ops primitive | 跨领域复用的配置引用、目标解析、Secret redaction、K8s apply/status、public exposure 和 bounded output helper。 |
|
||||
|
||||
## 4. 系统边界和接口
|
||||
|
||||
| 边界项 | 内容 |
|
||||
| --- | --- |
|
||||
| 外部使用者 | 平台维护者、发布操作人员、AgentRun/HWLAB lane 运维者、自动化任务和后续代码维护者。 |
|
||||
| 外部输入 | CLI 参数、YAML 配置、configRef/sourceRef、node/lane/target、desired-state manifest、Git commit 和运行面只读状态。 |
|
||||
| 受控资源 | target resolver、配置引用图、CI/CD manifest 渲染、Secret 同步摘要、public exposure 渲染、PipelineRun/Argo/git-mirror 状态和 CLI 输出。 |
|
||||
| 外部输出 | 有界 plan/status/dry-run、配置来源路径、resolved target、redacted Secret evidence、drill-down 命令和 legacy adapter 提示。 |
|
||||
| 用户接口 | `bun scripts/cli.ts ci ...`、`artifact-registry ...`、`deploy ...`、`hwlab ...`、`agentrun ...`、`platform-infra observability ...` 和 `secrets ...`。 |
|
||||
| 系统边界 | 本规格定义配置如何成为运行目标真相;不定义业务能力语义、不替代发布/源码/公开入口/监控规格,也不让运行面状态成为配置来源。 |
|
||||
|
||||
## 5. 内部分工与架构
|
||||
|
||||
### 5.1 配置引用图
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CICD[config/cicd/targets.yaml] --> OpsTarget[ops/targets.ts]
|
||||
Artifact[config/artifact-registry.yaml] --> OpsTarget
|
||||
Hwlab[config/hwlab-node-lanes.yaml] --> OpsTarget
|
||||
AgentRun[config/agentrun.yaml] --> OpsTarget
|
||||
Observability[config/platform-infra/observability.yaml] --> ConfigRef[ops/config-refs.ts]
|
||||
Secrets[config/secrets-distribution.yaml] --> OpsSecret[ops/secrets.ts]
|
||||
ConfigRef --> Hwlab
|
||||
ConfigRef --> AgentRun
|
||||
OpsTarget --> DomainCLI[domain CLI plan/status/dry-run]
|
||||
OpsSecret --> DomainCLI
|
||||
DomainCLI --> Runtime[controlled runtime or bounded output]
|
||||
```
|
||||
|
||||
### 5.2 target/lane 解析数据流
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Args[explicit --target/--node/--lane] --> Select[selection]
|
||||
Defaults[YAML defaults] --> Select
|
||||
Select --> Resolver[ops target resolver]
|
||||
Resolver --> Validate[shape and reference validation]
|
||||
Validate --> Plan[resolved target summary]
|
||||
Plan --> Domain[CI/deploy/artifact/HWLAB/AgentRun/platform-infra]
|
||||
Domain --> Output[bounded summary + drill-down]
|
||||
```
|
||||
|
||||
命令行或 issue 已明确 node/lane/target 时,resolver 只校验和解释该目标,不得用全局 default 覆盖。未显式传入目标时,resolver 可以读取 domain YAML default,并在输出中说明默认来源路径。
|
||||
|
||||
### 5.3 Secret sourceRef 同步链路
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant YAML as owning YAML
|
||||
participant CLI as controlled CLI
|
||||
participant Source as local sourceRef
|
||||
participant Runtime as runtime Secret
|
||||
YAML->>CLI: sourceRef + sourceKey + targetKey
|
||||
CLI->>Source: inspect presence and fingerprint
|
||||
Source-->>CLI: presence/fingerprint only
|
||||
CLI-->>CLI: valuesPrinted=false
|
||||
CLI->>Runtime: sync only when --confirm
|
||||
Runtime-->>CLI: object/key presence
|
||||
CLI-->>YAML: no reverse write-back
|
||||
```
|
||||
|
||||
缺少 sourceRef、targetKey、providerCredential 或 tool credential 时,CLI 必须暴露 YAML/AipodSpec binding 缺口,不得通过复制其他 lane Secret、手工创建 legacy Secret 或 patch runtime namespace 规避。
|
||||
|
||||
### 5.4 publicExposure/Caddy/FRPC 链路
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
PublicYaml[publicExposure YAML] --> PublicHelper[ops/public-exposure.ts]
|
||||
PublicHelper --> Frpc[FRPC Secret/render]
|
||||
PublicHelper --> Caddy[PK01 Caddy managed block]
|
||||
PublicHelper --> Probe[HTTPS public health probe]
|
||||
Probe --> Summary[public URL + diagnostic endpoint summary]
|
||||
```
|
||||
|
||||
正式用户入口来自 YAML 中的 public URL 和 publicExposure;FRP remote port、Caddy backend、本地 service DNS、direct port 或历史 nip.io 域名只能作为实现细节、诊断入口或 legacy 对照。
|
||||
|
||||
### 5.5 CI PipelineRun/Argo/git-mirror 状态链路
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CLI
|
||||
participant Target as YAML target
|
||||
participant Mirror as git-mirror
|
||||
participant Tekton
|
||||
participant Argo
|
||||
participant Runtime
|
||||
CLI->>Target: resolve CI/CD target
|
||||
CLI->>Mirror: read/sync/flush via controlled entry
|
||||
Mirror-->>CLI: refs and pendingFlush summary
|
||||
CLI->>Tekton: PipelineRun status or dry-run manifest
|
||||
Tekton-->>CLI: PipelineRun/TaskRun bounded summary
|
||||
CLI->>Argo: Application sync/health status
|
||||
Argo-->>Runtime: desired runtime state
|
||||
Runtime-->>CLI: readiness/public probe summary
|
||||
```
|
||||
|
||||
状态输出必须区分 PR merge commit、current source head、PipelineRun、Argo revision、git-mirror pendingFlush 和 runtime readiness;不得只用一个当前 head 推断所有阶段完成。
|
||||
|
||||
## 6. 原子需求
|
||||
|
||||
### 6.1 OPS-TARGET-REQ-001 YAML target 真相
|
||||
|
||||
| 编号 | 短名 | 主责模块 | 关联模块 |
|
||||
| --- | --- | --- | --- |
|
||||
| OPS-TARGET-REQ-001 | 目标真相 | PJ2026-01060308 YAML目标治理 | [YAML运维](PJ2026-010603-yaml-first-ops.md) |
|
||||
|
||||
CI/CD、deploy、artifact-registry、HWLAB node/lane、AgentRun 和 platform-infra 的 target、lane、namespace、route、workspace、registry endpoint、public URL、SecretRef、gitops path 和 CI/CD 参数必须来自 owning YAML 或显式参数。
|
||||
|
||||
代码不得在主路径中新增隐藏默认 `G14`、`D601`、`v02`、`v03`、namespace、route、registry endpoint 或工作目录。缺少配置时应报出 YAML 路径、字段名和下一步配置入口。
|
||||
|
||||
### 6.2 OPS-TARGET-REQ-002 configRef 解析
|
||||
|
||||
| 编号 | 短名 | 主责模块 | 关联模块 |
|
||||
| --- | --- | --- | --- |
|
||||
| OPS-TARGET-REQ-002 | 配置引用 | PJ2026-01060308 YAML目标治理 | [公共原语](PJ2026-010603-yaml-first-ops.md#66-ops-yaml-req-006-公共-ops-primitive) |
|
||||
|
||||
跨 YAML 目标事实必须通过 `path/to/file.yaml#object.path` configRef 表达。configRef parser 只负责读取、校验存在性、生成摘要 hash、展示引用链和暴露缺失字段;不得把解析后的大对象写回另一个 YAML 成为第二真相。
|
||||
|
||||
`platform-infra observability` 的 serviceConnection 不得复制 HWLAB/AgentRun 的 node/lane/namespace 事实,必须通过 configRef 指向对应 lane 配置,并在 `plan/status --full` 中展示解析链。
|
||||
|
||||
### 6.3 OPS-TARGET-REQ-003 公共 resolver 和 domain 注入默认
|
||||
|
||||
| 编号 | 短名 | 主责模块 | 关联模块 |
|
||||
| --- | --- | --- | --- |
|
||||
| OPS-TARGET-REQ-003 | resolver | PJ2026-01060308 YAML目标治理 | [控制面模块化](PJ2026-01060307-control-plane-modularity.md) |
|
||||
|
||||
公共 option parser 不允许内置 domain target 默认。公共 parser 可以返回 `targetId: null`,由调用 domain 把 YAML default 注入并输出 default 来源路径。`ciTarget()`、deploy provider resolver、artifact-registry options 和 platform-infra common options 都必须遵循该规则。
|
||||
|
||||
### 6.4 OPS-TARGET-REQ-004 Secret 与 credential 脱敏一致性
|
||||
|
||||
| 编号 | 短名 | 主责模块 | 关联模块 |
|
||||
| --- | --- | --- | --- |
|
||||
| OPS-TARGET-REQ-004 | Secret脱敏 | PJ2026-01060308 YAML目标治理 | [用户管理](PJ2026-0105-user-management.md)、[Agent编排](PJ2026-0102-agent-orchestration.md) |
|
||||
|
||||
Secret plan/status 只允许输出对象名、key 名、sourceRef、targetKey、presence、fingerprint、字节数、缺失项和 `valuesPrinted=false`。AgentRun provider credential、tool credential、HWLAB bootstrap Secret、platform-infra Secret 和通用 `secrets` 命令必须使用同一类 redacted sourceRef/fingerprint 语义。
|
||||
|
||||
### 6.5 OPS-TARGET-REQ-005 legacy adapter 隔离
|
||||
|
||||
| 编号 | 短名 | 主责模块 | 关联模块 |
|
||||
| --- | --- | --- | --- |
|
||||
| OPS-TARGET-REQ-005 | legacy隔离 | PJ2026-01060308 YAML目标治理 | [发布流水](PJ2026-010601-controlled-release.md) |
|
||||
|
||||
D601 maintenance deploy、G14 DEV/PROD retirement、v02-only runtime migration、Code Queue compat path 等历史入口可以保留为 legacy adapter,但必须在代码和输出中标注原因、替代命令和是否允许 mutation。legacy adapter 不得成为主 runtime lane 的 fallback,也不得补齐缺失 YAML/Secret。
|
||||
|
||||
### 6.6 OPS-TARGET-REQ-006 hardcode inventory 与验收
|
||||
|
||||
| 编号 | 短名 | 主责模块 | 关联模块 |
|
||||
| --- | --- | --- | --- |
|
||||
| OPS-TARGET-REQ-006 | inventory | PJ2026-01060308 YAML目标治理 | [平台运维](PJ2026-0106-platform-ops.md) |
|
||||
|
||||
每轮治理必须维护 hardcode inventory,并把命中分类为 `remove-code-default`、`keep-domain-special`、`legacy-adapter`、`test-fixture` 或 `help-example`。代码中保留的目标名必须属于后四类之一;`remove-code-default` 必须迁移到 YAML 或公共 helper。
|
||||
|
||||
验收至少覆盖 `ci plan/status --target <id>`、`artifact-registry plan/status --target <id>`、`deploy apply --dry-run` 目标来源说明、`hwlab nodes control-plane status --node <node> --lane <lane>` 的 YAML-declared lane、`platform-infra observability plan/status --full` 的 configRef 解析链和 `secrets status --config ... --full` 的脱敏输出。
|
||||
|
||||
## 7. 过程控制
|
||||
|
||||
本规格的执行 issue 为 [#911](https://github.com/pikasTech/unidesk/issues/911)。源码文件头部应标注 `SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets`;自动生成文件、纯配置、锁文件和无法承载注释头的二进制产物可例外。
|
||||
|
||||
首轮 hardcode inventory:
|
||||
|
||||
| 命中 | 分类 | 治理动作 |
|
||||
| --- | --- | --- |
|
||||
| `ciTarget()`、`d601ProviderId`、CI pipeline manifest、node label、host/root/workspace | remove-code-default | 迁入 `config/cicd/targets.yaml`,CI resolver 读取 YAML。 |
|
||||
| `artifact-registry` 默认 D601、`127.0.0.1:5000`、runtime image、storageDir | remove-code-default | 迁入 `config/artifact-registry.yaml`。 |
|
||||
| `deploy` artifact consumer provider 默认 D601 | remove-code-default | 从 artifact-registry YAML default 解析;compat path 显式 legacy。 |
|
||||
| `HwlabRuntimeLane = "v02" | "v03"` 和 `isSupportedLaneId()` | remove-code-default | lane id 改为 YAML 声明 string,新增 lane 不要求改 TypeScript union。 |
|
||||
| `platform-infra observability` 复制 `targetNode/lane/namespace` | remove-code-default | 改为 serviceConnection configRef 并输出解析链。 |
|
||||
| D601 maintenance deploy、Code Queue compat path、v02 runtime migration | legacy-adapter | 保留安全阻断和替代命令,不作为主路径默认。 |
|
||||
| 帮助示例、错误消息、历史说明中的 G14/D601/v02/v03 | help-example | 允许保留,必要时补 `configTruth` 或 legacy 标注。 |
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. compose-deploy module for scripts/src/artifact-registry.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/artifact-registry.ts:2579-3153 for #903.
|
||||
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
|
||||
import { composeRuntimeEnvValue } from "../runtime-env";
|
||||
|
||||
import type { ArtifactConsumerSpec, ArtifactConsumerTarget, ArtifactRegistryOptions } from "./types";
|
||||
import { artifactRegistryOptionsTargetSummary, type ArtifactConsumerSpec, type ArtifactConsumerTarget, type ArtifactRegistryOptions } from "./types";
|
||||
import { composeArtifactEnvValues, registryArtifactMissingMessage, registryArtifactProbeScript, syntheticComposeHealthDeployScript, verifyLocalArtifactLabels } from "./artifact-probe";
|
||||
import { base64, safeName, shellQuote } from "./bundle";
|
||||
import { artifactConsumerSpecs } from "./catalog";
|
||||
@@ -439,6 +440,7 @@ export function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spe
|
||||
error: verificationBlocked ? "runtime-verification-blocked" : undefined,
|
||||
environment,
|
||||
providerId: effectiveOptions.providerId,
|
||||
artifactRegistryTarget: artifactRegistryOptionsTargetSummary(effectiveOptions),
|
||||
serviceId: spec.serviceId,
|
||||
commit,
|
||||
sourceRepo: sourceRepoFor(effectiveOptions, spec),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. entry module for scripts/src/artifact-registry.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/artifact-registry.ts:3448-3700 for #903.
|
||||
|
||||
@@ -85,33 +86,33 @@ export function localHelp(): Record<string, unknown> {
|
||||
command: "artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service",
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts artifact-registry plan [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry render [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry status [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry health [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry install [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service baidu-netdisk --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env prod --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env dev --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service project-manager --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service oa-event-flow --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service code-queue-mgr --commit <full-sha> --dry-run [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service findjob --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service findjob --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service pipeline --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service pipeline --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service met-nonlinear --commit <full-sha> --dry-run [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service met-nonlinear --commit <full-sha> --dry-run [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service k3sctl-adapter --commit <full-sha> --dry-run [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service mdtodo --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service mdtodo --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service claudeqq --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service claudeqq --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service code-queue --commit <full-sha> --dry-run [--provider-id D601]",
|
||||
"bun scripts/cli.ts artifact-registry plan [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry render [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry status [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry health [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry install [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service baidu-netdisk --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env prod --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env dev --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service project-manager --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service oa-event-flow --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service code-queue-mgr --commit <full-sha> --dry-run [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service findjob --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service findjob --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service pipeline --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service pipeline --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service met-nonlinear --commit <full-sha> --dry-run [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service met-nonlinear --commit <full-sha> --dry-run [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service k3sctl-adapter --commit <full-sha> --dry-run [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service mdtodo --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service mdtodo --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service claudeqq --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service claudeqq --commit <full-sha> [--dry-run] [--run-now] [--target D601]",
|
||||
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service code-queue --commit <full-sha> --dry-run [--target D601]",
|
||||
],
|
||||
firstStage: "install now writes the rendered systemd/Compose/config files and starts the registry",
|
||||
artifactConsumers: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. options module for scripts/src/artifact-registry.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/artifact-registry.ts:881-978 for #903.
|
||||
|
||||
@@ -24,7 +25,8 @@ import { d601K3sGuardShellLines } from "../d601-k3s-guard";
|
||||
import { composeRuntimeEnvValue } from "../runtime-env";
|
||||
|
||||
import type { ArtifactDeployEnvironment, ArtifactRegistryOptions } from "./types";
|
||||
import { defaultOptions } from "./types";
|
||||
import { artifactRegistryOptionsFromTarget } from "./types";
|
||||
import { resolveArtifactRegistryTarget } from "../ops/targets";
|
||||
|
||||
export function isHelpArg(value: string | undefined): boolean {
|
||||
return value === undefined || value === "help" || value === "--help" || value === "-h";
|
||||
@@ -60,65 +62,93 @@ export function environmentValue(value: string, option: string): ArtifactDeployE
|
||||
}
|
||||
|
||||
export function parseArtifactRegistryOptions(args: string[]): ArtifactRegistryOptions {
|
||||
const options = { ...defaultOptions };
|
||||
let targetSelection: string | null = null;
|
||||
let environment: ArtifactDeployEnvironment | null = null;
|
||||
let host: string | null = null;
|
||||
let port: number | null = null;
|
||||
let image: string | null = null;
|
||||
let baseDir: string | null = null;
|
||||
let storageDir: string | null = null;
|
||||
let timeoutMs: number | null = null;
|
||||
let dryRun = false;
|
||||
let runNow = false;
|
||||
let commit: string | null = null;
|
||||
let targetImage: string | null = null;
|
||||
let serviceId: string | null = null;
|
||||
let sourceRepo: string | null = null;
|
||||
let sourceRepoExplicit = false;
|
||||
let deployRef: string | null = null;
|
||||
let deployJsonService: ArtifactRegistryOptions["deployJsonService"] = null;
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
if (arg === "--dry-run") {
|
||||
options.dryRun = true;
|
||||
dryRun = true;
|
||||
} else if (arg === "--run-now") {
|
||||
options.runNow = true;
|
||||
runNow = true;
|
||||
} else if (arg === "--env" || arg === "--environment") {
|
||||
const environment = requireValue(args, index, arg);
|
||||
if (environment !== "dev" && environment !== "prod") throw new Error(`${arg} must be dev or prod`);
|
||||
options.environment = environment;
|
||||
environment = environmentValue(requireValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else if (arg === "--provider-id") {
|
||||
options.providerId = requireValue(args, index, arg);
|
||||
} else if (arg === "--target" || arg === "--provider-id") {
|
||||
targetSelection = requireValue(args, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === "--host") {
|
||||
options.host = requireValue(args, index, arg);
|
||||
host = requireValue(args, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === "--port") {
|
||||
options.port = positiveInt(requireValue(args, index, arg), arg);
|
||||
port = positiveInt(requireValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else if (arg === "--image") {
|
||||
options.image = requireValue(args, index, arg);
|
||||
image = requireValue(args, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === "--base-dir") {
|
||||
options.baseDir = absolutePath(requireValue(args, index, arg), arg);
|
||||
baseDir = absolutePath(requireValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else if (arg === "--storage-dir") {
|
||||
options.storageDir = absolutePath(requireValue(args, index, arg), arg);
|
||||
storageDir = absolutePath(requireValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else if (arg === "--timeout-ms") {
|
||||
options.timeoutMs = positiveInt(requireValue(args, index, arg), arg);
|
||||
timeoutMs = positiveInt(requireValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else if (arg === "--commit") {
|
||||
options.commit = commitValue(requireValue(args, index, arg), arg);
|
||||
commit = commitValue(requireValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else if (arg === "--target-image") {
|
||||
options.targetImage = requireValue(args, index, arg);
|
||||
targetImage = requireValue(args, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === "--service" || arg === "--service-id") {
|
||||
options.serviceId = requireValue(args, index, arg);
|
||||
serviceId = requireValue(args, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === "--source-repo") {
|
||||
options.sourceRepo = requireValue(args, index, arg);
|
||||
options.sourceRepoExplicit = true;
|
||||
sourceRepo = requireValue(args, index, arg);
|
||||
sourceRepoExplicit = true;
|
||||
index += 1;
|
||||
} else if (arg === "--deploy-ref") {
|
||||
options.deployRef = requireValue(args, index, arg);
|
||||
deployRef = requireValue(args, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === "--deploy-json-service") {
|
||||
options.deployJsonService = parseDeployJsonServiceContractBase64(requireValue(args, index, arg));
|
||||
index += 1;
|
||||
} else if (arg === "--env" || arg === "--environment") {
|
||||
options.environment = environmentValue(requireValue(args, index, arg), arg);
|
||||
deployJsonService = parseDeployJsonServiceContractBase64(requireValue(args, index, arg));
|
||||
index += 1;
|
||||
} else {
|
||||
throw new Error(`unknown artifact-registry option: ${arg}`);
|
||||
}
|
||||
}
|
||||
const options = artifactRegistryOptionsFromTarget(resolveArtifactRegistryTarget(targetSelection));
|
||||
options.environment = environment;
|
||||
options.host = host ?? options.host;
|
||||
options.port = port ?? options.port;
|
||||
options.image = image ?? options.image;
|
||||
options.baseDir = baseDir ?? options.baseDir;
|
||||
options.storageDir = storageDir ?? options.storageDir;
|
||||
options.timeoutMs = timeoutMs ?? options.timeoutMs;
|
||||
options.dryRun = dryRun;
|
||||
options.runNow = runNow;
|
||||
options.commit = commit;
|
||||
options.targetImage = targetImage;
|
||||
options.serviceId = serviceId;
|
||||
options.sourceRepo = sourceRepo ?? options.sourceRepo;
|
||||
options.sourceRepoExplicit = sourceRepoExplicit;
|
||||
options.deployRef = deployRef;
|
||||
options.deployJsonService = deployJsonService;
|
||||
if (options.host !== "127.0.0.1") throw new Error("--host is first-stage restricted to 127.0.0.1");
|
||||
if (options.port !== 5000) throw new Error("--port is first-stage restricted to 5000");
|
||||
return options;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. readonly module for scripts/src/artifact-registry.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/artifact-registry.ts:1934-2116 for #903.
|
||||
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
|
||||
import { composeRuntimeEnvValue } from "../runtime-env";
|
||||
|
||||
import type { ArtifactRegistryCommandRuntime, ArtifactRegistryOptions, ArtifactRegistryReadonlyProbe } from "./types";
|
||||
import { artifactRegistryOptionsTargetSummary, type ArtifactRegistryCommandRuntime, type ArtifactRegistryOptions, type ArtifactRegistryReadonlyProbe } from "./types";
|
||||
import { renderBundle } from "./bundle";
|
||||
import { commandTail } from "./consumer";
|
||||
import { annotateRemoteFirstReadonlyResult, annotateRemoteReadonlyResult, controlPlaneMissingResult, parsedCliData, readonlyCommandFailureResult, remoteFrontendHostFromEnv, runRemoteScript, unwrapRemoteArtifactRegistryResult } from "./remote";
|
||||
@@ -41,7 +42,7 @@ export function artifactRegistryReadonlyAutoRemotePlan(
|
||||
providerId: options.providerId,
|
||||
host: remoteHost,
|
||||
transport: "frontend",
|
||||
command: remoteHost === null ? null : `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --provider-id ${options.providerId}`,
|
||||
command: remoteHost === null ? null : `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --target ${options.targetId}`,
|
||||
failureClassification: remoteHost === null ? "control-plane-missing" : null,
|
||||
};
|
||||
}
|
||||
@@ -81,8 +82,8 @@ export function runReadonlyStatusViaRemoteFrontend(
|
||||
remoteHost,
|
||||
"artifact-registry",
|
||||
action,
|
||||
"--provider-id",
|
||||
options.providerId,
|
||||
"--target",
|
||||
options.targetId,
|
||||
"--timeout-ms",
|
||||
String(options.timeoutMs),
|
||||
];
|
||||
@@ -146,6 +147,7 @@ export function statusFromValues(options: ArtifactRegistryOptions, values: Recor
|
||||
installed,
|
||||
healthy,
|
||||
...decision,
|
||||
target: artifactRegistryOptionsTargetSummary(options),
|
||||
remoteCommandShape: readonlyRemoteCommandShape(healthMode ? "health" : "status", options),
|
||||
checks,
|
||||
observed: {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. remote module for scripts/src/artifact-registry.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/artifact-registry.ts:1659-1933 for #903.
|
||||
|
||||
@@ -228,8 +229,8 @@ export function controlPlaneMissingResult(
|
||||
localBackendCoreMissing: true,
|
||||
classification: "control-plane-missing",
|
||||
retryCommand: remoteHost === null
|
||||
? `bun scripts/cli.ts --main-server-ip <host> artifact-registry ${action} --provider-id ${options.providerId}`
|
||||
: `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --provider-id ${options.providerId}`,
|
||||
? `bun scripts/cli.ts --main-server-ip <host> artifact-registry ${action} --target ${options.targetId}`
|
||||
: `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --target ${options.targetId}`,
|
||||
},
|
||||
localObservation: localResult,
|
||||
remoteObservation: remoteResult,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. status module for scripts/src/artifact-registry.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/artifact-registry.ts:1081-1318 for #903.
|
||||
|
||||
@@ -23,13 +24,14 @@ import {
|
||||
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
|
||||
import { composeRuntimeEnvValue } from "../runtime-env";
|
||||
|
||||
import type { ArtifactRegistryFailureClassification, ArtifactRegistryOptions, RenderedBundle } from "./types";
|
||||
import { artifactRegistryOptionsTargetSummary, type ArtifactRegistryFailureClassification, type ArtifactRegistryOptions, type RenderedBundle } from "./types";
|
||||
import { renderBundle, shellQuote } from "./bundle";
|
||||
|
||||
export function plan(options: ArtifactRegistryOptions): Record<string, unknown> {
|
||||
const bundle = renderBundle(options);
|
||||
return {
|
||||
ok: true,
|
||||
target: artifactRegistryOptionsTargetSummary(options),
|
||||
providerId: options.providerId,
|
||||
mode: "d601-host-managed",
|
||||
firstStage: true,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. types module for scripts/src/artifact-registry.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/artifact-registry.ts:1-248 for #903.
|
||||
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
} from "../deploy-json-contract";
|
||||
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
|
||||
import { composeRuntimeEnvValue } from "../runtime-env";
|
||||
import { artifactRegistryTargetSummary, resolveArtifactRegistryTarget, type TargetConfigSource } from "../ops/targets";
|
||||
|
||||
import { composeFile, sha256 } from "./bundle";
|
||||
|
||||
@@ -30,11 +32,14 @@ export type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "
|
||||
export type ArtifactDeployEnvironment = "prod" | "dev";
|
||||
|
||||
export interface ArtifactRegistryOptions {
|
||||
targetId: string;
|
||||
environment: ArtifactDeployEnvironment | null;
|
||||
providerId: string;
|
||||
mode: string;
|
||||
host: string;
|
||||
port: number;
|
||||
image: string;
|
||||
repositoryPrefix: string;
|
||||
baseDir: string;
|
||||
storageDir: string;
|
||||
unitName: string;
|
||||
@@ -51,6 +56,9 @@ export interface ArtifactRegistryOptions {
|
||||
sourceRepoExplicit: boolean;
|
||||
deployRef: string | null;
|
||||
deployJsonService: DeployJsonServiceContract | null;
|
||||
consumerTarget: Record<string, unknown>;
|
||||
consumerCatalogRef: string;
|
||||
configSource: TargetConfigSource;
|
||||
}
|
||||
|
||||
export interface ArtifactRegistryCommandRuntime {
|
||||
@@ -96,29 +104,63 @@ export type ArtifactRegistryFailureClassification =
|
||||
| "registry-not-installed"
|
||||
| "registry-unhealthy";
|
||||
|
||||
export const defaultOptions: ArtifactRegistryOptions = {
|
||||
environment: null,
|
||||
providerId: "D601",
|
||||
host: "127.0.0.1",
|
||||
port: 5000,
|
||||
image: "registry:2.8.3",
|
||||
baseDir: "/home/ubuntu/.unidesk/artifact-registry",
|
||||
storageDir: "/home/ubuntu/.unidesk/registry-storage",
|
||||
unitName: "unidesk-artifact-registry.service",
|
||||
composeProject: "unidesk-artifact-registry",
|
||||
serviceName: "registry",
|
||||
containerName: "unidesk-artifact-registry",
|
||||
timeoutMs: 30_000,
|
||||
dryRun: false,
|
||||
runNow: false,
|
||||
commit: null,
|
||||
targetImage: null,
|
||||
serviceId: null,
|
||||
sourceRepo: "https://github.com/pikasTech/unidesk",
|
||||
sourceRepoExplicit: false,
|
||||
deployRef: null,
|
||||
deployJsonService: null,
|
||||
};
|
||||
export function artifactRegistryOptionsFromTarget(target = resolveArtifactRegistryTarget(null)): ArtifactRegistryOptions {
|
||||
return {
|
||||
targetId: target.targetId,
|
||||
environment: null,
|
||||
providerId: target.providerId,
|
||||
mode: target.mode,
|
||||
host: target.host,
|
||||
port: target.port,
|
||||
image: target.image,
|
||||
repositoryPrefix: target.repositoryPrefix,
|
||||
baseDir: target.baseDir,
|
||||
storageDir: target.storageDir,
|
||||
unitName: target.unitName,
|
||||
composeProject: target.composeProject,
|
||||
serviceName: target.serviceName,
|
||||
containerName: target.containerName,
|
||||
timeoutMs: target.timeoutMs,
|
||||
dryRun: false,
|
||||
runNow: false,
|
||||
commit: null,
|
||||
targetImage: null,
|
||||
serviceId: null,
|
||||
sourceRepo: target.sourceRepo,
|
||||
sourceRepoExplicit: false,
|
||||
deployRef: null,
|
||||
deployJsonService: null,
|
||||
consumerTarget: target.consumerTarget,
|
||||
consumerCatalogRef: target.consumerCatalogRef,
|
||||
configSource: target.configSource,
|
||||
};
|
||||
}
|
||||
|
||||
export const defaultOptions: ArtifactRegistryOptions = artifactRegistryOptionsFromTarget();
|
||||
|
||||
export function artifactRegistryOptionsTargetSummary(options: ArtifactRegistryOptions): Record<string, unknown> {
|
||||
return artifactRegistryTargetSummary({
|
||||
targetId: options.targetId,
|
||||
providerId: options.providerId,
|
||||
mode: options.mode,
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
endpoint: `http://${options.host}:${options.port}`,
|
||||
image: options.image,
|
||||
repositoryPrefix: options.repositoryPrefix,
|
||||
baseDir: options.baseDir,
|
||||
storageDir: options.storageDir,
|
||||
unitName: options.unitName,
|
||||
composeProject: options.composeProject,
|
||||
serviceName: options.serviceName,
|
||||
containerName: options.containerName,
|
||||
timeoutMs: options.timeoutMs,
|
||||
sourceRepo: options.sourceRepo,
|
||||
consumerTarget: options.consumerTarget,
|
||||
consumerCatalogRef: options.consumerCatalogRef,
|
||||
configSource: options.configSource,
|
||||
});
|
||||
}
|
||||
|
||||
export const supportedArtifactConsumerServices = [
|
||||
"auth-broker",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cleanup module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:1206-1428 for #903.
|
||||
|
||||
@@ -24,7 +25,7 @@ import { runSshCommandCapture } from "../ssh";
|
||||
import type { CiCleanupFailedPodsOptions, CiCleanupRunsOptions, CiTarget } from "./types";
|
||||
import { ciTargetGuardShellLines, shellQuote, tailTextLines } from "./options";
|
||||
import { runRemoteKubectl } from "./remote";
|
||||
import { ciTarget, tektonPipelineVersion, tektonTriggersVersion } from "./types";
|
||||
import { ciTarget, ciTargetSourceSummary, tektonPipelineVersion, tektonTriggersVersion } from "./types";
|
||||
|
||||
export async function status(target = ciTarget(null)): Promise<Record<string, unknown>> {
|
||||
const summary = await runRemoteKubectl([
|
||||
@@ -40,6 +41,7 @@ export async function status(target = ciTarget(null)): Promise<Record<string, un
|
||||
return {
|
||||
ok: true,
|
||||
providerId: target.providerId,
|
||||
target: ciTargetSourceSummary(target),
|
||||
orchestrator: "native-k3s",
|
||||
tekton: {
|
||||
pipelineVersion: tektonPipelineVersion,
|
||||
@@ -156,8 +158,8 @@ export async function cleanupRuns(config: UniDeskConfig, options: CiCleanupRunsO
|
||||
stderrTail: tailTextLines(deletion.stderr, 80),
|
||||
},
|
||||
next: options.confirm
|
||||
? { status: `bun scripts/cli.ts ci status --provider-id ${options.target.providerId}` }
|
||||
: { confirm: `bun scripts/cli.ts ci cleanup-runs --provider-id ${options.target.providerId} --min-age-minutes ${options.minAgeMinutes} --limit ${options.limit} --confirm` },
|
||||
? { status: `bun scripts/cli.ts ci status --target ${options.target.targetId}` }
|
||||
: { confirm: `bun scripts/cli.ts ci cleanup-runs --target ${options.target.targetId} --min-age-minutes ${options.minAgeMinutes} --limit ${options.limit} --confirm` },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,7 +246,7 @@ export async function cleanupFailedPods(config: UniDeskConfig, options: CiCleanu
|
||||
stderrTail: tailTextLines(result.stderr, 20),
|
||||
},
|
||||
next: options.confirm
|
||||
? { status: `bun scripts/cli.ts ci status --provider-id ${options.target.providerId}` }
|
||||
: { confirm: `bun scripts/cli.ts ci cleanup-failed-pods --provider-id ${options.target.providerId} --namespace ${options.namespace} --min-age-minutes ${options.minAgeMinutes} --limit ${options.limit} --confirm` },
|
||||
? { status: `bun scripts/cli.ts ci status --target ${options.target.targetId}` }
|
||||
: { confirm: `bun scripts/cli.ts ci cleanup-failed-pods --target ${options.target.targetId} --namespace ${options.namespace} --min-age-minutes ${options.minAgeMinutes} --limit ${options.limit} --confirm` },
|
||||
};
|
||||
}
|
||||
|
||||
+20
-4
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. entry module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:3410-3600 for #903.
|
||||
|
||||
@@ -29,7 +30,7 @@ import { logs } from "./logs";
|
||||
import { blockedArtifactResult, blockedReason, boolFlag, ciCleanupFailedPodsOptions, ciCleanupRunsOptions, ciLogsOptions, isHelpArg, numberOption, publishTransportOption, requireDesiredRef, requireFullCommit, requireRepoRelativePath, requireRevision, requireServiceId, resolveCatalogArtifact, stringOption, userServicePublishBoundaryBlock } from "./options";
|
||||
import { backendCoreArtifactSourceHostPath, userServiceArtifactSourceHostPath } from "./pipelinerun";
|
||||
import { publishBackendCoreArtifact, publishUserServiceArtifact, run } from "./publish";
|
||||
import { ciTarget, d601ProviderId, providerIdOption } from "./types";
|
||||
import { ciTarget, ciTargetSourceSummary, providerIdOption } from "./types";
|
||||
|
||||
export async function runCiCommand(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown>> {
|
||||
const [action = "status", nameArg] = args;
|
||||
@@ -44,6 +45,21 @@ export async function runCiCommand(config: UniDeskConfig, args: string[]): Promi
|
||||
return options.wait ? install(config, options) : installAsync(options);
|
||||
}
|
||||
if (action === "install-status") return installStatus(nameArg ?? "latest");
|
||||
if (action === "plan") {
|
||||
const target = ciTarget(providerIdOption(args));
|
||||
return {
|
||||
ok: true,
|
||||
action: "ci-plan",
|
||||
mutation: false,
|
||||
target: ciTargetSourceSummary(target),
|
||||
configTruth: target.configSource.configPath,
|
||||
next: {
|
||||
status: `bun scripts/cli.ts ci status --target ${target.targetId}`,
|
||||
installDryRun: `bun scripts/cli.ts ci install --target ${target.targetId} --skip-prewarm --skip-tekton-install`,
|
||||
run: `bun scripts/cli.ts ci run --target ${target.targetId} --revision <commit>`,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (action === "status") return status(ciTarget(providerIdOption(args)));
|
||||
if (action === "run") {
|
||||
const target = ciTarget(providerIdOption(args));
|
||||
@@ -130,12 +146,12 @@ export async function runCiCommand(config: UniDeskConfig, args: string[]): Promi
|
||||
if (action === "logs") return logs(config, nameArg ?? "", ciTarget(providerIdOption(args)), ciLogsOptions(args));
|
||||
if (action === "cleanup-runs") return cleanupRuns(config, ciCleanupRunsOptions(args));
|
||||
if (action === "cleanup-failed-pods") return cleanupFailedPods(config, ciCleanupFailedPodsOptions(args));
|
||||
throw new Error("ci command must be one of: install, status, run, publish-backend-core, publish-user-service, run-dev-e2e, logs, cleanup-runs, cleanup-failed-pods");
|
||||
throw new Error("ci command must be one of: install, plan, status, run, publish-backend-core, publish-user-service, run-dev-e2e, logs, cleanup-runs, cleanup-failed-pods");
|
||||
}
|
||||
|
||||
export function startCiInstallJob(providerId = d601ProviderId): Record<string, unknown> {
|
||||
export function startCiInstallJob(targetId?: string): Record<string, unknown> {
|
||||
return installAsync({
|
||||
target: ciTarget(providerId),
|
||||
target: ciTarget(targetId ?? null),
|
||||
skipPrewarm: false,
|
||||
skipTektonInstall: false,
|
||||
wait: false,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. help module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:3273-3409 for #903.
|
||||
|
||||
@@ -58,9 +59,10 @@ export function ciHelp(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts ci install --skip-prewarm --skip-tekton-install",
|
||||
"bun scripts/cli.ts ci install-status latest",
|
||||
"bun scripts/cli.ts ci install --wait --skip-prewarm --skip-tekton-install",
|
||||
"bun scripts/cli.ts ci install --provider-id G14",
|
||||
"bun scripts/cli.ts ci plan --target D601",
|
||||
"bun scripts/cli.ts ci install --target G14",
|
||||
"bun scripts/cli.ts ci run --revision <commit>",
|
||||
"bun scripts/cli.ts ci run --provider-id G14 --revision <commit>",
|
||||
"bun scripts/cli.ts ci run --target G14 --revision <commit>",
|
||||
"bun scripts/cli.ts ci publish-backend-core --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci publish-user-service --service baidu-netdisk --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci publish-user-service --service mdtodo --commit <full-sha>",
|
||||
@@ -69,11 +71,11 @@ export function ciHelp(): Record<string, unknown> {
|
||||
"bun scripts/cli.ts ci publish-user-service --service decision-center --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci publish-user-service --service frontend --commit <full-sha>",
|
||||
"bun scripts/cli.ts ci run-dev-e2e --wait-ms 600000",
|
||||
"bun scripts/cli.ts ci logs <runId-or-pipelineRun> [--provider-id G14] [--tail-lines 80]",
|
||||
"bun scripts/cli.ts ci cleanup-runs --provider-id D601 --min-age-minutes 60 --limit 50 --dry-run",
|
||||
"bun scripts/cli.ts ci cleanup-runs --provider-id D601 --min-age-minutes 60 --limit 50 --confirm",
|
||||
"bun scripts/cli.ts ci cleanup-failed-pods --provider-id D601 --namespace unidesk-ci --min-age-minutes 60 --dry-run",
|
||||
"bun scripts/cli.ts ci cleanup-failed-pods --provider-id D601 --namespace unidesk-ci --min-age-minutes 60 --confirm",
|
||||
"bun scripts/cli.ts ci logs <runId-or-pipelineRun> [--target G14] [--tail-lines 80]",
|
||||
"bun scripts/cli.ts ci cleanup-runs --target D601 --min-age-minutes 60 --limit 50 --dry-run",
|
||||
"bun scripts/cli.ts ci cleanup-runs --target D601 --min-age-minutes 60 --limit 50 --confirm",
|
||||
"bun scripts/cli.ts ci cleanup-failed-pods --target D601 --namespace unidesk-ci --min-age-minutes 60 --dry-run",
|
||||
"bun scripts/cli.ts ci cleanup-failed-pods --target D601 --namespace unidesk-ci --min-age-minutes 60 --confirm",
|
||||
],
|
||||
tekton: {
|
||||
pipelineVersion: tektonPipelineVersion,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. install module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:1429-1559 for #903.
|
||||
|
||||
@@ -99,8 +100,8 @@ export function ciInstallCommand(options: CiInstallOptions): string[] {
|
||||
"scripts/cli.ts",
|
||||
"ci",
|
||||
"install",
|
||||
"--provider-id",
|
||||
options.target.providerId,
|
||||
"--target",
|
||||
options.target.targetId,
|
||||
"--wait",
|
||||
...(options.skipPrewarm ? ["--skip-prewarm"] : []),
|
||||
...(options.skipTektonInstall ? ["--skip-tekton-install"] : []),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. logs module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:3168-3272 for #903.
|
||||
|
||||
@@ -61,6 +62,7 @@ export function pipelineRunLogsScript(name: string, options: CiLogsOptions): str
|
||||
export function ciLogsResultFromCapture(args: {
|
||||
ok: boolean;
|
||||
providerId: string;
|
||||
targetId: string;
|
||||
kind: "run" | "pipelinerun";
|
||||
name: string;
|
||||
options: CiLogsOptions;
|
||||
@@ -86,8 +88,8 @@ export function ciLogsResultFromCapture(args: {
|
||||
...(args.dispatchTaskId === undefined ? {} : { dispatchTaskId: args.dispatchTaskId }),
|
||||
fullOutputAvailable: args.options.capture === "ssh-stream",
|
||||
next: [
|
||||
`bun scripts/cli.ts ci logs ${args.name} --provider-id ${args.providerId} --tail-lines ${Math.min(args.options.tailLines * 2, 2000)}`,
|
||||
`bun scripts/cli.ts ci status --provider-id ${args.providerId}`,
|
||||
`bun scripts/cli.ts ci logs ${args.name} --target ${args.targetId} --tail-lines ${Math.min(args.options.tailLines * 2, 2000)}`,
|
||||
`bun scripts/cli.ts ci status --target ${args.targetId}`,
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -104,6 +106,7 @@ export async function logs(config: UniDeskConfig, name: string, target = ciTarge
|
||||
return ciLogsResultFromCapture({
|
||||
ok: resultOk,
|
||||
providerId: target.providerId,
|
||||
targetId: target.targetId,
|
||||
kind: "run",
|
||||
name,
|
||||
options,
|
||||
@@ -121,6 +124,7 @@ export async function logs(config: UniDeskConfig, name: string, target = ciTarge
|
||||
return ciLogsResultFromCapture({
|
||||
ok: result.exitCode === 0,
|
||||
providerId: target.providerId,
|
||||
targetId: target.targetId,
|
||||
kind: "pipelinerun",
|
||||
name,
|
||||
options,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. publish-preflight module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:2317-2567 for #903.
|
||||
|
||||
@@ -89,7 +90,7 @@ export async function publishUserServicePreflight(
|
||||
raw: sshProbe.ok ? undefined : dispatchPreflightFailure("host.ssh readonly probe", sshProbe).raw,
|
||||
}));
|
||||
|
||||
const registryOptions = parseArtifactRegistryOptions(["--provider-id", providerId]);
|
||||
const registryOptions = parseArtifactRegistryOptions(["--target", providerId]);
|
||||
const registryProbe = buildArtifactRegistryReadonlyProbe("health", registryOptions);
|
||||
const registryDispatch = await transport.dispatchHostSsh(registryProbe.script, Math.max(registryProbe.timeoutMs, 30_000), registryProbe.timeoutMs);
|
||||
const registryCommand = commandResultFromDispatch(transport.artifactRegistryCommand(registryProbe), transport.commandCwd, registryDispatch);
|
||||
@@ -131,7 +132,7 @@ export async function publishUserServicePreflight(
|
||||
? "From a Code Queue runner, rerun this read-only preflight through the existing remote frontend transport: bun scripts/cli.ts --main-server-ip <host> ci publish-user-service --service <id> --commit <full-sha> --dry-run."
|
||||
: "Run from the main-server CLI or use remote frontend transport against a healthy frontend/backend-core path.",
|
||||
"Restore backend-core/database/provider-gateway/Host SSH connectivity before retrying artifact publication.",
|
||||
"Use bun scripts/cli.ts artifact-registry health --provider-id D601 to recheck registry reachability after the control bridge is restored.",
|
||||
"Use bun scripts/cli.ts artifact-registry health --target D601 to recheck registry reachability after the control bridge is restored.",
|
||||
],
|
||||
boundary: "preflight is read-only: no D601 source export, no Tekton PipelineRun, no image push, no deploy apply, no service restart",
|
||||
};
|
||||
@@ -182,7 +183,7 @@ export async function publishBackendCorePreflight(
|
||||
raw: ciRunnerReady ? undefined : dispatchPreflightFailure("backend-core ci runner readonly probe", ciProbe).raw,
|
||||
}));
|
||||
|
||||
const registryOptions = parseArtifactRegistryOptions(["--provider-id", providerId]);
|
||||
const registryOptions = parseArtifactRegistryOptions(["--target", providerId]);
|
||||
const registryProbe = buildArtifactRegistryReadonlyProbe("health", registryOptions);
|
||||
const registryDispatch = await transport.dispatchHostSsh(registryProbe.script, Math.max(registryProbe.timeoutMs, 30_000), registryProbe.timeoutMs);
|
||||
const registryCommand = commandResultFromDispatch(transport.artifactRegistryCommand(registryProbe), transport.commandCwd, registryDispatch);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. publish module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:2568-2959 for #903.
|
||||
|
||||
@@ -56,8 +57,8 @@ export async function run(options: CiOptions): Promise<Record<string, unknown>>
|
||||
},
|
||||
condition,
|
||||
next: [
|
||||
`bun scripts/cli.ts ci logs ${name} --provider-id ${options.target.providerId}`,
|
||||
`bun scripts/cli.ts ci status --provider-id ${options.target.providerId}`,
|
||||
`bun scripts/cli.ts ci logs ${name} --target ${options.target.targetId}`,
|
||||
`bun scripts/cli.ts ci status --target ${options.target.targetId}`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
+48
-31
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. types module for scripts/src/ci.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/ci.ts:1-267 for #903.
|
||||
|
||||
@@ -20,13 +21,17 @@ import {
|
||||
} from "../artifact-registry";
|
||||
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
|
||||
import { runSshCommandCapture } from "../ssh";
|
||||
import { cicdTargetSummary, resolveCicdTarget, type TargetConfigSource } from "../ops/targets";
|
||||
|
||||
import { status } from "./cleanup";
|
||||
import { stringOption } from "./options";
|
||||
|
||||
export const d601ProviderId = "D601";
|
||||
const d601YamlTarget = resolveCicdTarget("D601");
|
||||
const g14YamlTarget = resolveCicdTarget("G14");
|
||||
|
||||
export const d601Kubeconfig = d601NativeKubeconfig;
|
||||
export const d601ProviderId = d601YamlTarget.providerId;
|
||||
|
||||
export const d601Kubeconfig = d601YamlTarget.kubeconfig;
|
||||
|
||||
export const tektonPipelineVersion = "v1.12.0";
|
||||
|
||||
@@ -46,9 +51,9 @@ export const codeQueueDirectDockerBaseImage = "unidesk-code-queue:d601";
|
||||
|
||||
export const providerDispatchCompletionLagMs = 45_000;
|
||||
|
||||
export const defaultCiPipelineManifest = "src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.yaml";
|
||||
export const defaultCiPipelineManifest = d601YamlTarget.pipelineManifest;
|
||||
|
||||
export const g14CiPipelineManifest = "src/components/microservices/k3sctl-adapter/k3s/ci/unidesk-ci.pipeline.g14.yaml";
|
||||
export const g14CiPipelineManifest = g14YamlTarget.pipelineManifest;
|
||||
|
||||
export const ciRuntimeImages = [
|
||||
"rancher/mirrored-pause:3.6",
|
||||
@@ -65,36 +70,44 @@ export const ciRuntimeImages = [
|
||||
];
|
||||
|
||||
export function ciTarget(providerId: string | null): CiTarget {
|
||||
const normalized = providerId ?? d601ProviderId;
|
||||
if (normalized === d601ProviderId) {
|
||||
return {
|
||||
providerId: d601ProviderId,
|
||||
kubeconfig: d601Kubeconfig,
|
||||
hostCwd: "/home/ubuntu",
|
||||
homeDir: "/home/ubuntu",
|
||||
pipelineManifest: defaultCiPipelineManifest,
|
||||
codeQueueImage: ciCodeQueueImage,
|
||||
guardName: "d601_native_k3s_guard",
|
||||
requiredNodeName: "d601",
|
||||
};
|
||||
}
|
||||
if (normalized === "G14") {
|
||||
return {
|
||||
providerId: "G14",
|
||||
kubeconfig: d601Kubeconfig,
|
||||
hostCwd: "/root",
|
||||
homeDir: "/root",
|
||||
pipelineManifest: g14CiPipelineManifest,
|
||||
codeQueueImage: ciCodeQueueImage,
|
||||
guardName: "g14_native_k3s_guard",
|
||||
requiredNodeLabel: { key: "unidesk.ai/node-id", value: "G14" },
|
||||
};
|
||||
}
|
||||
throw new Error(`ci --provider-id currently supports D601 or G14, got ${normalized}`);
|
||||
const target = resolveCicdTarget(providerId);
|
||||
return {
|
||||
targetId: target.targetId,
|
||||
providerId: target.providerId,
|
||||
kubeRoute: target.kubeRoute,
|
||||
kubeconfig: target.kubeconfig,
|
||||
hostCwd: target.hostCwd,
|
||||
homeDir: target.homeDir,
|
||||
pipelineManifest: target.pipelineManifest,
|
||||
codeQueueImage: target.codeQueueImage,
|
||||
guardName: target.guardName,
|
||||
requiredNodeName: target.requiredNodeName,
|
||||
requiredNodeLabel: target.requiredNodeLabel,
|
||||
artifactRegistryConfigRef: target.artifactRegistryConfigRef,
|
||||
configSource: target.configSource,
|
||||
};
|
||||
}
|
||||
|
||||
export function providerIdOption(args: string[]): string | null {
|
||||
return stringOption(args, "--provider-id") ?? stringOption(args, "--provider");
|
||||
return stringOption(args, "--target") ?? stringOption(args, "--provider-id") ?? stringOption(args, "--provider");
|
||||
}
|
||||
|
||||
export function ciTargetSourceSummary(target: CiTarget): Record<string, unknown> {
|
||||
return cicdTargetSummary({
|
||||
targetId: target.targetId,
|
||||
providerId: target.providerId,
|
||||
kubeRoute: target.kubeRoute,
|
||||
kubeconfig: target.kubeconfig,
|
||||
hostCwd: target.hostCwd,
|
||||
homeDir: target.homeDir,
|
||||
pipelineManifest: target.pipelineManifest,
|
||||
codeQueueImage: target.codeQueueImage,
|
||||
guardName: target.guardName,
|
||||
requiredNodeName: target.requiredNodeName,
|
||||
requiredNodeLabel: target.requiredNodeLabel,
|
||||
artifactRegistryConfigRef: target.artifactRegistryConfigRef,
|
||||
configSource: target.configSource,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CiOptions {
|
||||
@@ -112,7 +125,9 @@ export interface CiInstallOptions {
|
||||
}
|
||||
|
||||
export interface CiTarget {
|
||||
targetId: string;
|
||||
providerId: string;
|
||||
kubeRoute: string;
|
||||
kubeconfig: string;
|
||||
hostCwd: string;
|
||||
homeDir: string;
|
||||
@@ -121,6 +136,8 @@ export interface CiTarget {
|
||||
guardName: string;
|
||||
requiredNodeName?: string;
|
||||
requiredNodeLabel?: { key: string; value: string };
|
||||
artifactRegistryConfigRef?: string;
|
||||
configSource: TargetConfigSource;
|
||||
}
|
||||
|
||||
export interface CiPublishBackendCoreOptions {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
import { buildExecutionDiagnostics, buildSchedulerHeartbeat, schedulerHeartbeatStaleMs, staleRecoveryCandidate, taskHasTraceGapButFreshHeartbeat } from "../../src/components/microservices/code-queue/src/execution-diagnostics";
|
||||
import type { ActiveRun } from "../../src/components/microservices/code-queue/src/code-agent/common";
|
||||
import type { CodeQueueExecutionDiagnostics, QueueTask, SchedulerActiveRunHeartbeat, TaskStatus } from "../../src/components/microservices/code-queue/src/types";
|
||||
@@ -24,6 +25,7 @@ const now = "2026-05-19T00:10:00.000Z";
|
||||
const freshAt = "2026-05-19T00:09:50.000Z";
|
||||
const oldTraceAt = "2026-05-18T23:40:00.000Z";
|
||||
const expiredAt = "2026-05-18T23:50:00.000Z";
|
||||
const fixtureProviderId = "D601";
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: Record<string, unknown> = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
@@ -38,7 +40,7 @@ function fixtureTask(id: string, status: TaskStatus, heartbeat: SchedulerActiveR
|
||||
basePrompt: `${id} prompt`,
|
||||
referenceTaskIds: [],
|
||||
referenceInjection: null,
|
||||
providerId: "D601",
|
||||
providerId: fixtureProviderId,
|
||||
cwd: "/workspace",
|
||||
model: "gpt-5.5",
|
||||
reasoningEffort: null,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. entry module for scripts/src/deploy.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/deploy.ts:3528-3700 for #903.
|
||||
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
type DeployJsonExecutorMirror,
|
||||
type DeployJsonServiceContract,
|
||||
} from "../deploy-json-contract";
|
||||
import { resolveArtifactRegistryTarget } from "../ops/targets";
|
||||
|
||||
import type { DeployAction } from "./types";
|
||||
import { applyJob, devArtifactApplyJob, prodArtifactApplyJob, runApplyNow, runArtifactConsumerApplyNow, runProdArtifactApplyNow } from "./artifact-jobs";
|
||||
@@ -108,7 +110,9 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
|
||||
|
||||
export async function runCodeQueueDeployCompatCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {
|
||||
if (args.includes("--skip-build")) throw new Error("codex deploy is disabled; --skip-build is not supported");
|
||||
const providerId = optionValue(args, ["--provider-id", "--provider"]) ?? "D601";
|
||||
if (providerId !== "D601") throw new Error(`codex deploy compatibility path only supports D601; got ${providerId}`);
|
||||
const selectedTarget = optionValue(args, ["--target", "--provider-id", "--provider"]);
|
||||
if (selectedTarget === undefined) throw new Error("codex deploy compatibility path is a legacy adapter and requires explicit --target D601; it no longer falls back to a hidden provider default");
|
||||
const target = resolveArtifactRegistryTarget(selectedTarget);
|
||||
if (target.providerId !== "D601") throw new Error(`codex deploy compatibility path only supports D601; got ${target.providerId}`);
|
||||
throw new Error("codex deploy is disabled because D601 maintenance-channel direct deployment must not deploy Code Queue. Use the dev-only artifact consumer with deploy apply --env dev --service code-queue or artifact-registry deploy-service --env dev --service code-queue; production Code Queue artifact deployment is unsupported.");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. options module for scripts/src/deploy.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/deploy.ts:213-535 for #903.
|
||||
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
type DeployJsonExecutorMirror,
|
||||
type DeployJsonServiceContract,
|
||||
} from "../deploy-json-contract";
|
||||
import { resolveArtifactRegistryTarget } from "../ops/targets";
|
||||
|
||||
import type { DeployEnvironment, DeployManifest, DeployManifestService, DeployOptions } from "./types";
|
||||
import { step } from "./remote";
|
||||
@@ -44,7 +46,7 @@ export function deployHelp(action: string | undefined = undefined): Record<strin
|
||||
usage: {
|
||||
check: "bun scripts/cli.ts deploy check [--file deploy.json | --env dev|prod] [--service id]",
|
||||
plan: "bun scripts/cli.ts deploy plan [--file deploy.json | --env dev|prod] [--service id]",
|
||||
apply: "bun scripts/cli.ts deploy apply [--file deploy.json | --env dev|prod] [--service id] [--commit full-sha] [--dry-run] [--force] [--timeout-ms N] [--provider-id D601|local] [--run-now]",
|
||||
apply: "bun scripts/cli.ts deploy apply [--file deploy.json | --env dev|prod] [--service id] [--commit full-sha] [--dry-run] [--force] [--timeout-ms N] [--target D601|local] [--run-now]",
|
||||
guard: "bun scripts/cli.ts deploy guard code-queue-source [--root /home/ubuntu/cq-deploy]",
|
||||
},
|
||||
actions: {
|
||||
@@ -67,7 +69,7 @@ export function deployHelp(action: string | undefined = undefined): Record<strin
|
||||
{ name: "--dry-run", description: "Prepare and validate without mutating the target service." },
|
||||
{ name: "--force", description: "Redeploy even when the live commit appears up to date." },
|
||||
{ name: "--timeout-ms <n>", default: defaultTimeoutMs, description: "Per-step timeout budget where supported." },
|
||||
{ name: "--provider-id <id>", default: "D601", description: "Provider used by artifact-registry consumers; use local only from the D601 host CLI." },
|
||||
{ name: "--target <id>", default: "config/artifact-registry.yaml#defaults.targetId", description: "Artifact-registry consumer target. --provider-id is accepted as a legacy alias." },
|
||||
{ name: "--run-now", description: "Run apply in the foreground worker process; omit it for fire-and-forget async job mode." },
|
||||
{ name: "guard code-queue-source --root <path>", description: "Validate Code Queue hostPath source relative imports before any scheduler rollout; failures report degradedReason and missing import targets." },
|
||||
],
|
||||
@@ -342,12 +344,13 @@ export function parseOptions(args: string[]): DeployOptions {
|
||||
if (commitOverride !== undefined && environment === null) throw new Error("deploy --commit is only supported for --env dev|prod artifact consumer apply");
|
||||
if (commitOverride !== undefined && !isFullGitSha(commitOverride)) throw new Error("deploy --commit must be a full 40-character commit SHA");
|
||||
if (commitOverride !== undefined && serviceId === null) throw new Error("deploy --commit requires --service so artifact consumer apply is unambiguous");
|
||||
const artifactTarget = resolveArtifactRegistryTarget(optionValue(args, ["--target", "--provider-id", "--provider"]) ?? null);
|
||||
return {
|
||||
file: optionValue(args, ["--file"]) ?? defaultDeployFile,
|
||||
environment,
|
||||
serviceId,
|
||||
commitOverride: commitOverride?.toLowerCase() ?? null,
|
||||
providerId: optionValue(args, ["--provider-id", "--provider"]) ?? "D601",
|
||||
providerId: artifactTarget.providerId,
|
||||
runNow: args.includes("--run-now"),
|
||||
dryRun: args.includes("--dry-run"),
|
||||
force: args.includes("--force"),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. service-plan module for scripts/src/deploy.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/deploy.ts:634-1236 for #903.
|
||||
|
||||
@@ -30,7 +31,7 @@ import {
|
||||
|
||||
import type { ArtifactConsumerPlanKind, DeployEnvironment, DeployEnvironmentTarget, DeployManifest, DeployManifestService } from "./types";
|
||||
import { targetIsMain, targetWorkDir } from "./paths";
|
||||
import { artifactConsumerDryRunBlockedServiceIds, d601MaintenanceDeployAllowedServiceIds, devApplySupportedServiceIds, devArtifactConsumerServiceIds, k8sNamespace, prodArtifactConsumerServiceIds, prodArtifactLiveApplyBlockedServiceIds, unideskRepoUrl } from "./types";
|
||||
import { artifactConsumerDryRunBlockedServiceIds, d601DeployNodeId, d601DeployProviderId, d601MaintenanceDeployAllowedServiceIds, devApplySupportedServiceIds, devArtifactConsumerServiceIds, k8sNamespace, prodArtifactConsumerServiceIds, prodArtifactLiveApplyBlockedServiceIds, unideskRepoUrl } from "./types";
|
||||
|
||||
export function frontendCoreDeployService(config: UniDeskConfig): UniDeskMicroserviceConfig {
|
||||
return {
|
||||
@@ -178,7 +179,7 @@ export function devK3sDeployService(id: string): UniDeskMicroserviceConfig | und
|
||||
return {
|
||||
id,
|
||||
name: spec.name,
|
||||
providerId: "D601",
|
||||
providerId: d601DeployProviderId,
|
||||
description: spec.description,
|
||||
repository: {
|
||||
url: spec.repoUrl ?? unideskRepoUrl,
|
||||
@@ -205,11 +206,11 @@ export function devK3sDeployService(id: string): UniDeskMicroserviceConfig | und
|
||||
adapterServiceId: "k3sctl-adapter",
|
||||
k3sServiceId: spec.composeService,
|
||||
namespace: "unidesk-dev",
|
||||
expectedNodeIds: ["D601"],
|
||||
activeNodeId: "D601",
|
||||
expectedNodeIds: [d601DeployNodeId],
|
||||
activeNodeId: d601DeployNodeId,
|
||||
},
|
||||
development: {
|
||||
providerId: "D601",
|
||||
providerId: d601DeployProviderId,
|
||||
sshPassthrough: true,
|
||||
worktreePath: id === "code-queue"
|
||||
? "/home/ubuntu/unidesk-dev-code-queue-deploy/code-queue"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. types module for scripts/src/deploy.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/deploy.ts:1-212 for #903.
|
||||
|
||||
@@ -15,6 +16,7 @@ import { coreInternalFetch } from "../microservices";
|
||||
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
|
||||
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
|
||||
import { composeRuntimeEnvValue } from "../runtime-env";
|
||||
import { resolveArtifactRegistryTarget } from "../ops/targets";
|
||||
import {
|
||||
compareDeployJsonExecutorMirrors,
|
||||
deployJsonCommitImage,
|
||||
@@ -151,6 +153,12 @@ export const shortRemoteTimeoutMs = 20_000;
|
||||
|
||||
export const providerDispatchCompletionLagMs = 45_000;
|
||||
|
||||
const d601ArtifactRegistryTarget = resolveArtifactRegistryTarget("D601");
|
||||
|
||||
export const d601DeployProviderId = d601ArtifactRegistryTarget.providerId;
|
||||
|
||||
export const d601DeployNodeId = d601ArtifactRegistryTarget.targetId;
|
||||
|
||||
export const pollIntervalMs = 5_000;
|
||||
|
||||
export const remoteDeployRoot = "/home/ubuntu/.unidesk/deploy";
|
||||
@@ -235,8 +243,8 @@ export const deployEnvironmentTargets: Record<DeployEnvironment, DeployEnvironme
|
||||
name: "unidesk",
|
||||
},
|
||||
provider: {
|
||||
providerId: "D601",
|
||||
nodeId: "D601",
|
||||
providerId: d601DeployProviderId,
|
||||
nodeId: d601DeployNodeId,
|
||||
credentialScope: "prod",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
|
||||
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-25-p0-web-probe-sentinel.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
// Responsibility: YAML source-of-truth parsing for HWLAB node/lane Workbench observability.
|
||||
import { readFileSync } from "node:fs";
|
||||
import { rootPath } from "./config";
|
||||
|
||||
export type HwlabRuntimeLane = "v02" | "v03";
|
||||
export type HwlabRuntimeLane = string;
|
||||
|
||||
export interface HwlabRuntimeNodeSpec {
|
||||
readonly id: string;
|
||||
@@ -520,8 +521,8 @@ function nodeConfig(id: string, raw: Record<string, unknown>): HwlabRuntimeNodeS
|
||||
};
|
||||
}
|
||||
|
||||
function isSupportedLaneId(id: string): id is HwlabRuntimeLane {
|
||||
return id === "v02" || id === "v03";
|
||||
function assertYamlDeclaredLaneId(id: string, path: string): asserts id is HwlabRuntimeLane {
|
||||
if (!/^[A-Za-z0-9._-]+$/u.test(id)) throw new Error(`${path} has an unsupported lane id; lane ids must be YAML-declared simple ids`);
|
||||
}
|
||||
|
||||
function laneConfig(id: HwlabRuntimeLane, raw: Record<string, unknown>): HwlabLaneConfig {
|
||||
@@ -1043,12 +1044,13 @@ function readHwlabNodeLaneConfig(): HwlabNodeLaneConfig {
|
||||
);
|
||||
const laneEntries = sortedRecordEntries(parsed.lanes, "lanes");
|
||||
const lanes = Object.fromEntries(laneEntries.map(([id, item]) => {
|
||||
if (!isSupportedLaneId(id)) throw new Error(`lanes.${id} is not supported by this CLI build`);
|
||||
assertYamlDeclaredLaneId(id, `lanes.${id}`);
|
||||
return [id, laneConfig(id, item)];
|
||||
})) as Record<HwlabRuntimeLane, HwlabLaneConfig>;
|
||||
const laneTargets: Partial<Record<HwlabRuntimeLane, Record<string, HwlabLaneConfig>>> = {};
|
||||
})) as Record<string, HwlabLaneConfig>;
|
||||
const laneTargets: Partial<Record<string, Record<string, HwlabLaneConfig>>> = {};
|
||||
for (const [id, item] of laneEntries) {
|
||||
if (!isSupportedLaneId(id) || item.targets === undefined) continue;
|
||||
assertYamlDeclaredLaneId(id, `lanes.${id}`);
|
||||
if (item.targets === undefined) continue;
|
||||
laneTargets[id] = Object.fromEntries(sortedRecordEntries(item.targets, `lanes.${id}.targets`).map(([nodeId, target]) => [
|
||||
nodeId,
|
||||
laneTargetConfig(id, nodeId, item, target),
|
||||
@@ -1080,7 +1082,7 @@ function validateConfigEnvelope(parsed: Record<string, unknown>): void {
|
||||
|
||||
function parseDefaultTarget(raw: Record<string, unknown>): { readonly node: string; readonly lane: HwlabRuntimeLane } {
|
||||
const lane = stringField(raw, "lane", `${HWLAB_NODE_LANE_CONFIG_PATH}.defaults`);
|
||||
if (!isSupportedLaneId(lane)) throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.defaults.lane is not supported by this CLI build`);
|
||||
assertYamlDeclaredLaneId(lane, `${HWLAB_NODE_LANE_CONFIG_PATH}.defaults.lane`);
|
||||
return {
|
||||
node: stringField(raw, "node", `${HWLAB_NODE_LANE_CONFIG_PATH}.defaults`),
|
||||
lane,
|
||||
@@ -1145,7 +1147,7 @@ function buildRuntimeLaneSpec(config: HwlabLaneConfig): HwlabRuntimeLaneSpec {
|
||||
|
||||
const RUNTIME_LANE_SPECS = Object.fromEntries(
|
||||
Object.values(HWLAB_NODE_LANE_CONFIG.lanes).map((config) => [config.id, buildRuntimeLaneSpec(config)]),
|
||||
) as Record<HwlabRuntimeLane, HwlabRuntimeLaneSpec>;
|
||||
) as Record<string, HwlabRuntimeLaneSpec>;
|
||||
|
||||
export function isHwlabRuntimeLane(value: string): value is HwlabRuntimeLane {
|
||||
return Object.prototype.hasOwnProperty.call(RUNTIME_LANE_SPECS, value);
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
// Responsibility: shared YAML configRef parsing and redacted reference summaries.
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { rootPath } from "../config";
|
||||
|
||||
export interface ParsedConfigRef {
|
||||
ref: string;
|
||||
file: string;
|
||||
fragment: string;
|
||||
}
|
||||
|
||||
export interface ResolvedConfigRef {
|
||||
ref: string;
|
||||
file: string;
|
||||
fragment: string;
|
||||
value: unknown;
|
||||
valueType: string;
|
||||
present: true;
|
||||
sha256: string;
|
||||
summary: unknown;
|
||||
}
|
||||
|
||||
export function parseConfigRef(ref: string, label = "configRef"): ParsedConfigRef {
|
||||
const [file, fragment, extra] = ref.split("#");
|
||||
if (extra !== undefined || file === undefined || fragment === undefined || file.length === 0 || fragment.length === 0) {
|
||||
throw new Error(`${label} must use path/to/file.yaml#object.path syntax`);
|
||||
}
|
||||
if (file.startsWith("/") || file.includes("\0") || file.includes("..") || !file.startsWith("config/") || !file.endsWith(".yaml")) {
|
||||
throw new Error(`${label} must reference a repo-relative config/*.yaml file without ..`);
|
||||
}
|
||||
if (!/^[A-Za-z0-9_.\-[\]]+$/u.test(fragment)) throw new Error(`${label} has an unsupported YAML path fragment`);
|
||||
return { ref, file, fragment };
|
||||
}
|
||||
|
||||
export function resolveConfigRef(ref: string, label = "configRef"): ResolvedConfigRef {
|
||||
const parsed = parseConfigRef(ref, label);
|
||||
const yaml = Bun.YAML.parse(readFileSync(rootPath(parsed.file), "utf8")) as unknown;
|
||||
const value = valueAtPath(yaml, parsed.fragment, `${parsed.file}#${parsed.fragment}`);
|
||||
const serialized = stableSerialize(value);
|
||||
return {
|
||||
...parsed,
|
||||
value,
|
||||
valueType: valueType(value),
|
||||
present: true,
|
||||
sha256: createHash("sha256").update(serialized).digest("hex"),
|
||||
summary: summarizeConfigValue(value),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveConfigRefString(ref: string, label = "configRef"): string {
|
||||
const resolved = resolveConfigRef(ref, label);
|
||||
if (typeof resolved.value !== "string" || resolved.value.length === 0) throw new Error(`${resolved.file}#${resolved.fragment} must resolve to a non-empty string`);
|
||||
return resolved.value;
|
||||
}
|
||||
|
||||
export function configRefSummary(ref: string, label = "configRef"): Record<string, unknown> {
|
||||
const resolved = resolveConfigRef(ref, label);
|
||||
return {
|
||||
ref: resolved.ref,
|
||||
file: resolved.file,
|
||||
path: resolved.fragment,
|
||||
present: resolved.present,
|
||||
valueType: resolved.valueType,
|
||||
sha256: resolved.sha256,
|
||||
summary: resolved.summary,
|
||||
};
|
||||
}
|
||||
|
||||
export function configRefGraph(refs: Array<{ id: string; ref: string }>): Array<Record<string, unknown>> {
|
||||
return refs.map((item) => ({ id: item.id, ...configRefSummary(item.ref, item.id) }));
|
||||
}
|
||||
|
||||
function valueAtPath(value: unknown, fragment: string, label: string): unknown {
|
||||
let current = value;
|
||||
for (const segment of fragment.split(".")) {
|
||||
if (segment.length === 0) throw new Error(`${label} has an empty path segment`);
|
||||
const parts = [...segment.matchAll(/([A-Za-z0-9_-]+)|\[(\d+)\]/gu)];
|
||||
if (parts.length === 0 || parts.map((match) => match[0]).join("") !== segment) throw new Error(`${label} has unsupported segment ${segment}`);
|
||||
for (const part of parts) {
|
||||
const key = part[1];
|
||||
const index = part[2];
|
||||
if (key !== undefined) {
|
||||
if (typeof current !== "object" || current === null || Array.isArray(current)) throw new Error(`${label} segment ${key} does not resolve through an object`);
|
||||
const record = current as Record<string, unknown>;
|
||||
if (!Object.prototype.hasOwnProperty.call(record, key)) throw new Error(`${label} is missing segment ${key}`);
|
||||
current = record[key];
|
||||
} else if (index !== undefined) {
|
||||
if (!Array.isArray(current)) throw new Error(`${label} segment [${index}] does not resolve through an array`);
|
||||
const parsed = Number(index);
|
||||
if (current[parsed] === undefined) throw new Error(`${label} is missing array item [${index}]`);
|
||||
current = current[parsed];
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function valueType(value: unknown): string {
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (value === null) return "null";
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
function summarizeConfigValue(value: unknown): unknown {
|
||||
if (typeof value === "string") return value.length > 160 ? `${value.slice(0, 157)}...` : value;
|
||||
if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
|
||||
if (Array.isArray(value)) return { kind: "array", count: value.length };
|
||||
if (typeof value === "object") {
|
||||
const keys = Object.keys(value as Record<string, unknown>);
|
||||
return { kind: "object", keys: keys.slice(0, 12), omittedKeys: Math.max(0, keys.length - 12) };
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function stableSerialize(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
|
||||
if (typeof value === "object" && value !== null) {
|
||||
return `{${Object.keys(value as Record<string, unknown>)
|
||||
.sort()
|
||||
.map((key) => `${JSON.stringify(key)}:${stableSerialize((value as Record<string, unknown>)[key])}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
// Responsibility: YAML-backed target resolvers shared by CI/CD control-plane commands.
|
||||
import { readFileSync } from "node:fs";
|
||||
import { rootPath } from "../config";
|
||||
import { configRefSummary } from "./config-refs";
|
||||
|
||||
export const CICD_TARGETS_CONFIG_PATH = "config/cicd/targets.yaml";
|
||||
export const ARTIFACT_REGISTRY_CONFIG_PATH = "config/artifact-registry.yaml";
|
||||
|
||||
export interface TargetConfigSource {
|
||||
configPath: string;
|
||||
targetPath: string;
|
||||
defaultPath: string;
|
||||
defaulted: boolean;
|
||||
}
|
||||
|
||||
export interface ResolvedCicdTarget {
|
||||
targetId: string;
|
||||
providerId: string;
|
||||
kubeRoute: string;
|
||||
kubeconfig: string;
|
||||
hostCwd: string;
|
||||
homeDir: string;
|
||||
pipelineManifest: string;
|
||||
codeQueueImage: string;
|
||||
guardName: string;
|
||||
requiredNodeName?: string;
|
||||
requiredNodeLabel?: { key: string; value: string };
|
||||
artifactRegistryConfigRef?: string;
|
||||
configSource: TargetConfigSource;
|
||||
}
|
||||
|
||||
export interface ResolvedArtifactRegistryTarget {
|
||||
targetId: string;
|
||||
providerId: string;
|
||||
mode: string;
|
||||
host: string;
|
||||
port: number;
|
||||
endpoint: string;
|
||||
image: string;
|
||||
repositoryPrefix: string;
|
||||
baseDir: string;
|
||||
storageDir: string;
|
||||
unitName: string;
|
||||
composeProject: string;
|
||||
serviceName: string;
|
||||
containerName: string;
|
||||
timeoutMs: number;
|
||||
sourceRepo: string;
|
||||
consumerTarget: Record<string, unknown>;
|
||||
consumerCatalogRef: string;
|
||||
configSource: TargetConfigSource;
|
||||
}
|
||||
|
||||
interface CodedTargetConfig<T> {
|
||||
defaults: { targetId: string };
|
||||
targets: Record<string, T>;
|
||||
}
|
||||
|
||||
export function resolveCicdTarget(selection: string | null | undefined): ResolvedCicdTarget {
|
||||
const config = readTargetConfig(CICD_TARGETS_CONFIG_PATH, "UnideskCicdTargets");
|
||||
const { targetId, record, defaulted } = selectTarget(config, selection, CICD_TARGETS_CONFIG_PATH);
|
||||
const label = `targets.${targetId}`;
|
||||
const requiredNodeLabel = optionalRecord(record.requiredNodeLabel, `${label}.requiredNodeLabel`);
|
||||
return {
|
||||
targetId,
|
||||
providerId: stringField(record, "providerId", label),
|
||||
kubeRoute: stringField(record, "kubeRoute", label),
|
||||
kubeconfig: absolutePathField(record, "kubeconfig", label),
|
||||
hostCwd: absolutePathField(record, "hostCwd", label),
|
||||
homeDir: absolutePathField(record, "homeDir", label),
|
||||
pipelineManifest: stringField(record, "pipelineManifest", label),
|
||||
codeQueueImage: stringField(record, "codeQueueImage", label),
|
||||
guardName: stringField(record, "guardName", label),
|
||||
...(record.requiredNodeName === undefined ? {} : { requiredNodeName: stringField(record, "requiredNodeName", label) }),
|
||||
...(requiredNodeLabel === undefined
|
||||
? {}
|
||||
: { requiredNodeLabel: { key: stringField(requiredNodeLabel, "key", `${label}.requiredNodeLabel`), value: stringField(requiredNodeLabel, "value", `${label}.requiredNodeLabel`) } }),
|
||||
...(record.artifactRegistry === undefined
|
||||
? {}
|
||||
: { artifactRegistryConfigRef: stringField(asRecord(record.artifactRegistry, `${label}.artifactRegistry`), "configRef", `${label}.artifactRegistry`) }),
|
||||
configSource: configSource(CICD_TARGETS_CONFIG_PATH, targetId, defaulted),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveArtifactRegistryTarget(selection: string | null | undefined): ResolvedArtifactRegistryTarget {
|
||||
const config = readTargetConfig(ARTIFACT_REGISTRY_CONFIG_PATH, "UnideskArtifactRegistry");
|
||||
const { targetId, record, defaulted } = selectTarget(config, selection, ARTIFACT_REGISTRY_CONFIG_PATH);
|
||||
const label = `targets.${targetId}`;
|
||||
const registry = asRecord(record.registry, `${label}.registry`);
|
||||
const runtime = asRecord(record.runtime, `${label}.runtime`);
|
||||
const source = asRecord(record.source, `${label}.source`);
|
||||
const consumers = asRecord(record.consumers, `${label}.consumers`);
|
||||
return {
|
||||
targetId,
|
||||
providerId: stringField(record, "providerId", label),
|
||||
mode: stringField(record, "mode", label),
|
||||
host: stringField(registry, "host", `${label}.registry`),
|
||||
port: portField(registry, "port", `${label}.registry`),
|
||||
endpoint: stringField(registry, "endpoint", `${label}.registry`),
|
||||
image: stringField(registry, "image", `${label}.registry`),
|
||||
repositoryPrefix: stringField(registry, "repositoryPrefix", `${label}.registry`),
|
||||
baseDir: absolutePathField(runtime, "baseDir", `${label}.runtime`),
|
||||
storageDir: absolutePathField(runtime, "storageDir", `${label}.runtime`),
|
||||
unitName: stringField(runtime, "unitName", `${label}.runtime`),
|
||||
composeProject: stringField(runtime, "composeProject", `${label}.runtime`),
|
||||
serviceName: stringField(runtime, "serviceName", `${label}.runtime`),
|
||||
containerName: stringField(runtime, "containerName", `${label}.runtime`),
|
||||
timeoutMs: positiveIntegerField(runtime, "timeoutMs", `${label}.runtime`),
|
||||
sourceRepo: stringField(source, "repo", `${label}.source`),
|
||||
consumerTarget: asRecord(consumers.target, `${label}.consumers.target`),
|
||||
consumerCatalogRef: stringField(consumers, "catalogRef", `${label}.consumers`),
|
||||
configSource: configSource(ARTIFACT_REGISTRY_CONFIG_PATH, targetId, defaulted),
|
||||
};
|
||||
}
|
||||
|
||||
export function targetSourceSummary(target: { configSource: TargetConfigSource }): Record<string, unknown> {
|
||||
return {
|
||||
configPath: target.configSource.configPath,
|
||||
targetPath: target.configSource.targetPath,
|
||||
defaultPath: target.configSource.defaultPath,
|
||||
defaulted: target.configSource.defaulted,
|
||||
};
|
||||
}
|
||||
|
||||
export function cicdTargetSummary(target: ResolvedCicdTarget): Record<string, unknown> {
|
||||
return {
|
||||
targetId: target.targetId,
|
||||
providerId: target.providerId,
|
||||
kubeRoute: target.kubeRoute,
|
||||
kubeconfig: target.kubeconfig,
|
||||
hostCwd: target.hostCwd,
|
||||
homeDir: target.homeDir,
|
||||
pipelineManifest: target.pipelineManifest,
|
||||
codeQueueImage: target.codeQueueImage,
|
||||
guardName: target.guardName,
|
||||
requiredNodeName: target.requiredNodeName ?? null,
|
||||
requiredNodeLabel: target.requiredNodeLabel ?? null,
|
||||
artifactRegistryConfigRef: target.artifactRegistryConfigRef === undefined ? null : configRefSummary(target.artifactRegistryConfigRef, `${target.configSource.targetPath}.artifactRegistry.configRef`),
|
||||
source: targetSourceSummary(target),
|
||||
};
|
||||
}
|
||||
|
||||
export function artifactRegistryTargetSummary(target: ResolvedArtifactRegistryTarget): Record<string, unknown> {
|
||||
return {
|
||||
targetId: target.targetId,
|
||||
providerId: target.providerId,
|
||||
mode: target.mode,
|
||||
registry: {
|
||||
image: target.image,
|
||||
host: target.host,
|
||||
port: target.port,
|
||||
endpoint: target.endpoint,
|
||||
repositoryPrefix: target.repositoryPrefix,
|
||||
},
|
||||
runtime: {
|
||||
baseDir: target.baseDir,
|
||||
storageDir: target.storageDir,
|
||||
unitName: target.unitName,
|
||||
composeProject: target.composeProject,
|
||||
serviceName: target.serviceName,
|
||||
containerName: target.containerName,
|
||||
timeoutMs: target.timeoutMs,
|
||||
},
|
||||
consumerTarget: target.consumerTarget,
|
||||
consumerCatalogRef: target.consumerCatalogRef,
|
||||
source: targetSourceSummary(target),
|
||||
};
|
||||
}
|
||||
|
||||
function readTargetConfig(path: string, expectedKind: string): CodedTargetConfig<Record<string, unknown>> {
|
||||
const parsed = Bun.YAML.parse(readFileSync(rootPath(path), "utf8")) as unknown;
|
||||
const root = asRecord(parsed, path);
|
||||
const version = root.version;
|
||||
if (version !== 1) throw new Error(`${path}.version must be 1`);
|
||||
if (root.kind !== expectedKind) throw new Error(`${path}.kind must be ${expectedKind}`);
|
||||
const defaults = asRecord(root.defaults, `${path}.defaults`);
|
||||
const targets = asRecord(root.targets, `${path}.targets`);
|
||||
const config = {
|
||||
defaults: { targetId: stringField(defaults, "targetId", `${path}.defaults`) },
|
||||
targets: Object.fromEntries(Object.entries(targets).map(([key, value]) => [key, asRecord(value, `${path}.targets.${key}`)])),
|
||||
};
|
||||
if (config.targets[config.defaults.targetId] === undefined) throw new Error(`${path}.defaults.targetId references missing target ${config.defaults.targetId}`);
|
||||
return config;
|
||||
}
|
||||
|
||||
function selectTarget(config: CodedTargetConfig<Record<string, unknown>>, selection: string | null | undefined, path: string): { targetId: string; record: Record<string, unknown>; defaulted: boolean } {
|
||||
const raw = selection === undefined || selection === null || selection.length === 0 ? config.defaults.targetId : selection;
|
||||
const targetId = Object.keys(config.targets).find((candidate) => candidate.toLowerCase() === raw.toLowerCase())
|
||||
?? Object.entries(config.targets).find(([, target]) => typeof target.providerId === "string" && target.providerId.toLowerCase() === raw.toLowerCase())?.[0];
|
||||
if (targetId === undefined) throw new Error(`${path} has no target ${raw}; known targets: ${Object.keys(config.targets).join(", ")}`);
|
||||
return { targetId, record: config.targets[targetId], defaulted: raw === config.defaults.targetId && (selection === undefined || selection === null || selection.length === 0) };
|
||||
}
|
||||
|
||||
function configSource(path: string, targetId: string, defaulted: boolean): TargetConfigSource {
|
||||
return {
|
||||
configPath: path,
|
||||
targetPath: `${path}#targets.${targetId}`,
|
||||
defaultPath: `${path}#defaults.targetId`,
|
||||
defaulted,
|
||||
};
|
||||
}
|
||||
|
||||
function asRecord(value: unknown, path: string): Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`);
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function optionalRecord(value: unknown, path: string): Record<string, unknown> | undefined {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
return asRecord(value, path);
|
||||
}
|
||||
|
||||
function stringField(record: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = record[key];
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path}.${key} must be a non-empty string`);
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function absolutePathField(record: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(record, key, path);
|
||||
if (!value.startsWith("/")) throw new Error(`${path}.${key} must be an absolute path`);
|
||||
return value.replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
function positiveIntegerField(record: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = record[key];
|
||||
if (!Number.isInteger(value) || Number(value) <= 0) throw new Error(`${path}.${key} must be a positive integer`);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
function portField(record: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = positiveIntegerField(record, key, path);
|
||||
if (value > 65535) throw new Error(`${path}.${key} must be a TCP port`);
|
||||
return value;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. actions module for scripts/src/platform-infra-observability.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/platform-infra-observability.ts:515-623 for #903.
|
||||
|
||||
@@ -119,6 +120,7 @@ export async function status(config: UniDeskConfig, options: CommonOptions): Pro
|
||||
action: "platform-infra-observability-status",
|
||||
mutation: false,
|
||||
target: targetSummary(target),
|
||||
config: configSummary(observability, target),
|
||||
summary,
|
||||
remote: options.raw ? parsed : compactStatus(parsed, options.full) ?? compactCapture(result, { full: true }),
|
||||
next: {
|
||||
@@ -146,6 +148,7 @@ export async function validate(config: UniDeskConfig, options: CommonOptions): P
|
||||
action: "platform-infra-observability-validate",
|
||||
mutation: false,
|
||||
target: targetSummary(target),
|
||||
config: configSummary(observability, target),
|
||||
summary,
|
||||
validation: {
|
||||
readiness: ready ? "passed" : "failed",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. config module for scripts/src/platform-infra-observability.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/platform-infra-observability.ts:370-514 for #903.
|
||||
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
import type { ImageSpec, ObservabilityConfig, ObservabilityTarget, OtlpPorts, ServiceConnection } from "./types";
|
||||
import { assertKnownEnabledTarget, parseStatusEndpoint } from "./actions";
|
||||
import { apiPathField, arrayOfRecords, asRecord, booleanField, configFile, configLabel, enumField, integerField, kubernetesNameField, numberArrayField, objectField, portField, stringArrayField, stringField } from "./types";
|
||||
import { configRefGraph, resolveConfigRefString } from "../ops/config-refs";
|
||||
|
||||
export function readObservabilityConfig(): ObservabilityConfig {
|
||||
const parsed = Bun.YAML.parse(readFileSync(configFile, "utf8")) as unknown;
|
||||
@@ -134,12 +136,33 @@ export function parseOtlpPorts(record: Record<string, unknown>, path: string): O
|
||||
|
||||
export function parseServiceConnection(record: Record<string, unknown>, index: number): ServiceConnection {
|
||||
const path = `instrumentation.serviceConnections[${index}]`;
|
||||
const refs = record.configRefs === undefined ? null : objectField(record, "configRefs", path);
|
||||
const configRefs = refs === null
|
||||
? {}
|
||||
: {
|
||||
targetNode: stringField(refs, "targetNode", `${path}.configRefs`),
|
||||
lane: stringField(refs, "lane", `${path}.configRefs`),
|
||||
namespace: stringField(refs, "namespace", `${path}.configRefs`),
|
||||
};
|
||||
return {
|
||||
serviceName: stringField(record, "serviceName", path),
|
||||
owningRepo: stringField(record, "owningRepo", path),
|
||||
targetNode: stringField(record, "targetNode", path),
|
||||
lane: stringField(record, "lane", path),
|
||||
namespace: kubernetesNameField(record, "namespace", path),
|
||||
targetNode: refs === null ? stringField(record, "targetNode", path) : resolveConfigRefString(configRefs.targetNode, `${path}.configRefs.targetNode`),
|
||||
lane: refs === null ? stringField(record, "lane", path) : resolveConfigRefString(configRefs.lane, `${path}.configRefs.lane`),
|
||||
namespace: refs === null ? kubernetesNameField(record, "namespace", path) : kubernetesNameValue(resolveConfigRefString(configRefs.namespace, `${path}.configRefs.namespace`), `${path}.configRefs.namespace`),
|
||||
configRefs,
|
||||
configRefGraph: refs === null
|
||||
? []
|
||||
: configRefGraph([
|
||||
{ id: `${path}.targetNode`, ref: configRefs.targetNode },
|
||||
{ id: `${path}.lane`, ref: configRefs.lane },
|
||||
{ id: `${path}.namespace`, ref: configRefs.namespace },
|
||||
]),
|
||||
requiredSpans: stringArrayField(record, "requiredSpans", path),
|
||||
};
|
||||
}
|
||||
|
||||
function kubernetesNameValue(value: string, path: string): string {
|
||||
if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(value)) throw new Error(`${configLabel}.${path} must resolve to a Kubernetes name`);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. summary module for scripts/src/platform-infra-observability.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/platform-infra-observability.ts:3031-4400 for #903.
|
||||
|
||||
@@ -35,6 +36,17 @@ export function configSummary(observability: ObservabilityConfig, target: Observ
|
||||
collector: observability.collector,
|
||||
traceBackend: observability.traceBackend,
|
||||
sampling: observability.sampling,
|
||||
serviceConnections: observability.instrumentation.serviceConnections.map((item) => ({
|
||||
serviceName: item.serviceName,
|
||||
owningRepo: item.owningRepo,
|
||||
resolved: {
|
||||
targetNode: item.targetNode,
|
||||
lane: item.lane,
|
||||
namespace: item.namespace,
|
||||
},
|
||||
configRefs: item.configRefGraph,
|
||||
requiredSpans: item.requiredSpans,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. types module for scripts/src/platform-infra-observability.ts.
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
|
||||
// Moved mechanically from scripts/src/platform-infra-observability.ts:1-149 for #903.
|
||||
|
||||
@@ -112,6 +113,8 @@ export interface ServiceConnection {
|
||||
targetNode: string;
|
||||
lane: string;
|
||||
namespace: string;
|
||||
configRefs: Record<string, string>;
|
||||
configRefGraph: Array<Record<string, unknown>>;
|
||||
requiredSpans: string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { basename, dirname, join, normalize, relative } from "node:path";
|
||||
@@ -8,7 +9,7 @@ import { coreInternalFetch } from "./microservices";
|
||||
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
|
||||
|
||||
export interface OpsCommonOptions {
|
||||
targetId: string;
|
||||
targetId: string | null;
|
||||
full: boolean;
|
||||
raw: boolean;
|
||||
}
|
||||
@@ -120,10 +121,10 @@ export function shQuote(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
export function parseOpsCommonOptions(args: string[], spec: OpsCommandOptionSpec = {}): OpsCommonOptions & Record<string, string | boolean> {
|
||||
export function parseOpsCommonOptions(args: string[], spec: OpsCommandOptionSpec = {}): OpsCommonOptions & Record<string, string | boolean | null> {
|
||||
const stringOptions = new Set(["--target", ...(spec.stringOptions ?? [])]);
|
||||
const flagOptions = new Set(["--full", "--raw", ...(spec.flagOptions ?? [])]);
|
||||
const values: Record<string, string | boolean> = { targetId: "G14", full: false, raw: false };
|
||||
const values: Record<string, string | boolean | null> = { full: false, raw: false };
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
if (stringOptions.has(arg)) {
|
||||
@@ -144,9 +145,9 @@ export function parseOpsCommonOptions(args: string[], spec: OpsCommandOptionSpec
|
||||
throw new Error(`unsupported option: ${arg}`);
|
||||
}
|
||||
}
|
||||
const targetId = String(values.targetId);
|
||||
if (!/^[A-Za-z0-9._-]+$/u.test(targetId)) throw new Error("--target must be a simple target id");
|
||||
return values as OpsCommonOptions & Record<string, string | boolean>;
|
||||
const targetId = values.targetId === undefined ? null : String(values.targetId);
|
||||
if (targetId !== null && !/^[A-Za-z0-9._-]+$/u.test(targetId)) throw new Error("--target must be a simple target id");
|
||||
return { ...values, targetId } as OpsCommonOptions & Record<string, string | boolean | null>;
|
||||
}
|
||||
|
||||
export function parseOpsApplyOptions(args: string[]): OpsApplyOptions {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
@@ -1348,8 +1349,9 @@ function validateConfig(config: WechatArchiveConfig): void {
|
||||
validatePersonalWechatIngress(config.personalWechatIngress);
|
||||
}
|
||||
|
||||
function assertTarget(config: WechatArchiveConfig, targetId: string): void {
|
||||
if (targetId !== config.target.id) throw new Error(`wechat archive target ${targetId} is not declared in ${configLabel}`);
|
||||
function assertTarget(config: WechatArchiveConfig, targetId: string | null): void {
|
||||
const resolved = targetId ?? config.target.id;
|
||||
if (resolved !== config.target.id) throw new Error(`wechat archive target ${resolved} is not declared in ${configLabel}`);
|
||||
}
|
||||
|
||||
function configSummary(config: WechatArchiveConfig): Record<string, unknown> {
|
||||
|
||||
Reference in New Issue
Block a user