swarm-controller: OpenAPI spec + gateway swagger UI wiring
Rust half mirrors hive-c0re/src/dashboard/mod.rs's utoipa pattern
exactly: an ApiDoc root, #[utoipa::path(...)] on /health (the one
existing route), and a raw JSON route at /api/openapi.json served
via OpenApiRouter::split_for_parts(). Only annotated routes appear
in the spec.
Gateway wiring extends the swarm-UI vhost (the only vhost swarm-
controller is reachable from) with:
- /api/ — proxied to the controller's unix socket untouched (no URI
segment after the socket path), so a route swarm-controller
registers is the path nginx forwards, no prefix-stripping to keep
in sync by hand.
- /api/docs/ (+ the bare /api/docs redirect) — the same
swagger-ui-theme dist the per-hive dashboard already serves at its
own /api/docs/, reused as-is since it's generic.
Both new locations reuse the same auth_request block the vhost's own
'/' already applies, factored into a shared swarmAuthRequest string —
auth_request does not inherit across sibling nginx locations, so
without this the page itself would be gated while its own API and
API docs sat open.
cargo test -p swarm-controller + cargo clippy --all-targets both
clean. Verified the new nginx wiring evaluates correctly with a
throwaway nixosSystem eval (services.hyperhive.swarm.{controller,ui}
enabled): /api/ proxies to the socket, /api/docs redirects, and both
require auth_request the same as the vhost root.
Fixes hyperhive#3212
This commit is contained in:
parent
8642d4acf6
commit
4828c96957
5 changed files with 94 additions and 9 deletions
|
|
@ -11,12 +11,23 @@
|
|||
//!
|
||||
//! Distinct from `hive-c0re`, which is per-hive: c0re owns the agents on
|
||||
//! one host, this owns what is true across hives.
|
||||
//!
|
||||
//! `OpenAPI` spec generation mirrors `hive-c0re/src/dashboard/mod.rs`
|
||||
//! exactly: `#[utoipa::path(...)]` per handler, an `ApiDoc` root, and a
|
||||
//! raw JSON route at `/api/openapi.json`. Swagger UI itself is
|
||||
//! nginx-hosted from the nix store, same shape as the per-hive
|
||||
//! dashboard's (`nix/host-modules/hive-gateway/vhosts.nix`'s
|
||||
//! `swarmUiVhost` — a swagger-ui-theme dist under `/api/docs/`, no
|
||||
//! fallback to this daemon). Only annotated routes appear in the spec;
|
||||
//! an unannotated one just doesn't show up, nothing breaks.
|
||||
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use axum::{Router, routing::get};
|
||||
use axum::{Json, routing::get};
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_axum::{router::OpenApiRouter, routes};
|
||||
|
||||
/// Where the daemon binds, overridable via `SWARM_CONTROLLER_SOCKET`.
|
||||
///
|
||||
|
|
@ -38,8 +49,29 @@ fn socket_path() -> PathBuf {
|
|||
.map_or_else(|| PathBuf::from(DEFAULT_SOCKET), PathBuf::from)
|
||||
}
|
||||
|
||||
/// Root of the auto-generated `OpenAPI` spec, served raw at
|
||||
/// `/api/openapi.json` — see the module doc comment above. One tag today
|
||||
/// (`health`); grows alongside the swarm-level surfaces this daemon picks
|
||||
/// up, same as `hive-c0re::dashboard::ApiDoc`'s tag list did.
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
info(
|
||||
title = "hyperhive swarm-controller API",
|
||||
description = "swarm-controller's HTTP surface, served over its unix \
|
||||
socket behind the gateway's swarm-UI vhost."
|
||||
),
|
||||
tags((name = "health", description = "liveness probe"))
|
||||
)]
|
||||
struct ApiDoc;
|
||||
|
||||
/// Liveness probe. Returns the build's version so an operator can tell
|
||||
/// *which* controller answered without shelling onto the host.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/health",
|
||||
responses((status = 200, description = "process is up, body is \"swarm-controller <version>\"", body = String)),
|
||||
tag = "health"
|
||||
)]
|
||||
async fn health() -> &'static str {
|
||||
concat!("swarm-controller ", env!("CARGO_PKG_VERSION"), "\n")
|
||||
}
|
||||
|
|
@ -82,7 +114,17 @@ async fn main() -> Result<()> {
|
|||
.with_context(|| format!("chmod {}", path.display()))?;
|
||||
tracing::info!(socket = %path.display(), "swarm-controller listening");
|
||||
|
||||
let app = Router::new().route("/health", get(health));
|
||||
let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi())
|
||||
.routes(routes!(health))
|
||||
.split_for_parts();
|
||||
// Just the JSON, not the UI — Swagger UI itself is nginx-hosted from
|
||||
// the nix store (see the module doc comment above). `api` is
|
||||
// `Clone`; each request gets its own owned copy for `Json` to
|
||||
// serialize, same as `hive-c0re::dashboard::serve`.
|
||||
let app = router.route(
|
||||
"/api/openapi.json",
|
||||
get(move || async move { Json(api.clone()) }),
|
||||
);
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
.context("serving swarm-controller")
|
||||
|
|
|
|||
Loading…
Reference in a new issue