use manta_backend_dispatcher::error::Error;
use manta_backend_dispatcher::interfaces::bss::BootParametersTrait;
use manta_backend_dispatcher::interfaces::hsm::group::GroupTrait;
use manta_backend_dispatcher::interfaces::ims::ImsTrait;
use manta_backend_dispatcher::types::Group;
use manta_backend_dispatcher::types::bss::BootParameters;
use manta_backend_dispatcher::types::ims::Image;
use regex::Regex;
use crate::common::app_context::InfraContext;
use crate::common::authorization::get_groups_names_available;
use crate::common::boot_parameters::get_restricted_boot_parameters;
pub struct GetImagesParams {
pub id: Option<String>,
pub pattern: Option<String>,
pub settings_hsm_group_name: Option<String>,
pub limit: Option<u8>,
}
pub async fn get_images(
infra: &InfraContext<'_>,
token: &str,
params: &GetImagesParams,
) -> Result<Vec<Image>, Error> {
let limit_ref = params.limit.as_ref();
let mut image_vec = infra
.backend
.get_images(token, params.id.as_deref())
.await?;
if let Some(pattern) = ¶ms.pattern {
let re = Regex::new(pattern).map_err(|e| {
Error::InvalidPattern(format!("invalid regex '{pattern}': {e}"))
})?;
image_vec.retain(|image| re.is_match(&image.name));
}
image_vec.sort_by_key(|image| image.created.clone());
if let Some(limit) = limit_ref {
image_vec.truncate(*limit as usize);
}
Ok(image_vec)
}
pub async fn validate_image_deletion(
infra: &InfraContext<'_>,
token: &str,
image_id_vec: &[&str],
settings_hsm_group_name_opt: Option<&str>,
) -> Result<(), Error> {
let backend = infra.backend;
get_groups_names_available(backend, token, None, settings_hsm_group_name_opt)
.await?;
let (group_available_vec, boot_parameter_vec) = tokio::try_join!(
backend.get_group_available(token),
backend.get_all_bootparameters(token),
)?;
let image_used_to_boot_nodes: Vec<String> = boot_parameter_vec
.iter()
.map(|boot_param| boot_param.try_get_boot_image_id())
.collect::<Option<Vec<String>>>()
.ok_or_else(|| {
Error::MissingField(
"Could not get image ids used to boot nodes".to_string(),
)
})?;
let image_xnames_boot_map: Vec<&&str> = image_id_vec
.iter()
.filter(|id| image_used_to_boot_nodes.contains(&id.to_string()))
.collect();
if !image_xnames_boot_map.is_empty() {
return Err(Error::BadRequest(format!(
"The following images could not be deleted \
since they boot nodes.\n{}",
image_xnames_boot_map
.iter()
.map(|id| id.to_string())
.collect::<Vec<_>>()
.join(", ")
)));
}
let image_restricted_vec =
get_restricted_image_ids(&group_available_vec, &boot_parameter_vec)
.ok_or_else(|| {
Error::MissingField(
"Could not get restricted image ids used by boot parameters"
.to_string(),
)
})?;
if !image_restricted_vec.is_empty() {
return Err(Error::BadRequest(format!(
"The following image ids are not deletable \
because they are used by hosts that are not part \
of the groups available to the user:\n{}",
image_restricted_vec.join(", ")
)));
}
Ok(())
}
pub async fn delete_images(
infra: &InfraContext<'_>,
token: &str,
image_id_vec: &[&str],
settings_hsm_group_name_opt: Option<&str>,
) -> Result<Vec<String>, Error> {
validate_image_deletion(
infra,
token,
image_id_vec,
settings_hsm_group_name_opt,
)
.await?;
let mut deleted = Vec::new();
for image_id in image_id_vec {
let del_rslt = infra
.backend
.delete_image(
token,
infra.shasta_base_url,
infra.shasta_root_cert,
image_id,
)
.await;
match del_rslt {
Ok(_) => {
tracing::info!("Image {} deleted successfully", image_id);
deleted.push(image_id.to_string());
}
Err(e) => {
tracing::error!(
"Failed to delete image {}: {}. Continuing",
image_id,
e
);
}
}
}
Ok(deleted)
}
fn get_restricted_image_ids(
group_available_vec: &[Group],
boot_parameter_vec: &[BootParameters],
) -> Option<Vec<String>> {
get_restricted_boot_parameters(group_available_vec, boot_parameter_vec)
.iter()
.map(|boot_param| boot_param.try_get_boot_image_id())
.collect()
}