fix(cicd): add NC01 PaC status closeout
This commit is contained in:
@@ -10,6 +10,8 @@ HWLAB G14 和 AgentRun CI/CD 的受控入口。任何 PR 监控、Tekton/Argo、
|
|||||||
## 高频入口
|
## 高频入口
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
bun scripts/cli.ts cicd status --node NC01
|
||||||
|
bun scripts/cli.ts cicd status --node <NODE>
|
||||||
bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --once --dry-run
|
bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --once --dry-run
|
||||||
bun scripts/cli.ts hwlab g14 control-plane status --lane v02
|
bun scripts/cli.ts hwlab g14 control-plane status --lane v02
|
||||||
bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm --wait
|
bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm --wait
|
||||||
@@ -27,6 +29,8 @@ bun scripts/cli.ts cicd branch-follower status
|
|||||||
bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step state-read
|
bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel-master --step state-read
|
||||||
```
|
```
|
||||||
|
|
||||||
|
节点级只读状态必须优先用 `cicd status --node <NODE>`。它从 `config/platform-infra/pipelines-as-code.yaml` 找到该 node 的所有当前 PaC consumer,一次性汇总 PipelineRun、Argo/GitOps、runtime readiness 和诊断;不要再靠阅读源码或手动拼三条 consumer 命令来回答 “NC01 的 CI/CD 流水线情况”。`platform-infra pipelines-as-code status --target <NODE> --consumer <id>` 只作为单 consumer drill-down。
|
||||||
|
|
||||||
按职责读取拆分后的 reference:
|
按职责读取拆分后的 reference:
|
||||||
|
|
||||||
- PR monitor 与自动合并: [references/pr-monitor.md](references/pr-monitor.md)。
|
- PR monitor 与自动合并: [references/pr-monitor.md](references/pr-monitor.md)。
|
||||||
@@ -41,8 +45,8 @@ bun scripts/cli.ts cicd branch-follower debug-step --follower web-probe-sentinel
|
|||||||
## P0 边界
|
## P0 边界
|
||||||
|
|
||||||
- CI/CD、GitOps、rollout、PipelineRun、Argo、git-mirror 和 AgentRun 部署必须走受控 CLI;不要用裸 `kubectl`、`argo`、`tkn`、`curl` 当正式控制入口。
|
- CI/CD、GitOps、rollout、PipelineRun、Argo、git-mirror 和 AgentRun 部署必须走受控 CLI;不要用裸 `kubectl`、`argo`、`tkn`、`curl` 当正式控制入口。
|
||||||
- CI/CD source authority 只能来自 YAML 声明的 Kubernetes 托管 source authority:legacy lane 使用 k8s git-mirror snapshot,JD01 migrated lane 使用 Gitea controlled mirror + immutable snapshot ref + Pipelines-as-Code。受控命令先在 k8s 内同步/创建不可变 `refs/unidesk/snapshots/.../<commit>` stage ref;build/status/publish 只消费该 snapshot,host worktree、本地 `git fetch/pull`、可变 branch ref 或 Pipeline 内直连 GitHub 都不能作为 authoritative source。
|
- CI/CD source authority 只能来自 YAML 声明的 Kubernetes 托管 source authority:legacy lane 使用 k8s git-mirror snapshot,Gitea/PaC migrated lane 使用 GitHub PR merge -> GitHub webhook bridge -> Gitea controlled mirror + immutable snapshot ref -> Pipelines-as-Code。受控命令先在 k8s 内同步/创建不可变 `refs/unidesk/snapshots/.../<commit>` stage ref;build/status/publish 只消费该 snapshot,host worktree、本地 `git fetch/pull`、可变 branch ref 或 Pipeline 内直连 GitHub 都不能作为 authoritative source。
|
||||||
- JD01 `agentrun-jd01-v02`、`sentinel-jd01-v03`、`hwlab-jd01-v03` 的正式 CI/CD closeout 入口是 `platform-infra pipelines-as-code closeout|status|history --target JD01`。`closeout` 是通用 consumer 引导入口,只读取/等待 PaC、Tekton、GitOps、Argo 和 runtime 对齐,不触发旧手动发布。`cicd branch-follower` 和 `cicd gitea-actions-poc` 对这三者只保留历史/迁移只读用途,不得作为当前交付判断入口。
|
- JD01/NC01 `agentrun-<node>-v02`、`sentinel-<node>-v03`、`hwlab-<node>-v03` 的正式 CI/CD closeout 入口是 `cicd status --node <NODE>` 和 `platform-infra pipelines-as-code closeout|status|history --target <NODE> --consumer <id>`。`closeout` 是通用 consumer 引导入口,只读取/等待 PaC、Tekton、GitOps、Argo 和 runtime 对齐,不触发旧手动发布。`cicd branch-follower` 和 `cicd gitea-actions-poc` 对这些 consumer 只保留历史/迁移只读用途,不得作为当前交付判断入口。
|
||||||
- GitHub/Git 相关 egress 必须走 YAML-first host proxy/sourceRef:branch-follower controller 读 `config/cicd-branch-followers.yaml#controller.source.githubSsh`,runtime legacy git-mirror 读 owning lane/control-plane YAML 的 host proxy 和 `githubTransport`,Gitea/PaC 迁移 lane 读 `config/platform-infra/gitea.yaml` 与 `config/platform-infra/pipelines-as-code.yaml`;禁止依赖未声明 host env、trans proxy、裸直连 GitHub 或 CLI 输出解析。
|
- GitHub/Git 相关 egress 必须走 YAML-first host proxy/sourceRef:branch-follower controller 读 `config/cicd-branch-followers.yaml#controller.source.githubSsh`,runtime legacy git-mirror 读 owning lane/control-plane YAML 的 host proxy 和 `githubTransport`,Gitea/PaC 迁移 lane 读 `config/platform-infra/gitea.yaml` 与 `config/platform-infra/pipelines-as-code.yaml`;禁止依赖未声明 host env、trans proxy、裸直连 GitHub 或 CLI 输出解析。
|
||||||
- Gitea/PaC lane 的 GitHub -> Gitea 自动同步必须用 `platform-infra gitea mirror webhook apply|status|test`。该路径是单向同步,只更新 Gitea branch 和 immutable snapshot ref;不得引入 Gitea -> GitHub 回写、轮询 fallback、Gitea Actions/act_runner fallback 或第二套状态存储。
|
- Gitea/PaC lane 的 GitHub -> Gitea 自动同步必须用 `platform-infra gitea mirror webhook apply|status|test`。该路径是单向同步,只更新 Gitea branch 和 immutable snapshot ref;不得引入 Gitea -> GitHub 回写、轮询 fallback、Gitea Actions/act_runner fallback 或第二套状态存储。
|
||||||
- Gitea webhook/FRPC/Caddy 变更的 closeout 必须看受控 CLI 的 rollout/status/test 证据;Secret 或 ConfigMap apply 成功不等于 connector Pod 已加载新 proxy/path。
|
- Gitea webhook/FRPC/Caddy 变更的 closeout 必须看受控 CLI 的 rollout/status/test 证据;Secret 或 ConfigMap apply 成功不等于 connector Pod 已加载新 proxy/path。
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
# Gitea + Pipelines-as-Code
|
# Gitea + Pipelines-as-Code
|
||||||
|
|
||||||
JD01 migrated CI/CD uses one formal path:
|
Gitea/Pipelines-as-Code migrated CI/CD uses one formal path:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
GitHub push -> Gitea controlled mirror -> Pipelines-as-Code Repository webhook -> Tekton PipelineRun -> GitOps/Argo -> k8s runtime
|
GitHub PR merge -> GitHub webhook bridge -> Gitea controlled mirror and immutable snapshot refs -> Gitea Repository webhook -> Pipelines-as-Code -> Tekton PipelineRun -> GitOps/Argo -> k8s runtime
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not add Gitea Actions, `act_runner`, branch-follower, host worktree, direct GitHub-in-Pipeline, or custom script fallback for the migrated JD01 consumers. Gitea Actions material under `config/cicd-gitea-actions-poc.yaml` is archived read-only POC context only.
|
GitHub remains the upstream write authority. A delivery should be triggered by merging the GitHub PR into the consumer's YAML-declared source branch, then observing the target node's Gitea mirror, PaC, Tekton, GitOps, Argo and runtime status. Do not replace this with Gitea Actions, `act_runner`, branch-follower, host worktree, direct GitHub-in-Pipeline, direct Gitea pushes, manual PipelineRun creation, `trigger-current`, `webhook-test`, or custom script fallback. Gitea Actions material under `config/cicd-gitea-actions-poc.yaml` is archived read-only POC context only; `webhook-test` is only a connectivity diagnostic.
|
||||||
|
|
||||||
## Source Of Truth
|
## Source Of Truth
|
||||||
|
|
||||||
- Gitea source authority, GitHub webhook bridge, public exposure and source snapshots: `config/platform-infra/gitea.yaml`.
|
- Gitea source authority, GitHub webhook bridge, public exposure and source snapshots: `config/platform-infra/gitea.yaml`.
|
||||||
- PaC controller, Repository CRs, consumer parameters, Tekton pipeline names and Argo applications: `config/platform-infra/pipelines-as-code.yaml`.
|
- PaC controller, Repository CRs, consumer parameters, Tekton pipeline names and Argo applications: `config/platform-infra/pipelines-as-code.yaml`.
|
||||||
- Historical snapshot prefixes containing `gitea-actions` are retained only for existing refs. They do not mean Gitea Actions is the active trigger architecture.
|
- Historical snapshot prefixes containing `gitea-actions` are retained only for existing refs. They do not mean Gitea Actions is the active trigger architecture.
|
||||||
|
- Node-level status for these consumers is `bun scripts/cli.ts cicd status --node <NODE>`. Consumer drill-down is `platform-infra pipelines-as-code status|history|closeout --target <NODE> --consumer <id>`.
|
||||||
|
|
||||||
## Coverage Matrix
|
## Coverage Matrix
|
||||||
|
|
||||||
@@ -21,17 +22,20 @@ Do not add Gitea Actions, `act_runner`, branch-follower, host worktree, direct G
|
|||||||
| `agentrun-jd01-v02` | `pikasTech/agentrun@v0.2` | `agentrun-ci` | `agentrun-jd01-v02-ci-image-publish` | `agentrun-jd01-v02` | `platform-infra pipelines-as-code closeout --target JD01 --consumer agentrun-jd01-v02 --source-commit <sha> --wait` |
|
| `agentrun-jd01-v02` | `pikasTech/agentrun@v0.2` | `agentrun-ci` | `agentrun-jd01-v02-ci-image-publish` | `agentrun-jd01-v02` | `platform-infra pipelines-as-code closeout --target JD01 --consumer agentrun-jd01-v02 --source-commit <sha> --wait` |
|
||||||
| `sentinel-jd01-v03` | `pikasTech/unidesk@master` | `devops-infra` | `hwlab-web-probe-sentinel-jd01-pac` | `hwlab-web-probe-sentinel-jd01` | `platform-infra pipelines-as-code closeout --target JD01 --consumer sentinel-jd01-v03 --source-commit <sha> --wait` |
|
| `sentinel-jd01-v03` | `pikasTech/unidesk@master` | `devops-infra` | `hwlab-web-probe-sentinel-jd01-pac` | `hwlab-web-probe-sentinel-jd01` | `platform-infra pipelines-as-code closeout --target JD01 --consumer sentinel-jd01-v03 --source-commit <sha> --wait` |
|
||||||
| `hwlab-jd01-v03` | `pikasTech/HWLAB@v0.3` | `hwlab-ci` | `hwlab-jd01-v03-ci-image-publish` | `hwlab-node-v03` | `platform-infra pipelines-as-code closeout --target JD01 --consumer hwlab-jd01-v03 --source-commit <sha> --wait` |
|
| `hwlab-jd01-v03` | `pikasTech/HWLAB@v0.3` | `hwlab-ci` | `hwlab-jd01-v03-ci-image-publish` | `hwlab-node-v03` | `platform-infra pipelines-as-code closeout --target JD01 --consumer hwlab-jd01-v03 --source-commit <sha> --wait` |
|
||||||
|
| `agentrun-nc01-v02` | `pikasTech/agentrun@v0.2` | `agentrun-ci` | `agentrun-nc01-v02-ci-image-publish` | `agentrun-nc01-v02` | `platform-infra pipelines-as-code closeout --target NC01 --consumer agentrun-nc01-v02 --source-commit <sha> --wait` |
|
||||||
|
| `sentinel-nc01-v03` | `pikasTech/unidesk@master` | `devops-infra` | `hwlab-web-probe-sentinel-nc01-pac` | `hwlab-web-probe-sentinel-nc01` | `platform-infra pipelines-as-code closeout --target NC01 --consumer sentinel-nc01-v03 --source-commit <sha> --wait` |
|
||||||
|
| `hwlab-nc01-v03` | `pikasTech/HWLAB@v0.3` | `hwlab-ci` | `hwlab-nc01-v03-ci-image-publish` | `hwlab-node-v03` | `platform-infra pipelines-as-code closeout --target NC01 --consumer hwlab-nc01-v03 --source-commit <sha> --wait` |
|
||||||
|
|
||||||
Use `bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 --limit 10` for all-consumer trigger, timing and reuse audit. It must read live Gitea Repository CR plus Tekton PipelineRun/TaskRun objects on JD01, aggregate on the target side, and print `READ_ERRORS`.
|
Use `bun scripts/cli.ts platform-infra pipelines-as-code history --target <NODE> --limit 10` for all-consumer trigger, timing and reuse audit. It must read live Gitea Repository CR plus Tekton PipelineRun/TaskRun objects on the target node, aggregate on the target side, and print `READ_ERRORS`.
|
||||||
|
|
||||||
## Closeout Order
|
## Closeout Order
|
||||||
|
|
||||||
1. `bun scripts/cli.ts platform-infra gitea mirror status --target JD01`
|
1. `bun scripts/cli.ts platform-infra gitea mirror status --target <NODE>`
|
||||||
2. `bun scripts/cli.ts platform-infra gitea mirror webhook status --target JD01`
|
2. `bun scripts/cli.ts platform-infra gitea mirror webhook status --target <NODE>`
|
||||||
3. `bun scripts/cli.ts platform-infra pipelines-as-code closeout --target JD01 --consumer <id> --source-commit <sha> --wait`
|
3. `bun scripts/cli.ts platform-infra pipelines-as-code closeout --target <NODE> --consumer <id> --source-commit <sha> --wait`
|
||||||
4. `bun scripts/cli.ts platform-infra pipelines-as-code status --target JD01 --consumer <id>`
|
4. `bun scripts/cli.ts platform-infra pipelines-as-code status --target <NODE> --consumer <id>`
|
||||||
5. `bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 --consumer <id> --limit 10`
|
5. `bun scripts/cli.ts platform-infra pipelines-as-code history --target <NODE> --consumer <id> --limit 10`
|
||||||
6. For ambiguous rows, drill into the PipelineRun with `bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 --id <pipelinerun>`.
|
6. For ambiguous rows, drill into the PipelineRun with `bun scripts/cli.ts platform-infra pipelines-as-code history --target <NODE> --id <pipelinerun>`.
|
||||||
|
|
||||||
`closeout` is the common formal entry for all migrated JD01 consumers. It waits only for PaC/Tekton/GitOps/Argo/runtime observation from live target-side summaries and does not trigger legacy publish paths. Consumer-specific debug commands such as Web sentinel `publish-current` are recovery/test steps only and must not be shown as the normal next action for formal delivery.
|
`closeout` is the common formal entry for all migrated JD01 consumers. It waits only for PaC/Tekton/GitOps/Argo/runtime observation from live target-side summaries and does not trigger legacy publish paths. Consumer-specific debug commands such as Web sentinel `publish-current` are recovery/test steps only and must not be shown as the normal next action for formal delivery.
|
||||||
|
|
||||||
|
|||||||
+10
-4
@@ -924,7 +924,7 @@ controlPlane:
|
|||||||
- fenced
|
- fenced
|
||||||
- late-write-rejected
|
- late-write-rejected
|
||||||
localPostgres:
|
localPostgres:
|
||||||
enabled: true
|
enabled: false
|
||||||
serviceName: agentrun-v02-postgres
|
serviceName: agentrun-v02-postgres
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
storage: 5Gi
|
storage: 5Gi
|
||||||
@@ -962,11 +962,17 @@ controlPlane:
|
|||||||
repository: pikasTech/agent_skills
|
repository: pikasTech/agent_skills
|
||||||
sourceBranch: master
|
sourceBranch: master
|
||||||
database:
|
database:
|
||||||
mode: local-postgres
|
mode: external-postgres
|
||||||
|
provider: NC01
|
||||||
|
configRef: config/platform-db/postgres-nc01.yaml
|
||||||
|
database: agentrun_v02
|
||||||
|
user: agentrun_v02
|
||||||
|
sslmode: require
|
||||||
|
secretSourceRef: agentrun/nc01-v02-mgr-db.env
|
||||||
secretRef:
|
secretRef:
|
||||||
name: agentrun-v02-mgr-db
|
name: agentrun-v02-mgr-db
|
||||||
key: DATABASE_URL
|
key: DATABASE_URL
|
||||||
localPostgresExpectedAbsent: false
|
localPostgresExpectedAbsent: true
|
||||||
secrets:
|
secrets:
|
||||||
- id: manager-api-key
|
- id: manager-api-key
|
||||||
sourceRef: hwlab/nc01-v03-admin.env
|
sourceRef: hwlab/nc01-v03-admin.env
|
||||||
@@ -1357,4 +1363,4 @@ controlPlane:
|
|||||||
targetRef:
|
targetRef:
|
||||||
namespace: agentrun-v02
|
namespace: agentrun-v02
|
||||||
name: agentrun-v01-tool-unidesk-ssh
|
name: agentrun-v01-tool-unidesk-ssh
|
||||||
key: UNIDESK_SSH_CLIENT_TOKEN
|
key: UNIDESK_SSH_CLIENT_TOKEN
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ kind: postgres-host-cluster
|
|||||||
|
|
||||||
metadata:
|
metadata:
|
||||||
id: nc01-host-postgres
|
id: nc01-host-postgres
|
||||||
description: NC01 host-native PostgreSQL 16 for local UniDesk/HWLAB runtime state
|
description: NC01 host-native PostgreSQL 16 for local UniDesk/HWLAB/AgentRun runtime state
|
||||||
owner: unidesk
|
owner: unidesk
|
||||||
|
relatedIssues: []
|
||||||
|
|
||||||
cluster:
|
cluster:
|
||||||
role: primary
|
role: primary
|
||||||
@@ -17,23 +18,69 @@ node:
|
|||||||
route: NC01
|
route: NC01
|
||||||
mode: host-systemd
|
mode: host-systemd
|
||||||
osFamily: ubuntu
|
osFamily: ubuntu
|
||||||
|
requiredHostFacts:
|
||||||
|
minCpu: 1
|
||||||
|
minMemoryGiB: 1
|
||||||
|
minDiskFreeGiB: 5
|
||||||
|
requireSwap: false
|
||||||
|
swap:
|
||||||
|
ensure: false
|
||||||
|
size: 1G
|
||||||
|
path: /swapfile
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
package:
|
package:
|
||||||
manager: apt
|
manager: apt
|
||||||
version: "16"
|
version: "16"
|
||||||
|
repo: pgdg
|
||||||
|
repoUrl: https://apt.postgresql.org/pub/repos/apt
|
||||||
|
suite: trixie-pgdg
|
||||||
|
component: main
|
||||||
|
signingKeyUrl: https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
||||||
|
signedBy: /usr/share/keyrings/postgresql-pgdg.gpg
|
||||||
|
sourceList: /etc/apt/sources.list.d/pgdg.list
|
||||||
service:
|
service:
|
||||||
name: postgresql
|
name: postgresql
|
||||||
enabled: true
|
enabled: true
|
||||||
state: running
|
state: running
|
||||||
|
paths:
|
||||||
|
dataDir: /var/lib/postgresql/16/main
|
||||||
|
configDir: /etc/postgresql/16/main
|
||||||
|
logDir: /var/log/postgresql
|
||||||
network:
|
network:
|
||||||
port: 5432
|
port: 5432
|
||||||
listenAddresses:
|
listenAddresses:
|
||||||
- 127.0.0.1
|
- 127.0.0.1
|
||||||
- 10.42.0.1
|
- 10.42.0.1
|
||||||
connectionHost: 10.42.0.1
|
connectionHost: 10.42.0.1
|
||||||
|
publicDns: nc01-host-postgres.local
|
||||||
transport: postgres-native-tls
|
transport: postgres-native-tls
|
||||||
sslmode: require
|
sslmode: require
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
mode: self-signed-server-cert
|
||||||
|
commonName: nc01-host-postgres
|
||||||
|
certFile: /etc/postgresql/16/main/server.crt
|
||||||
|
keyFile: /etc/postgresql/16/main/server.key
|
||||||
|
futureClientSslmode: require
|
||||||
|
firewall:
|
||||||
|
mode: pg-hba-declared-sources
|
||||||
|
defaultDeny: true
|
||||||
|
allowSources:
|
||||||
|
- id: nc01-local
|
||||||
|
cidr: 127.0.0.1/32
|
||||||
|
purpose: local-admin
|
||||||
|
- id: nc01-k3s-pods
|
||||||
|
cidr: 10.42.0.0/16
|
||||||
|
purpose: node-local-k3s-workloads
|
||||||
|
tuning:
|
||||||
|
maxConnections: 100
|
||||||
|
sharedBuffers: 128MB
|
||||||
|
effectiveCacheSize: 4GB
|
||||||
|
workMem: 4MB
|
||||||
|
maintenanceWorkMem: 64MB
|
||||||
|
walCompression: false
|
||||||
|
checkpointCompletionTarget: 0.9
|
||||||
auth:
|
auth:
|
||||||
passwordEncryption: scram-sha-256
|
passwordEncryption: scram-sha-256
|
||||||
pgHba:
|
pgHba:
|
||||||
@@ -46,11 +93,80 @@ postgres:
|
|||||||
user: all
|
user: all
|
||||||
address: 127.0.0.1/32
|
address: 127.0.0.1/32
|
||||||
method: scram-sha-256
|
method: scram-sha-256
|
||||||
- type: host
|
- type: hostssl
|
||||||
database: all
|
database: agentrun_v02
|
||||||
user: all
|
user: agentrun_v02
|
||||||
|
address: 10.42.0.0/16
|
||||||
|
method: scram-sha-256
|
||||||
|
- type: hostssl
|
||||||
|
database: postgres
|
||||||
|
user: agentrun_v02
|
||||||
address: 10.42.0.0/16
|
address: 10.42.0.0/16
|
||||||
method: scram-sha-256
|
method: scram-sha-256
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
|
source: master-local
|
||||||
root: /root/unidesk/.state/secrets
|
root: /root/unidesk/.state/secrets
|
||||||
|
entries:
|
||||||
|
- name: agentrun-nc01-v02-db-credentials
|
||||||
|
sourceRef: platform-db/agentrun-nc01-v02-db.env
|
||||||
|
type: env
|
||||||
|
requiredKeys:
|
||||||
|
- AGENTRUN_NC01_V02_DB_USER
|
||||||
|
- AGENTRUN_NC01_V02_DB_PASSWORD
|
||||||
|
- AGENTRUN_NC01_V02_DB_NAME
|
||||||
|
createIfMissing:
|
||||||
|
enabled: true
|
||||||
|
values:
|
||||||
|
AGENTRUN_NC01_V02_DB_USER: agentrun_v02
|
||||||
|
AGENTRUN_NC01_V02_DB_NAME: agentrun_v02
|
||||||
|
randomHex:
|
||||||
|
AGENTRUN_NC01_V02_DB_PASSWORD: 32
|
||||||
|
|
||||||
|
objects:
|
||||||
|
roles:
|
||||||
|
- name: agentrun_v02
|
||||||
|
passwordRef:
|
||||||
|
sourceRef: platform-db/agentrun-nc01-v02-db.env
|
||||||
|
key: AGENTRUN_NC01_V02_DB_PASSWORD
|
||||||
|
login: true
|
||||||
|
attributes:
|
||||||
|
createdb: false
|
||||||
|
createrole: false
|
||||||
|
superuser: false
|
||||||
|
databases:
|
||||||
|
- name: agentrun_v02
|
||||||
|
owner: agentrun_v02
|
||||||
|
encoding: UTF8
|
||||||
|
locale: C.UTF-8
|
||||||
|
extensions: []
|
||||||
|
|
||||||
|
exports:
|
||||||
|
connectionStrings:
|
||||||
|
- name: agentrun-nc01-v02-database-url
|
||||||
|
sourceSecretRef: platform-db/agentrun-nc01-v02-db.env
|
||||||
|
render:
|
||||||
|
envKey: DATABASE_URL
|
||||||
|
format: postgresql://$(AGENTRUN_NC01_V02_DB_USER):$(AGENTRUN_NC01_V02_DB_PASSWORD)@$(PGHOST):5432/$(AGENTRUN_NC01_V02_DB_NAME)?sslmode=require&uselibpqcompat=true
|
||||||
|
variables:
|
||||||
|
PGHOST: 10.42.0.1
|
||||||
|
writeToSecretSource:
|
||||||
|
sourceRef: agentrun/nc01-v02-mgr-db.env
|
||||||
|
key: DATABASE_URL
|
||||||
|
mode: update-or-insert
|
||||||
|
consumers:
|
||||||
|
- scope: agentrun-v02
|
||||||
|
secret: agentrun-v02-mgr-db
|
||||||
|
key: DATABASE_URL
|
||||||
|
|
||||||
|
backup:
|
||||||
|
logicalDump:
|
||||||
|
enabled: false
|
||||||
|
schedule: "*-*-* 03:37:00"
|
||||||
|
database: agentrun_v02
|
||||||
|
retentionDays: 7
|
||||||
|
destination:
|
||||||
|
type: local-directory
|
||||||
|
path: /var/backups/unidesk/platform-db/nc01/agentrun-v02
|
||||||
|
encryption:
|
||||||
|
enabled: false
|
||||||
|
|||||||
@@ -98,9 +98,9 @@ bun scripts/cli.ts platform-infra pipelines-as-code status --target JD01
|
|||||||
|
|
||||||
`status` 只读观察 YAML 选中 lane 的 source authority、对应 PipelineRun、GitOps latest、Argo Application、runtime workload、manager source commit 和 git mirror/Gitea 摘要,并报告 Argo revision 是否对齐该 lane 的 GitOps latest。未迁移的 legacy `v0.2` lane source authority 只来自 k8s git-mirror snapshot:受控 sync 先为 branch tip 创建 `refs/unidesk/snapshots/agentrun-yaml-lane/<branch>/<commit>`,`status` / `trigger-current` / build 只读取该 snapshot 和 `sourceStageRef`,不得把 host source workspace、本地 fetch/pull、可变 branch ref 或 Pipeline 直连 GitHub 当 authoritative source。默认输出是 compact commander 视图:`target` 只保留 node/lane/source/runtime/CI/GitOps/git-mirror/database 摘要,关键结论在 `summary` 和 `alignment`,成功 probe 的 stdout/stderr tail、完整 YAML target、原始 `source`、`runtime`、`gitMirror` payload 默认省略;需要完整展开时使用返回的 `disclosure.fullCommand` 或显式加 `--full`,需要原始调试视图时加 `--raw`。`status` 额外支持 `--pipeline-run <name>` 与 `--source-commit <sha>` 定点查询;`--pipeline-run` 会读取 PipelineRun `revision` 参数作为 pinned source commit,并在 `alignment.branchDrift` / `summary.branchDrift` 中同时披露当前 snapshot tip、目标 source commit、PipelineRun source commit、是否已被当前 snapshot supersede 以及 `triggerLatest` 下一步。`status` 会向 stderr 输出 `agentrun.control-plane.status.progress` 阶段事件,覆盖 `source`、`runtime` 和 `git-mirror`,避免长时间聚合时无可见进展。legacy `trigger-current` 会先执行 k8s git-mirror sync 并以 snapshot commit 创建 commit-pinned PipelineRun;同名 PipelineRun 正在运行或已经成功时必须拒绝重复触发,只允许在失败态或不存在时创建。该命令只提交 CI/CD 工作,不等待完整 PipelineRun 或 rollout 完成,后续用 `job status` 和 `status --pipeline-run <name>` 轮询。`refresh` 只对 YAML 声明的 Argo Application 执行 hard refresh,用于 GitOps promotion 已完成但 Argo 仍停留旧 revision 时的受控同步入口;它不直接 patch runtime workload。
|
`status` 只读观察 YAML 选中 lane 的 source authority、对应 PipelineRun、GitOps latest、Argo Application、runtime workload、manager source commit 和 git mirror/Gitea 摘要,并报告 Argo revision 是否对齐该 lane 的 GitOps latest。未迁移的 legacy `v0.2` lane source authority 只来自 k8s git-mirror snapshot:受控 sync 先为 branch tip 创建 `refs/unidesk/snapshots/agentrun-yaml-lane/<branch>/<commit>`,`status` / `trigger-current` / build 只读取该 snapshot 和 `sourceStageRef`,不得把 host source workspace、本地 fetch/pull、可变 branch ref 或 Pipeline 直连 GitHub 当 authoritative source。默认输出是 compact commander 视图:`target` 只保留 node/lane/source/runtime/CI/GitOps/git-mirror/database 摘要,关键结论在 `summary` 和 `alignment`,成功 probe 的 stdout/stderr tail、完整 YAML target、原始 `source`、`runtime`、`gitMirror` payload 默认省略;需要完整展开时使用返回的 `disclosure.fullCommand` 或显式加 `--full`,需要原始调试视图时加 `--raw`。`status` 额外支持 `--pipeline-run <name>` 与 `--source-commit <sha>` 定点查询;`--pipeline-run` 会读取 PipelineRun `revision` 参数作为 pinned source commit,并在 `alignment.branchDrift` / `summary.branchDrift` 中同时披露当前 snapshot tip、目标 source commit、PipelineRun source commit、是否已被当前 snapshot supersede 以及 `triggerLatest` 下一步。`status` 会向 stderr 输出 `agentrun.control-plane.status.progress` 阶段事件,覆盖 `source`、`runtime` 和 `git-mirror`,避免长时间聚合时无可见进展。legacy `trigger-current` 会先执行 k8s git-mirror sync 并以 snapshot commit 创建 commit-pinned PipelineRun;同名 PipelineRun 正在运行或已经成功时必须拒绝重复触发,只允许在失败态或不存在时创建。该命令只提交 CI/CD 工作,不等待完整 PipelineRun 或 rollout 完成,后续用 `job status` 和 `status --pipeline-run <name>` 轮询。`refresh` 只对 YAML 声明的 Argo Application 执行 hard refresh,用于 GitOps promotion 已完成但 Argo 仍停留旧 revision 时的受控同步入口;它不直接 patch runtime workload。
|
||||||
|
|
||||||
JD01 `agentrun-jd01-v02` 已迁移为 Gitea webhook -> Pipelines-as-Code -> Tekton -> GitOps/Argo -> k8s runtime 的单一路径。该 lane 不再使用 branch-follower、Gitea Actions、act_runner 或自维护 `trigger-current` 作为 CI 触发器;对应 source authority、repo visibility、public exposure 和 mirror credentials 属于 `config/platform-infra/gitea.yaml`,PaC controller/Repository/webhook/Tekton 参数属于 `config/platform-infra/pipelines-as-code.yaml`。Gitea 的公开 Web UI 是 `https://gitea.pikapython.com`,但 CI、Argo 和 runtime 内部读取必须使用 `gitea-http.devops-infra.svc.cluster.local:3000` 的 ClusterIP URL,避免公网回环和 legacy git-mirror commit 缺失。
|
JD01/NC01 `agentrun-<node>-v02` PaC 迁移 lane 的正式触发链路是:GitHub PR 合并到 `pikasTech/agentrun@v0.2` -> GitHub webhook bridge 同步到 Gitea controlled mirror 和 immutable snapshot refs -> Gitea repository push webhook 触发 Pipelines-as-Code -> Tekton PipelineRun -> GitOps/Argo -> k8s runtime。该 lane 不再使用 branch-follower、Gitea Actions、act_runner 或自维护 `trigger-current` 作为正式 CI 触发器;对应 source authority、repo visibility、public exposure 和 mirror credentials 属于 `config/platform-infra/gitea.yaml`,PaC controller/Repository/webhook/Tekton 参数属于 `config/platform-infra/pipelines-as-code.yaml`。Gitea 的公开 Web UI 是 `https://gitea.pikapython.com`,但 CI、Argo 和 runtime 内部读取必须使用 `gitea-http.devops-infra.svc.cluster.local:3000` 的 ClusterIP URL,避免公网回环和 legacy git-mirror commit 缺失。
|
||||||
|
|
||||||
JD01 v0.2 PaC closeout 以 `bun scripts/cli.ts platform-infra pipelines-as-code status --target JD01` 为首选状态入口。默认输出必须能直接看到 webhook 是否存在、最新 PipelineRun/TaskRun duration、image status、env identity、digest、GitOps commit、Argo revision/health 和 runtime source/env annotation。env reuse 的通过证据是 `IMAGE_STATUS=reused`、同一 env identity 和稳定 digest;首次 cache miss 可以作为冷启动事实记录,但不能替代后续 env-reuse 秒级收口。
|
AgentRun PaC lane 的 closeout 以 `bun scripts/cli.ts cicd status --node <NODE>` 或 `bun scripts/cli.ts platform-infra pipelines-as-code status --target <NODE> --consumer agentrun-<node>-v02` 为首选状态入口。默认输出必须能直接看到 webhook 是否存在、最新 PipelineRun/TaskRun duration、image status、env identity、digest、GitOps commit、Argo revision/health、manager Deployment readiness 和 runtime source/env annotation。env reuse 的通过证据是 `IMAGE_STATUS=reused`、同一 env identity 和稳定 digest;首次 cache miss 可以作为冷启动事实记录,但不能替代后续 env-reuse 秒级收口。若 GitHub PR 已合并但没有新 PipelineRun,应先排查 `platform-infra gitea mirror webhook status|test` 与 `platform-infra pipelines-as-code history`,不得改用 `trigger-current`、直接创建 PipelineRun、手工 push Gitea 或裸 `kubectl` 作为正式补触发。
|
||||||
|
|
||||||
YAML-only lane 的 `trigger-current --confirm` 是受控长流程入口;k8s git-mirror snapshot sync、image build、GitOps publish、git-mirror flush 和 PipelineRun 创建必须拆成短提交与状态轮询,不得把 clone、build、push 或长时间 polling 放进一个顶层 `trans` 长连接。`trigger-current` 返回异步 job 时,先用 `bun scripts/cli.ts job status <jobId> --tail-bytes 12000` 观察 `agentrun-yaml-lane-trigger` progress,再用 `agentrun control-plane status --node <node> --lane <lane> --pipeline-run <name>` 观察 Tekton、GitOps 和 Argo 对齐。后台步骤的 `status` 与 `ok` 必须共同判定,`status=succeeded` 但 `ok=false` 是终态失败,不能继续轮询到超时。GitOps publish 必须使用隔离临时 clone/worktree,不能切换或污染任何固定 source workspace;`v0.2` 历史失败 publish 若留下 dirty/detached/GitOps branch 状态,不得通过 host workspace repair 恢复 source authority,只清理已知发布残留并从 git-mirror snapshot 重新触发。
|
YAML-only lane 的 `trigger-current --confirm` 是受控长流程入口;k8s git-mirror snapshot sync、image build、GitOps publish、git-mirror flush 和 PipelineRun 创建必须拆成短提交与状态轮询,不得把 clone、build、push 或长时间 polling 放进一个顶层 `trans` 长连接。`trigger-current` 返回异步 job 时,先用 `bun scripts/cli.ts job status <jobId> --tail-bytes 12000` 观察 `agentrun-yaml-lane-trigger` progress,再用 `agentrun control-plane status --node <node> --lane <lane> --pipeline-run <name>` 观察 Tekton、GitOps 和 Argo 对齐。后台步骤的 `status` 与 `ok` 必须共同判定,`status=succeeded` 但 `ok=false` 是终态失败,不能继续轮询到超时。GitOps publish 必须使用隔离临时 clone/worktree,不能切换或污染任何固定 source workspace;`v0.2` 历史失败 publish 若留下 dirty/detached/GitOps branch 状态,不得通过 host workspace repair 恢复 source authority,只清理已知发布残留并从 git-mirror snapshot 重新触发。
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
- Applying Gitea public exposure or webhook sync must roll the affected connector workloads after Secret or ConfigMap changes. A successful Secret apply is not sufficient evidence that `frpc` loaded a new proxy; closeout should use `platform-infra gitea apply` output plus `platform-infra gitea mirror webhook status|test` rather than assuming unchanged connector Pods picked up mounted config.
|
- Applying Gitea public exposure or webhook sync must roll the affected connector workloads after Secret or ConfigMap changes. A successful Secret apply is not sufficient evidence that `frpc` loaded a new proxy; closeout should use `platform-infra gitea apply` output plus `platform-infra gitea mirror webhook status|test` rather than assuming unchanged connector Pods picked up mounted config.
|
||||||
- The canonical PaC entrypoints are `bun scripts/cli.ts platform-infra pipelines-as-code plan|apply|status|history|webhook-test --target <node>`. PaC status is the operator-facing closeout surface for migrated CI lanes and must expose webhook count, latest PipelineRun/TaskRun duration, image status, env identity, digest, GitOps commit, Argo revision and runtime provenance without requiring raw `kubectl`, `tkn` or Gitea UI inspection.
|
- The canonical PaC entrypoints are `bun scripts/cli.ts platform-infra pipelines-as-code plan|apply|status|history|webhook-test --target <node>`. PaC status is the operator-facing closeout surface for migrated CI lanes and must expose webhook count, latest PipelineRun/TaskRun duration, image status, env identity, digest, GitOps commit, Argo revision and runtime provenance without requiring raw `kubectl`, `tkn` or Gitea UI inspection.
|
||||||
- PaC history is the trigger/timing audit surface for Gitea/PaC-managed lanes. It must query Gitea Repository CR and Tekton PipelineRun/TaskRun live objects on the target node, aggregate there, return Beijing-time display by YAML timezone, expose a detail id for drill-down, and report read errors explicitly; a large namespace or unreadable target object must never be rendered as a successful empty table.
|
- PaC history is the trigger/timing audit surface for Gitea/PaC-managed lanes. It must query Gitea Repository CR and Tekton PipelineRun/TaskRun live objects on the target node, aggregate there, return Beijing-time display by YAML timezone, expose a detail id for drill-down, and report read errors explicitly; a large namespace or unreadable target object must never be rendered as a successful empty table.
|
||||||
- `config/platform-infra/pipelines-as-code.yaml` may declare multiple repositories and consumers. JD01 currently has `agentrun-jd01-v02`, `sentinel-jd01-v03` and `hwlab-jd01-v03`; use `history --target JD01` for all-consumer audit and `status --target JD01 --consumer <id>` for consumer-scoped closeout. Consumer-scoped status must not mix PipelineRuns or env reuse evidence across repositories.
|
- Formal delivery for a PaC-migrated consumer starts with merging the GitHub PR into that consumer's YAML-declared upstream branch. The GitHub webhook bridge then syncs GitHub to the controlled Gitea mirror and immutable snapshot refs; the Gitea repository push webhook triggers Pipelines-as-Code, which creates the Tekton PipelineRun. Operators must not replace this with `trigger-current`, `webhook-test`, a direct Gitea push, raw PipelineRun creation, or a local Git mirror edit. `webhook-test` is only a connectivity diagnostic.
|
||||||
|
- `config/platform-infra/pipelines-as-code.yaml` may declare multiple repositories and consumers. JD01 and NC01 both declare AgentRun, Web sentinel and HWLAB consumers; use `cicd status --node <NODE>` for node-level status, `history --target <NODE>` for all-consumer audit, and `status --target <NODE> --consumer <id>` for consumer-scoped closeout. Consumer-scoped status must not mix PipelineRuns or env reuse evidence across repositories.
|
||||||
- Public Gitea UI may use the YAML-declared HTTPS hostname, but k8s-internal consumers must use the ClusterIP service URL from YAML. Internal CI/Argo/runtime reads must not loop through public DNS/Caddy/FRP, and migrated lanes must not fall back to legacy git-mirror read URLs when the commit exists only in Gitea.
|
- Public Gitea UI may use the YAML-declared HTTPS hostname, but k8s-internal consumers must use the ClusterIP service URL from YAML. Internal CI/Argo/runtime reads must not loop through public DNS/Caddy/FRP, and migrated lanes must not fall back to legacy git-mirror read URLs when the commit exists only in Gitea.
|
||||||
- A PaC-migrated lane must keep a single trigger path: Gitea webhook -> Pipelines-as-Code -> Tekton -> GitOps/Argo -> k8s runtime. Do not add Gitea Actions, `act_runner`, branch-follower or custom script fallback unless a later issue explicitly changes the architecture. `config/cicd-gitea-actions-poc.yaml` is archived POC context only; snapshot refs that still contain `gitea-actions` are historical names retained for compatibility, not evidence that Gitea Actions is active.
|
- A PaC-migrated lane must keep a single trigger path: Gitea webhook -> Pipelines-as-Code -> Tekton -> GitOps/Argo -> k8s runtime. Do not add Gitea Actions, `act_runner`, branch-follower or custom script fallback unless a later issue explicitly changes the architecture. `config/cicd-gitea-actions-poc.yaml` is archived POC context only; snapshot refs that still contain `gitea-actions` are historical names retained for compatibility, not evidence that Gitea Actions is active.
|
||||||
- k8s runtime remains Docker-free from the point it pulls already built images. CI build steps may use YAML-declared native build tooling, but Docker socket/daemon access must not become part of the runtime plane.
|
- k8s runtime remains Docker-free from the point it pulls already built images. CI build steps may use YAML-declared native build tooling, but Docker socket/daemon access must not become part of the runtime plane.
|
||||||
|
|||||||
+1
-1
@@ -270,7 +270,7 @@ async function main(): Promise<void> {
|
|||||||
|
|
||||||
if (top === "cicd") {
|
if (top === "cicd") {
|
||||||
const { runCicdCommand } = await import("./src/cicd");
|
const { runCicdCommand } = await import("./src/cicd");
|
||||||
const result = await runCicdCommand(null, args.slice(1));
|
const result = await runCicdCommand(readConfig(), args.slice(1));
|
||||||
const ok = (result as { ok?: unknown }).ok !== false;
|
const ok = (result as { ok?: unknown }).ok !== false;
|
||||||
if (isRenderedCliResult(result)) {
|
if (isRenderedCliResult(result)) {
|
||||||
emitText(result.renderedText, result.command || commandName);
|
emitText(result.renderedText, result.command || commandName);
|
||||||
|
|||||||
@@ -649,8 +649,9 @@ function agentRunManagerManifests(spec: AgentRunLaneSpec, sourceCommit: string,
|
|||||||
imagePullPolicy: "IfNotPresent",
|
imagePullPolicy: "IfNotPresent",
|
||||||
ports: [{ name: "http", containerPort: 8080 }],
|
ports: [{ name: "http", containerPort: 8080 }],
|
||||||
env: managerEnv(spec, sourceCommit, imageRef, image.envIdentity),
|
env: managerEnv(spec, sourceCommit, imageRef, image.envIdentity),
|
||||||
readinessProbe: { httpGet: { path: "/health/readiness", port: "http" } },
|
readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, timeoutSeconds: 2 },
|
||||||
livenessProbe: { httpGet: { path: "/health/live", port: "http" } },
|
livenessProbe: { httpGet: { path: "/health/live", port: "http" }, timeoutSeconds: 2 },
|
||||||
|
startupProbe: { httpGet: { path: "/health/live", port: "http" }, periodSeconds: 10, timeoutSeconds: 2, failureThreshold: 90 },
|
||||||
resources: spec.deployment.manager.resources,
|
resources: spec.deployment.manager.resources,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ export async function statusPipelinesAsCodeLane(config: UniDeskConfig, options:
|
|||||||
...(expectedGitopsRevision === null || argoSyncedToGitops ? [] : ["argo-revision-stale"]),
|
...(expectedGitopsRevision === null || argoSyncedToGitops ? [] : ["argo-revision-stale"]),
|
||||||
...(runtimePayload.runtimeNamespaceExists === true ? [] : ["runtime-namespace-missing"]),
|
...(runtimePayload.runtimeNamespaceExists === true ? [] : ["runtime-namespace-missing"]),
|
||||||
...(manager.deploymentExists === true ? [] : ["manager-deployment-missing"]),
|
...(manager.deploymentExists === true ? [] : ["manager-deployment-missing"]),
|
||||||
|
...(manager.deploymentExists === true && manager.ready !== true ? ["manager-not-ready"] : []),
|
||||||
...(manager.deploymentExists !== true || sourceCommit === null || managerSourceMatchesExpected ? [] : ["manager-source-stale"]),
|
...(manager.deploymentExists !== true || sourceCommit === null || managerSourceMatchesExpected ? [] : ["manager-source-stale"]),
|
||||||
...(manager.serviceExists === true ? [] : ["manager-service-missing"]),
|
...(manager.serviceExists === true ? [] : ["manager-service-missing"]),
|
||||||
...(spec.database.mode === "external-postgres" && database.secretPresent !== true ? ["database-secret-missing"] : []),
|
...(spec.database.mode === "external-postgres" && database.secretPresent !== true ? ["database-secret-missing"] : []),
|
||||||
@@ -446,9 +447,12 @@ export async function statusPipelinesAsCodeLane(config: UniDeskConfig, options:
|
|||||||
namespaceExists: runtimePayload.runtimeNamespaceExists ?? false,
|
namespaceExists: runtimePayload.runtimeNamespaceExists ?? false,
|
||||||
manager: {
|
manager: {
|
||||||
deploymentExists: manager.deploymentExists ?? false,
|
deploymentExists: manager.deploymentExists ?? false,
|
||||||
|
ready: manager.ready ?? false,
|
||||||
serviceExists: manager.serviceExists ?? false,
|
serviceExists: manager.serviceExists ?? false,
|
||||||
sourceCommit: stringOrNull(manager.sourceCommit),
|
sourceCommit: stringOrNull(manager.sourceCommit),
|
||||||
sourceMatchesExpected: managerSourceMatchesExpected,
|
sourceMatchesExpected: managerSourceMatchesExpected,
|
||||||
|
replicas: manager.replicas ?? null,
|
||||||
|
conditions: manager.conditions ?? [],
|
||||||
envIdentity: manager.envIdentity ?? null,
|
envIdentity: manager.envIdentity ?? null,
|
||||||
},
|
},
|
||||||
databaseSecretPresent: database.secretPresent ?? null,
|
databaseSecretPresent: database.secretPresent ?? null,
|
||||||
@@ -590,6 +594,7 @@ export async function statusYamlLane(config: UniDeskConfig, options: StatusOptio
|
|||||||
...(argo.exists !== true || expectedGitopsRevision === null || argoSyncedToGitops ? [] : ["argo-revision-stale"]),
|
...(argo.exists !== true || expectedGitopsRevision === null || argoSyncedToGitops ? [] : ["argo-revision-stale"]),
|
||||||
...(runtimePayload.runtimeNamespaceExists === true ? [] : ["runtime-namespace-missing"]),
|
...(runtimePayload.runtimeNamespaceExists === true ? [] : ["runtime-namespace-missing"]),
|
||||||
...(manager.deploymentExists === true ? [] : ["manager-deployment-missing"]),
|
...(manager.deploymentExists === true ? [] : ["manager-deployment-missing"]),
|
||||||
|
...(manager.deploymentExists === true && manager.ready !== true ? ["manager-not-ready"] : []),
|
||||||
...(manager.deploymentExists !== true || sourceCommit === null || managerSourceMatchesExpected ? [] : ["manager-source-stale"]),
|
...(manager.deploymentExists !== true || sourceCommit === null || managerSourceMatchesExpected ? [] : ["manager-source-stale"]),
|
||||||
...(manager.serviceExists === true ? [] : ["manager-service-missing"]),
|
...(manager.serviceExists === true ? [] : ["manager-service-missing"]),
|
||||||
...(spec.database.mode === "external-postgres" && database.secretPresent !== true ? ["database-secret-missing"] : []),
|
...(spec.database.mode === "external-postgres" && database.secretPresent !== true ? ["database-secret-missing"] : []),
|
||||||
@@ -678,9 +683,12 @@ export async function statusYamlLane(config: UniDeskConfig, options: StatusOptio
|
|||||||
namespaceExists: runtimePayload.runtimeNamespaceExists ?? false,
|
namespaceExists: runtimePayload.runtimeNamespaceExists ?? false,
|
||||||
manager: {
|
manager: {
|
||||||
deploymentExists: manager.deploymentExists ?? false,
|
deploymentExists: manager.deploymentExists ?? false,
|
||||||
|
ready: manager.ready ?? false,
|
||||||
serviceExists: manager.serviceExists ?? false,
|
serviceExists: manager.serviceExists ?? false,
|
||||||
sourceCommit: stringOrNull(manager.sourceCommit),
|
sourceCommit: stringOrNull(manager.sourceCommit),
|
||||||
sourceMatchesExpected: managerSourceMatchesExpected,
|
sourceMatchesExpected: managerSourceMatchesExpected,
|
||||||
|
replicas: manager.replicas ?? null,
|
||||||
|
conditions: manager.conditions ?? [],
|
||||||
},
|
},
|
||||||
databaseSecretPresent: database.secretPresent ?? null,
|
databaseSecretPresent: database.secretPresent ?? null,
|
||||||
secretsReady: secrets.ready ?? null,
|
secretsReady: secrets.ready ?? null,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import { agentRunGetKindHelp, runAgentRunResourceCommand } from "./resource-acti
|
|||||||
import { runAgentRunRestCompatCommand, runGitMirrorJob, startAsyncAgentRunJob } from "./rest-bridge";
|
import { runAgentRunRestCompatCommand, runGitMirrorJob, startAsyncAgentRunJob } from "./rest-bridge";
|
||||||
import { exposeAgentRun, restartYamlLane, secretSync, triggerCurrent } from "./trigger";
|
import { exposeAgentRun, restartYamlLane, secretSync, triggerCurrent } from "./trigger";
|
||||||
import { unsupported } from "./utils";
|
import { unsupported } from "./utils";
|
||||||
import { cleanupReleasedPvs, cleanupRunners, cleanupRuns, cleanupSessionPvcs, refresh } from "./yaml-lane";
|
import { cleanupLocalPostgres, cleanupReleasedPvs, cleanupRunners, cleanupRuns, cleanupSessionPvcs, refresh } from "./yaml-lane";
|
||||||
|
|
||||||
export function agentRunHelp(): unknown {
|
export function agentRunHelp(): unknown {
|
||||||
return {
|
return {
|
||||||
@@ -92,6 +92,8 @@ export function agentRunHelp(): unknown {
|
|||||||
"bun scripts/cli.ts agentrun control-plane cleanup-runs --min-age-minutes 30 --limit 200 --confirm",
|
"bun scripts/cli.ts agentrun control-plane cleanup-runs --min-age-minutes 30 --limit 200 --confirm",
|
||||||
"bun scripts/cli.ts agentrun control-plane cleanup-released-pvs --limit 200 --dry-run",
|
"bun scripts/cli.ts agentrun control-plane cleanup-released-pvs --limit 200 --dry-run",
|
||||||
"bun scripts/cli.ts agentrun control-plane cleanup-released-pvs --limit 200 --confirm",
|
"bun scripts/cli.ts agentrun control-plane cleanup-released-pvs --limit 200 --confirm",
|
||||||
|
"bun scripts/cli.ts agentrun control-plane cleanup-local-postgres --node NC01 --lane nc01-v02 --dry-run",
|
||||||
|
"bun scripts/cli.ts agentrun control-plane cleanup-local-postgres --node NC01 --lane nc01-v02 --confirm",
|
||||||
"bun scripts/cli.ts agentrun git-mirror status",
|
"bun scripts/cli.ts agentrun git-mirror status",
|
||||||
"bun scripts/cli.ts agentrun git-mirror status --full",
|
"bun scripts/cli.ts agentrun git-mirror status --full",
|
||||||
"bun scripts/cli.ts agentrun git-mirror sync --confirm",
|
"bun scripts/cli.ts agentrun git-mirror sync --confirm",
|
||||||
@@ -147,6 +149,7 @@ export async function runAgentRunCommand(config: UniDeskConfig | null, args: str
|
|||||||
if (action === "cleanup-session-pvcs") {
|
if (action === "cleanup-session-pvcs") {
|
||||||
return await cleanupSessionPvcs(config, parseCleanupSessionPvcsOptions(actionArgs));
|
return await cleanupSessionPvcs(config, parseCleanupSessionPvcsOptions(actionArgs));
|
||||||
}
|
}
|
||||||
|
if (action === "cleanup-local-postgres") return await cleanupLocalPostgres(config, parseLaneConfirmOptions(actionArgs));
|
||||||
if (action === "cleanup-released-pvs") return await cleanupReleasedPvs(config, parseCleanupReleasedPvOptions(actionArgs));
|
if (action === "cleanup-released-pvs") return await cleanupReleasedPvs(config, parseCleanupReleasedPvOptions(actionArgs));
|
||||||
}
|
}
|
||||||
if (group === "git-mirror") {
|
if (group === "git-mirror") {
|
||||||
@@ -275,7 +278,7 @@ export function agentRunHelpText(args: string[]): string {
|
|||||||
return [
|
return [
|
||||||
"Usage: bun scripts/cli.ts agentrun control-plane <action> [options]",
|
"Usage: bun scripts/cli.ts agentrun control-plane <action> [options]",
|
||||||
"",
|
"",
|
||||||
"Actions: plan, apply, status, secret-sync, expose, trigger-current, refresh, cleanup-runners, cleanup-runs, cleanup-session-pvcs, cleanup-released-pvs",
|
"Actions: plan, apply, status, secret-sync, expose, trigger-current, refresh, cleanup-runners, cleanup-runs, cleanup-session-pvcs, cleanup-local-postgres, cleanup-released-pvs",
|
||||||
"Examples:",
|
"Examples:",
|
||||||
" bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02",
|
" bun scripts/cli.ts agentrun control-plane plan --node D601 --lane v02",
|
||||||
" bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --dry-run",
|
" bun scripts/cli.ts agentrun control-plane apply --node D601 --lane v02 --dry-run",
|
||||||
@@ -288,6 +291,7 @@ export function agentRunHelpText(args: string[]): string {
|
|||||||
" bun scripts/cli.ts agentrun control-plane trigger-current --dry-run",
|
" bun scripts/cli.ts agentrun control-plane trigger-current --dry-run",
|
||||||
" bun scripts/cli.ts agentrun control-plane cleanup-runners --node D601 --lane v02 --dry-run",
|
" bun scripts/cli.ts agentrun control-plane cleanup-runners --node D601 --lane v02 --dry-run",
|
||||||
" bun scripts/cli.ts agentrun control-plane cleanup-session-pvcs --node JD01 --lane jd01-v02 --dry-run",
|
" bun scripts/cli.ts agentrun control-plane cleanup-session-pvcs --node JD01 --lane jd01-v02 --dry-run",
|
||||||
|
" bun scripts/cli.ts agentrun control-plane cleanup-local-postgres --node NC01 --lane nc01-v02 --dry-run",
|
||||||
" bun scripts/cli.ts agentrun control-plane cleanup-runs --min-age-minutes 30 --limit 200 --dry-run",
|
" bun scripts/cli.ts agentrun control-plane cleanup-runs --min-age-minutes 30 --limit 200 --dry-run",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ export function agentRunControlPlaneStatusCommand(spec: AgentRunLaneSpec, option
|
|||||||
].filter(Boolean).join(" ");
|
].filter(Boolean).join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deploymentReplicaSummary(manager: Record<string, unknown>): string {
|
||||||
|
const replicas = record(manager.replicas);
|
||||||
|
const desired = displayValue(replicas.desired);
|
||||||
|
const ready = displayValue(replicas.ready);
|
||||||
|
const available = displayValue(replicas.available);
|
||||||
|
return `${ready}/${desired} available=${available}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function renderAgentRunControlPlaneStatusSummary(result: Record<string, unknown>): RenderedCliResult {
|
export function renderAgentRunControlPlaneStatusSummary(result: Record<string, unknown>): RenderedCliResult {
|
||||||
const summary = record(result.summary);
|
const summary = record(result.summary);
|
||||||
const target = record(result.target);
|
const target = record(result.target);
|
||||||
@@ -104,7 +112,7 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
|||||||
["pipelinerun", yesNo(latestPipelineRun.status === "True"), `run=${displayValue(latestPipelineRun.name)} status=${displayValue(latestPipelineRun.status)} reason=${displayValue(latestPipelineRun.reason)} duration=${displayValue(latestPipelineRun.durationSeconds)}s`],
|
["pipelinerun", yesNo(latestPipelineRun.status === "True"), `run=${displayValue(latestPipelineRun.name)} status=${displayValue(latestPipelineRun.status)} reason=${displayValue(latestPipelineRun.reason)} duration=${displayValue(latestPipelineRun.durationSeconds)}s`],
|
||||||
["image", yesNo(artifact.imageStatus === "reused" || artifact.imageStatus === "built"), `status=${displayValue(artifact.imageStatus)} env=${displayValue(artifact.envIdentity)} digest=${shortSha(artifact.digest)} gitops=${shortSha(artifact.gitopsCommit)}`],
|
["image", yesNo(artifact.imageStatus === "reused" || artifact.imageStatus === "built"), `status=${displayValue(artifact.imageStatus)} env=${displayValue(artifact.envIdentity)} digest=${shortSha(artifact.digest)} gitops=${shortSha(artifact.gitopsCommit)}`],
|
||||||
["argo", yesNo(argo.syncedToGitops), `sync=${displayValue(argo.syncStatus)} health=${displayValue(argo.healthStatus)} revision=${shortSha(argo.revision)}`],
|
["argo", yesNo(argo.syncedToGitops), `sync=${displayValue(argo.syncStatus)} health=${displayValue(argo.healthStatus)} revision=${shortSha(argo.revision)}`],
|
||||||
["runtime", yesNo(runtimeManager.sourceMatchesExpected), `manager=${shortSha(runtimeManager.sourceCommit)} secrets=${yesNo(runtime.secretsReady)} dbSecret=${yesNo(runtime.databaseSecretPresent)} localPgAbsent=${yesNo(runtime.localPostgresAbsent)}`],
|
["runtime", yesNo(runtimeManager.ready === true && runtimeManager.sourceMatchesExpected === true), `manager=${shortSha(runtimeManager.sourceCommit)} ready=${yesNo(runtimeManager.ready)} replicas=${deploymentReplicaSummary(runtimeManager)} secrets=${yesNo(runtime.secretsReady)} dbSecret=${yesNo(runtime.databaseSecretPresent)} localPgAbsent=${yesNo(runtime.localPostgresAbsent)}`],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
"",
|
"",
|
||||||
@@ -151,7 +159,7 @@ export function renderAgentRunControlPlaneStatusSummary(result: Record<string, u
|
|||||||
["git-mirror", yesNo(gitMirror.alreadySynced), `read=${yesNo(gitMirror.readReady)} write=${yesNo(gitMirror.writeReady)} gitops=${shortSha(gitMirror.gitopsCommit)}`],
|
["git-mirror", yesNo(gitMirror.alreadySynced), `read=${yesNo(gitMirror.readReady)} write=${yesNo(gitMirror.writeReady)} gitops=${shortSha(gitMirror.gitopsCommit)}`],
|
||||||
["ci", yesNo(ciRun.status === "True"), `run=${displayValue(ciRun.name)} status=${displayValue(ciRun.status)} reason=${displayValue(ciRun.reason)} evidenceMissing=${yesNo(ci.evidenceMissing)}`],
|
["ci", yesNo(ciRun.status === "True"), `run=${displayValue(ciRun.name)} status=${displayValue(ciRun.status)} reason=${displayValue(ciRun.reason)} evidenceMissing=${yesNo(ci.evidenceMissing)}`],
|
||||||
["argo", yesNo(argo.syncedToGitops), `sync=${displayValue(argo.syncStatus)} health=${displayValue(argo.healthStatus)} revision=${shortSha(argo.revision)}`],
|
["argo", yesNo(argo.syncedToGitops), `sync=${displayValue(argo.syncStatus)} health=${displayValue(argo.healthStatus)} revision=${shortSha(argo.revision)}`],
|
||||||
["runtime", yesNo(runtimeManager.sourceMatchesExpected), `manager=${shortSha(runtimeManager.sourceCommit)} secrets=${yesNo(runtime.secretsReady)} dbSecret=${yesNo(runtime.databaseSecretPresent)} localPgAbsent=${yesNo(runtime.localPostgresAbsent)}`],
|
["runtime", yesNo(runtimeManager.ready === true && runtimeManager.sourceMatchesExpected === true), `manager=${shortSha(runtimeManager.sourceCommit)} ready=${yesNo(runtimeManager.ready)} replicas=${deploymentReplicaSummary(runtimeManager)} secrets=${yesNo(runtime.secretsReady)} dbSecret=${yesNo(runtime.databaseSecretPresent)} localPgAbsent=${yesNo(runtime.localPostgresAbsent)}`],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
} from "../agentrun-manifests";
|
} from "../agentrun-manifests";
|
||||||
import { sha256Fingerprint } from "../platform-infra-ops-library";
|
import { sha256Fingerprint } from "../platform-infra-ops-library";
|
||||||
|
|
||||||
import type { CleanupReleasedPvOptions, CleanupRunnersOptions, CleanupRunsOptions, CleanupSessionPvcsOptions, RefreshOptions } from "./options";
|
import type { CleanupReleasedPvOptions, CleanupRunnersOptions, CleanupRunsOptions, CleanupSessionPvcsOptions, LaneConfirmOptions, RefreshOptions } from "./options";
|
||||||
import { cleanupReleasedPvsFinalizeNodeScript, cleanupReleasedPvsPlanNodeScript, cleanupRunnersFinalizeNodeScript, cleanupRunsFinalizeNodeScript, cleanupRunsPlanNodeScript, refreshYamlLaneScript } from "./git-mirror";
|
import { cleanupReleasedPvsFinalizeNodeScript, cleanupReleasedPvsPlanNodeScript, cleanupRunnersFinalizeNodeScript, cleanupRunsFinalizeNodeScript, cleanupRunsPlanNodeScript, refreshYamlLaneScript } from "./git-mirror";
|
||||||
import { cleanupRunnersFactsNodeScript, cleanupRunnersPlanNodeScript, collectLaneSecretSources, createYamlLaneJobScript, yamlLaneGitopsPublishJobManifest, yamlLaneGitopsPublishPayloadFromProbe, yamlLaneJobProbeScript } from "./secrets";
|
import { cleanupRunnersFactsNodeScript, cleanupRunnersPlanNodeScript, collectLaneSecretSources, createYamlLaneJobScript, yamlLaneGitopsPublishJobManifest, yamlLaneGitopsPublishPayloadFromProbe, yamlLaneJobProbeScript } from "./secrets";
|
||||||
import { capture, captureJsonPayload, compactCapture, progressEvent, shQuote, sleep, stringOrNull } from "./utils";
|
import { capture, captureJsonPayload, compactCapture, progressEvent, shQuote, sleep, stringOrNull } from "./utils";
|
||||||
@@ -234,6 +234,105 @@ export async function cleanupSessionPvcs(config: UniDeskConfig, options: Cleanup
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cleanupLocalPostgres(config: UniDeskConfig, options: LaneConfirmOptions): Promise<Record<string, unknown>> {
|
||||||
|
const { configPath, spec } = resolveAgentRunLaneTarget(options);
|
||||||
|
const name = spec.deployment.localPostgres.serviceName;
|
||||||
|
const plan = {
|
||||||
|
namespace: spec.runtime.namespace,
|
||||||
|
statefulSet: name,
|
||||||
|
service: name,
|
||||||
|
pvcDisposition: "retained",
|
||||||
|
requiresDatabaseMode: "external-postgres",
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
if (spec.database.mode !== "external-postgres" || spec.deployment.localPostgres.enabled || name === null) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
command: "agentrun control-plane cleanup-local-postgres",
|
||||||
|
mode: "blocked",
|
||||||
|
mutation: false,
|
||||||
|
configPath,
|
||||||
|
target: agentRunLaneSummary(spec),
|
||||||
|
degradedReason: "lane-is-not-external-postgres",
|
||||||
|
plan,
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const confirmed = options.confirm && !options.dryRun;
|
||||||
|
const result = await capture(config, spec.nodeKubeRoute, ["sh", "--", cleanupLocalPostgresScript(spec.runtime.namespace, name, confirmed)]);
|
||||||
|
const payload = captureJsonPayload(result);
|
||||||
|
const base = {
|
||||||
|
ok: result.exitCode === 0 && payload.ok !== false,
|
||||||
|
command: "agentrun control-plane cleanup-local-postgres",
|
||||||
|
mode: confirmed ? "confirmed-cleanup" : "dry-run",
|
||||||
|
mutation: confirmed,
|
||||||
|
configPath,
|
||||||
|
target: agentRunLaneSummary(spec),
|
||||||
|
plan,
|
||||||
|
result: payload,
|
||||||
|
capture: compactCapture(result, { full: result.exitCode !== 0, stdoutTailChars: 3000, stderrTailChars: 3000 }),
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
if (!confirmed) {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
next: {
|
||||||
|
confirm: `bun scripts/cli.ts agentrun control-plane cleanup-local-postgres --node ${spec.nodeId} --lane ${spec.lane} --confirm`,
|
||||||
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
next: {
|
||||||
|
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanupLocalPostgresScript(namespace: string, name: string, confirm: boolean): string {
|
||||||
|
return [
|
||||||
|
"set +e",
|
||||||
|
`namespace=${shQuote(namespace)}`,
|
||||||
|
`name=${shQuote(name)}`,
|
||||||
|
`confirm=${confirm ? "true" : "false"}`,
|
||||||
|
"tmp_dir=$(mktemp -d)",
|
||||||
|
"trap 'rm -rf \"$tmp_dir\"' EXIT",
|
||||||
|
"kubectl -n \"$namespace\" get statefulset \"$name\" -o name > \"$tmp_dir/sts-before\" 2>/dev/null; sts_before=$?",
|
||||||
|
"kubectl -n \"$namespace\" get service \"$name\" -o name > \"$tmp_dir/svc-before\" 2>/dev/null; svc_before=$?",
|
||||||
|
"delete_sts_rc=0",
|
||||||
|
"delete_svc_rc=0",
|
||||||
|
"if [ \"$confirm\" = \"true\" ]; then",
|
||||||
|
" kubectl -n \"$namespace\" delete statefulset \"$name\" --ignore-not-found > \"$tmp_dir/delete-sts.out\" 2> \"$tmp_dir/delete-sts.err\"; delete_sts_rc=$?",
|
||||||
|
" kubectl -n \"$namespace\" delete service \"$name\" --ignore-not-found > \"$tmp_dir/delete-svc.out\" 2> \"$tmp_dir/delete-svc.err\"; delete_svc_rc=$?",
|
||||||
|
"else",
|
||||||
|
" : > \"$tmp_dir/delete-sts.out\"; : > \"$tmp_dir/delete-sts.err\"; : > \"$tmp_dir/delete-svc.out\"; : > \"$tmp_dir/delete-svc.err\"",
|
||||||
|
"fi",
|
||||||
|
"kubectl -n \"$namespace\" get statefulset \"$name\" -o name > \"$tmp_dir/sts-after\" 2>/dev/null; sts_after=$?",
|
||||||
|
"kubectl -n \"$namespace\" get service \"$name\" -o name > \"$tmp_dir/svc-after\" 2>/dev/null; svc_after=$?",
|
||||||
|
"NAMESPACE=\"$namespace\" NAME=\"$name\" CONFIRM=\"$confirm\" STS_BEFORE=\"$sts_before\" SVC_BEFORE=\"$svc_before\" STS_AFTER=\"$sts_after\" SVC_AFTER=\"$svc_after\" DELETE_STS_RC=\"$delete_sts_rc\" DELETE_SVC_RC=\"$delete_svc_rc\" TMP_DIR=\"$tmp_dir\" python3 - <<'PY'",
|
||||||
|
"import json, os, pathlib",
|
||||||
|
"base = pathlib.Path(os.environ['TMP_DIR'])",
|
||||||
|
"def present(name): return os.environ.get(name) == '0'",
|
||||||
|
"def text(name):",
|
||||||
|
" try: return (base / name).read_text().strip()",
|
||||||
|
" except Exception: return ''",
|
||||||
|
"payload = {",
|
||||||
|
" 'ok': os.environ.get('DELETE_STS_RC') == '0' and os.environ.get('DELETE_SVC_RC') == '0',",
|
||||||
|
" 'namespace': os.environ.get('NAMESPACE'),",
|
||||||
|
" 'name': os.environ.get('NAME'),",
|
||||||
|
" 'confirmed': os.environ.get('CONFIRM') == 'true',",
|
||||||
|
" 'before': {'statefulSetPresent': present('STS_BEFORE'), 'servicePresent': present('SVC_BEFORE')},",
|
||||||
|
" 'after': {'statefulSetPresent': present('STS_AFTER'), 'servicePresent': present('SVC_AFTER')},",
|
||||||
|
" 'deleted': {'statefulSet': text('delete-sts.out'), 'service': text('delete-svc.out')},",
|
||||||
|
" 'pvcDisposition': 'retained',",
|
||||||
|
" 'valuesPrinted': False,",
|
||||||
|
"}",
|
||||||
|
"print(json.dumps(payload, ensure_ascii=False))",
|
||||||
|
"PY",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
export function cleanupSessionPvcsScript(options: CleanupSessionPvcsOptions, spec: AgentRunLaneSpec): string {
|
export function cleanupSessionPvcsScript(options: CleanupSessionPvcsOptions, spec: AgentRunLaneSpec): string {
|
||||||
const retention = spec.deployment.runner.retention.sessionPvcRetention;
|
const retention = spec.deployment.runner.retention.sessionPvcRetention;
|
||||||
const script = readFileSync(rootPath("scripts/src/agentrun/cleanup-session-pvcs.mjs"), "utf8");
|
const script = readFileSync(rootPath("scripts/src/agentrun/cleanup-session-pvcs.mjs"), "utf8");
|
||||||
@@ -573,6 +672,34 @@ export function yamlLaneRuntimeStatusScript(spec: AgentRunLaneSpec, pipelineRun:
|
|||||||
" for entry in envs:",
|
" for entry in envs:",
|
||||||
" if entry.get('name') == name: return entry.get('value')",
|
" if entry.get('name') == name: return entry.get('value')",
|
||||||
" return None",
|
" return None",
|
||||||
|
"def int_value(value, default=0):",
|
||||||
|
" try: return int(value)",
|
||||||
|
" except Exception: return default",
|
||||||
|
"def deployment_replicas(deploy):",
|
||||||
|
" spec = (deploy or {}).get('spec') or {}",
|
||||||
|
" status = (deploy or {}).get('status') or {}",
|
||||||
|
" metadata = (deploy or {}).get('metadata') or {}",
|
||||||
|
" desired = int_value(spec.get('replicas'), 1)",
|
||||||
|
" return {",
|
||||||
|
" 'desired': desired,",
|
||||||
|
" 'ready': int_value(status.get('readyReplicas'), 0),",
|
||||||
|
" 'available': int_value(status.get('availableReplicas'), 0),",
|
||||||
|
" 'updated': int_value(status.get('updatedReplicas'), 0),",
|
||||||
|
" 'unavailable': int_value(status.get('unavailableReplicas'), 0),",
|
||||||
|
" 'generation': int_value(metadata.get('generation'), 0),",
|
||||||
|
" 'observedGeneration': int_value(status.get('observedGeneration'), 0),",
|
||||||
|
" }",
|
||||||
|
"def deployment_conditions(deploy):",
|
||||||
|
" return [{'type': c.get('type'), 'status': c.get('status'), 'reason': c.get('reason')} for c in (((deploy or {}).get('status') or {}).get('conditions') or [])]",
|
||||||
|
"def deployment_ready(deploy):",
|
||||||
|
" if deploy is None: return False",
|
||||||
|
" replicas = deployment_replicas(deploy)",
|
||||||
|
" desired = replicas['desired']",
|
||||||
|
" if desired == 0: return True",
|
||||||
|
" observed = replicas['observedGeneration']",
|
||||||
|
" generation = replicas['generation']",
|
||||||
|
" generation_current = observed == 0 or generation == 0 or observed >= generation",
|
||||||
|
" return generation_current and replicas['ready'] >= desired and replicas['available'] >= desired and replicas['updated'] >= desired",
|
||||||
"def pipeline_run_param(obj, name):",
|
"def pipeline_run_param(obj, name):",
|
||||||
" for entry in (((obj or {}).get('spec') or {}).get('params') or []):",
|
" for entry in (((obj or {}).get('spec') or {}).get('params') or []):",
|
||||||
" if entry.get('name') == name: return entry.get('value')",
|
" if entry.get('name') == name: return entry.get('value')",
|
||||||
@@ -589,6 +716,9 @@ export function yamlLaneRuntimeStatusScript(spec: AgentRunLaneSpec, pipelineRun:
|
|||||||
"ports = []",
|
"ports = []",
|
||||||
"for port in ((((manager_svc or {}).get('spec') or {}).get('ports')) or []):",
|
"for port in ((((manager_svc or {}).get('spec') or {}).get('ports')) or []):",
|
||||||
" ports.append({'name': port.get('name'), 'port': port.get('port'), 'targetPort': port.get('targetPort')})",
|
" ports.append({'name': port.get('name'), 'port': port.get('port'), 'targetPort': port.get('targetPort')})",
|
||||||
|
"manager_replicas = deployment_replicas(manager_deploy)",
|
||||||
|
"manager_ready = exists('MANAGER_DEPLOY_EXIT') and deployment_ready(manager_deploy)",
|
||||||
|
"manager_conditions = deployment_conditions(manager_deploy)",
|
||||||
"matching_postgres = [line for line in re.split(r'\\r?\\n', names) if re.search('postgres', line, re.I)][:20]",
|
"matching_postgres = [line for line in re.split(r'\\r?\\n', names) if re.search('postgres', line, re.I)][:20]",
|
||||||
"print(json.dumps({",
|
"print(json.dumps({",
|
||||||
" 'ok': exists('RUNTIME_NS_EXIT') and exists('CI_NS_EXIT'),",
|
" 'ok': exists('RUNTIME_NS_EXIT') and exists('CI_NS_EXIT'),",
|
||||||
@@ -598,7 +728,7 @@ export function yamlLaneRuntimeStatusScript(spec: AgentRunLaneSpec, pipelineRun:
|
|||||||
" 'pipeline': {'exists': exists('PIPELINE_EXIT'), 'name': os.environ.get('pipeline_name')},",
|
" 'pipeline': {'exists': exists('PIPELINE_EXIT'), 'name': os.environ.get('pipeline_name')},",
|
||||||
" 'pipelineRun': {'exists': exists('PIPELINERUN_EXIT'), 'name': os.environ.get('pipeline_run') or None, 'status': c.get('status'), 'reason': c.get('reason'), 'sourceCommit': pipeline_run_param(pipeline_run, 'revision'), 'startTime': ((pipeline_run or {}).get('status') or {}).get('startTime'), 'completionTime': ((pipeline_run or {}).get('status') or {}).get('completionTime')},",
|
" 'pipelineRun': {'exists': exists('PIPELINERUN_EXIT'), 'name': os.environ.get('pipeline_run') or None, 'status': c.get('status'), 'reason': c.get('reason'), 'sourceCommit': pipeline_run_param(pipeline_run, 'revision'), 'startTime': ((pipeline_run or {}).get('status') or {}).get('startTime'), 'completionTime': ((pipeline_run or {}).get('status') or {}).get('completionTime')},",
|
||||||
" 'argo': {'exists': exists('ARGO_EXIT'), 'namespace': os.environ.get('argo_namespace'), 'application': os.environ.get('argo_application'), 'revision': (((argo or {}).get('status') or {}).get('sync') or {}).get('revision'), 'syncStatus': (((argo or {}).get('status') or {}).get('sync') or {}).get('status'), 'healthStatus': (((argo or {}).get('status') or {}).get('health') or {}).get('status')},",
|
" 'argo': {'exists': exists('ARGO_EXIT'), 'namespace': os.environ.get('argo_namespace'), 'application': os.environ.get('argo_application'), 'revision': (((argo or {}).get('status') or {}).get('sync') or {}).get('revision'), 'syncStatus': (((argo or {}).get('status') or {}).get('sync') or {}).get('status'), 'healthStatus': (((argo or {}).get('status') or {}).get('health') or {}).get('status')},",
|
||||||
" 'manager': {'deploymentExists': exists('MANAGER_DEPLOY_EXIT'), 'serviceExists': exists('MANAGER_SVC_EXIT'), 'deployment': os.environ.get('manager_deployment'), 'service': os.environ.get('manager_service'), 'image': ((((((manager_deploy or {}).get('spec') or {}).get('template') or {}).get('spec') or {}).get('containers') or [{}])[0]).get('image'), 'sourceCommit': env_value(manager_deploy, 'AGENTRUN_SOURCE_COMMIT'), 'servicePorts': ports},",
|
" 'manager': {'deploymentExists': exists('MANAGER_DEPLOY_EXIT'), 'serviceExists': exists('MANAGER_SVC_EXIT'), 'ready': manager_ready, 'deployment': os.environ.get('manager_deployment'), 'service': os.environ.get('manager_service'), 'image': ((((((manager_deploy or {}).get('spec') or {}).get('template') or {}).get('spec') or {}).get('containers') or [{}])[0]).get('image'), 'sourceCommit': env_value(manager_deploy, 'AGENTRUN_SOURCE_COMMIT'), 'servicePorts': ports, 'replicas': manager_replicas, 'conditions': manager_conditions},",
|
||||||
" 'database': {'secretPresent': exists('DB_SECRET_EXIT'), 'secretName': os.environ.get('database_secret'), 'key': os.environ.get('database_key'), 'keyPresent': os.environ.get('database_key') in (((db_secret or {}).get('data') or {})), 'valuesPrinted': False},",
|
" 'database': {'secretPresent': exists('DB_SECRET_EXIT'), 'secretName': os.environ.get('database_secret'), 'key': os.environ.get('database_key'), 'keyPresent': os.environ.get('database_key') in (((db_secret or {}).get('data') or {})), 'valuesPrinted': False},",
|
||||||
" 'secrets': secrets,",
|
" 'secrets': secrets,",
|
||||||
" 'localPostgres': {'absent': len(matching_postgres) == 0, 'matchingObjects': matching_postgres},",
|
" 'localPostgres': {'absent': len(matching_postgres) == 0, 'matchingObjects': matching_postgres},",
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import type { UniDeskConfig } from "./config";
|
||||||
|
import { renderMachine } from "./cicd-render";
|
||||||
|
import type { RenderedCliResult } from "./output";
|
||||||
|
import { getPlatformInfraPipelinesAsCodeNodeStatus } from "./platform-infra-pipelines-as-code";
|
||||||
|
|
||||||
|
interface NodeStatusOptions {
|
||||||
|
nodeId: string | null;
|
||||||
|
targetId: string | null;
|
||||||
|
full: boolean;
|
||||||
|
raw: boolean;
|
||||||
|
output: "text" | "json";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cicdNodeStatusHelp(): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
command: "cicd status --node <NODE>",
|
||||||
|
description: "Read a node-level CI/CD closeout summary for every Pipelines-as-Code consumer on the node.",
|
||||||
|
usage: [
|
||||||
|
"bun scripts/cli.ts cicd status --node NC01",
|
||||||
|
"bun scripts/cli.ts cicd status --node NC01 --full",
|
||||||
|
"bun scripts/cli.ts cicd status --node NC01 --json",
|
||||||
|
],
|
||||||
|
output: "compact text by default; --json/--raw returns machine payload",
|
||||||
|
source: "config/platform-infra/pipelines-as-code.yaml consumers filtered by node",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runCicdNodeStatusCommand(config: UniDeskConfig | null, args: string[]): Promise<RenderedCliResult> {
|
||||||
|
if (args[0] === undefined || args.some(isHelpToken)) {
|
||||||
|
return renderMachine("cicd status", cicdNodeStatusHelp(), "json");
|
||||||
|
}
|
||||||
|
const options = parseNodeStatusOptions(args);
|
||||||
|
if (config === null) throw new Error("cicd status requires UniDesk config; run through bun scripts/cli.ts cicd status --node <NODE>");
|
||||||
|
const result = await getPlatformInfraPipelinesAsCodeNodeStatus(config, {
|
||||||
|
targetId: options.targetId,
|
||||||
|
nodeId: options.nodeId,
|
||||||
|
full: options.full,
|
||||||
|
raw: options.raw,
|
||||||
|
});
|
||||||
|
if (options.output === "json" || options.raw) return renderMachine("cicd status", result, "json", result.ok !== false);
|
||||||
|
return renderNodeStatus(result, options.full);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNodeStatusOptions(args: string[]): NodeStatusOptions {
|
||||||
|
let nodeId: string | null = null;
|
||||||
|
let targetId: string | null = null;
|
||||||
|
let full = false;
|
||||||
|
let raw = false;
|
||||||
|
let output: "text" | "json" = "text";
|
||||||
|
for (let index = 0; index < args.length; index += 1) {
|
||||||
|
const arg = args[index];
|
||||||
|
if (arg === "status") continue;
|
||||||
|
if (arg === "--node" || arg === "--target") {
|
||||||
|
const value = args[index + 1];
|
||||||
|
if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`);
|
||||||
|
if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${arg} must be a simple node id`);
|
||||||
|
if (arg === "--node") nodeId = value;
|
||||||
|
else targetId = value;
|
||||||
|
index += 1;
|
||||||
|
} else if (arg === "--full") {
|
||||||
|
full = true;
|
||||||
|
} else if (arg === "--raw" || arg === "-o=json" || (arg === "-o" && args[index + 1] === "json")) {
|
||||||
|
raw = true;
|
||||||
|
full = true;
|
||||||
|
output = "json";
|
||||||
|
if (arg === "-o") index += 1;
|
||||||
|
} else if (arg === "--json") {
|
||||||
|
output = "json";
|
||||||
|
} else if (isHelpToken(arg)) {
|
||||||
|
output = "json";
|
||||||
|
} else {
|
||||||
|
throw new Error(`unsupported cicd status option: ${arg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nodeId === null && targetId === null) throw new Error("cicd status requires --node <NODE>");
|
||||||
|
return { nodeId, targetId, full, raw, output };
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNodeStatus(result: Record<string, unknown>, full: boolean): RenderedCliResult {
|
||||||
|
const target = record(result.target);
|
||||||
|
const summary = record(result.summary);
|
||||||
|
const consumers = arrayRecords(result.consumers);
|
||||||
|
const rows = consumers.map((row) => [
|
||||||
|
stringValue(row.consumer),
|
||||||
|
boolText(row.ready),
|
||||||
|
stringValue(row.pipelineStatus),
|
||||||
|
`${stringValue(row.durationSeconds)}s`,
|
||||||
|
short(stringValue(row.sourceCommit)),
|
||||||
|
short(stringValue(row.gitopsCommit)),
|
||||||
|
`${stringValue(record(row.argo).sync)}/${stringValue(record(row.argo).health)}`,
|
||||||
|
runtimeText(record(row.runtime)),
|
||||||
|
stringValue(row.reason),
|
||||||
|
]);
|
||||||
|
const detailRows = consumers.map((row) => [
|
||||||
|
stringValue(row.consumer),
|
||||||
|
stringValue(row.latestPipelineRun),
|
||||||
|
short(stringValue(row.digest), 18),
|
||||||
|
stringValue(row.imageStatus),
|
||||||
|
compactLine(stringValue(record(row.diagnostics).hint)),
|
||||||
|
]);
|
||||||
|
const lines = [
|
||||||
|
"CI/CD NODE STATUS",
|
||||||
|
...table(["NODE", "TARGET", "READY", "READY_COUNT", "BLOCKED", "ELAPSED_MS"], [[
|
||||||
|
stringValue(result.node),
|
||||||
|
stringValue(target.id),
|
||||||
|
boolText(summary.ready),
|
||||||
|
`${stringValue(summary.readyCount)}/${stringValue(summary.total)}`,
|
||||||
|
stringValue(summary.blockedCount),
|
||||||
|
stringValue(summary.elapsedMs),
|
||||||
|
]]),
|
||||||
|
"",
|
||||||
|
"CONSUMERS",
|
||||||
|
...(rows.length === 0 ? ["-"] : table(["CONSUMER", "READY", "PIPELINE", "DUR", "SOURCE", "GITOPS", "ARGO", "RUNTIME", "REASON"], rows)),
|
||||||
|
"",
|
||||||
|
...(full ? [
|
||||||
|
"DETAIL",
|
||||||
|
...(detailRows.length === 0 ? ["-"] : table(["CONSUMER", "PIPELINERUN", "DIGEST", "IMAGE_STATUS", "HINT"], detailRows)),
|
||||||
|
"",
|
||||||
|
] : []),
|
||||||
|
"NEXT",
|
||||||
|
` refresh: ${stringValue(record(result.next).status)}`,
|
||||||
|
` history: ${stringValue(record(result.next).history)}`,
|
||||||
|
` closeout: ${stringValue(record(result.next).closeout)}`,
|
||||||
|
];
|
||||||
|
return { ok: result.ok !== false, command: "cicd status", renderedText: lines.join("\n"), contentType: "text/plain" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function isHelpToken(value: string | undefined): boolean {
|
||||||
|
return value === "help" || value === "--help" || value === "-h";
|
||||||
|
}
|
||||||
|
|
||||||
|
function record(value: unknown): Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayRecords(value: unknown): Record<string, unknown>[] {
|
||||||
|
return Array.isArray(value) ? value.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)) as Record<string, unknown>[] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringValue(value: unknown, fallback = "-"): string {
|
||||||
|
if (typeof value === "string" && value.length > 0) return value;
|
||||||
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function boolText(value: unknown): string {
|
||||||
|
return value === true ? "true" : "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
function short(value: string, length = 12): string {
|
||||||
|
if (value === "-" || value.length <= length) return value;
|
||||||
|
return value.slice(0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtimeText(runtime: Record<string, unknown>): string {
|
||||||
|
const ready = `${stringValue(runtime.readyReplicas)}/${stringValue(runtime.replicas)}`;
|
||||||
|
const digest = short(stringValue(runtime.digest), 18);
|
||||||
|
return digest === "-" ? ready : `${ready}:${digest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compactLine(value: string): string {
|
||||||
|
const trimmed = value.replace(/\s+/gu, " ").trim();
|
||||||
|
return trimmed.length > 0 ? trimmed.slice(0, 120) : "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
function table(headers: string[], rows: string[][]): string[] {
|
||||||
|
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
|
||||||
|
const format = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ");
|
||||||
|
return [format(headers), format(headers.map((header, index) => "-".repeat(Math.max(header.length, widths[index])))), ...rows.map(format)];
|
||||||
|
}
|
||||||
+9
-2
@@ -5,11 +5,16 @@ import { renderMachine } from "./cicd-render";
|
|||||||
import type { RenderedCliResult } from "./output";
|
import type { RenderedCliResult } from "./output";
|
||||||
import { cicdHelp as branchFollowerHelp, runCicdCommand as runBranchFollowerCommand } from "./cicd-branch-follower";
|
import { cicdHelp as branchFollowerHelp, runCicdCommand as runBranchFollowerCommand } from "./cicd-branch-follower";
|
||||||
import { cicdGiteaActionsPocHelp, runGiteaActionsPocCommand } from "./cicd-gitea-actions-poc";
|
import { cicdGiteaActionsPocHelp, runGiteaActionsPocCommand } from "./cicd-gitea-actions-poc";
|
||||||
|
import { cicdNodeStatusHelp, runCicdNodeStatusCommand } from "./cicd-node-status";
|
||||||
|
|
||||||
export function cicdHelp(): unknown {
|
export function cicdHelp(): unknown {
|
||||||
return {
|
return {
|
||||||
command: "cicd gitea-actions-poc|branch-follower",
|
command: "cicd status|gitea-actions-poc|branch-follower",
|
||||||
output: "text by default for subcommands; top-level help is json",
|
output: "text by default for subcommands; top-level help is json",
|
||||||
|
nodeStatus: {
|
||||||
|
primary: "bun scripts/cli.ts cicd status --node NC01",
|
||||||
|
note: "Use this for node-level CI/CD status. It aggregates all current Pipelines-as-Code consumers for the node, including AgentRun, HWLAB and Web sentinel.",
|
||||||
|
},
|
||||||
migration: {
|
migration: {
|
||||||
issue: "https://github.com/pikasTech/unidesk/issues/1560",
|
issue: "https://github.com/pikasTech/unidesk/issues/1560",
|
||||||
primary: "platform-infra pipelines-as-code",
|
primary: "platform-infra pipelines-as-code",
|
||||||
@@ -20,6 +25,7 @@ export function cicdHelp(): unknown {
|
|||||||
note: "JD01 CI/CD source/trigger authority is Gitea mirror -> Pipelines-as-Code -> Tekton -> Argo/k8s runtime. Gitea Actions POC and branch-follower are historical or migration-only surfaces.",
|
note: "JD01 CI/CD source/trigger authority is Gitea mirror -> Pipelines-as-Code -> Tekton -> Argo/k8s runtime. Gitea Actions POC and branch-follower are historical or migration-only surfaces.",
|
||||||
},
|
},
|
||||||
subcommands: [
|
subcommands: [
|
||||||
|
cicdNodeStatusHelp(),
|
||||||
cicdGiteaActionsPocHelp(),
|
cicdGiteaActionsPocHelp(),
|
||||||
branchFollowerHelp(),
|
branchFollowerHelp(),
|
||||||
],
|
],
|
||||||
@@ -29,7 +35,8 @@ export function cicdHelp(): unknown {
|
|||||||
export async function runCicdCommand(config: UniDeskConfig | null, args: string[]): Promise<RenderedCliResult> {
|
export async function runCicdCommand(config: UniDeskConfig | null, args: string[]): Promise<RenderedCliResult> {
|
||||||
const top = args[0];
|
const top = args[0];
|
||||||
if (top === undefined || top === "help" || top === "--help" || top === "-h") return renderMachine("cicd", cicdHelp(), "json");
|
if (top === undefined || top === "help" || top === "--help" || top === "-h") return renderMachine("cicd", cicdHelp(), "json");
|
||||||
|
if (top === "status") return await runCicdNodeStatusCommand(config, args);
|
||||||
if (top === "branch-follower") return await runBranchFollowerCommand(config, args);
|
if (top === "branch-follower") return await runBranchFollowerCommand(config, args);
|
||||||
if (top === "gitea-actions-poc" || top === "gitea-builder-poc") return await runGiteaActionsPocCommand(config, args.slice(1), top);
|
if (top === "gitea-actions-poc" || top === "gitea-builder-poc") return await runGiteaActionsPocCommand(config, args.slice(1), top);
|
||||||
throw new Error("cicd usage: cicd gitea-actions-poc|branch-follower");
|
throw new Error("cicd usage: cicd status --node <NODE> | cicd gitea-actions-poc|branch-follower");
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -58,7 +58,8 @@ export function rootHelp(): unknown {
|
|||||||
{ command: "decision requirement list|create|show|update|upsert [id|docNo] [--title text] [--body-file path] [--type external_goal|internal_goal|goal|decision|blocker|debt|experiment] [--doc-no DC-...] [--doc-type ...] [--doc-priority P0|P1|P2|P3] [--signer text] [--issued-at ISO]", description: "Manage productized requirement records over the PostgreSQL records model, excluding meeting records." },
|
{ command: "decision requirement list|create|show|update|upsert [id|docNo] [--title text] [--body-file path] [--type external_goal|internal_goal|goal|decision|blocker|debt|experiment] [--doc-no DC-...] [--doc-type ...] [--doc-priority P0|P1|P2|P3] [--signer text] [--issued-at ISO]", description: "Manage productized requirement records over the PostgreSQL records model, excluding meeting records." },
|
||||||
{ command: "decision show <id|docNo>", description: "Show one Decision Center record." },
|
{ command: "decision show <id|docNo>", description: "Show one Decision Center record." },
|
||||||
{ command: "deploy check|plan|apply [--file deploy.json|--env dev|prod] [--service id] [--commit full-sha] [--dry-run] [--force]", description: "Reconcile services from origin/master:deploy.json environments; --commit overrides one reviewed artifact consumer such as frontend for release/v1 validation or rollback. code-queue artifact consumption is dev-only." },
|
{ command: "deploy check|plan|apply [--file deploy.json|--env dev|prod] [--service id] [--commit full-sha] [--dry-run] [--force]", description: "Reconcile services from origin/master:deploy.json environments; --commit overrides one reviewed artifact consumer such as frontend for release/v1 validation or rollback. code-queue artifact consumption is dev-only." },
|
||||||
{ command: "cicd gitea-actions-poc plan|status", description: "Recommended CI/CD migration path for GH-1548/GH-1549: Gitea mirror and Gitea Actions driven orchestration with controlled Docker/BuildKit builder-plane, runtime plane 0 Docker and env reuse as a P0 no-regression contract." },
|
{ command: "cicd status --node <NODE>", description: "Show one node's CI/CD closeout summary across current Pipelines-as-Code consumers, including PipelineRun, Argo and runtime readiness." },
|
||||||
|
{ command: "cicd gitea-actions-poc plan|status", description: "Historical CI/CD migration path for GH-1548/GH-1549; current node status should use cicd status --node <NODE>." },
|
||||||
{ command: "cicd branch-follower plan|apply|status|run-once|events|logs", description: "Deprecated migration-only Kubernetes branch follower controller; keep existing production status/debug during cutover, but do not add new self-maintained branch-following features." },
|
{ command: "cicd branch-follower plan|apply|status|run-once|events|logs", description: "Deprecated migration-only Kubernetes branch follower controller; keep existing production status/debug during cutover, but do not add new self-maintained branch-following features." },
|
||||||
{ command: "dev-env validate|prewarm-images", description: "Validate D601 unidesk-dev guardrails or prewarm dev foundation images into native k3s containerd through a bounded async job." },
|
{ command: "dev-env validate|prewarm-images", description: "Validate D601 unidesk-dev guardrails or prewarm dev foundation images into native k3s containerd through a bounded async job." },
|
||||||
{ command: "artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service", description: "Manage the D601 host-managed CNCF Distribution registry and run pull-only artifact CD for supported services, including D601 direct, k3s-managed, and code-queue dev-only consumers." },
|
{ command: "artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service", description: "Manage the D601 host-managed CNCF Distribution registry and run pull-only artifact CD for supported services, including D601 direct, k3s-managed, and code-queue dev-only consumers." },
|
||||||
@@ -749,15 +750,17 @@ function webProbeHelpSummary(): unknown {
|
|||||||
|
|
||||||
function cicdHelpSummary(): unknown {
|
function cicdHelpSummary(): unknown {
|
||||||
return {
|
return {
|
||||||
command: "cicd gitea-actions-poc plan|status | branch-follower ...",
|
command: "cicd status --node <NODE> | gitea-actions-poc ... | branch-follower ...",
|
||||||
output: "text by default; use --json, --raw, or -o json|yaml for machine output",
|
output: "text by default; use --json, --raw, or -o json|yaml for machine output",
|
||||||
usage: [
|
usage: [
|
||||||
|
"bun scripts/cli.ts cicd status --node NC01",
|
||||||
|
"bun scripts/cli.ts cicd status --node NC01 --full",
|
||||||
"bun scripts/cli.ts cicd gitea-actions-poc plan",
|
"bun scripts/cli.ts cicd gitea-actions-poc plan",
|
||||||
"bun scripts/cli.ts cicd gitea-actions-poc status",
|
"bun scripts/cli.ts cicd gitea-actions-poc status",
|
||||||
"bun scripts/cli.ts cicd branch-follower plan",
|
"bun scripts/cli.ts cicd branch-follower plan",
|
||||||
"bun scripts/cli.ts cicd branch-follower status",
|
"bun scripts/cli.ts cicd branch-follower status",
|
||||||
],
|
],
|
||||||
description: "Gitea Actions driven CI/CD migration path; branch-follower remains deprecated migration-only status/debug during cutover.",
|
description: "Node-level CI/CD status is the primary read path. Gitea Actions and branch-follower remain deprecated migration-only diagnostics.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,13 @@ interface CommonOptions {
|
|||||||
raw: boolean;
|
raw: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PipelinesAsCodeNodeStatusOptions {
|
||||||
|
targetId: string | null;
|
||||||
|
nodeId: string | null;
|
||||||
|
full: boolean;
|
||||||
|
raw: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface HistoryOptions extends CommonOptions {
|
interface HistoryOptions extends CommonOptions {
|
||||||
limit: number;
|
limit: number;
|
||||||
detailId: string | null;
|
detailId: string | null;
|
||||||
@@ -446,6 +453,75 @@ async function status(config: UniDeskConfig, options: CommonOptions): Promise<Re
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPlatformInfraPipelinesAsCodeNodeStatus(config: UniDeskConfig, options: PipelinesAsCodeNodeStatusOptions): Promise<Record<string, unknown>> {
|
||||||
|
const pac = readPacConfig();
|
||||||
|
const target = resolveTarget(pac, options.targetId ?? options.nodeId);
|
||||||
|
const nodeId = options.nodeId ?? target.id;
|
||||||
|
const consumers = pac.consumers.filter((consumer) => consumer.node.toLowerCase() === nodeId.toLowerCase());
|
||||||
|
if (consumers.length === 0) {
|
||||||
|
throw new Error(`no Pipelines-as-Code consumers are configured for node ${nodeId}; known nodes: ${Array.from(new Set(pac.consumers.map((item) => item.node))).sort().join(", ")}`);
|
||||||
|
}
|
||||||
|
const startedAt = Date.now();
|
||||||
|
const statuses = await Promise.all(consumers.map(async (consumer) => {
|
||||||
|
const repository = resolveRepository(pac, consumer.repositoryRef);
|
||||||
|
try {
|
||||||
|
const current = await status(config, { targetId: target.id, consumerId: consumer.id, full: false, raw: false });
|
||||||
|
return nodeStatusRow(target.id, consumer, repository, current);
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
return {
|
||||||
|
consumer: consumer.id,
|
||||||
|
node: consumer.node,
|
||||||
|
lane: consumer.lane,
|
||||||
|
repository: `${repository.owner}/${repository.repo}`,
|
||||||
|
namespace: consumer.namespace,
|
||||||
|
pipeline: consumer.pipeline,
|
||||||
|
argoApplication: consumer.argoApplication,
|
||||||
|
ready: false,
|
||||||
|
ciReady: false,
|
||||||
|
argoReady: false,
|
||||||
|
runtimeReady: false,
|
||||||
|
diagnosticsReady: false,
|
||||||
|
reason: "status-read-failed",
|
||||||
|
hint: message,
|
||||||
|
error: message,
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const ready = statuses.every((row) => row.ready === true);
|
||||||
|
return {
|
||||||
|
ok: ready,
|
||||||
|
action: "cicd-node-status",
|
||||||
|
mutation: false,
|
||||||
|
node: nodeId,
|
||||||
|
target: targetSummary(target),
|
||||||
|
config: {
|
||||||
|
path: configLabel,
|
||||||
|
source: "Gitea Repository CR + Tekton PipelineRun/TaskRun + Argo + runtime live objects.",
|
||||||
|
displayTimeZone: pac.display.timeZone,
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
consumers: statuses,
|
||||||
|
summary: {
|
||||||
|
ready,
|
||||||
|
total: statuses.length,
|
||||||
|
readyCount: statuses.filter((row) => row.ready === true).length,
|
||||||
|
blockedCount: statuses.filter((row) => row.ready !== true).length,
|
||||||
|
elapsedMs: Date.now() - startedAt,
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
status: `bun scripts/cli.ts cicd status --node ${nodeId}`,
|
||||||
|
history: `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${target.id} --limit 10`,
|
||||||
|
closeout: `bun scripts/cli.ts platform-infra pipelines-as-code closeout --target ${target.id} --wait`,
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
details: options.full || options.raw ? statuses : undefined,
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function closeout(config: UniDeskConfig, options: CloseoutOptions): Promise<Record<string, unknown>> {
|
async function closeout(config: UniDeskConfig, options: CloseoutOptions): Promise<Record<string, unknown>> {
|
||||||
const pac = readPacConfig();
|
const pac = readPacConfig();
|
||||||
const target = resolveTarget(pac, options.targetId);
|
const target = resolveTarget(pac, options.targetId);
|
||||||
@@ -723,6 +799,75 @@ function statusSummary(payload: Record<string, unknown>): Record<string, unknown
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nodeStatusRow(targetId: string, consumer: PacConsumer, repository: PacRepository, current: Record<string, unknown>): Record<string, unknown> {
|
||||||
|
const summary = record(current.summary);
|
||||||
|
const latest = record(summary.latestPipelineRun);
|
||||||
|
const artifact = record(summary.artifact);
|
||||||
|
const argo = record(summary.argo);
|
||||||
|
const runtime = record(summary.runtime);
|
||||||
|
const diagnostics = record(summary.diagnostics);
|
||||||
|
const pipelineGate = record(summary.pipelineRunGate);
|
||||||
|
const desired = numericValue(runtime.replicas);
|
||||||
|
const readyReplicas = numericValue(runtime.readyReplicas);
|
||||||
|
const runtimeReady = desired !== null && desired > 0 && readyReplicas === desired;
|
||||||
|
const argoReady = stringValue(argo.sync) === "Synced" && stringValue(argo.health) === "Healthy";
|
||||||
|
const ciReady = pipelineGate.ok === true;
|
||||||
|
const diagnosticsReady = diagnostics.ok !== false;
|
||||||
|
const ready = current.ok === true && ciReady && argoReady && runtimeReady && diagnosticsReady;
|
||||||
|
return {
|
||||||
|
consumer: consumer.id,
|
||||||
|
node: consumer.node,
|
||||||
|
lane: consumer.lane,
|
||||||
|
repository: `${repository.owner}/${repository.repo}`,
|
||||||
|
namespace: consumer.namespace,
|
||||||
|
pipeline: consumer.pipeline,
|
||||||
|
argoApplication: consumer.argoApplication,
|
||||||
|
ready,
|
||||||
|
ciReady,
|
||||||
|
argoReady,
|
||||||
|
runtimeReady,
|
||||||
|
diagnosticsReady,
|
||||||
|
latestPipelineRun: stringValue(latest.name),
|
||||||
|
pipelineStatus: statusText(latest),
|
||||||
|
durationSeconds: latest.durationSeconds ?? null,
|
||||||
|
sourceCommit: stringValue(latest.sourceCommit),
|
||||||
|
imageStatus: stringValue(artifact.imageStatus),
|
||||||
|
envReuse: envReuseText(record(artifact.envReuse)),
|
||||||
|
digest: stringValue(artifact.digest),
|
||||||
|
gitopsCommit: stringValue(artifact.gitopsCommit),
|
||||||
|
argo: {
|
||||||
|
sync: stringValue(argo.sync),
|
||||||
|
health: stringValue(argo.health),
|
||||||
|
revision: stringValue(argo.revision),
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
deployment: stringValue(runtime.deployment),
|
||||||
|
readyReplicas: runtime.readyReplicas ?? null,
|
||||||
|
replicas: runtime.replicas ?? null,
|
||||||
|
digest: stringValue(runtime.digest),
|
||||||
|
image: stringValue(runtime.image),
|
||||||
|
},
|
||||||
|
diagnostics: {
|
||||||
|
ok: diagnostics.ok !== false,
|
||||||
|
code: stringValue(diagnostics.code),
|
||||||
|
phase: stringValue(diagnostics.phase),
|
||||||
|
hint: compactLine(stringValue(diagnostics.hint)),
|
||||||
|
},
|
||||||
|
reason: ready ? "ready" : nodeStatusReason({ ciReady, argoReady, runtimeReady, diagnosticsReady, pipelineStatus: statusText(latest), diagnostics }),
|
||||||
|
statusCommand: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${targetId} --consumer ${consumer.id}`,
|
||||||
|
historyCommand: `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${targetId} --consumer ${consumer.id} --limit 10`,
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeStatusReason(input: { ciReady: boolean; argoReady: boolean; runtimeReady: boolean; diagnosticsReady: boolean; pipelineStatus: string; diagnostics: Record<string, unknown> }): string {
|
||||||
|
if (!input.ciReady) return `ci:${input.pipelineStatus}`;
|
||||||
|
if (!input.argoReady) return "argo-not-ready";
|
||||||
|
if (!input.runtimeReady) return "runtime-not-ready";
|
||||||
|
if (!input.diagnosticsReady) return stringValue(input.diagnostics.code, "diagnostics-not-ready");
|
||||||
|
return "not-ready";
|
||||||
|
}
|
||||||
|
|
||||||
function policyChecks(repository: PacRepository): Array<Record<string, unknown>> {
|
function policyChecks(repository: PacRepository): Array<Record<string, unknown>> {
|
||||||
return [
|
return [
|
||||||
{ name: "single-path", ok: true, detail: "Gitea webhook -> Pipelines-as-Code -> Tekton -> Argo/k8s runtime; no Gitea Actions/act_runner/branch-follower fallback." },
|
{ name: "single-path", ok: true, detail: "Gitea webhook -> Pipelines-as-Code -> Tekton -> Argo/k8s runtime; no Gitea Actions/act_runner/branch-follower fallback." },
|
||||||
|
|||||||
Reference in New Issue
Block a user