manta_server/server/handlers/
image.rs

1//! GET/DELETE /api/v1/images.
2
3use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
4use serde::{Deserialize, Serialize};
5use utoipa::{IntoParams, ToSchema};
6
7use super::{
8  ErrorResponse, RequestCtx, SiteHeader, serialize_or_500, to_handler_error,
9};
10use crate::service;
11
12// ---------------------------------------------------------------------------
13// GET /api/v1/images
14// ---------------------------------------------------------------------------
15
16/// Query parameters for `GET /images`.
17#[derive(Deserialize, IntoParams)]
18pub struct ImageQuery {
19  /// Exact IMS image ID; returns just that image when set.
20  pub id: Option<String>,
21  /// HSM group whose associated images should be returned.
22  pub hsm_group: Option<String>,
23  /// Cap on the number of images returned (most recent first).
24  pub limit: Option<u8>,
25}
26
27/// Wrapper so the image tuple serializes to named fields.
28#[derive(Serialize, ToSchema)]
29pub struct ImageEntry {
30  /// Raw IMS image object (CSM / OpenCHAMI shape, passed through).
31  pub image: serde_json::Value,
32  /// Name of the CFS configuration linked to this image at build time.
33  pub configuration_name: String,
34  /// IMS image ID (UUID). Convenience copy of `image.id` for clients
35  /// that don't want to parse the inner JSON.
36  pub image_id: String,
37  /// Whether the image is still linked to its configuration (vs.
38  /// configuration was deleted but image survives).
39  pub is_linked: bool,
40}
41
42/// GET /images — list IMS images with their associated CFS configuration names.
43#[utoipa::path(get, path = "/images", tag = "images",
44  params(ImageQuery, SiteHeader),
45  security(("bearerAuth" = [])),
46  responses(
47    (status = 200, description = "List of images", body = Vec<ImageEntry>),
48    (status = 401, description = "Unauthorized",   body = ErrorResponse),
49    (status = 500, description = "Internal error", body = ErrorResponse),
50  )
51)]
52#[tracing::instrument(skip_all)]
53pub async fn get_images(
54  ctx: RequestCtx,
55  Query(q): Query<ImageQuery>,
56) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
57  let infra = ctx.infra();
58
59  let params = service::image::GetImagesParams {
60    id: q.id,
61    hsm_group: q.hsm_group,
62    settings_hsm_group_name: None,
63    limit: q.limit,
64  };
65
66  let images = service::image::get_images(&infra, &ctx.token, &params)
67    .await
68    .map_err(to_handler_error)?;
69
70  let mut entries = Vec::with_capacity(images.len());
71  for (img, config_name, image_id, linked) in images {
72    let image = serialize_or_500(&img)?;
73    entries.push(ImageEntry {
74      image,
75      configuration_name: config_name,
76      image_id,
77      is_linked: linked,
78    });
79  }
80
81  Ok(Json(entries))
82}
83
84// ---------------------------------------------------------------------------
85// DELETE /api/v1/images — with ?ids=id1,id2&dry_run=true
86// ---------------------------------------------------------------------------
87
88/// Query parameters for `DELETE /images`.
89#[derive(Deserialize, IntoParams)]
90pub struct DeleteImagesQuery {
91  /// Comma-separated list of IMS image IDs to delete.
92  pub ids: String,
93  /// When true, validates deletion eligibility without removing anything.
94  #[serde(default)]
95  pub dry_run: bool,
96}
97
98/// `DELETE /api/v1/images` — delete IMS images by ID; validates only when `dry_run=true`.
99#[utoipa::path(delete, path = "/images", tag = "images",
100  params(DeleteImagesQuery, SiteHeader),
101  security(("bearerAuth" = [])),
102  responses(
103    (status = 200, description = "Images deleted or validation result", body = serde_json::Value),
104    (status = 400, description = "Bad request",                         body = ErrorResponse),
105    (status = 401, description = "Unauthorized",                        body = ErrorResponse),
106    (status = 500, description = "Internal error",                      body = ErrorResponse),
107  )
108)]
109#[tracing::instrument(skip_all)]
110pub async fn delete_images(
111  ctx: RequestCtx,
112  Query(q): Query<DeleteImagesQuery>,
113) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
114  tracing::info!("delete_images ids={} dry_run={}", q.ids, q.dry_run);
115  let infra = ctx.infra();
116
117  let id_strings: Vec<String> =
118    q.ids.split(',').map(|s| s.trim().to_string()).collect();
119  let id_refs: Vec<&str> =
120    id_strings.iter().map(std::string::String::as_str).collect();
121
122  if q.dry_run {
123    service::image::validate_image_deletion(&infra, &ctx.token, &id_refs, None)
124      .await
125      .map_err(to_handler_error)?;
126    return Ok((
127      StatusCode::OK,
128      Json(serde_json::json!({ "validated_ids": id_strings })),
129    ));
130  }
131
132  let deleted =
133    service::image::delete_images(&infra, &ctx.token, &id_refs, None)
134      .await
135      .map_err(to_handler_error)?;
136
137  Ok((
138    StatusCode::OK,
139    Json(serde_json::json!({ "deleted": deleted })),
140  ))
141}