manta_server/server/handlers/
configuration.rs

1//! GET/DELETE /api/v1/configurations.
2
3use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
4use serde::Deserialize;
5use utoipa::IntoParams;
6
7use super::{
8  ErrorResponse, RequestCtx, SiteHeader, parse_iso_datetime, serialize_or_500,
9  to_handler_error,
10};
11use crate::service;
12
13// ---------------------------------------------------------------------------
14// GET /api/v1/configurations
15// ---------------------------------------------------------------------------
16
17/// Query parameters for `GET /configurations`.
18#[derive(Deserialize, IntoParams)]
19pub struct ConfigurationQuery {
20  /// Exact configuration name to fetch.
21  pub name: Option<String>,
22  /// Glob pattern matched against configuration names.
23  pub pattern: Option<String>,
24  /// HSM group whose associated configurations should be returned.
25  pub hsm_group: Option<String>,
26  /// Cap on the number of configurations returned (most recent first).
27  pub limit: Option<u8>,
28}
29
30/// GET /configurations — list CFS configurations with optional name/pattern/group filters.
31#[utoipa::path(get, path = "/configurations", tag = "configurations",
32  params(ConfigurationQuery, SiteHeader),
33  security(("bearerAuth" = [])),
34  responses(
35    (status = 200, description = "List of configurations", body = serde_json::Value),
36    (status = 401, description = "Unauthorized",           body = ErrorResponse),
37    (status = 500, description = "Internal error",         body = ErrorResponse),
38  )
39)]
40#[tracing::instrument(skip_all)]
41pub async fn get_configurations(
42  ctx: RequestCtx,
43  Query(q): Query<ConfigurationQuery>,
44) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
45  let infra = ctx.infra();
46
47  let params = service::configuration::GetConfigurationParams {
48    name: q.name,
49    pattern: q.pattern,
50    hsm_group: q.hsm_group,
51    settings_hsm_group_name: None,
52    since: None,
53    until: None,
54    limit: q.limit,
55  };
56
57  let configs =
58    service::configuration::get_configurations(&infra, &ctx.token, &params)
59      .await
60      .map_err(to_handler_error)?;
61
62  Ok(Json(configs))
63}
64
65// ---------------------------------------------------------------------------
66// DELETE /api/v1/configurations — with ?pattern=...&since=...&until=...&dry_run=true
67// ---------------------------------------------------------------------------
68
69/// Query parameters for `DELETE /configurations`.
70#[derive(Deserialize, IntoParams)]
71pub struct DeleteConfigurationsQuery {
72  /// Glob pattern to match configuration names.
73  pub pattern: Option<String>,
74  /// ISO-8601 lower bound — only delete configurations created after this date.
75  pub since: Option<String>,
76  /// ISO-8601 upper bound — only delete configurations created before this date.
77  pub until: Option<String>,
78  /// When true, returns deletion candidates without removing anything.
79  #[serde(default)]
80  pub dry_run: bool,
81}
82
83/// `DELETE /api/v1/configurations` — delete CFS configurations and all derived artifacts.
84#[utoipa::path(delete, path = "/configurations", tag = "configurations",
85  params(DeleteConfigurationsQuery, SiteHeader),
86  security(("bearerAuth" = [])),
87  responses(
88    (status = 200, description = "Configurations deleted or preview", body = serde_json::Value),
89    (status = 400, description = "Bad request",                       body = ErrorResponse),
90    (status = 401, description = "Unauthorized",                      body = ErrorResponse),
91    (status = 500, description = "Internal error",                    body = ErrorResponse),
92  )
93)]
94#[tracing::instrument(skip_all)]
95pub async fn delete_configurations(
96  ctx: RequestCtx,
97  Query(q): Query<DeleteConfigurationsQuery>,
98) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
99  tracing::info!("delete_configurations dry_run={}", q.dry_run);
100  let infra = ctx.infra();
101
102  let since = q
103    .since
104    .as_deref()
105    .map(|s| parse_iso_datetime("since", s))
106    .transpose()?;
107  let until = q
108    .until
109    .as_deref()
110    .map(|s| parse_iso_datetime("until", s))
111    .transpose()?;
112
113  let candidates = service::configuration::get_deletion_candidates(
114    &infra,
115    &ctx.token,
116    None,
117    q.pattern.as_deref(),
118    since,
119    until,
120  )
121  .await
122  .map_err(to_handler_error)?;
123
124  if q.dry_run {
125    return Ok((StatusCode::OK, Json(serialize_or_500(&candidates)?)));
126  }
127
128  service::configuration::delete_configurations_and_derivatives(
129    &infra,
130    &ctx.token,
131    &candidates,
132  )
133  .await
134  .map_err(to_handler_error)?;
135
136  Ok((
137    StatusCode::OK,
138    Json(serde_json::json!({
139      "deleted_configurations": candidates.configuration_names,
140      "deleted_images": candidates.image_ids,
141    })),
142  ))
143}
144
145// ===========================================================================
146// BATCH A — MEDIUM-COMPLEXITY WRITE ENDPOINTS
147// ===========================================================================