1use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
4use serde::Deserialize;
5use utoipa::{IntoParams, ToSchema};
6
7use super::{
8 ErrorResponse, RequestCtx, SiteHeader, default_true,
9 resolve_xnames_from_request, serialize_or_500, to_handler_error,
10};
11use crate::service;
12
13#[derive(Deserialize, IntoParams)]
19pub struct KernelParametersQuery {
20 pub hsm_group: Option<String>,
22 pub nodes: Option<String>,
25}
26
27#[utoipa::path(get, path = "/kernel-parameters", tag = "kernel-parameters",
29 params(KernelParametersQuery, SiteHeader),
30 security(("bearerAuth" = [])),
31 responses(
32 (status = 200, description = "Kernel parameters", body = serde_json::Value),
33 (status = 401, description = "Unauthorized", body = ErrorResponse),
34 (status = 500, description = "Internal error", body = ErrorResponse),
35 )
36)]
37#[tracing::instrument(skip_all)]
38pub async fn get_kernel_parameters(
39 ctx: RequestCtx,
40 Query(q): Query<KernelParametersQuery>,
41) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
42 let infra = ctx.infra();
43
44 let params = service::kernel_parameters::GetKernelParametersParams {
45 hsm_group: q.hsm_group,
46 nodes: q.nodes,
47 settings_hsm_group_name: None,
48 };
49
50 let kernel_params = service::kernel_parameters::get_kernel_parameters(
51 &infra, &ctx.token, ¶ms,
52 )
53 .await
54 .map_err(to_handler_error)?;
55
56 Ok(Json(kernel_params))
57}
58
59#[derive(Debug, Deserialize, ToSchema)]
65#[serde(rename_all = "lowercase")]
66pub enum KernelParamOp {
67 Add,
69 Apply,
71 Delete,
73}
74
75#[derive(Deserialize, ToSchema)]
77pub struct ApplyKernelParametersRequest {
78 pub xnames_expression: Option<String>,
80 pub hsm_group: Option<String>,
82 pub operation: KernelParamOp,
84 pub params: String,
86 #[serde(default)]
88 pub overwrite: bool,
89 #[serde(default = "default_true")]
91 pub project_sbps: bool,
92 #[serde(default)]
94 pub dry_run: bool,
95}
96
97#[utoipa::path(post, path = "/kernel-parameters/apply", tag = "kernel-parameters",
99 params(SiteHeader),
100 request_body = ApplyKernelParametersRequest,
101 security(("bearerAuth" = [])),
102 responses(
103 (status = 200, description = "Kernel parameters applied or preview", body = serde_json::Value),
104 (status = 400, description = "Bad request", body = ErrorResponse),
105 (status = 401, description = "Unauthorized", body = ErrorResponse),
106 (status = 500, description = "Internal error", body = ErrorResponse),
107 )
108)]
109#[tracing::instrument(skip_all)]
110pub async fn apply_kernel_parameters(
111 ctx: RequestCtx,
112 Json(body): Json<ApplyKernelParametersRequest>,
113) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
114 let infra = ctx.infra();
115
116 let xnames = resolve_xnames_from_request(
117 infra.backend,
118 &ctx.token,
119 body.xnames_expression.as_deref(),
120 body.hsm_group.as_deref(),
121 )
122 .await?;
123
124 tracing::info!(
125 "apply_kernel_parameters xnames={:?} op={:?} dry_run={}",
126 xnames,
127 body.operation,
128 body.dry_run
129 );
130
131 let operation = match body.operation {
132 KernelParamOp::Add => {
133 service::kernel_parameters::KernelParamOperation::Add {
134 params: &body.params,
135 overwrite: body.overwrite,
136 }
137 }
138 KernelParamOp::Apply => {
139 service::kernel_parameters::KernelParamOperation::Apply {
140 params: &body.params,
141 }
142 }
143 KernelParamOp::Delete => {
144 service::kernel_parameters::KernelParamOperation::Delete {
145 params: &body.params,
146 }
147 }
148 };
149
150 let changeset = service::kernel_parameters::prepare_kernel_params_changes(
151 &infra, &ctx.token, &xnames, &operation,
152 )
153 .await
154 .map_err(to_handler_error)?;
155
156 if body.dry_run {
157 return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
158 }
159
160 let images_to_project = service::kernel_parameters::build_images_to_project(
161 &changeset,
162 body.project_sbps,
163 );
164
165 service::kernel_parameters::apply_kernel_params_changes(
166 &infra,
167 &ctx.token,
168 &changeset,
169 &images_to_project,
170 )
171 .await
172 .map_err(to_handler_error)?;
173
174 Ok((
175 StatusCode::OK,
176 Json(serde_json::json!({
177 "applied": true,
178 "has_changes": changeset.has_changes,
179 "xnames_to_reboot": changeset.xnames_to_reboot,
180 })),
181 ))
182}
183
184#[derive(Deserialize, ToSchema)]
190pub struct AddKernelParametersRequest {
191 pub params: String,
193 pub xnames_expression: Option<String>,
195 pub hsm_group: Option<String>,
197 #[serde(default)]
199 pub overwrite: bool,
200 #[serde(default = "default_true")]
202 pub project_sbps: bool,
203 #[serde(default)]
205 pub dry_run: bool,
206}
207
208#[utoipa::path(post, path = "/kernel-parameters/add", tag = "kernel-parameters",
210 params(SiteHeader),
211 request_body = AddKernelParametersRequest,
212 security(("bearerAuth" = [])),
213 responses(
214 (status = 200, description = "Parameters added or preview", body = serde_json::Value),
215 (status = 400, description = "Bad request", body = ErrorResponse),
216 (status = 401, description = "Unauthorized", body = ErrorResponse),
217 (status = 500, description = "Internal error", body = ErrorResponse),
218 )
219)]
220#[tracing::instrument(skip_all)]
221pub async fn add_kernel_parameters(
222 ctx: RequestCtx,
223 Json(body): Json<AddKernelParametersRequest>,
224) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
225 let infra = ctx.infra();
226 let xnames = resolve_xnames_from_request(
227 infra.backend,
228 &ctx.token,
229 body.xnames_expression.as_deref(),
230 body.hsm_group.as_deref(),
231 )
232 .await?;
233
234 tracing::info!(
235 "add_kernel_parameters xnames={:?} dry_run={}",
236 xnames,
237 body.dry_run
238 );
239
240 let operation = service::kernel_parameters::KernelParamOperation::Add {
241 params: &body.params,
242 overwrite: body.overwrite,
243 };
244
245 let changeset = service::kernel_parameters::prepare_kernel_params_changes(
246 &infra, &ctx.token, &xnames, &operation,
247 )
248 .await
249 .map_err(to_handler_error)?;
250
251 if body.dry_run {
252 return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
253 }
254
255 let images_to_project = service::kernel_parameters::build_images_to_project(
256 &changeset,
257 body.project_sbps,
258 );
259
260 service::kernel_parameters::apply_kernel_params_changes(
261 &infra,
262 &ctx.token,
263 &changeset,
264 &images_to_project,
265 )
266 .await
267 .map_err(to_handler_error)?;
268
269 Ok((
270 StatusCode::OK,
271 Json(serde_json::json!({
272 "applied": true,
273 "has_changes": changeset.has_changes,
274 "xnames_to_reboot": changeset.xnames_to_reboot,
275 })),
276 ))
277}
278
279#[derive(Deserialize, ToSchema)]
285pub struct DeleteKernelParametersRequest {
286 pub params: String,
288 pub xnames_expression: Option<String>,
290 pub hsm_group: Option<String>,
292 #[serde(default)]
294 pub dry_run: bool,
295}
296
297#[utoipa::path(delete, path = "/kernel-parameters", tag = "kernel-parameters",
299 params(SiteHeader),
300 request_body = DeleteKernelParametersRequest,
301 security(("bearerAuth" = [])),
302 responses(
303 (status = 200, description = "Parameters removed or preview", body = serde_json::Value),
304 (status = 400, description = "Bad request", body = ErrorResponse),
305 (status = 401, description = "Unauthorized", body = ErrorResponse),
306 (status = 500, description = "Internal error", body = ErrorResponse),
307 )
308)]
309#[tracing::instrument(skip_all)]
310pub async fn delete_kernel_parameters(
311 ctx: RequestCtx,
312 Json(body): Json<DeleteKernelParametersRequest>,
313) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
314 let infra = ctx.infra();
315 let xnames = resolve_xnames_from_request(
316 infra.backend,
317 &ctx.token,
318 body.xnames_expression.as_deref(),
319 body.hsm_group.as_deref(),
320 )
321 .await?;
322
323 tracing::info!(
324 "delete_kernel_parameters xnames={:?} dry_run={}",
325 xnames,
326 body.dry_run
327 );
328
329 let operation = service::kernel_parameters::KernelParamOperation::Delete {
330 params: &body.params,
331 };
332
333 let changeset = service::kernel_parameters::prepare_kernel_params_changes(
334 &infra, &ctx.token, &xnames, &operation,
335 )
336 .await
337 .map_err(to_handler_error)?;
338
339 if body.dry_run {
340 return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
341 }
342
343 service::kernel_parameters::apply_kernel_params_changes(
344 &infra,
345 &ctx.token,
346 &changeset,
347 &std::collections::HashMap::new(),
348 )
349 .await
350 .map_err(to_handler_error)?;
351
352 Ok((
353 StatusCode::OK,
354 Json(serde_json::json!({
355 "applied": true,
356 "has_changes": changeset.has_changes,
357 "xnames_to_reboot": changeset.xnames_to_reboot,
358 })),
359 ))
360}