1use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
4use serde::Deserialize;
5use utoipa::{IntoParams, ToSchema};
6
7use super::{
8 ErrorResponse, RequestCtx, SiteHeader, serialize_or_500, to_handler_error,
9};
10use crate::service;
11
12#[derive(Deserialize, IntoParams)]
18pub struct BootParametersQuery {
19 pub hsm_group: Option<String>,
21 pub nodes: Option<String>,
24}
25
26#[utoipa::path(get, path = "/boot-parameters", tag = "boot-parameters",
28 params(BootParametersQuery, SiteHeader),
29 security(("bearerAuth" = [])),
30 responses(
31 (status = 200, description = "Boot parameters", body = serde_json::Value),
32 (status = 401, description = "Unauthorized", body = ErrorResponse),
33 (status = 500, description = "Internal error", body = ErrorResponse),
34 )
35)]
36#[tracing::instrument(skip_all)]
37pub async fn get_boot_parameters(
38 ctx: RequestCtx,
39 Query(q): Query<BootParametersQuery>,
40) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
41 let infra = ctx.infra();
42
43 let params = service::boot_parameters::GetBootParametersParams {
44 hsm_group: q.hsm_group,
45 nodes: q.nodes,
46 settings_hsm_group_name: None,
47 };
48
49 let boot_params =
50 service::boot_parameters::get_boot_parameters(&infra, &ctx.token, ¶ms)
51 .await
52 .map_err(to_handler_error)?;
53
54 Ok(Json(boot_params))
55}
56
57#[derive(Deserialize, ToSchema)]
63pub struct DeleteBootParametersRequest {
64 pub hosts: Vec<String>,
66}
67
68#[utoipa::path(delete, path = "/boot-parameters", tag = "boot-parameters",
70 params(SiteHeader),
71 request_body = DeleteBootParametersRequest,
72 security(("bearerAuth" = [])),
73 responses(
74 (status = 204, description = "Boot parameters removed"),
75 (status = 400, description = "Bad request", body = ErrorResponse),
76 (status = 401, description = "Unauthorized", body = ErrorResponse),
77 (status = 500, description = "Internal error", body = ErrorResponse),
78 )
79)]
80#[tracing::instrument(skip_all)]
81pub async fn delete_boot_parameters(
82 ctx: RequestCtx,
83 Json(body): Json<DeleteBootParametersRequest>,
84) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
85 if body.hosts.is_empty() {
86 return Err((
87 StatusCode::BAD_REQUEST,
88 Json(ErrorResponse {
89 error: "hosts list must not be empty".to_string(),
90 }),
91 ));
92 }
93 tracing::info!("delete_boot_parameters hosts={:?}", body.hosts);
94 let infra = ctx.infra();
95
96 service::boot_parameters::delete_boot_parameters(
97 &infra, &ctx.token, body.hosts,
98 )
99 .await
100 .map_err(to_handler_error)?;
101
102 Ok(StatusCode::NO_CONTENT)
103}
104
105#[utoipa::path(post, path = "/boot-parameters", tag = "boot-parameters",
111 params(SiteHeader),
112 request_body = manta_backend_dispatcher::types::bss::BootParameters,
113 security(("bearerAuth" = [])),
114 responses(
115 (status = 201, description = "Boot parameters created", body = serde_json::Value),
116 (status = 401, description = "Unauthorized", body = ErrorResponse),
117 (status = 500, description = "Internal error", body = ErrorResponse),
118 )
119)]
120#[tracing::instrument(skip_all)]
121pub async fn add_boot_parameters(
122 ctx: RequestCtx,
123 Json(boot_params): Json<
124 ::manta_backend_dispatcher::types::bss::BootParameters,
125 >,
126) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
127 tracing::info!("add_boot_parameters");
128 let infra = ctx.infra();
129
130 service::boot_parameters::add_boot_parameters(
131 &infra,
132 &ctx.token,
133 &boot_params,
134 )
135 .await
136 .map_err(to_handler_error)?;
137
138 Ok((
139 StatusCode::CREATED,
140 Json(serde_json::json!({ "created": true })),
141 ))
142}
143
144#[utoipa::path(put, path = "/boot-parameters", tag = "boot-parameters",
150 params(SiteHeader),
151 request_body = crate::service::boot_parameters::UpdateBootParametersParams,
152 security(("bearerAuth" = [])),
153 responses(
154 (status = 204, description = "Boot parameters updated"),
155 (status = 400, description = "Bad request", body = ErrorResponse),
156 (status = 401, description = "Unauthorized", body = ErrorResponse),
157 (status = 500, description = "Internal error", body = ErrorResponse),
158 )
159)]
160#[tracing::instrument(skip_all)]
161pub async fn update_boot_parameters(
162 ctx: RequestCtx,
163 Json(params): Json<service::boot_parameters::UpdateBootParametersParams>,
164) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
165 tracing::info!("update_boot_parameters");
166 let infra = ctx.infra();
167
168 service::boot_parameters::update_boot_parameters(&infra, &ctx.token, params)
169 .await
170 .map_err(to_handler_error)?;
171
172 Ok(StatusCode::NO_CONTENT)
173}
174
175#[derive(Deserialize, ToSchema)]
181pub struct ApplyBootConfigRequest {
182 pub hosts_expression: String,
184 pub boot_image_id: Option<String>,
186 pub boot_image_configuration: Option<String>,
188 pub kernel_parameters: Option<String>,
190 pub runtime_configuration: Option<String>,
192 #[serde(default)]
194 pub dry_run: bool,
195}
196
197#[utoipa::path(post, path = "/boot-config", tag = "boot-parameters",
199 params(SiteHeader),
200 request_body = ApplyBootConfigRequest,
201 security(("bearerAuth" = [])),
202 responses(
203 (status = 200, description = "Boot config applied or preview", body = serde_json::Value),
204 (status = 400, description = "Bad request", body = ErrorResponse),
205 (status = 401, description = "Unauthorized", body = ErrorResponse),
206 (status = 500, description = "Internal error", body = ErrorResponse),
207 )
208)]
209#[tracing::instrument(skip_all)]
210pub async fn apply_boot_config(
211 ctx: RequestCtx,
212 Json(body): Json<ApplyBootConfigRequest>,
213) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
214 tracing::info!(
215 "apply_boot_config hosts={} dry_run={}",
216 body.hosts_expression,
217 body.dry_run
218 );
219 let infra = ctx.infra();
220
221 let changeset = service::boot_parameters::prepare_boot_config(
222 &infra,
223 &ctx.token,
224 &body.hosts_expression,
225 body.boot_image_id.as_deref(),
226 body.boot_image_configuration.as_deref(),
227 body.kernel_parameters.as_deref(),
228 )
229 .await
230 .map_err(to_handler_error)?;
231
232 if body.dry_run {
233 return Ok((StatusCode::OK, Json(serialize_or_500(&changeset)?)));
234 }
235
236 service::boot_parameters::persist_boot_config(
237 &infra,
238 &ctx.token,
239 &changeset,
240 body.runtime_configuration.as_deref(),
241 )
242 .await
243 .map_err(to_handler_error)?;
244
245 Ok((
246 StatusCode::OK,
247 Json(serde_json::json!({
248 "applied": true,
249 "nodes": changeset.xname_vec,
250 "need_restart": changeset.need_restart,
251 })),
252 ))
253}