manta_server/service/
image.rs

1//! IMS image queries and safety-checked deletion (rejects images that boot live nodes).
2
3use manta_backend_dispatcher::error::Error;
4use manta_backend_dispatcher::interfaces::bss::BootParametersTrait;
5use manta_backend_dispatcher::interfaces::hsm::group::GroupTrait;
6use manta_backend_dispatcher::interfaces::ims::GetImagesAndDetailsTrait;
7use manta_backend_dispatcher::interfaces::ims::ImsTrait;
8use manta_backend_dispatcher::types::Group;
9use manta_backend_dispatcher::types::bss::BootParameters;
10use manta_backend_dispatcher::types::ims::Image;
11
12use crate::server::common::app_context::InfraContext;
13use crate::server::common::authorization::get_groups_names_available;
14use crate::server::common::boot_parameters::get_restricted_boot_parameters;
15pub use manta_shared::shared::params::image::GetImagesParams;
16
17/// Fetch images and their associated details from the backend.
18///
19/// Returns tuples of (Image, CFS config name, HSM groups string, bool).
20pub async fn get_images(
21  infra: &InfraContext<'_>,
22  token: &str,
23  params: &GetImagesParams,
24) -> Result<Vec<(Image, String, String, bool)>, Error> {
25  let target_hsm_group_vec = get_groups_names_available(
26    infra.backend,
27    token,
28    params.hsm_group.as_deref(),
29    params.settings_hsm_group_name.as_deref(),
30  )
31  .await?;
32
33  let limit_ref = params.limit.as_ref();
34
35  let image_detail_vec = infra
36    .backend
37    .get_images_and_details(
38      token,
39      infra.shasta_base_url,
40      infra.shasta_root_cert,
41      &target_hsm_group_vec,
42      params.id.as_deref(),
43      limit_ref,
44    )
45    .await?;
46
47  Ok(image_detail_vec)
48}
49
50/// Validate that images can be deleted (not used by boot nodes,
51/// not restricted). Does NOT perform deletion.
52pub async fn validate_image_deletion(
53  infra: &InfraContext<'_>,
54  token: &str,
55  image_id_vec: &[&str],
56  settings_hsm_group_name_opt: Option<&str>,
57) -> Result<(), Error> {
58  let backend = infra.backend;
59
60  get_groups_names_available(backend, token, None, settings_hsm_group_name_opt)
61    .await?;
62
63  let (group_available_vec, boot_parameter_vec) = tokio::try_join!(
64    backend.get_group_available(token),
65    backend.get_all_bootparameters(token),
66  )?;
67
68  // Check if any requested image is used to boot nodes
69  let image_used_to_boot_nodes: Vec<String> = boot_parameter_vec
70    .iter()
71    .map(manta_backend_dispatcher::types::bss::BootParameters::try_get_boot_image_id)
72    .collect::<Option<Vec<String>>>()
73    .ok_or_else(|| {
74      Error::MissingField(
75        "Could not get image ids used to boot nodes".to_string(),
76      )
77    })?;
78
79  let image_xnames_boot_map: Vec<&&str> = image_id_vec
80    .iter()
81    .filter(|id| image_used_to_boot_nodes.contains(&id.to_string()))
82    .collect();
83
84  if !image_xnames_boot_map.is_empty() {
85    return Err(Error::BadRequest(format!(
86      "The following images could not be deleted \
87       since they boot nodes.\n{}",
88      image_xnames_boot_map
89        .iter()
90        .map(std::string::ToString::to_string)
91        .collect::<Vec<_>>()
92        .join(", ")
93    )));
94  }
95
96  // Check restricted images
97  let image_restricted_vec =
98    get_restricted_image_ids(&group_available_vec, &boot_parameter_vec)
99      .ok_or_else(|| {
100        Error::MissingField(
101          "Could not get restricted image ids used by boot parameters"
102            .to_string(),
103        )
104      })?;
105
106  if !image_restricted_vec.is_empty() {
107    return Err(Error::BadRequest(format!(
108      "The following image ids are not deletable \
109       because they are used by hosts that are not part \
110       of the groups available to the user:\n{}",
111      image_restricted_vec.join(", ")
112    )));
113  }
114
115  Ok(())
116}
117
118/// Validate and delete IMS images.
119/// Returns list of image IDs that were successfully deleted.
120pub async fn delete_images(
121  infra: &InfraContext<'_>,
122  token: &str,
123  image_id_vec: &[&str],
124  settings_hsm_group_name_opt: Option<&str>,
125) -> Result<Vec<String>, Error> {
126  validate_image_deletion(
127    infra,
128    token,
129    image_id_vec,
130    settings_hsm_group_name_opt,
131  )
132  .await?;
133
134  let mut deleted = Vec::new();
135  for image_id in image_id_vec {
136    let del_rslt = infra
137      .backend
138      .delete_image(
139        token,
140        infra.shasta_base_url,
141        infra.shasta_root_cert,
142        image_id,
143      )
144      .await;
145
146    match del_rslt {
147      Ok(_) => {
148        tracing::info!("Image {} deleted successfully", image_id);
149        deleted.push(image_id.to_string());
150      }
151      Err(e) => {
152        tracing::error!(
153          "Failed to delete image {}: {}. Continuing",
154          image_id,
155          e
156        );
157      }
158    }
159  }
160
161  Ok(deleted)
162}
163
164fn get_restricted_image_ids(
165  group_available_vec: &[Group],
166  boot_parameter_vec: &[BootParameters],
167) -> Option<Vec<String>> {
168  get_restricted_boot_parameters(group_available_vec, boot_parameter_vec)
169    .iter()
170    .map(manta_backend_dispatcher::types::bss::BootParameters::try_get_boot_image_id)
171    .collect()
172}