Merge pull request #2084 from pikasTech/feat/pikaoa-test-nc01-runtime
PikaOA:声明 NC01 YAML-first 测试运行面
This commit is contained in:
@@ -13,10 +13,9 @@ testRuntime:
|
||||
TEST01:
|
||||
enabled: true
|
||||
validationOnly: true
|
||||
dedicated: true
|
||||
node: TEST01
|
||||
route: TEST01:k3s
|
||||
namespacePrefix: pikaoa-test
|
||||
namespace: pikaoa-test
|
||||
ttlSeconds: 7200
|
||||
waitTimeoutSeconds: 180
|
||||
fieldManager: unidesk-pikaoa-test-target
|
||||
@@ -34,27 +33,32 @@ testRuntime:
|
||||
images:
|
||||
api: registry.invalid/pikaoa/api:${commit}
|
||||
worker: registry.invalid/pikaoa/worker:${commit}
|
||||
web: registry.invalid/pikaoa/web:${commit}
|
||||
migration: registry.invalid/pikaoa/migration:${commit}
|
||||
pullPolicy: IfNotPresent
|
||||
database:
|
||||
image: postgres:16-alpine
|
||||
configRef: config/platform-db/postgres-pk01.yaml#exports.connectionStrings.pikaoa-test-database-url
|
||||
name: pikaoa_test
|
||||
username: pikaoa_test
|
||||
schema: pikaoa
|
||||
port: 5432
|
||||
password:
|
||||
sourceRef: ./secrets/pikaoa-test-database-password.txt
|
||||
targetKey: PIKAOA_DATABASE_PASSWORD
|
||||
connection:
|
||||
sourceRef: ./secrets/pikaoa-test.env
|
||||
sourceKey: DATABASE_URL
|
||||
targetKey: DATABASE_URL
|
||||
runtime:
|
||||
secretName: pikaoa-test-runtime
|
||||
apiServiceName: pikaoa-api
|
||||
apiPort: 8080
|
||||
workerMetricsPort: 8081
|
||||
webPort: 8080
|
||||
webApiUpstreamEnv: PIKAOA_API_UPSTREAM
|
||||
attachment:
|
||||
storageRoot: /var/lib/pikaoa/attachments
|
||||
maxBytes: 104857600
|
||||
claimName: pikaoa-attachments
|
||||
storageRequest: 1Gi
|
||||
storageClassName: null
|
||||
fsGroup: 65532
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
workerInterval: 2s
|
||||
@@ -75,7 +79,8 @@ testRuntime:
|
||||
sourceRef: ./secrets/pikaoa-test-employee-password.txt
|
||||
targetKey: PIKAOA_BOOTSTRAP_EMPLOYEE_PASSWORD
|
||||
sessionSecret:
|
||||
sourceRef: ./secrets/pikaoa-test-session-secret.txt
|
||||
sourceRef: ./secrets/pikaoa-test.env
|
||||
sourceKey: PIKAOA_SESSION_SECRET
|
||||
targetKey: PIKAOA_SESSION_SECRET
|
||||
migration:
|
||||
jobName: pikaoa-migrate
|
||||
@@ -86,6 +91,11 @@ testRuntime:
|
||||
- up
|
||||
- --config
|
||||
- /etc/pikaoa/pikaoa.yaml
|
||||
exposure:
|
||||
serviceName: pikaoa-web
|
||||
serviceType: NodePort
|
||||
hostIP: 192.0.2.10
|
||||
port: 32080
|
||||
observability:
|
||||
otlpEndpoint: http://otel-collector.observability.svc.cluster.local:4318
|
||||
prometheus:
|
||||
@@ -102,6 +112,9 @@ testRuntime:
|
||||
worker:
|
||||
livenessPath: /healthz
|
||||
readinessPath: /readyz
|
||||
web:
|
||||
livenessPath: /healthz
|
||||
readinessPath: /healthz
|
||||
|
||||
delivery:
|
||||
targets:
|
||||
|
||||
+108
-3
@@ -27,9 +27,114 @@ modules:
|
||||
workers: named-consumer
|
||||
|
||||
testRuntime:
|
||||
# 专用测试集群 target 由用户明确声明后才能启用;空对象表示当前不允许远端测试运行。
|
||||
defaultTargetId: null
|
||||
targets: {}
|
||||
defaultTargetId: NC01
|
||||
targets:
|
||||
NC01:
|
||||
enabled: true
|
||||
validationOnly: false
|
||||
node: NC01
|
||||
route: NC01:k3s
|
||||
namespace: pikaoa-test
|
||||
ttlSeconds: 86400
|
||||
waitTimeoutSeconds: 300
|
||||
fieldManager: unidesk-pikaoa-test-target
|
||||
taskState:
|
||||
rootPath: /var/lib/unidesk/pikaoa-test-target
|
||||
statusRequestTimeoutSeconds: 10
|
||||
logTailLines: 80
|
||||
cleanup:
|
||||
mode: delete-namespace
|
||||
requireOwnershipLabels: true
|
||||
source:
|
||||
mode: prebuilt-images
|
||||
repository: pikainc/pikaoa
|
||||
commitPolicy: cli-required
|
||||
images:
|
||||
api: 127.0.0.1:5000/pikaoa/api:${commit}
|
||||
worker: 127.0.0.1:5000/pikaoa/worker:${commit}
|
||||
web: 127.0.0.1:5000/pikaoa/web:${commit}
|
||||
migration: 127.0.0.1:5000/pikaoa/api:${commit}
|
||||
pullPolicy: IfNotPresent
|
||||
database:
|
||||
configRef: config/platform-db/postgres-pk01.yaml#exports.connectionStrings.pikaoa-test-database-url
|
||||
name: pikaoa_test
|
||||
username: pikaoa_test
|
||||
schema: pikaoa
|
||||
connection:
|
||||
sourceRef: /root/.unidesk/.state/secrets/platform-infra/pikaoa-test.env
|
||||
sourceKey: DATABASE_URL
|
||||
targetKey: DATABASE_URL
|
||||
runtime:
|
||||
secretName: pikaoa-test-runtime
|
||||
apiServiceName: pikaoa-api
|
||||
apiPort: 8080
|
||||
workerMetricsPort: 8081
|
||||
webPort: 8080
|
||||
webApiUpstreamEnv: PIKAOA_API_UPSTREAM
|
||||
attachment:
|
||||
storageRoot: /var/lib/pikaoa/attachments
|
||||
maxBytes: 104857600
|
||||
claimName: pikaoa-attachments
|
||||
storageRequest: 10Gi
|
||||
storageClassName: null
|
||||
fsGroup: 65532
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
workerInterval: 2s
|
||||
outbox:
|
||||
batchSize: 50
|
||||
maxAttempts: 4
|
||||
retryDelay: 2s
|
||||
sessionTTL: 8h
|
||||
shutdownTimeout: 10s
|
||||
administrator:
|
||||
username: admin
|
||||
password:
|
||||
sourceRef: ~/.unidesk/.env/pikaoa-admin-password.txt
|
||||
targetKey: PIKAOA_BOOTSTRAP_ADMIN_PASSWORD
|
||||
employee:
|
||||
username: employee
|
||||
password:
|
||||
sourceRef: ~/.unidesk/.env/pikaoa-employee-password.txt
|
||||
targetKey: PIKAOA_BOOTSTRAP_EMPLOYEE_PASSWORD
|
||||
sessionSecret:
|
||||
sourceRef: /root/.unidesk/.state/secrets/platform-infra/pikaoa-test.env
|
||||
sourceKey: PIKAOA_SESSION_SECRET
|
||||
targetKey: PIKAOA_SESSION_SECRET
|
||||
migration:
|
||||
jobName: pikaoa-migrate
|
||||
backoffLimit: 2
|
||||
command:
|
||||
- /usr/local/bin/pikaoa
|
||||
- migrate
|
||||
- up
|
||||
- --config
|
||||
- /etc/pikaoa/pikaoa.yaml
|
||||
exposure:
|
||||
serviceName: pikaoa-web
|
||||
serviceType: NodePort
|
||||
hostIP: 152.53.229.148
|
||||
port: 32080
|
||||
observability:
|
||||
configRef: config/platform-infra/observability.yaml
|
||||
otlpEndpoint: http://otel-collector.platform-infra.svc.cluster.local:4318
|
||||
prometheus:
|
||||
scrape: true
|
||||
apiPath: /metrics
|
||||
workerPath: /metrics
|
||||
exporterFailure:
|
||||
warning: true
|
||||
blocking: false
|
||||
probes:
|
||||
api:
|
||||
livenessPath: /healthz
|
||||
readinessPath: /readyz
|
||||
worker:
|
||||
livenessPath: /healthz
|
||||
readinessPath: /readyz
|
||||
web:
|
||||
livenessPath: /healthz
|
||||
readinessPath: /healthz
|
||||
|
||||
delivery:
|
||||
targets:
|
||||
|
||||
@@ -99,6 +99,17 @@ sources:
|
||||
PIKAOA_SESSION_SECRET:
|
||||
bytes: 32
|
||||
prefix: poa_
|
||||
- sourceRef: platform-infra/pikaoa-test.env
|
||||
type: env
|
||||
requiredKeys:
|
||||
- DATABASE_URL
|
||||
- PIKAOA_SESSION_SECRET
|
||||
createIfMissing:
|
||||
enabled: true
|
||||
randomBase64Url:
|
||||
PIKAOA_SESSION_SECRET:
|
||||
bytes: 32
|
||||
prefix: poa_
|
||||
- sourceRef: hwlab/jd01-v03-opencode.env
|
||||
type: env
|
||||
requiredKeys:
|
||||
@@ -234,6 +245,11 @@ targets:
|
||||
namespace: pikaoa
|
||||
scope: pikaoa
|
||||
enabled: true
|
||||
- id: pikaoa-test-nc01
|
||||
route: NC01:k3s
|
||||
namespace: pikaoa-test
|
||||
scope: pikaoa-test
|
||||
enabled: true
|
||||
|
||||
kubernetesSecrets:
|
||||
- name: pikaoa-runtime
|
||||
@@ -256,6 +272,23 @@ kubernetesSecrets:
|
||||
- sourceRef: platform-infra/pk01-frp.env
|
||||
sourceKey: FRP_TOKEN
|
||||
targetKey: FRP_TOKEN
|
||||
- name: pikaoa-test-runtime
|
||||
targetId: pikaoa-test-nc01
|
||||
secretName: pikaoa-test-runtime
|
||||
type: Opaque
|
||||
data:
|
||||
- sourceRef: platform-infra/pikaoa-test.env
|
||||
sourceKey: DATABASE_URL
|
||||
targetKey: DATABASE_URL
|
||||
- sourceRef: platform-infra/pikaoa-test.env
|
||||
sourceKey: PIKAOA_SESSION_SECRET
|
||||
targetKey: PIKAOA_SESSION_SECRET
|
||||
- sourceRef: ~/.unidesk/.env/pikaoa-admin-password.txt
|
||||
sourceKey: contents
|
||||
targetKey: PIKAOA_BOOTSTRAP_ADMIN_PASSWORD
|
||||
- sourceRef: ~/.unidesk/.env/pikaoa-employee-password.txt
|
||||
sourceKey: contents
|
||||
targetKey: PIKAOA_BOOTSTRAP_EMPLOYEE_PASSWORD
|
||||
- name: selfmedia-access
|
||||
targetId: selfmedia-nc01
|
||||
secretName: selfmedia-access
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# R1.5.9.5 任务报告
|
||||
|
||||
## 结论
|
||||
|
||||
已在 PR #2084 完成 NC01 固定 `pikaoa-test` 测试运行面的:
|
||||
|
||||
- YAML-first Secret 初始化闭环;
|
||||
- 单步 CLI;
|
||||
- 可重跑异步状态机。
|
||||
|
||||
测试运行面边界:
|
||||
|
||||
- 使用 PK01 host PostgreSQL 独立 `pikaoa_test` role/database;
|
||||
- 固定使用 NodePort `32080`;
|
||||
- 不创建专用测试集群、动态 namespace 或 namespace-local PostgreSQL;
|
||||
- 不恢复 dedicated 门禁。
|
||||
|
||||
## 实现
|
||||
|
||||
- `config/secrets-distribution.yaml`:
|
||||
- 声明 `platform-infra/pikaoa-test.env`,必需 key 为 `DATABASE_URL` 与 `PIKAOA_SESSION_SECRET`;
|
||||
- `createIfMissing` 只生成 `PIKAOA_SESSION_SECRET`,保留 platform-db export 写入的 `DATABASE_URL`;
|
||||
- 增加 `pikaoa-test-nc01` target,固定 `NC01:k3s`、`pikaoa-test` namespace 和 `pikaoa-test` scope;
|
||||
- 增加 `pikaoa-test-runtime` Secret 映射,继续消费既有管理员和员工 external raw sources。
|
||||
- `config/pikaoa.yaml`:
|
||||
- `testRuntime.runtime.sessionSecret` 改读 `platform-infra/pikaoa-test.env#PIKAOA_SESSION_SECRET`;
|
||||
- 保留 PK01 `pikaoa_test` 数据库、固定 namespace、附件 PVC、NodePort、OTel/Prometheus 和单步工作负载声明。
|
||||
- `pikaoa test-target`:
|
||||
- 新增 `--step foundation`;
|
||||
- foundation 不要求 `--commit` 或业务 Secret,只提交带所有权标签的固定 Namespace/PVC;
|
||||
- migration、api、worker、web 和 all 继续要求 commit 与 Secret source;
|
||||
- plan 只投影当前 step 所需对象,并保持 Secret 脱敏;
|
||||
- request identity 包含 target、instance、commit、step 和当前 step 稳定 structural manifest fingerprint;
|
||||
- request fingerprint 继续脱敏 Secret `stringData`,并排除每次渲染变化的 `pikaoa.unidesk.io/expires-at`;
|
||||
- 完全相同 request 返回已有任务;
|
||||
- 不同 request 遇 queued/running 且 worker 存活时返回 `start-instance-conflict`;
|
||||
- 不同 request 遇终态或 worker-missing 时,原子退役旧有界状态目录并提交新任务;
|
||||
- `status` 继续读取同一 `target/instance/step` 的最新任务,不引入第二状态库、控制器或租约。
|
||||
- `docs/reference/pikaoa.md`:
|
||||
- 删除动态 namespace 和临时 PostgreSQL 旧设计;
|
||||
- 写明 foundation、Secret sync、migration、api、worker、web 顺序;
|
||||
- 写明终态可重跑、运行中去重和稳定 request fingerprint;
|
||||
- 写明先稳定 CLI,再建设独立 test CI/CD,且不得成为业务阻塞门禁。
|
||||
|
||||
## 验证
|
||||
|
||||
- `bun --check scripts/src/pikaoa-test-target.ts`:通过。
|
||||
- `bun test scripts/src/pikaoa-test-target-async.test.ts`:2 pass,0 fail。
|
||||
- 异步回归覆盖:
|
||||
- 同 request 在任务终态后仍返回 `start-already-succeeded`;
|
||||
- 不同 commit 在旧任务运行中返回 `start-instance-conflict`;
|
||||
- 不同 commit 在旧任务终态后重置并重新提交;
|
||||
- 同 commit 但探针 manifest 变化时重置并重新提交;
|
||||
- 仅 `expires-at` 渲染时间变化时仍识别为同 request;
|
||||
- worker-missing 时允许安全重置并重新提交;
|
||||
- foundation 终态后可用新 request 重跑。
|
||||
- `bun scripts/cli.ts check --syntax-only`:11 pass,0 fail。
|
||||
- `pikaoa test-target plan --step foundation`:
|
||||
- `readyForMutation=true`;
|
||||
- `missingFields=[]`;
|
||||
- `blockers=[]`;
|
||||
- 仅投影 Namespace 与 PersistentVolumeClaim;
|
||||
- `secretRequired=false`;
|
||||
- `valuesPrinted=false`。
|
||||
- `secrets plan --scope pikaoa-test`:
|
||||
- target 为 `pikaoa-test-nc01`;
|
||||
- Secret 为 `pikaoa-test-runtime`;
|
||||
- 投影四个声明 key;
|
||||
- `mutation=false`;
|
||||
- `valuesPrinted=false`。
|
||||
- `git diff --check`:通过。
|
||||
|
||||
## 边界
|
||||
|
||||
- 本任务只修改代码、YAML、测试和文档。
|
||||
- 未执行 foundation、Secret sync、migration、API、Worker、Web 或任何部署。
|
||||
- 未读取、打印或从 Kubernetes 反解 Secret 值。
|
||||
- 原入口真实部署与 NodePort 验收由主代理在合并后按文档顺序执行。
|
||||
@@ -105,7 +105,7 @@
|
||||
##### R1.5.9.4 [completed]
|
||||
|
||||
在 PK01 host PostgreSQL 声明独立 pikaoa_test database/role、Secret export 与 NC01 HBA,执行记录见 [pikasTech/unidesk#2078](https://github.com/pikasTech/unidesk/issues/2078),完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.5.9.4_Task_Report.md)。
|
||||
##### R1.5.9.5 [in_progress]
|
||||
##### R1.5.9.5 [completed]
|
||||
|
||||
在 NC01 pikaoa-test namespace 实现外部 PK01 测试 DSN、API/Worker/Web/迁移/附件 PVC、OTel/Prometheus、YAML hostIP 端口和受控单步 CLI,执行记录见 [pikasTech/unidesk#2079](https://github.com/pikasTech/unidesk/issues/2079),完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.5.9.5_Task_Report.md)。
|
||||
##### R1.5.9.6 [completed]
|
||||
|
||||
+117
-81
@@ -1,99 +1,135 @@
|
||||
# PikaOA 开发与测试运行面
|
||||
# PikaOA
|
||||
|
||||
## 配置真相
|
||||
|
||||
- PikaOA 平台配置唯一真相是 `config/pikaoa.yaml`。
|
||||
- 正式交付与临时测试运行面职责分离:
|
||||
- `delivery.targets` 只描述正式 CI/CD、GitOps、运行 namespace、数据库和公网暴露;
|
||||
- `testRuntime.targets` 只描述专用测试集群上的临时后端运行面。
|
||||
- 禁止把现有节点推断成 PikaOA 专用测试集群。
|
||||
- `testRuntime.targets` 为空时表示当前没有获准的测试运行面:
|
||||
- `plan` 和 `status` 返回 `configured=false`、`mutation=false`;
|
||||
- `start` 和 `stop` 在任何远端调用前失败。
|
||||
- 测试运行面固定使用:
|
||||
- Target:`NC01`;
|
||||
- route:`NC01:k3s`;
|
||||
- namespace:`pikaoa-test`;
|
||||
- NodePort:`32080`。
|
||||
- 测试 namespace 不根据 commit、instance 或任务动态生成。
|
||||
- `pikaoa` 与 `pikaoa-ci` 是正式交付保护 namespace。
|
||||
- 测试入口不得渲染、覆盖或删除保护 namespace 中的对象。
|
||||
|
||||
## 受控入口
|
||||
## 数据库边界
|
||||
|
||||
```bash
|
||||
bun scripts/cli.ts pikaoa test-target plan
|
||||
bun scripts/cli.ts pikaoa test-target status
|
||||
bun scripts/cli.ts pikaoa test-target start --target <id> --instance <run-id> --commit <sha> --confirm
|
||||
bun scripts/cli.ts pikaoa test-target stop --target <id> --instance <run-id> --confirm
|
||||
```
|
||||
- 测试数据库固定使用 PK01 host PostgreSQL。
|
||||
- `config/platform-db/postgres-pk01.yaml` 声明独立的:
|
||||
- role:`pikaoa_test`;
|
||||
- database:`pikaoa_test`;
|
||||
- schema:`pikaoa`;
|
||||
- `DATABASE_URL` export:`platform-infra/pikaoa-test.env`。
|
||||
- `pikaoa-test` namespace 不创建 PostgreSQL StatefulSet、Service 或临时数据库。
|
||||
- 数据库生命周期独立于 namespace 和业务 workload。
|
||||
- 测试数据清理由 PK01 PostgreSQL 受控入口负责,不依赖删除 namespace。
|
||||
|
||||
- `plan` 是本地只读渲染入口,不接受 `--dry-run`。
|
||||
- `start` 和 `stop` 只在显式 `--confirm` 后执行。
|
||||
- `--config <path>` 可用于 fixture 或本地 override;`validationOnly=true` 的 target 永远不连接 route。
|
||||
- 默认输出是紧凑文本;`--output json` 或 `--json` 输出单一 JSON 文档。
|
||||
- 长启动使用提交与短轮询分离的远程任务:
|
||||
- `start --confirm` 只提交由 `target/instance` 唯一定位的启动任务,返回 `start-submitted`、`jobId`、namespace 和 `mutation=true`;
|
||||
- 相同 `target/instance/commit` 重复提交只返回已有任务状态,不再启动并行任务;
|
||||
- 相同 `target/instance` 改用其他 commit 时返回 `start-instance-conflict`,操作者应使用新 instance;
|
||||
- `status` 返回 `running/succeeded/failed/canceled`、当前阶段、时间、终态 code、exit code 和有界事件尾部;
|
||||
- `status` 的运行面查询只使用 YAML 声明的短 request timeout,不进入启动等待循环。
|
||||
- 远程任务事实只保存在 target YAML `taskState.rootPath` 对应的单一状态目录:
|
||||
- `taskState.statusRequestTimeoutSeconds` 控制短查询预算;
|
||||
- `taskState.logTailLines` 控制事件尾部上限;
|
||||
- 不建立第二数据库、控制器、租约或独立锁服务。
|
||||
## Secret 初始化
|
||||
|
||||
## 临时运行面
|
||||
- `config/secrets-distribution.yaml` 声明:
|
||||
- source:`platform-infra/pikaoa-test.env`;
|
||||
- target:`pikaoa-test-nc01`;
|
||||
- scope:`pikaoa-test`;
|
||||
- Kubernetes Secret:`pikaoa-test-runtime`。
|
||||
- `platform-infra/pikaoa-test.env` 必须包含:
|
||||
- `DATABASE_URL`;
|
||||
- `PIKAOA_SESSION_SECRET`。
|
||||
- `DATABASE_URL` 由 `platform-db postgres export-secrets` 写入并保留。
|
||||
- `createIfMissing` 只为 `PIKAOA_SESSION_SECRET` 生成值,不覆盖已有 `DATABASE_URL`。
|
||||
- 管理员和员工密码继续使用既有 external raw sources:
|
||||
- `~/.unidesk/.env/pikaoa-admin-password.txt`;
|
||||
- `~/.unidesk/.env/pikaoa-employee-password.txt`。
|
||||
- Secret 输出只披露对象、key、presence、fingerprint 和摘要,不读取或打印值。
|
||||
|
||||
- namespace 固定由 YAML `namespacePrefix` 和 CLI `--instance` 组成。
|
||||
- namespace、API、Worker、临时 PostgreSQL、Service 和 Secret 都带以下可审计标签:
|
||||
## Test Target CLI
|
||||
|
||||
- 受控入口是 `bun scripts/cli.ts pikaoa test-target`。
|
||||
- `config/pikaoa.yaml#testRuntime` 声明固定 namespace、镜像、PVC、Secret 引用、迁移、工作负载、探针和暴露端口。
|
||||
- 所有创建对象都带以下所有权标签:
|
||||
- `app.kubernetes.io/managed-by=unidesk-pikaoa-test-target`;
|
||||
- `pikaoa.unidesk.io/test-runtime=true`;
|
||||
- `pikaoa.unidesk.io/target=<target-id>`;
|
||||
- `pikaoa.unidesk.io/instance=<run-id>`。
|
||||
- `stop` 删除前重新校验 target、instance 和 managed-by 标签,只删除精确实例 namespace。
|
||||
- `stop` 发现同一实例的启动任务仍在运行时,先写入精确任务的取消请求,再删除 namespace;返回 `startTaskDisposition` 说明是 `cancel-requested`、`cancel-already-requested`、`already-terminal` 或 `not-found`。
|
||||
- 正式 namespace 保护集合来自以下 YAML 字段:
|
||||
- `delivery.targets.*.namespace`;
|
||||
- `delivery.targets.*.ci.namespace`。
|
||||
- 测试入口不得渲染或删除保护集合中的任何对象。
|
||||
- PostgreSQL 使用 namespace 内的临时存储,namespace 删除后测试数据随之清理。
|
||||
- 临时数据库连接只由 target YAML 渲染:
|
||||
- `database.name`、`database.username` 和 `database.schema` 都是必填 PostgreSQL identifier;
|
||||
- renderer 使用结构化 URL 参数写入 `sslmode` 和 `search_path`,不提供隐藏 schema 默认值;
|
||||
- 缺少 schema 或形状不合法时,在任何 route 调用前返回具名配置错误。
|
||||
- 附件运行参数与 namespace 内的临时 PVC 来自 target YAML:
|
||||
- `runtime.attachment.storageRoot` 和 `runtime.attachment.maxBytes` 直接写入严格 runtime YAML;
|
||||
- PVC 名称、容量、StorageClass 和 access mode 只从同一 target 声明读取;
|
||||
- API 挂载该 PVC,删除临时 namespace 时一并删除 PVC。
|
||||
- 空数据库启动固定分为三个受控阶段:
|
||||
- 先渲染基础对象并等待 namespace 内 PostgreSQL 就绪;
|
||||
- 再使用 `source.images.migration` 和 `migration.command` 运行产品内置 `pikaoa migrate up` Job;
|
||||
- 迁移 Job 完成后才创建 API 和 Worker Deployment 并等待 rollout。
|
||||
- PostgreSQL、迁移 Job 或业务 Deployment 失败会返回具名失败码,不会静默进入下一阶段。
|
||||
- TTL 和清理策略来自 target YAML:
|
||||
- TTL 写入 namespace annotation;
|
||||
- 当前 `cleanup.mode=delete-namespace`,由显式 `stop` 执行;
|
||||
- `cleanup.requireOwnershipLabels=true` 时,删除前必须校验归属标签;
|
||||
- 不建立额外控制器或长期状态库。
|
||||
- `pikaoa.unidesk.io/instance=<instance-id>`。
|
||||
- `stop` 只允许删除同时匹配固定 namespace 和全部所有权标签的运行面。
|
||||
- CLI 使用异步任务状态目录记录有界状态和事件,不建立第二数据库、控制器、租约或锁服务。
|
||||
|
||||
## Secret 与可观测性
|
||||
## 初始化顺序
|
||||
|
||||
- Secret 值只从 YAML `sourceRef` 读取并写入目标 Secret。
|
||||
- CLI 输出只显示 `sourceRef`、`targetKey`、presence 和 fingerprint,始终保持 `valuesPrinted=false`。
|
||||
- `plan` 的数据库投影只显示 schema 和 DSN 参数 presence,不显示完整 DSN、用户名、密码或参数值。
|
||||
- 含 Secret 的 runner 文件使用 owner-only 权限,运行后立即删除;manifest 只在 runner 的临时目录存活,终态前由 trap 清理。
|
||||
- 状态和事件只记录公开 ID、阶段、typed code 和时间,不保存命令 stderr、DSN、manifest 正文或 Secret 值。
|
||||
- Secret 缺失会在连接测试集群前阻塞 `start`。
|
||||
- 迁移 Job、API 和 Worker 从同一 Secret key 挂载严格的 PikaOA runtime YAML,数据库 schema 与 `search_path` 因此保持一致。
|
||||
- OTel endpoint、Prometheus scrape、指标路径和探针全部由 target YAML 声明。
|
||||
- 配置一致性、镜像/commit 漂移和 OTel exporter 失败只产生 `blocking=false` warning,不作为 MVP 门禁。
|
||||
1. 使用 `platform-db postgres` 受控入口准备 PK01 `pikaoa_test` role/database,并 export `DATABASE_URL`。
|
||||
2. 执行 foundation:
|
||||
|
||||
## 本地验收
|
||||
```bash
|
||||
bun scripts/cli.ts pikaoa test-target start \
|
||||
--target NC01 \
|
||||
--instance default \
|
||||
--step foundation \
|
||||
--confirm
|
||||
```
|
||||
|
||||
`config/fixtures/pikaoa-test-target.yaml` 只用于本地验证:
|
||||
3. foundation 只创建带所有权标签的固定 `pikaoa-test` Namespace 和附件 PVC,不要求业务 Secret 已存在。
|
||||
4. 执行 Secret 下发:
|
||||
|
||||
- 证明 namespace、选择器、附件 PVC、迁移 Job、工作负载和 Secret 引用的渲染结果;
|
||||
- 证明 TTL、OTel、Prometheus 和正式 namespace 保护;
|
||||
- fixture target 固定为 `validationOnly=true`,不得用于真实集群操作。
|
||||
```bash
|
||||
bun scripts/cli.ts secrets sync \
|
||||
--config config/secrets-distribution.yaml \
|
||||
--scope pikaoa-test \
|
||||
--confirm
|
||||
```
|
||||
|
||||
```bash
|
||||
bun scripts/cli.ts pikaoa test-target plan \
|
||||
--config config/fixtures/pikaoa-test-target.yaml \
|
||||
--target TEST01 \
|
||||
--instance issue-2046 \
|
||||
--commit 0123456789abcdef \
|
||||
--output json
|
||||
```
|
||||
5. `secrets sync` 按 YAML 声明补齐允许生成的本地来源,并下发 `pikaoa-test-runtime`。
|
||||
6. 使用同一 commit 依次执行:
|
||||
- `--step migration`;
|
||||
- `--step api`;
|
||||
- `--step worker`;
|
||||
- `--step web`。
|
||||
7. Web 固定通过 `152.53.229.148:32080` 的 NodePort 入口验收。
|
||||
|
||||
## 单步运行
|
||||
|
||||
- foundation 不需要 `--commit`,也不检查业务 Secret。
|
||||
- migration、api、worker、web 和 all 必须显式提供 `--commit`。
|
||||
- 每个 `start` 立即返回任务 ID;使用同一 target、instance 和 step 的 `status` 查询终态。
|
||||
- request identity 包含:
|
||||
- target;
|
||||
- instance;
|
||||
- 产品 commit;
|
||||
- step;
|
||||
- 当前 step 的稳定 structural manifest fingerprint。
|
||||
- request fingerprint 对 Secret `stringData` 只保留 key 结构和脱敏占位,不包含 Secret 原值。
|
||||
- request fingerprint 排除 `pikaoa.unidesk.io/expires-at` 等每次渲染变化的易变字段。
|
||||
- 完全相同 request 重复提交只返回已有任务状态,不并行创建第二任务。
|
||||
- 不同 request 遇旧任务处于 queued/running 且 worker 存活时返回 `start-instance-conflict`。
|
||||
- 不同 request 遇旧任务已 succeeded/failed/canceled,或 queued/running 但 worker 明确缺失时:
|
||||
- 原子退役该 step 的旧有界状态目录;
|
||||
- 继续使用同一 `target/instance/step` 状态路径提交新任务;
|
||||
- `status` 只读取该 step 的最新任务。
|
||||
- foundation 也遵循相同规则。
|
||||
- foundation 需要显式形成新 request 时,可以:
|
||||
- 提供新的 `--commit` 修订标识;
|
||||
- 修改 foundation 的稳定 manifest 结构。
|
||||
- 不为重跑引入第二状态库、控制器、租约或长期历史存储。
|
||||
- 迁移、rollout 或运行面查询失败返回具名失败码,不静默进入下一阶段。
|
||||
- OTel exporter、配置版本和 commit 对齐漂移只产生 `blocking=false` warning,不恢复 dedicated 门禁。
|
||||
|
||||
## 交付阶段
|
||||
|
||||
- 当前阶段先稳定 YAML-first CLI 原入口和固定 NC01 测试运行面。
|
||||
- CLI 闭环通过后,再建设独立 test CI/CD:
|
||||
- 复用同一 owning YAML;
|
||||
- 按 foundation、Secret sync、migration、api、worker、web 顺序编排;
|
||||
- 不创建专用测试集群;
|
||||
- 不生成动态 namespace;
|
||||
- 不创建 namespace-local PostgreSQL;
|
||||
- 不把测试 CI/CD 变成生产交付或用户业务的阻塞门禁。
|
||||
- PR、构建或单测不能替代固定 NodePort 原入口验收。
|
||||
|
||||
## 本地验证
|
||||
|
||||
- `config/fixtures/pikaoa-test-target.yaml` 只用于 renderer 和 CLI 测试。
|
||||
- fixture target 固定为 `validationOnly=true`,不得连接真实 route。
|
||||
- 最小验证包括:
|
||||
- `bun --check scripts/src/pikaoa-test-target.ts`;
|
||||
- `bun test scripts/src/pikaoa-test-target-async.test.ts`;
|
||||
- `bun scripts/cli.ts check --syntax-only`;
|
||||
- `pikaoa test-target plan --step foundation`;
|
||||
- `git diff --check`。
|
||||
|
||||
@@ -21,10 +21,9 @@ test("PikaOA test target submits once and exposes bounded typed status", async (
|
||||
|
||||
const secretValues = ["fixture-db-password", "fixture-admin-password", "fixture-employee-password", "fixture-session-secret"];
|
||||
for (const [name, value] of [
|
||||
["pikaoa-test-database-password.txt", secretValues[0]],
|
||||
["pikaoa-test.env", `DATABASE_URL=postgresql://pikaoa_test:${secretValues[0]}@db.example.invalid:5432/pikaoa_test?sslmode=require\nPIKAOA_SESSION_SECRET=${secretValues[3]}`],
|
||||
["pikaoa-test-admin-password.txt", secretValues[1]],
|
||||
["pikaoa-test-employee-password.txt", secretValues[2]],
|
||||
["pikaoa-test-session-secret.txt", secretValues[3]],
|
||||
]) writeFileSync(join(secretsDir, name), `${value}\n`, { mode: 0o600 });
|
||||
|
||||
const fakeKubectl = join(binDir, "kubectl");
|
||||
@@ -53,18 +52,20 @@ if printf '%s' "$args" | grep -Eq '(^| )apply( |$)'; then
|
||||
previous="$argument"
|
||||
done
|
||||
if grep -q '"kind":"Namespace"' "$file"; then
|
||||
applied_namespace="$(python3 - "$file" <<'PY'
|
||||
applied_identity="$(python3 - "$file" <<'PY'
|
||||
import json, pathlib, sys
|
||||
for line in pathlib.Path(sys.argv[1]).read_text().splitlines():
|
||||
if not line or line == "---":
|
||||
continue
|
||||
item = json.loads(line)
|
||||
if item.get("kind") == "Namespace":
|
||||
print(item["metadata"]["name"])
|
||||
print(item["metadata"]["name"] + "|" + item["metadata"]["labels"]["pikaoa.unidesk.io/instance"])
|
||||
break
|
||||
PY
|
||||
)"
|
||||
: >"$root/namespaces/$applied_namespace"
|
||||
applied_namespace="\${applied_identity%%|*}"
|
||||
applied_instance="\${applied_identity##*|}"
|
||||
printf '%s' "$applied_instance" >"$root/namespaces/$applied_namespace"
|
||||
fi
|
||||
[ ! -f "$root/slow" ] || sleep 0.2
|
||||
exit 0
|
||||
@@ -88,14 +89,14 @@ if printf '%s' "$args" | grep -Eq '(^| )get namespace '; then
|
||||
printf 'Error from server (NotFound): namespaces "%s" not found\n' "$name" >&2
|
||||
exit 1
|
||||
fi
|
||||
instance="\${name#pikaoa-test-}"
|
||||
instance="$(cat "$root/namespaces/$name")"
|
||||
if printf '%s' "$args" | grep -q 'managed-by'; then printf '%s' 'unidesk-pikaoa-test-target'; exit 0; fi
|
||||
if printf '%s' "$args" | grep -q 'unidesk.*target'; then printf '%s' 'TEST01'; exit 0; fi
|
||||
if printf '%s' "$args" | grep -q 'unidesk.*instance'; then printf '%s' "$instance"; exit 0; fi
|
||||
printf '{"metadata":{"name":"%s","labels":{"app.kubernetes.io/managed-by":"unidesk-pikaoa-test-target","pikaoa.unidesk.io/target":"TEST01","pikaoa.unidesk.io/instance":"%s"}}}\n' "$name" "$instance"
|
||||
exit 0
|
||||
fi
|
||||
if printf '%s' "$args" | grep -q ' get deployment,statefulset,job,service,persistentvolumeclaim,pod '; then
|
||||
if printf '%s' "$args" | grep -q ' get deployment,job,service,persistentvolumeclaim,pod '; then
|
||||
if [ -f "$root/fail-runtime-resource-query" ]; then
|
||||
printf '%s\n' 'resource query failed' >&2
|
||||
exit 1
|
||||
@@ -137,19 +138,26 @@ exit 64
|
||||
lastCapture = { stdout, stderr, exitCode };
|
||||
return lastCapture;
|
||||
};
|
||||
const args = (action: string, instance: string, withCommit = false): string[] => [
|
||||
const args = (action: string, instance: string, withCommit = false, step = "all"): string[] => [
|
||||
"test-target", action, "--config", fixturePath, "--target", "TEST01", "--instance", instance,
|
||||
...(withCommit ? ["--commit", "0123456789abcdef"] : []),
|
||||
"--step", step,
|
||||
...((action === "start" || action === "stop") ? ["--confirm"] : []),
|
||||
"--output", "json",
|
||||
];
|
||||
const projection = async (action: string, instance: string, withCommit = false): Promise<Record<string, unknown>> => {
|
||||
const result = await runPikaoaCommand(config, args(action, instance, withCommit), localCapture);
|
||||
const projection = async (action: string, instance: string, withCommit = false, step = "all"): Promise<Record<string, unknown>> => {
|
||||
const result = await runPikaoaCommand(config, args(action, instance, withCommit, step), localCapture);
|
||||
return JSON.parse(result.renderedText) as Record<string, unknown>;
|
||||
};
|
||||
const waitFor = async (instance: string, expectedCode: string): Promise<Record<string, unknown>> => {
|
||||
const projectionWithCommit = async (action: string, instance: string, commit: string, step = "all"): Promise<Record<string, unknown>> => {
|
||||
const commandArgs = args(action, instance, true, step);
|
||||
commandArgs[commandArgs.indexOf("--commit") + 1] = commit;
|
||||
const result = await runPikaoaCommand(config, commandArgs, localCapture);
|
||||
return JSON.parse(result.renderedText) as Record<string, unknown>;
|
||||
};
|
||||
const waitFor = async (instance: string, expectedCode: string, step = "all"): Promise<Record<string, unknown>> => {
|
||||
for (let attempt = 0; attempt < 80; attempt += 1) {
|
||||
const value = await projection("status", instance);
|
||||
const value = await projection("status", instance, false, step);
|
||||
const status = value.status as Record<string, unknown>;
|
||||
if (status.code === expectedCode) return value;
|
||||
await Bun.sleep(25);
|
||||
@@ -158,11 +166,23 @@ exit 64
|
||||
};
|
||||
|
||||
try {
|
||||
const missing = await projection("status", "missing");
|
||||
const planned = await projection("plan", "runtime", true);
|
||||
const plan = planned.plan as Record<string, unknown>;
|
||||
const secret = plan.secret as Record<string, unknown>;
|
||||
const sources = secret.sources as Array<Record<string, unknown>>;
|
||||
assert.equal(sources.find((source) => source.purpose === "database.connection")?.presence, true);
|
||||
const objects = plan.objects as Array<Record<string, unknown>>;
|
||||
assert.equal(objects.some((object) => object.kind === "StatefulSet" || object.name === "pikaoa-postgres"), false);
|
||||
assert.equal(objects.some((object) => object.kind === "Deployment" && object.name === "pikaoa-web"), true);
|
||||
assert.equal(objects.find((object) => object.kind === "Deployment" && object.name === "pikaoa-api")?.podFsGroup, 65532);
|
||||
assert.deepEqual(plan.exposure, { serviceType: "NodePort", serviceName: "pikaoa-web", hostIP: "192.0.2.10", port: 32080 });
|
||||
assert.deepEqual(plan.webApiUpstream, { envName: "PIKAOA_API_UPSTREAM", value: "http://pikaoa-api:8080", scope: "same-namespace" });
|
||||
|
||||
const missing = await projection("status", "runtime", false, "api");
|
||||
assert.equal((missing.status as Record<string, unknown>).code, "start-not-found");
|
||||
|
||||
writeFileSync(join(fakeRoot, "slow"), "1\n");
|
||||
const submitted = await projection("start", "success", true);
|
||||
const submitted = await projection("start", "runtime", true);
|
||||
assert.equal(submitted.code, "start-submitted", JSON.stringify(lastCapture));
|
||||
assert.equal(submitted.mutation, true);
|
||||
assert.deepEqual((submitted.target as Record<string, unknown>).taskState, {
|
||||
@@ -170,27 +190,32 @@ exit 64
|
||||
statusRequestTimeoutSeconds: 31,
|
||||
logTailLines: 101,
|
||||
});
|
||||
const running = await projection("status", "success");
|
||||
const running = await projection("status", "runtime");
|
||||
assert.equal((running.status as Record<string, unknown>).code, "start-running");
|
||||
const concurrentCommit = "fedcba9876543210";
|
||||
const concurrent = await projectionWithCommit("start", "runtime", concurrentCommit);
|
||||
assert.equal(concurrent.code, "start-instance-conflict");
|
||||
assert.equal(concurrent.mutation, false);
|
||||
assert.equal(concurrent.previousTaskReset, false);
|
||||
rmSync(join(fakeRoot, "slow"));
|
||||
const succeeded = await waitFor("success", "start-succeeded");
|
||||
const succeeded = await waitFor("runtime", "start-succeeded");
|
||||
const succeededStatus = succeeded.status as Record<string, unknown>;
|
||||
assert.equal((succeededStatus.task as Record<string, unknown>).exitCode, 0);
|
||||
assert.ok((succeededStatus.logTail as unknown[]).length <= 101);
|
||||
|
||||
writeFileSync(join(fakeRoot, "fail-runtime-query"), "1\n");
|
||||
const runtimeQueryFailed = await projection("status", "success");
|
||||
const runtimeQueryFailed = await projection("status", "runtime");
|
||||
const runtimeQueryFailedStatus = runtimeQueryFailed.status as Record<string, unknown>;
|
||||
assert.equal(runtimeQueryFailed.ok, false);
|
||||
assert.equal(runtimeQueryFailedStatus.code, "runtime-query-failed");
|
||||
assert.equal(runtimeQueryFailedStatus.taskCode, "start-succeeded");
|
||||
assert.equal((runtimeQueryFailedStatus.runtime as Record<string, unknown>).code, "runtime-query-failed");
|
||||
const runtimeQueryFailedText = await runPikaoaCommand(config, args("status", "success").slice(0, -2), localCapture);
|
||||
const runtimeQueryFailedText = await runPikaoaCommand(config, args("status", "runtime").slice(0, -2), localCapture);
|
||||
assert.match(runtimeQueryFailedText.renderedText, /status=runtime-query-failed/);
|
||||
rmSync(join(fakeRoot, "fail-runtime-query"));
|
||||
|
||||
writeFileSync(join(fakeRoot, "fail-runtime-resource-query"), "1\n");
|
||||
const runtimeResourceQueryFailed = await projection("status", "success");
|
||||
const runtimeResourceQueryFailed = await projection("status", "runtime");
|
||||
const runtimeResourceQueryFailedStatus = runtimeResourceQueryFailed.status as Record<string, unknown>;
|
||||
assert.equal(runtimeResourceQueryFailed.ok, false);
|
||||
assert.equal(runtimeResourceQueryFailedStatus.code, "runtime-resource-query-failed");
|
||||
@@ -198,32 +223,99 @@ exit 64
|
||||
assert.equal((runtimeResourceQueryFailedStatus.runtime as Record<string, unknown>).code, "runtime-resource-query-failed");
|
||||
rmSync(join(fakeRoot, "fail-runtime-resource-query"));
|
||||
|
||||
const repeated = await projection("start", "success", true);
|
||||
const repeated = await projection("start", "runtime", true);
|
||||
assert.equal(repeated.code, "start-already-succeeded");
|
||||
assert.equal(repeated.mutation, false);
|
||||
const rerunSubmitted = await projectionWithCommit("start", "runtime", concurrentCommit);
|
||||
assert.equal(rerunSubmitted.code, "start-submitted");
|
||||
assert.equal(rerunSubmitted.previousTaskReset, true);
|
||||
const rerunSucceeded = await waitFor("runtime", "start-succeeded");
|
||||
assert.equal(((rerunSucceeded.status as Record<string, unknown>).task as Record<string, unknown>).sourceCommit, concurrentCommit);
|
||||
writeFileSync(fixturePath, readFileSync(fixturePath, "utf8").replace(" livenessPath: /healthz\n", " livenessPath: /healthz-v2\n"));
|
||||
const manifestRerunSubmitted = await projectionWithCommit("start", "runtime", concurrentCommit);
|
||||
assert.equal(manifestRerunSubmitted.code, "start-submitted");
|
||||
assert.equal(manifestRerunSubmitted.previousTaskReset, true);
|
||||
const manifestRerunSucceeded = await waitFor("runtime", "start-succeeded");
|
||||
await Bun.sleep(5);
|
||||
const sameRequestAfterRenderTimeChanged = await projectionWithCommit("start", "runtime", concurrentCommit);
|
||||
assert.equal(sameRequestAfterRenderTimeChanged.code, "start-already-succeeded");
|
||||
assert.equal(sameRequestAfterRenderTimeChanged.previousTaskReset, false);
|
||||
|
||||
writeFileSync(join(fakeRoot, "slow"), "1\n");
|
||||
const workerMissingSubmitted = await projection("start", "runtime", true, "api");
|
||||
const workerPid = Number((workerMissingSubmitted.remote as Record<string, unknown>).pid);
|
||||
assert.equal(Number.isSafeInteger(workerPid) && workerPid > 0, true);
|
||||
process.kill(workerPid, "SIGKILL");
|
||||
await Bun.sleep(25);
|
||||
const workerMissingReset = await projectionWithCommit("start", "runtime", concurrentCommit, "api");
|
||||
assert.equal(workerMissingReset.code, "start-submitted");
|
||||
assert.equal(workerMissingReset.previousTaskReset, true);
|
||||
rmSync(join(fakeRoot, "slow"));
|
||||
const workerMissingSucceeded = await waitFor("runtime", "start-succeeded", "api");
|
||||
|
||||
writeFileSync(join(fakeRoot, "fail-migration"), "1\n");
|
||||
await projection("start", "failed", true);
|
||||
const failed = await waitFor("failed", "start-failed");
|
||||
await projection("start", "runtime", true, "migration");
|
||||
const failed = await waitFor("runtime", "start-failed", "migration");
|
||||
const failedTask = ((failed.status as Record<string, unknown>).task as Record<string, unknown>);
|
||||
assert.equal(failedTask.code, "migration-wait-failed");
|
||||
assert.equal(failedTask.exitCode, 46);
|
||||
rmSync(join(fakeRoot, "fail-migration"));
|
||||
|
||||
writeFileSync(join(fakeRoot, "slow"), "1\n");
|
||||
await projection("start", "cancel", true);
|
||||
const stopped = await projection("stop", "cancel");
|
||||
await projection("start", "runtime", true, "web");
|
||||
const stopped = await projection("stop", "runtime", false, "web");
|
||||
assert.equal(stopped.startTaskDisposition, "cancel-requested");
|
||||
assert.equal(stopped.mutation, true);
|
||||
await waitFor("cancel", "start-canceled");
|
||||
await waitFor("runtime", "start-canceled", "web");
|
||||
|
||||
const serialized = JSON.stringify({ submitted, succeeded, failed, stopped });
|
||||
for (const name of [
|
||||
"pikaoa-test.env",
|
||||
"pikaoa-test-admin-password.txt",
|
||||
"pikaoa-test-employee-password.txt",
|
||||
]) rmSync(join(secretsDir, name));
|
||||
const foundationPlan = await projection("plan", "foundation", false, "foundation");
|
||||
assert.equal(foundationPlan.readyForMutation, true);
|
||||
assert.deepEqual(foundationPlan.missingFields, []);
|
||||
assert.deepEqual((foundationPlan.blockers as unknown[]), []);
|
||||
const foundationObjects = ((foundationPlan.plan as Record<string, unknown>).objects as Array<Record<string, unknown>>);
|
||||
assert.deepEqual(foundationObjects.map((object) => object.kind), ["Namespace", "PersistentVolumeClaim"]);
|
||||
assert.equal(((foundationPlan.plan as Record<string, unknown>).secret as Record<string, unknown>).requiredForStep, false);
|
||||
const foundationSubmitted = await projection("start", "foundation", false, "foundation");
|
||||
assert.equal(foundationSubmitted.code, "start-submitted", JSON.stringify(lastCapture));
|
||||
const foundationSucceeded = await waitFor("foundation", "start-succeeded", "foundation");
|
||||
const foundationRepeated = await projection("start", "foundation", false, "foundation");
|
||||
assert.equal(foundationRepeated.code, "start-already-succeeded");
|
||||
const foundationRerun = await projectionWithCommit("start", "foundation", concurrentCommit, "foundation");
|
||||
assert.equal(foundationRerun.code, "start-submitted");
|
||||
assert.equal(foundationRerun.previousTaskReset, true);
|
||||
const foundationRerunSucceeded = await waitFor("foundation", "start-succeeded", "foundation");
|
||||
|
||||
const serialized = JSON.stringify({
|
||||
submitted,
|
||||
succeeded,
|
||||
concurrent,
|
||||
rerunSubmitted,
|
||||
rerunSucceeded,
|
||||
manifestRerunSubmitted,
|
||||
manifestRerunSucceeded,
|
||||
sameRequestAfterRenderTimeChanged,
|
||||
workerMissingSubmitted,
|
||||
workerMissingReset,
|
||||
workerMissingSucceeded,
|
||||
failed,
|
||||
stopped,
|
||||
foundationSubmitted,
|
||||
foundationSucceeded,
|
||||
foundationRepeated,
|
||||
foundationRerun,
|
||||
foundationRerunSucceeded,
|
||||
});
|
||||
for (const value of secretValues) assert.equal(serialized.includes(value), false);
|
||||
assert.equal(serialized.includes("postgres://pikaoa_test:"), false);
|
||||
assert.equal(serialized.includes("postgresql://pikaoa_test:"), false);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
}, 15_000);
|
||||
}, 30_000);
|
||||
|
||||
test("validation-only fixture never invokes remote capture", async () => {
|
||||
let calls = 0;
|
||||
@@ -245,7 +337,16 @@ test("validation-only fixture never invokes remote capture", async () => {
|
||||
const planPayload = JSON.parse(planResult.renderedText) as Record<string, unknown>;
|
||||
const plan = planPayload.plan as Record<string, unknown>;
|
||||
assert.deepEqual(plan.database, {
|
||||
configRef: "config/platform-db/postgres-pk01.yaml#exports.connectionStrings.pikaoa-test-database-url",
|
||||
name: "pikaoa_test",
|
||||
username: "pikaoa_test",
|
||||
schema: "pikaoa",
|
||||
connection: {
|
||||
sourceRef: "./secrets/pikaoa-test.env",
|
||||
sourceKey: "DATABASE_URL",
|
||||
targetKey: "DATABASE_URL",
|
||||
valuesPrinted: false,
|
||||
},
|
||||
dsnParameterPresence: { sslmode: true, search_path: true },
|
||||
valuesPrinted: false,
|
||||
});
|
||||
@@ -277,9 +378,16 @@ test("validation-only fixture never invokes remote capture", async () => {
|
||||
rmSync(missingSchemaRoot, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const unconfigured = await runPikaoaCommand({} as UniDeskConfig, ["test-target", "status", "--output", "json"], forbiddenCapture);
|
||||
const unconfiguredPayload = JSON.parse(unconfigured.renderedText) as Record<string, unknown>;
|
||||
assert.equal(calls, 0);
|
||||
assert.equal(unconfiguredPayload.configured, false);
|
||||
assert.equal(unconfiguredPayload.remoteQueried, false);
|
||||
const unconfiguredRoot = mkdtempSync(join(tmpdir(), "pikaoa-test-target-unconfigured-"));
|
||||
const unconfiguredPath = join(unconfiguredRoot, "pikaoa.yaml");
|
||||
writeFileSync(unconfiguredPath, "version: 1\nkind: pikaoa-platform-delivery\ntestRuntime:\n defaultTargetId: null\n targets: {}\n");
|
||||
try {
|
||||
const unconfigured = await runPikaoaCommand({} as UniDeskConfig, ["test-target", "status", "--config", unconfiguredPath, "--output", "json"], forbiddenCapture);
|
||||
const unconfiguredPayload = JSON.parse(unconfigured.renderedText) as Record<string, unknown>;
|
||||
assert.equal(calls, 0);
|
||||
assert.equal(unconfiguredPayload.configured, false);
|
||||
assert.equal(unconfiguredPayload.remoteQueried, false);
|
||||
} finally {
|
||||
rmSync(unconfiguredRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
+341
-169
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user