feat(#2591): add GET /api/build-log/<node_id> {,/raw} query endpoints
The raw-graph wire drops the inline build_log_id; the client fetches a
node's captured output on demand. Two handlers resolve node id -> log-row
id (JobQueue::build_log_id_of, now keyed by the wire u64) then delegate to
the existing get_full / raw handlers: /api/build-log/<node_id> serves the
BuildLogFull JSON ({stdout, stderr} + header), /raw serves text/plain.
404 when the node has no linked log.
This commit is contained in:
parent
02e2bf895e
commit
52dd9ede23
4 changed files with 55 additions and 7 deletions
|
|
@ -90,6 +90,42 @@ pub(super) async fn get_build_log_full(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `GET /api/build-log/{node_id}` — the build log for a **queue node**,
|
||||||
|
/// resolved node id → log-row id → full log. Same `BuildLogFull` JSON
|
||||||
|
/// (`stdout` / `stderr` + header) as `get_build_log_full`; HTTP 404 when the
|
||||||
|
/// node has no linked log (the client gates the request on `NodeView.has_log`,
|
||||||
|
/// but a vacuum race can still 404). This is the on-demand log fetch the
|
||||||
|
/// raw-graph dashboard uses instead of an inline `build_log_id` on the wire.
|
||||||
|
pub(super) async fn get_build_log_for_node(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
AxumPath(node_id): AxumPath<u64>,
|
||||||
|
) -> Response {
|
||||||
|
match state.coord.job_queue.build_log_id_of(node_id) {
|
||||||
|
Some(log_id) => get_build_log_full(State(state), AxumPath(log_id)).await,
|
||||||
|
None => (
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
format!("node #{node_id} has no build log"),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `GET /api/build-log/{node_id}/raw` — the node's build log as `text/plain`
|
||||||
|
/// for download (delegates to `get_build_log_raw` after resolving the node id).
|
||||||
|
pub(super) async fn get_build_log_raw_for_node(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
AxumPath(node_id): AxumPath<u64>,
|
||||||
|
) -> Response {
|
||||||
|
match state.coord.job_queue.build_log_id_of(node_id) {
|
||||||
|
Some(log_id) => get_build_log_raw(State(state), AxumPath(log_id)).await,
|
||||||
|
None => (
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
format!("node #{node_id} has no build log"),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// JSON frame sent on the `/api/build-logs/id/{id}/stream` SSE channel.
|
/// JSON frame sent on the `/api/build-logs/id/{id}/stream` SSE channel.
|
||||||
/// `stdout_append` / `stderr_append` carry only the new bytes since the
|
/// `stdout_append` / `stderr_append` carry only the new bytes since the
|
||||||
/// last frame; `done = true` means the build finished and the stream
|
/// last frame; `done = true` means the build finished and the stream
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,14 @@ pub async fn serve(
|
||||||
)
|
)
|
||||||
.route("/api/audit-log", get(misc_api::api_audit_log))
|
.route("/api/audit-log", get(misc_api::api_audit_log))
|
||||||
.route("/api/build-logs", get(build_logs::get_build_logs_all))
|
.route("/api/build-logs", get(build_logs::get_build_logs_all))
|
||||||
|
.route(
|
||||||
|
"/api/build-log/{node_id}",
|
||||||
|
get(build_logs::get_build_log_for_node),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/build-log/{node_id}/raw",
|
||||||
|
get(build_logs::get_build_log_raw_for_node),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/build-logs/{agent}",
|
"/api/build-logs/{agent}",
|
||||||
get(build_logs::get_build_logs_agent),
|
get(build_logs::get_build_logs_agent),
|
||||||
|
|
|
||||||
|
|
@ -511,15 +511,19 @@ impl JobQueue {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `build_logs` row id linked to `node_id`, if any — the lookup behind
|
/// The `build_logs` row id linked to the wire node id `node_id`, if any —
|
||||||
/// the `GET /api/build-log/<node_id>` query endpoint (the client fetches a
|
/// the lookup behind the `GET /api/build-log/<node_id>` query endpoint (the
|
||||||
/// node's captured build output on demand rather than receiving it inline).
|
/// client fetches a node's captured build output on demand rather than
|
||||||
|
/// receiving it inline). Takes the raw wire `u64` (the endpoint's path
|
||||||
|
/// param); `node_rt` is keyed by the opaque `NodeId`, so this scans for the
|
||||||
|
/// matching id — the map is small (live + recently-terminal nodes).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn build_log_id_of(&self, node_id: NodeId) -> Option<i64> {
|
pub fn build_log_id_of(&self, node_id: u64) -> Option<i64> {
|
||||||
self.lock()
|
self.lock()
|
||||||
.node_rt
|
.node_rt
|
||||||
.get(&node_id)
|
.iter()
|
||||||
.and_then(|r| r.build_log_id)
|
.find(|(nid, _)| nid.get() == node_id)
|
||||||
|
.and_then(|(_, rt)| rt.build_log_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A DAG's terminal roll-up summary, computed on demand from its container.
|
/// A DAG's terminal roll-up summary, computed on demand from its container.
|
||||||
|
|
|
||||||
|
|
@ -896,7 +896,7 @@ fn set_build_log_id_links_running_node() {
|
||||||
// The log id is fetched by node id (the `GET /api/build-log/<id>` lookup),
|
// The log id is fetched by node id (the `GET /api/build-log/<id>` lookup),
|
||||||
// not carried on the wire — it survives completion in the node runtime.
|
// not carried on the wire — it survives completion in the node runtime.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
q.build_log_id_of(c.node_id),
|
q.build_log_id_of(c.node_id.get()),
|
||||||
Some(43),
|
Some(43),
|
||||||
"log id survives completion"
|
"log id survives completion"
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue