manta_server/server/handlers/
boot_parameters.rs

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