manta_server/server/
routes.rs

1//! Axum router registration: maps every `/api/v1/` path to its handler.
2//!
3//! The OpenAPI JSON spec is served at `GET /openapi.json` and the
4//! Swagger UI is served at `GET /docs`.
5
6use std::sync::Arc;
7
8use axum::{
9  Extension, Router, middleware,
10  routing::{delete, get, post},
11};
12use utoipa::OpenApi as _;
13use utoipa_swagger_ui::SwaggerUi;
14
15use super::ServerState;
16use super::api_doc::ApiDoc;
17use super::auth_middleware::{
18  AuthRateLimiter, rate_limit, strip_body_for_logs,
19};
20use super::handlers;
21
22/// Build the axum router with all API endpoints and OpenAPI doc routes.
23pub fn build_router(state: Arc<ServerState>) -> Router {
24  let api = Router::new()
25    // --- GET endpoints ---
26    .route("/sessions", get(handlers::get_sessions))
27    .route("/configurations", get(handlers::get_configurations))
28    .route("/nodes", get(handlers::get_nodes))
29    .route("/groups", get(handlers::get_groups))
30    .route("/groups/available", get(handlers::get_available_groups))
31    .route("/groups/all", get(handlers::get_all_groups))
32    .route("/images", get(handlers::get_images))
33    .route("/templates", get(handlers::get_templates))
34    .route("/boot-parameters", get(handlers::get_boot_parameters))
35    .route("/kernel-parameters", get(handlers::get_kernel_parameters))
36    .route("/redfish-endpoints", get(handlers::get_redfish_endpoints))
37    // Canonical (group-centric) read endpoints
38    .route("/groups/nodes", get(handlers::get_groups_nodes))
39    .route("/groups/hardware", get(handlers::get_groups_hardware))
40    // Deprecated aliases retained for one release. Each handler logs
41    // a server-side warning and forwards to the canonical impl.
42    .route("/clusters", get(handlers::get_clusters_deprecated))
43    .route(
44      "/hardware-clusters",
45      get(handlers::get_hardware_clusters_deprecated),
46    )
47    .route(
48      "/hardware-nodes-list",
49      get(handlers::get_hardware_nodes_list),
50    )
51    // --- Write endpoints ---
52    // Nodes
53    .route("/nodes", post(handlers::add_node))
54    .route("/nodes/{id}", delete(handlers::delete_node))
55    // Groups
56    .route("/groups", post(handlers::create_group))
57    .route("/groups/{label}", delete(handlers::delete_group))
58    .route(
59      "/groups/{name}/members",
60      post(handlers::add_nodes_to_group).delete(handlers::delete_group_members),
61    )
62    // Boot parameters
63    .route(
64      "/boot-parameters",
65      post(handlers::add_boot_parameters)
66        .put(handlers::update_boot_parameters)
67        .delete(handlers::delete_boot_parameters),
68    )
69    // Redfish endpoints
70    .route(
71      "/redfish-endpoints",
72      post(handlers::add_redfish_endpoint)
73        .put(handlers::update_redfish_endpoint),
74    )
75    .route(
76      "/redfish-endpoints/{id}",
77      delete(handlers::delete_redfish_endpoint),
78    )
79    // Sessions (delete with dry_run)
80    .route("/sessions/{name}", delete(handlers::delete_session))
81    // Sessions (create)
82    .route("/sessions", post(handlers::create_session))
83    // Images (delete with dry_run)
84    .route("/images", delete(handlers::delete_images))
85    // Configurations (delete with dry_run)
86    .route("/configurations", delete(handlers::delete_configurations))
87    // Boot config (apply with dry_run)
88    .route("/boot-config", post(handlers::apply_boot_config))
89    // Kernel parameters (apply, add, delete)
90    .route(
91      "/kernel-parameters/apply",
92      post(handlers::apply_kernel_parameters),
93    )
94    .route(
95      "/kernel-parameters/add",
96      post(handlers::add_kernel_parameters),
97    )
98    .route(
99      "/kernel-parameters",
100      delete(handlers::delete_kernel_parameters),
101    )
102    // Migrate
103    .route("/migrate/nodes", post(handlers::migrate_nodes))
104    .route("/migrate/backup", post(handlers::migrate_backup))
105    .route("/migrate/restore", post(handlers::migrate_restore))
106    // Ephemeral environment
107    .route("/ephemeral-env", post(handlers::create_ephemeral_env))
108    // Power management
109    .route("/power", post(handlers::post_power))
110    // BOS session from template
111    .route(
112      "/templates/{name}/sessions",
113      post(handlers::post_template_session),
114    )
115    // CFS session logs (SSE)
116    .route("/sessions/{name}/logs", get(handlers::get_session_logs))
117    // SAT file apply
118    .route("/sat-file", post(handlers::post_sat_file))
119    // Health check
120    .route("/health", get(handlers::health))
121    // Hardware cluster member management
122    .route(
123      "/hardware-clusters/{target}/members",
124      post(handlers::add_hw_component).delete(handlers::delete_hw_component),
125    )
126    // Hardware cluster configuration (pin/unpin)
127    .route(
128      "/hardware-clusters/{target}/configuration",
129      post(handlers::apply_hw_configuration),
130    )
131    .merge(build_ws_routes());
132
133  // /api/v1/auth/* — credential-handling sub-router. No Bearer
134  // extractor (chicken-and-egg). Two layered defences applied:
135  // (1) per-IP rate limit, (2) body redaction from any log span.
136  let limiter = AuthRateLimiter::new();
137  let auth = Router::new()
138    .route("/token", post(handlers::auth_token))
139    .route("/validate", post(handlers::auth_validate))
140    .layer(middleware::from_fn(strip_body_for_logs))
141    .layer(middleware::from_fn_with_state(state.clone(), rate_limit))
142    .layer(Extension(limiter));
143
144  Router::new()
145    .nest("/api/v1", api)
146    .nest("/api/v1/auth", auth)
147    .merge(SwaggerUi::new("/docs").url("/openapi.json", ApiDoc::openapi()))
148    .with_state(state)
149}
150
151/// WebSocket upgrade routes — kept separate so they're easy to identify
152/// and so the upgrade protocol is not mixed with plain HTTP routes.
153fn build_ws_routes() -> Router<Arc<ServerState>> {
154  Router::new()
155    .route("/nodes/{xname}/console", get(handlers::console_node_ws))
156    .route(
157      "/sessions/{name}/console",
158      get(handlers::console_session_ws),
159    )
160}