swarm-controller: track agent config-PR status independently of hives
This commit is contained in:
parent
3c390f8132
commit
66c494697f
3 changed files with 217 additions and 2 deletions
|
|
@ -29,13 +29,18 @@ use std::path::PathBuf;
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use axum::{Json, extract::State, routing::get};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
routing::get,
|
||||
};
|
||||
use hive_jobq_wire::GraphWire as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{OpenApi, ToSchema};
|
||||
use utoipa_axum::{router::OpenApiRouter, routes};
|
||||
|
||||
mod auth;
|
||||
mod config_pr;
|
||||
mod forge;
|
||||
mod status;
|
||||
mod webhook;
|
||||
|
|
@ -352,6 +357,11 @@ struct AppState {
|
|||
/// startup, and this way `as_deref()` yields the `&str` the verifier
|
||||
/// takes without a second hop through `String`.
|
||||
webhook_secret: Option<Arc<str>>,
|
||||
/// Last successful `agent-configs/*` scan, kept current by
|
||||
/// [`config_pr::spawn`]. `None` when this host has no forge configured
|
||||
/// — same "absent means don't ask" shape as `status` and `forge` above,
|
||||
/// not a startup failure.
|
||||
config_prs: Option<Arc<config_pr::ConfigPrCache>>,
|
||||
/// The swarm's human display name (`services.hyperhive.swarm.name`),
|
||||
/// loaded once at startup (`load_swarm_name`). `None` when the
|
||||
/// operator never set it — a swarm without a display name is a
|
||||
|
|
@ -760,6 +770,38 @@ async fn get_jobq_rollup(State(state): State<AppState>) -> Json<Vec<hive_jobq_wi
|
|||
Json(hive_jobq_wire::state_rollup(graph, roots))
|
||||
}
|
||||
|
||||
/// `agent`'s open config PR, from the last successful swarm-level scan
|
||||
/// ([`config_pr::spawn`]) — not a live forge read, so this answers even when
|
||||
/// the forge itself is momentarily unreachable, at the cost of being up to
|
||||
/// one [`config_pr::POLL_INTERVAL`] stale.
|
||||
///
|
||||
/// `200` with a `null` body means "no open PR" (or "no scan has completed
|
||||
/// yet") — not distinguished, for the same reason [`ConfigPrCache::get`]
|
||||
/// doesn't: both read as "nothing to show" to the swarm-ui config-PR panel
|
||||
/// this feeds, and a cache is a best-effort read, not a source of truth
|
||||
/// callers should expect to disambiguate against.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/agents/{name}/config-pr",
|
||||
params(("name" = String, Path, description = "agent name")),
|
||||
responses(
|
||||
(status = 200, description = "the agent's open config PR, or null if none / not scanned yet", body = Option<forge::ConfigPrStatus>),
|
||||
(status = 503, description = "no forge is configured on this host", body = String),
|
||||
),
|
||||
tag = "agents"
|
||||
)]
|
||||
async fn get_agent_config_pr(
|
||||
State(state): State<AppState>,
|
||||
Path(name): Path<String>,
|
||||
) -> Result<Json<Option<forge::ConfigPrStatus>>, StatusUnavailable> {
|
||||
let Some(cache) = state.config_prs.as_ref() else {
|
||||
return Err(StatusUnavailable(
|
||||
"no forge is configured on this host".to_owned(),
|
||||
));
|
||||
};
|
||||
Ok(Json(cache.get(&name)))
|
||||
}
|
||||
|
||||
/// The swarm's own public base URL, as the forge must address it.
|
||||
///
|
||||
/// Set by `swarm-controller.nix` **only when this host actually serves the
|
||||
|
|
@ -938,6 +980,8 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
};
|
||||
|
||||
let config_prs = forge_client.clone().map(config_pr::spawn);
|
||||
|
||||
register_swarm_webhooks(forge_client, webhook_secret.clone());
|
||||
|
||||
let state = AppState {
|
||||
|
|
@ -946,6 +990,7 @@ async fn main() -> Result<()> {
|
|||
status,
|
||||
jobq,
|
||||
webhook_secret,
|
||||
config_prs,
|
||||
swarm_name: load_swarm_name().map(Arc::from),
|
||||
};
|
||||
|
||||
|
|
@ -957,6 +1002,7 @@ async fn main() -> Result<()> {
|
|||
.routes(routes!(get_swarm_info))
|
||||
.routes(routes!(get_jobq_graph))
|
||||
.routes(routes!(get_jobq_rollup))
|
||||
.routes(routes!(get_agent_config_pr))
|
||||
.routes(routes!(create_agent))
|
||||
.routes(routes!(webhook::post_webhook_forge))
|
||||
.split_for_parts();
|
||||
|
|
@ -1063,6 +1109,7 @@ mod tests {
|
|||
status: None,
|
||||
jobq: std::sync::Arc::clone(&sched),
|
||||
webhook_secret: None,
|
||||
config_prs: None,
|
||||
swarm_name: None,
|
||||
};
|
||||
(state, sched)
|
||||
|
|
|
|||
Loading…
Reference in a new issue