1use std::sync::Arc;
9
10use axum::{
11 Extension, Router,
12 http::StatusCode,
13 middleware,
14 routing::{delete, get, post, put},
15};
16use tower_http::timeout::TimeoutLayer;
17use utoipa::OpenApi as _;
18use utoipa_swagger_ui::SwaggerUi;
19
20use super::ServerState;
21use super::api_doc::ApiDoc;
22use super::auth_middleware::{
23 AuthRateLimiter, rate_limit, strip_body_for_logs,
24};
25use super::handlers;
26
27pub fn build_router(state: Arc<ServerState>) -> Router {
44 let api = Router::new()
45 .route("/sessions", get(handlers::get_sessions))
47 .route("/analysis/images", get(handlers::get_image_analysis))
48 .route("/configurations", get(handlers::get_configurations))
49 .route("/nodes", get(handlers::get_nodes))
50 .route("/groups", get(handlers::get_groups))
51 .route("/groups/available", get(handlers::get_available_groups))
52 .route("/images", get(handlers::get_images))
53 .route("/templates", get(handlers::get_templates))
54 .route("/boot-parameters", get(handlers::get_boot_parameters))
55 .route("/kernel-parameters", get(handlers::get_kernel_parameters))
56 .route("/redfish-endpoints", get(handlers::get_redfish_endpoints))
57 .route("/groups/nodes", get(handlers::get_groups_nodes))
59 .route("/groups/hardware", get(handlers::get_groups_hardware))
60 .route("/clusters", get(handlers::get_clusters_deprecated))
63 .route(
64 "/hardware-clusters",
65 get(handlers::get_hardware_clusters_deprecated),
66 )
67 .route(
68 "/hardware-nodes-list",
69 get(handlers::get_hardware_nodes_list),
70 )
71 .route("/nodes", post(handlers::add_node))
74 .route("/nodes/{id}", delete(handlers::delete_node))
75 .route("/groups", post(handlers::create_group))
77 .route("/groups/{label}", delete(handlers::delete_group))
78 .route(
79 "/groups/{name}/members",
80 post(handlers::add_nodes_to_group).delete(handlers::delete_group_members),
81 )
82 .route(
84 "/boot-parameters",
85 post(handlers::add_boot_parameters)
86 .put(handlers::update_boot_parameters)
87 .delete(handlers::delete_boot_parameters),
88 )
89 .route(
91 "/redfish-endpoints",
92 post(handlers::add_redfish_endpoint)
93 .put(handlers::update_redfish_endpoint),
94 )
95 .route(
96 "/redfish-endpoints/{id}",
97 delete(handlers::delete_redfish_endpoint),
98 )
99 .route("/sessions/{name}", delete(handlers::delete_session))
101 .route("/sessions", post(handlers::create_session))
103 .route("/images", delete(handlers::delete_images))
105 .route("/configurations", delete(handlers::delete_configurations))
107 .route("/boot-config", post(handlers::apply_boot_config))
109 .route(
112 "/runtime-configuration",
113 put(handlers::apply_runtime_configuration),
114 )
115 .route(
117 "/kernel-parameters/apply",
118 post(handlers::apply_kernel_parameters),
119 )
120 .route(
121 "/kernel-parameters/add",
122 post(handlers::add_kernel_parameters),
123 )
124 .route(
125 "/kernel-parameters",
126 delete(handlers::delete_kernel_parameters),
127 )
128 .route("/migrate/nodes", post(handlers::migrate_nodes))
130 .route("/migrate/backup", post(handlers::migrate_backup))
131 .route("/migrate/restore", post(handlers::migrate_restore))
132 .route("/ephemeral-env", post(handlers::create_ephemeral_env))
134 .route("/power", post(handlers::post_power))
137 .route(
138 "/power/transitions/{id}",
139 get(handlers::get_power_transition),
140 )
141 .route(
143 "/templates/{name}/sessions",
144 post(handlers::post_template_session),
145 )
146 .route("/sessions/{name}/logs", get(handlers::get_session_logs))
148 .route(
153 "/sat-file/configurations",
154 post(handlers::post_sat_configuration),
155 )
156 .route(
157 "/sat-file/images/cfs-session",
158 post(handlers::post_sat_image_cfs_session),
159 )
160 .route(
161 "/sat-file/images/stamp",
162 post(handlers::post_sat_image_stamp),
163 )
164 .route(
165 "/sat-file/session-templates",
166 post(handlers::post_sat_session_template),
167 )
168 .route("/health", get(handlers::health))
170 .route(
172 "/hardware-clusters/{target}/members",
173 post(handlers::add_hw_component).delete(handlers::delete_hw_component),
174 )
175 .route(
177 "/hardware-clusters/{target}/configuration",
178 post(handlers::apply_hw_configuration),
179 )
180 .merge(build_ws_routes())
181 .layer(TimeoutLayer::with_status_code(
184 StatusCode::REQUEST_TIMEOUT,
185 state.request_timeout,
186 ));
187
188 let limiter = AuthRateLimiter::new();
192 let auth = Router::new()
193 .route("/token", post(handlers::auth_token))
194 .route("/validate", post(handlers::auth_validate))
195 .layer(middleware::from_fn(strip_body_for_logs))
196 .layer(middleware::from_fn_with_state(state.clone(), rate_limit))
197 .layer(Extension(limiter));
198
199 Router::new()
200 .nest("/v2", api)
201 .nest("/v2/auth", auth)
202 .merge(SwaggerUi::new("/docs").url("/openapi.json", ApiDoc::openapi()))
203 .layer(middleware::from_fn(add_hsts_header))
209 .with_state(state)
210}
211
212async fn add_hsts_header(
215 request: axum::extract::Request,
216 next: middleware::Next,
217) -> axum::response::Response {
218 let mut response = next.run(request).await;
219 response.headers_mut().insert(
220 axum::http::header::STRICT_TRANSPORT_SECURITY,
221 axum::http::HeaderValue::from_static("max-age=31536000; includeSubDomains"),
222 );
223 response
224}
225
226fn build_ws_routes() -> Router<Arc<ServerState>> {
229 Router::new()
230 .route("/nodes/{xname}/console", get(handlers::console_node_ws))
231 .route(
232 "/sessions/{name}/console",
233 get(handlers::console_session_ws),
234 )
235}