manta_server/server/handlers/
boot_parameters.rs

1//! BSS boot-parameter handlers.
2//!
3//! - `GET    /api/v1/boot-parameters` → [`get_boot_parameters`]
4//! - `POST   /api/v1/boot-parameters` → [`add_boot_parameters`]
5//! - `PUT    /api/v1/boot-parameters` → [`update_boot_parameters`]
6//! - `DELETE /api/v1/boot-parameters` → [`delete_boot_parameters`]
7//! - `POST   /api/v1/boot-config`     → [`apply_boot_config`]
8//!
9//! All five wrap `crate::service::boot_parameters::*`. `apply_boot_config`
10//! optionally runs as dry-run (`?dry_run=true`) and returns the prepared
11//! changeset without persisting.
12
13use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
14use serde::Deserialize;
15use utoipa::ToSchema;
16
17use super::{
18  ErrorResponse, RequestCtx, SiteHeader, serialize_or_500, to_handler_error,
19};
20use crate::service;
21
22// ---------------------------------------------------------------------------
23// GET /api/v1/boot-parameters
24// ---------------------------------------------------------------------------
25
26pub use manta_shared::types::api::queries::BootParametersQuery;
27
28/// GET /boot-parameters — fetch BSS boot parameters for a group or node list.
29#[utoipa::path(get, path = "/boot-parameters", tag = "boot-parameters",
30  params(BootParametersQuery, SiteHeader),
31  security(("bearerAuth" = [])),
32  responses(
33    (status = 200, description = "Boot parameters",  body = Vec<manta_backend_dispatcher::types::bss::BootParameters>),
34    (status = 401, description = "Unauthorized",     body = ErrorResponse),
35    (status = 500, description = "Internal error",   body = ErrorResponse),
36  )
37)]
38#[tracing::instrument(skip_all)]
39pub async fn get_boot_parameters(
40  ctx: RequestCtx,
41  Query(q): Query<BootParametersQuery>,
42) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
43  let infra = ctx.infra();
44
45  let params = service::boot_parameters::GetBootParametersParams {
46    group_name: q.hsm_group,
47    host_expression: q.nodes,
48    settings_group_name: None,
49  };
50
51  let boot_params =
52    service::boot_parameters::get_boot_parameters(&infra, &ctx.token, &params)
53      .await
54      .map_err(to_handler_error)?;
55
56  Ok(Json(boot_params))
57}
58
59// ---------------------------------------------------------------------------
60// DELETE /api/v1/boot-parameters
61// ---------------------------------------------------------------------------
62
63/// Body for `DELETE /boot-parameters`.
64#[derive(Deserialize, ToSchema)]
65pub struct DeleteBootParametersRequest {
66  /// Xnames whose BSS boot-parameter entries should be deleted.
67  pub hosts: Vec<String>,
68}
69
70/// DELETE /boot-parameters — remove BSS boot parameter entries for specified hosts.
71#[utoipa::path(delete, path = "/boot-parameters", tag = "boot-parameters",
72  params(SiteHeader),
73  request_body = DeleteBootParametersRequest,
74  security(("bearerAuth" = [])),
75  responses(
76    (status = 204, description = "Boot parameters removed"),
77    (status = 400, description = "Bad request",      body = ErrorResponse),
78    (status = 401, description = "Unauthorized",     body = ErrorResponse),
79    (status = 500, description = "Internal error",   body = ErrorResponse),
80  )
81)]
82#[tracing::instrument(skip_all)]
83pub async fn delete_boot_parameters(
84  ctx: RequestCtx,
85  Json(body): Json<DeleteBootParametersRequest>,
86) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
87  if body.hosts.is_empty() {
88    return Err((
89      StatusCode::BAD_REQUEST,
90      Json(ErrorResponse {
91        error: "hosts list must not be empty".to_string(),
92      }),
93    ));
94  }
95  tracing::info!("delete_boot_parameters hosts={:?}", body.hosts);
96  let infra = ctx.infra();
97
98  service::boot_parameters::delete_boot_parameters(
99    &infra, &ctx.token, body.hosts,
100  )
101  .await
102  .map_err(to_handler_error)?;
103
104  Ok(StatusCode::NO_CONTENT)
105}
106
107// ---------------------------------------------------------------------------
108// POST /api/v1/boot-parameters
109// ---------------------------------------------------------------------------
110
111/// POST /boot-parameters — create a new BSS boot parameters entry.
112#[utoipa::path(post, path = "/boot-parameters", tag = "boot-parameters",
113  params(SiteHeader),
114  request_body = manta_backend_dispatcher::types::bss::BootParameters,
115  security(("bearerAuth" = [])),
116  responses(
117    (status = 201, description = "Boot parameters created",  body = manta_shared::types::api::responses::CreatedResponse),
118    (status = 401, description = "Unauthorized",             body = ErrorResponse),
119    (status = 500, description = "Internal error",           body = ErrorResponse),
120  )
121)]
122#[tracing::instrument(skip_all)]
123pub async fn add_boot_parameters(
124  ctx: RequestCtx,
125  Json(boot_params): Json<
126    ::manta_backend_dispatcher::types::bss::BootParameters,
127  >,
128) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
129  tracing::info!("add_boot_parameters");
130  let infra = ctx.infra();
131
132  service::boot_parameters::add_boot_parameters(
133    &infra,
134    &ctx.token,
135    &boot_params,
136  )
137  .await
138  .map_err(to_handler_error)?;
139
140  Ok((
141    StatusCode::CREATED,
142    Json(serde_json::json!({ "created": true })),
143  ))
144}
145
146// ---------------------------------------------------------------------------
147// PUT /api/v1/boot-parameters
148// ---------------------------------------------------------------------------
149
150/// PUT /boot-parameters — update boot image, kernel params, or runtime config for nodes.
151#[utoipa::path(put, path = "/boot-parameters", tag = "boot-parameters",
152  params(SiteHeader),
153  request_body = crate::service::boot_parameters::UpdateBootParametersParams,
154  security(("bearerAuth" = [])),
155  responses(
156    (status = 204, description = "Boot parameters updated"),
157    (status = 400, description = "Bad request",    body = ErrorResponse),
158    (status = 401, description = "Unauthorized",   body = ErrorResponse),
159    (status = 500, description = "Internal error", body = ErrorResponse),
160  )
161)]
162#[tracing::instrument(skip_all)]
163pub async fn update_boot_parameters(
164  ctx: RequestCtx,
165  Json(params): Json<service::boot_parameters::UpdateBootParametersParams>,
166) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
167  tracing::info!("update_boot_parameters");
168  let infra = ctx.infra();
169
170  service::boot_parameters::update_boot_parameters(&infra, &ctx.token, params)
171    .await
172    .map_err(to_handler_error)?;
173
174  Ok(StatusCode::NO_CONTENT)
175}
176
177// ---------------------------------------------------------------------------
178// POST /api/v1/boot-config — Apply boot configuration (with ?dry_run=true)
179// ---------------------------------------------------------------------------
180
181pub use manta_shared::types::api::boot_parameters::ApplyBootConfigRequest;
182
183/// `POST /api/v1/boot-config` — apply BSS boot configuration to a set of nodes.
184#[utoipa::path(post, path = "/boot-config", tag = "boot-parameters",
185  params(SiteHeader),
186  request_body = ApplyBootConfigRequest,
187  security(("bearerAuth" = [])),
188  responses(
189    // dry_run/real result union — kept as Value until the union shape is formalised
190    (status = 200, description = "Boot config applied or preview", body = serde_json::Value),
191    (status = 400, description = "Bad request",                    body = ErrorResponse),
192    (status = 401, description = "Unauthorized",                   body = ErrorResponse),
193    (status = 500, description = "Internal error",                 body = ErrorResponse),
194  )
195)]
196#[tracing::instrument(skip_all)]
197pub async fn apply_boot_config(
198  ctx: RequestCtx,
199  Json(body): Json<ApplyBootConfigRequest>,
200) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
201  tracing::info!(
202    "apply_boot_config hosts={} dry_run={}",
203    body.hosts_expression,
204    body.dry_run
205  );
206  let infra = ctx.infra();
207
208  let changeset = service::boot_parameters::prepare_boot_config(
209    &infra,
210    &ctx.token,
211    &body.hosts_expression,
212    body.boot_image_id.as_deref(),
213    body.boot_image_configuration.as_deref(),
214    body.kernel_parameters.as_deref(),
215  )
216  .await
217  .map_err(to_handler_error)?;
218
219  if body.dry_run {
220    return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
221  }
222
223  service::boot_parameters::persist_boot_config(
224    &infra,
225    &ctx.token,
226    &changeset,
227    body.runtime_configuration.as_deref(),
228  )
229  .await
230  .map_err(to_handler_error)?;
231
232  Ok((
233    StatusCode::OK,
234    Json(serde_json::json!({
235      "applied": true,
236      "nodes": changeset.xname_vec,
237      "need_restart": changeset.need_restart,
238    })),
239  ))
240}