1use std::sync::Arc;
7
8use axum::{
9 Extension, Router,
10 http::StatusCode,
11 middleware,
12 routing::{delete, get, post},
13};
14use tower_http::timeout::TimeoutLayer;
15use utoipa::OpenApi as _;
16use utoipa_swagger_ui::SwaggerUi;
17
18use super::ServerState;
19use super::api_doc::ApiDoc;
20use super::auth_middleware::{
21 AuthRateLimiter, rate_limit, strip_body_for_logs,
22};
23use super::handlers;
24
25pub fn build_router(state: Arc<ServerState>) -> Router {
32 let api = Router::new()
33 .route("/sessions", get(handlers::get_sessions))
35 .route("/analysis/images", get(handlers::get_image_analysis))
36 .route("/configurations", get(handlers::get_configurations))
37 .route("/nodes", get(handlers::get_nodes))
38 .route("/groups", get(handlers::get_groups))
39 .route("/groups/available", get(handlers::get_available_groups))
40 .route("/images", get(handlers::get_images))
41 .route("/templates", get(handlers::get_templates))
42 .route("/boot-parameters", get(handlers::get_boot_parameters))
43 .route("/kernel-parameters", get(handlers::get_kernel_parameters))
44 .route("/redfish-endpoints", get(handlers::get_redfish_endpoints))
45 .route("/groups/nodes", get(handlers::get_groups_nodes))
47 .route("/groups/hardware", get(handlers::get_groups_hardware))
48 .route("/clusters", get(handlers::get_clusters_deprecated))
51 .route(
52 "/hardware-clusters",
53 get(handlers::get_hardware_clusters_deprecated),
54 )
55 .route(
56 "/hardware-nodes-list",
57 get(handlers::get_hardware_nodes_list),
58 )
59 .route("/nodes", post(handlers::add_node))
62 .route("/nodes/{id}", delete(handlers::delete_node))
63 .route("/groups", post(handlers::create_group))
65 .route("/groups/{label}", delete(handlers::delete_group))
66 .route(
67 "/groups/{name}/members",
68 post(handlers::add_nodes_to_group).delete(handlers::delete_group_members),
69 )
70 .route(
72 "/boot-parameters",
73 post(handlers::add_boot_parameters)
74 .put(handlers::update_boot_parameters)
75 .delete(handlers::delete_boot_parameters),
76 )
77 .route(
79 "/redfish-endpoints",
80 post(handlers::add_redfish_endpoint)
81 .put(handlers::update_redfish_endpoint),
82 )
83 .route(
84 "/redfish-endpoints/{id}",
85 delete(handlers::delete_redfish_endpoint),
86 )
87 .route("/sessions/{name}", delete(handlers::delete_session))
89 .route("/sessions", post(handlers::create_session))
91 .route("/images", delete(handlers::delete_images))
93 .route("/configurations", delete(handlers::delete_configurations))
95 .route("/boot-config", post(handlers::apply_boot_config))
97 .route(
99 "/kernel-parameters/apply",
100 post(handlers::apply_kernel_parameters),
101 )
102 .route(
103 "/kernel-parameters/add",
104 post(handlers::add_kernel_parameters),
105 )
106 .route(
107 "/kernel-parameters",
108 delete(handlers::delete_kernel_parameters),
109 )
110 .route("/migrate/nodes", post(handlers::migrate_nodes))
112 .route("/migrate/backup", post(handlers::migrate_backup))
113 .route("/migrate/restore", post(handlers::migrate_restore))
114 .route("/ephemeral-env", post(handlers::create_ephemeral_env))
116 .route("/power", post(handlers::post_power))
119 .route(
120 "/power/transitions/{id}",
121 get(handlers::get_power_transition),
122 )
123 .route(
125 "/templates/{name}/sessions",
126 post(handlers::post_template_session),
127 )
128 .route("/sessions/{name}/logs", get(handlers::get_session_logs))
130 .route(
135 "/sat-file/configurations",
136 post(handlers::post_sat_configuration),
137 )
138 .route(
139 "/sat-file/images/cfs-session",
140 post(handlers::post_sat_image_cfs_session),
141 )
142 .route(
143 "/sat-file/images/stamp",
144 post(handlers::post_sat_image_stamp),
145 )
146 .route(
147 "/sat-file/session-templates",
148 post(handlers::post_sat_session_template),
149 )
150 .route("/sat-file/validate", post(handlers::post_sat_validate))
151 .route("/health", get(handlers::health))
153 .route(
155 "/hardware-clusters/{target}/members",
156 post(handlers::add_hw_component).delete(handlers::delete_hw_component),
157 )
158 .route(
160 "/hardware-clusters/{target}/configuration",
161 post(handlers::apply_hw_configuration),
162 )
163 .merge(build_ws_routes())
164 .layer(TimeoutLayer::with_status_code(
167 StatusCode::REQUEST_TIMEOUT,
168 state.request_timeout,
169 ));
170
171 let limiter = AuthRateLimiter::new();
175 let auth = Router::new()
176 .route("/token", post(handlers::auth_token))
177 .route("/validate", post(handlers::auth_validate))
178 .layer(middleware::from_fn(strip_body_for_logs))
179 .layer(middleware::from_fn_with_state(state.clone(), rate_limit))
180 .layer(Extension(limiter));
181
182 Router::new()
183 .nest("/api/v1", api)
184 .nest("/api/v1/auth", auth)
185 .merge(SwaggerUi::new("/docs").url("/openapi.json", ApiDoc::openapi()))
186 .layer(middleware::from_fn(add_hsts_header))
192 .with_state(state)
193}
194
195async fn add_hsts_header(
198 request: axum::extract::Request,
199 next: middleware::Next,
200) -> axum::response::Response {
201 let mut response = next.run(request).await;
202 response.headers_mut().insert(
203 axum::http::header::STRICT_TRANSPORT_SECURITY,
204 axum::http::HeaderValue::from_static("max-age=31536000; includeSubDomains"),
205 );
206 response
207}
208
209fn build_ws_routes() -> Router<Arc<ServerState>> {
212 Router::new()
213 .route("/nodes/{xname}/console", get(handlers::console_node_ws))
214 .route(
215 "/sessions/{name}/console",
216 get(handlers::console_session_ws),
217 )
218}