diff --git a/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md b/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md index de41c048d..786295289 100644 --- a/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md +++ b/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md @@ -94,6 +94,8 @@ SpacetimeDB 2.7 CLI 发布到受控 Compose 网络地址时固定使用 `--yes=r Jenkins 在构建完成、归档 artifact 和更新 REST 状态之间可能短暂返回不可解析的状态正文。控制服务对队列、构建状态和 artifact 查询执行有限重试;单次瞬态响应不得把已经成功并健康的部署永久写成 `failed`。 +控制服务查询 Jenkins 队列与构建状态时必须使用 `tree` 参数限制到所需字段,避免完整 `api/json` 的大体积深层对象触发 JSON 递归深度限制。预览 Compose 中的外部生成 worker 使用 `restart: on-failure`;它若早于 API 完成模型定价运行时身份初始化而启动失败,应由 Docker 自动重启并在身份就绪后稳定运行。 + ## 安全边界 - 服务端缺少控制面访问口令或 Jenkins service account 凭据时必须拒绝启动,不允许退化成匿名写接口。 diff --git a/scripts/check-preview-deployer.mjs b/scripts/check-preview-deployer.mjs index a4dd379a8..4680d1d0a 100644 --- a/scripts/check-preview-deployer.mjs +++ b/scripts/check-preview-deployer.mjs @@ -111,6 +111,16 @@ assertIncludes( 'spacetimedb:\n mem_limit: 2g\n ports: !reset []', '预览 SpacetimeDB 必须覆盖压测基线内存限制,避免模块首次加载时 OOM。', ); +assertIncludes( + deployer, + 'external-generation-worker:\n build:', + '预览 compose override 必须配置外部生成 worker。', +); +assertIncludes( + deployer, + 'restart: on-failure', + '预览外部生成 worker 必须在运行时身份初始化竞态后自动重启。', +); assertIncludes( deployer, '^preview-[0-9a-f]{16}$', diff --git a/scripts/jenkins-preview-deployer.sh b/scripts/jenkins-preview-deployer.sh index ed942b973..0171628b8 100644 --- a/scripts/jenkins-preview-deployer.sh +++ b/scripts/jenkins-preview-deployer.sh @@ -296,6 +296,7 @@ services: build: context: ${GENARRATIVE_PREVIEW_SOURCE_DIR} dockerfile: ${GENARRATIVE_PREVIEW_CONTROLLER_ROOT}/deploy/container/api-server.Dockerfile + restart: on-failure nginx: build: context: ${GENARRATIVE_PREVIEW_SOURCE_DIR} diff --git a/server-rs/crates/preview-deployer-server/src/jenkins.rs b/server-rs/crates/preview-deployer-server/src/jenkins.rs index 27f8538c3..19d9e8be3 100644 --- a/server-rs/crates/preview-deployer-server/src/jenkins.rs +++ b/server-rs/crates/preview-deployer-server/src/jenkins.rs @@ -205,10 +205,13 @@ impl JenkinsClient { Fut: Future, { let build_url = loop { - let item_url = reference + let mut item_url = reference .queue_url .join("api/json") .map_err(|error| error.to_string())?; + item_url + .query_pairs_mut() + .append_pair("tree", "cancelled,executable[url]"); let item: QueueItem = self.get_json_with_retry(item_url).await?; if item.cancelled.unwrap_or(false) { return Ok(JenkinsOutcome { @@ -226,9 +229,12 @@ impl JenkinsClient { on_build(build_url.as_str()).await; let successful = loop { - let state_url = build_url + let mut state_url = build_url .join("api/json") .map_err(|error| error.to_string())?; + state_url + .query_pairs_mut() + .append_pair("tree", "building,result"); let state: BuildState = self.get_json_with_retry(state_url).await?; if state.building { tokio::time::sleep(self.poll_interval).await; diff --git a/server-rs/crates/preview-deployer-server/src/tests.rs b/server-rs/crates/preview-deployer-server/src/tests.rs index 03f90e6ed..615981c31 100644 --- a/server-rs/crates/preview-deployer-server/src/tests.rs +++ b/server-rs/crates/preview-deployer-server/src/tests.rs @@ -103,7 +103,13 @@ async fn mock_queue(axum::extract::Path(id): axum::extract::Path) -> Json) -> impl IntoResponse { +async fn mock_build( + State(state): State, + uri: axum::http::Uri, +) -> impl IntoResponse { + if uri.query() != Some("tree=building%2Cresult") { + return (StatusCode::BAD_REQUEST, "missing bounded tree query").into_response(); + } if state .invalid_build_responses .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| {