1use axum::{Json, extract::Path, http::StatusCode, response::IntoResponse};
4use serde::Deserialize;
5use utoipa::ToSchema;
6
7use super::{
8 ErrorResponse, RequestCtx, SiteHeader, default_true, display_error,
9 to_handler_error,
10};
11use crate::service;
12
13#[derive(Deserialize, ToSchema)]
19pub struct AddHwComponentRequest {
20 pub parent_cluster: String,
22 pub pattern: String,
24 #[serde(default)]
26 pub create_hsm_group: bool,
27 #[serde(default)]
29 pub dry_run: bool,
30}
31
32#[utoipa::path(post, path = "/hardware-clusters/{target}/members", tag = "hardware",
34 params(("target" = String, Path, description = "Target cluster name"), SiteHeader),
35 request_body = AddHwComponentRequest,
36 security(("bearerAuth" = [])),
37 responses(
38 (status = 200, description = "Members added or preview", body = serde_json::Value),
39 (status = 401, description = "Unauthorized", body = ErrorResponse),
40 (status = 500, description = "Internal error", body = ErrorResponse),
41 )
42)]
43#[tracing::instrument(skip_all)]
44pub async fn add_hw_component(
45 ctx: RequestCtx,
46 Path(target): Path<String>,
47 Json(body): Json<AddHwComponentRequest>,
48) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
49 tracing::info!(
50 "add_hw_component target={} parent={} dry_run={}",
51 target,
52 body.parent_cluster,
53 body.dry_run
54 );
55 let infra = ctx.infra();
56
57 service::group::validate_hsm_group_access(&infra, &ctx.token, &target)
58 .await
59 .map_err(to_handler_error)?;
60 service::group::validate_hsm_group_access(
61 &infra,
62 &ctx.token,
63 &body.parent_cluster,
64 )
65 .await
66 .map_err(to_handler_error)?;
67
68 let result = crate::service::hw_cluster::add_hw_component(
69 infra.backend,
70 &ctx.token,
71 &target,
72 &body.parent_cluster,
73 &body.pattern,
74 body.dry_run,
75 body.create_hsm_group,
76 )
77 .await
78 .map_err(display_error)?;
79
80 Ok(Json(serde_json::json!({
81 "dry_run": body.dry_run,
82 "nodes_moved": result.nodes_moved,
83 "target_cluster": target,
84 "target_nodes": result.target_nodes,
85 "parent_cluster": body.parent_cluster,
86 "parent_nodes": result.parent_nodes,
87 })))
88}
89
90#[derive(Deserialize, ToSchema)]
96pub struct DeleteHwComponentRequest {
97 pub parent_cluster: String,
99 pub pattern: String,
101 #[serde(default)]
103 pub delete_hsm_group: bool,
104 #[serde(default)]
106 pub dry_run: bool,
107}
108
109#[utoipa::path(delete, path = "/hardware-clusters/{target}/members", tag = "hardware",
111 params(("target" = String, Path, description = "Target cluster name"), SiteHeader),
112 request_body = DeleteHwComponentRequest,
113 security(("bearerAuth" = [])),
114 responses(
115 (status = 200, description = "Members removed or preview", 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 delete_hw_component(
122 ctx: RequestCtx,
123 Path(target): Path<String>,
124 Json(body): Json<DeleteHwComponentRequest>,
125) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
126 tracing::info!(
127 "delete_hw_component target={} parent={} dry_run={}",
128 target,
129 body.parent_cluster,
130 body.dry_run
131 );
132 let infra = ctx.infra();
133
134 service::group::validate_hsm_group_access(&infra, &ctx.token, &target)
135 .await
136 .map_err(to_handler_error)?;
137 service::group::validate_hsm_group_access(
138 &infra,
139 &ctx.token,
140 &body.parent_cluster,
141 )
142 .await
143 .map_err(to_handler_error)?;
144
145 let result = crate::service::hw_cluster::delete_hw_component(
146 infra.backend,
147 &ctx.token,
148 &target,
149 &body.parent_cluster,
150 &body.pattern,
151 body.dry_run,
152 body.delete_hsm_group,
153 )
154 .await
155 .map_err(display_error)?;
156
157 Ok(Json(serde_json::json!({
158 "dry_run": body.dry_run,
159 "nodes_moved": result.nodes_moved,
160 "target_cluster": target,
161 "target_nodes": result.target_nodes,
162 "parent_cluster": body.parent_cluster,
163 "parent_nodes": result.parent_nodes,
164 })))
165}
166
167#[derive(Debug, Deserialize, Default, ToSchema)]
173#[serde(rename_all = "lowercase")]
174pub enum HwClusterMode {
175 #[default]
177 Pin,
178 Unpin,
180}
181
182#[derive(Deserialize, ToSchema)]
184pub struct ApplyHwConfigurationRequest {
185 pub parent_cluster: String,
187 pub pattern: String,
189 #[serde(default)]
191 pub mode: HwClusterMode,
192 #[serde(default = "default_true")]
194 pub create_target_hsm_group: bool,
195 #[serde(default = "default_true")]
197 pub delete_empty_parent_hsm_group: bool,
198 #[serde(default)]
200 pub dry_run: bool,
201}
202
203#[utoipa::path(post, path = "/hardware-clusters/{target}/configuration", tag = "hardware",
205 params(("target" = String, Path, description = "Target cluster name"), SiteHeader),
206 request_body = ApplyHwConfigurationRequest,
207 security(("bearerAuth" = [])),
208 responses(
209 (status = 200, description = "Configuration applied or preview", body = serde_json::Value),
210 (status = 401, description = "Unauthorized", body = ErrorResponse),
211 (status = 500, description = "Internal error", body = ErrorResponse),
212 )
213)]
214#[tracing::instrument(skip_all)]
215pub async fn apply_hw_configuration(
216 ctx: RequestCtx,
217 Path(target): Path<String>,
218 Json(body): Json<ApplyHwConfigurationRequest>,
219) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
220 tracing::info!(
221 "apply_hw_configuration target={} parent={} dry_run={}",
222 target,
223 body.parent_cluster,
224 body.dry_run
225 );
226 let infra = ctx.infra();
227
228 service::group::validate_hsm_group_access(&infra, &ctx.token, &target)
229 .await
230 .map_err(to_handler_error)?;
231 service::group::validate_hsm_group_access(
232 &infra,
233 &ctx.token,
234 &body.parent_cluster,
235 )
236 .await
237 .map_err(to_handler_error)?;
238
239 let mode = match body.mode {
240 HwClusterMode::Pin => crate::service::hw_cluster::HwClusterMode::Pin,
241 HwClusterMode::Unpin => crate::service::hw_cluster::HwClusterMode::Unpin,
242 };
243
244 let result = crate::service::hw_cluster::apply_hw_configuration(
245 infra.backend,
246 mode,
247 &ctx.token,
248 &target,
249 &body.parent_cluster,
250 &body.pattern,
251 body.dry_run,
252 body.create_target_hsm_group,
253 body.delete_empty_parent_hsm_group,
254 )
255 .await
256 .map_err(display_error)?;
257
258 Ok(Json(serde_json::json!({
259 "dry_run": body.dry_run,
260 "target_cluster": target,
261 "target_nodes": result.target_nodes,
262 "parent_cluster": body.parent_cluster,
263 "parent_nodes": result.parent_nodes,
264 })))
265}