use core::time;
use std::collections::HashMap;
use std::time::Instant;
use chrono::NaiveDateTime;
use comfy_table::Table;
use dialoguer::{theme::ColorfulTheme, Confirm};
use crate::{
bos::{self},
bss::{self, types::BootParameters},
cfs, common,
error::Error,
ims,
};
pub async fn exec(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_name_available_vec: &[String],
configuration_name_pattern_opt: Option<&String>,
since_opt: Option<NaiveDateTime>,
until_opt: Option<NaiveDateTime>,
assume_yes: bool,
) -> Result<(), Error> {
let xname_from_groups_vec =
crate::hsm::group::utils::get_member_vec_from_hsm_name_vec(
shasta_token,
shasta_base_url,
shasta_root_cert,
hsm_name_available_vec,
)
.await?;
let start = Instant::now();
log::info!("Fetching data from the backend...");
let (
cfs_component_vec,
mut cfs_configuration_vec,
cfs_session_vec,
bos_sessiontemplate_vec,
bss_bootparameters_vec,
) = tokio::try_join!(
cfs::component::http_client::v2::get_all(
shasta_token,
shasta_base_url,
shasta_root_cert
),
cfs::configuration::http_client::v2::get_all(
shasta_token,
shasta_base_url,
shasta_root_cert
),
cfs::session::http_client::v2::get_all(
shasta_token,
shasta_base_url,
shasta_root_cert
),
bos::template::http_client::v2::get_all(
shasta_token,
shasta_base_url,
shasta_root_cert
),
bss::http_client::get_all(shasta_token, shasta_base_url, shasta_root_cert)
)?;
let duration = start.elapsed();
log::info!(
"Time elapsed to fetch information from backend: {:?}",
duration
);
let mut cfs_session_to_delete_vec = cfs_session_vec.clone();
let mut bos_sessiontemplate_to_delete_vec = bos_sessiontemplate_vec.clone();
let keep_generic_sessions = common::jwt_ops::is_user_admin(shasta_token);
cfs::configuration::utils::filter(
&mut cfs_configuration_vec,
&xname_from_groups_vec,
&mut Vec::new(),
&mut Vec::new(),
&Vec::new(),
configuration_name_pattern_opt.map(|elem| elem.as_str()),
hsm_name_available_vec,
since_opt,
until_opt,
None,
keep_generic_sessions,
)?;
cfs::configuration::utils::filter(
&mut cfs_configuration_vec,
&xname_from_groups_vec,
&mut cfs_session_to_delete_vec,
&mut bos_sessiontemplate_to_delete_vec,
&cfs_component_vec,
configuration_name_pattern_opt.map(|elem| elem.as_str()),
hsm_name_available_vec,
since_opt,
until_opt,
None,
keep_generic_sessions,
)?;
let cfs_configuration_name_from_bos_sessiontemplate_value_iter =
bos_sessiontemplate_to_delete_vec
.iter()
.map(|bos_sessiontemplate| {
bos_sessiontemplate.get_configuration().unwrap_or_default()
});
let cfs_configuration_name_from_cfs_sessions = cfs_session_to_delete_vec
.iter()
.map(|cfs_session| cfs_session.configuration_name().unwrap_or_default());
let mut cfs_configuration_name_vec =
cfs_configuration_name_from_bos_sessiontemplate_value_iter
.chain(cfs_configuration_name_from_cfs_sessions)
.collect::<Vec<&str>>();
cfs_configuration_name_vec.sort();
cfs_configuration_name_vec.dedup();
cfs_configuration_vec.retain(|cfs_configuration| {
cfs_configuration_name_vec.contains(&cfs_configuration.name.as_ref())
});
let mut image_id_vec =
cfs::session::utils::images_id_from_cfs_session(&cfs_session_to_delete_vec)
.collect::<Vec<&str>>();
log::info!("Image ids to delete: {:?}", image_id_vec);
let cfs_session_cfs_configuration_image_id_tuple_vec: Vec<(
&str,
&str,
&str,
)> = cfs_session_to_delete_vec
.iter()
.filter(|cfs_session| cfs_session.first_result_id().is_some())
.map(|cfs_session| {
(
cfs_session.name().unwrap_or_default(),
cfs_session.configuration_name().unwrap_or_default(),
cfs_session.first_result_id().unwrap_or_default(),
)
})
.collect();
let mut bos_sessiontemplate_cfs_configuration_image_id_tuple_vec: Vec<(
&str,
&str,
&str,
)> = Vec::new();
for bos_sessiontemplate in &bos_sessiontemplate_to_delete_vec {
let bos_sessiontemplate_name: &str =
bos_sessiontemplate.name.as_deref().unwrap_or_default();
let cfs_configuration_name: &str =
bos_sessiontemplate.get_configuration().unwrap_or_default();
for image_id in bos_sessiontemplate.images_id() {
bos_sessiontemplate_cfs_configuration_image_id_tuple_vec.push((
bos_sessiontemplate_name,
cfs_configuration_name,
image_id,
));
}
}
let mut cfs_configuration_image_id: HashMap<&str, Vec<&str>> = HashMap::new();
for (_, cfs_configuration, image_id) in
&bos_sessiontemplate_cfs_configuration_image_id_tuple_vec
{
cfs_configuration_image_id
.entry(cfs_configuration)
.and_modify(|image_vec| image_vec.push(image_id))
.or_insert(vec![image_id]);
}
for (_, cfs_configuration, image_id) in
&cfs_session_cfs_configuration_image_id_tuple_vec
{
cfs_configuration_image_id
.entry(cfs_configuration)
.and_modify(|image_vec| image_vec.push(image_id))
.or_insert(vec![image_id]);
}
let mut cfs_configuration_name_used_to_configure_nodes_vec: Vec<&str> =
Vec::new();
let mut image_id_used_to_boot_nodes_vec: Vec<&str> = Vec::new();
for (cfs_configuration_name, mut image_id_vec) in cfs_configuration_image_id {
let mut nodes_using_cfs_configuration_as_dessired_configuration_vec =
cfs_component_vec
.iter()
.filter(|cfs_component| {
cfs_component
.desired_config
.as_ref()
.unwrap()
.eq(cfs_configuration_name)
})
.map(|cfs_component| cfs_component.id.as_ref().unwrap().as_str())
.collect::<Vec<&str>>();
if !nodes_using_cfs_configuration_as_dessired_configuration_vec.is_empty() {
cfs_configuration_name_used_to_configure_nodes_vec
.push(cfs_configuration_name);
nodes_using_cfs_configuration_as_dessired_configuration_vec.sort();
eprintln!(
"CFS configuration '{}' can't be deleted. Reason:\nCFS configuration '{}' used as desired configuration for nodes: {}",
cfs_configuration_name, cfs_configuration_name, nodes_using_cfs_configuration_as_dessired_configuration_vec.join(", "));
}
image_id_vec.dedup();
for image_id in &image_id_vec {
let node_vec =
get_node_vec_booting_image(image_id, &bss_bootparameters_vec);
if !node_vec.is_empty() {
image_id_used_to_boot_nodes_vec.push(image_id);
eprintln!(
"Image '{}' used to boot nodes: {}",
image_id,
node_vec.join(", ")
);
}
}
}
cfs_configuration_vec.retain(|cfs_configuration_value| {
!cfs_configuration_name_used_to_configure_nodes_vec
.contains(&cfs_configuration_value.name.as_str())
});
let cfs_session_cfs_configuration_image_id_tuple_filtered_vec: Vec<(
&str,
&str,
&str,
)>;
let bos_sessiontemplate_cfs_configuration_image_id_tuple_filtered_vec: Vec<
(&str, &str, &str),
>;
if !cfs_configuration_name_used_to_configure_nodes_vec.is_empty()
|| !image_id_used_to_boot_nodes_vec.is_empty()
{
log::error!(
"User trying to delete configurations or images used by other clusters/nodes"
);
return Err(
Error::ConfigurationUsedAsRuntimeConfigurationOrUsedToBuildBootImageUsed,
);
} else {
cfs_configuration_name_vec.retain(|cfs_configuration_name| {
!cfs_configuration_name_used_to_configure_nodes_vec
.contains(&cfs_configuration_name)
});
image_id_vec
.retain(|image_id| !image_id_used_to_boot_nodes_vec.contains(&image_id));
cfs_session_cfs_configuration_image_id_tuple_filtered_vec =
cfs_session_cfs_configuration_image_id_tuple_vec
.into_iter()
.filter(|(_, cfs_configuration_name, image_id)| {
!cfs_configuration_name_used_to_configure_nodes_vec
.contains(&cfs_configuration_name)
&& !image_id_used_to_boot_nodes_vec.contains(&image_id)
})
.collect();
bos_sessiontemplate_cfs_configuration_image_id_tuple_filtered_vec =
bos_sessiontemplate_cfs_configuration_image_id_tuple_vec
.into_iter()
.filter(|(_, cfs_configuration_name, image_id)| {
!cfs_configuration_name_used_to_configure_nodes_vec
.contains(cfs_configuration_name)
&& !image_id_used_to_boot_nodes_vec.contains(image_id)
})
.collect();
}
if image_id_vec.is_empty()
&& cfs_session_cfs_configuration_image_id_tuple_filtered_vec.is_empty()
&& bos_sessiontemplate_cfs_configuration_image_id_tuple_filtered_vec
.is_empty()
{
log::error!(
"Delete configuration - Not enough information to proceed. Could not find information related to CFS configurations '{}'",
cfs_configuration_name_vec.join(", ")
);
return Err(Error::ConfigurationDerivativesNotFound(
cfs_configuration_name_vec.join(", "),
));
}
println!("CFS sessions to delete:");
let mut cfs_session_table = Table::new();
cfs_session_table.set_header(vec!["Name", "Configuration", "Image ID"]);
for cfs_session in &cfs_session_vec {
cfs_session_table.add_row(vec![
cfs_session.name.as_ref().unwrap_or(&"".to_string()),
&cfs_session
.configuration_name()
.unwrap_or_default()
.to_string(),
&cfs_session
.first_result_id()
.unwrap_or_default()
.to_string(),
]);
}
println!("{cfs_session_table}");
println!("BOS sessiontemplates to delete:");
let mut bos_sessiontemplate_table = Table::new();
bos_sessiontemplate_table.set_header(vec![
"Name",
"Configuration",
"Image ID",
]);
for bos_sessiontemplate_tuple in
&bos_sessiontemplate_cfs_configuration_image_id_tuple_filtered_vec
{
bos_sessiontemplate_table.add_row(vec![
bos_sessiontemplate_tuple.0,
bos_sessiontemplate_tuple.1,
bos_sessiontemplate_tuple.2,
]);
}
println!("{bos_sessiontemplate_table}");
println!("CFS configurations to delete:");
let mut cfs_configuration_table = Table::new();
cfs_configuration_table.set_header(vec!["Name", "Last Update"]);
for cfs_configuration_value in cfs_configuration_vec {
cfs_configuration_table.add_row(vec![
cfs_configuration_value.name,
cfs_configuration_value.last_updated,
]);
}
println!("{cfs_configuration_table}");
println!("Images to delete:");
let mut image_id_table = Table::new();
image_id_table.set_header(vec!["Image ID"]);
for image_id in &image_id_vec {
image_id_table.add_row(vec![image_id]);
}
println!("{image_id_table}");
if !assume_yes {
if Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Please revew the data above and confirm to delete:")
.interact()
.unwrap()
{
println!("Continue");
} else {
println!("Cancelled by user. Aborting.");
std::process::exit(0);
}
}
delete(
shasta_token,
shasta_base_url,
shasta_root_cert,
cfs_configuration_name_vec,
image_id_vec,
cfs_session_cfs_configuration_image_id_tuple_filtered_vec
.into_iter()
.map(|(session, _, _)| session)
.collect(),
bos_sessiontemplate_cfs_configuration_image_id_tuple_filtered_vec
.into_iter()
.map(|(sessiontemplate, _, _)| sessiontemplate)
.collect(),
)
.await;
Ok(())
}
pub async fn delete(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
cfs_configuration_name_vec: Vec<&str>,
image_id_vec: Vec<&str>,
cfs_session_name_vec: Vec<&str>,
bos_sessiontemplate_name_vec: Vec<&str>,
) {
for image_id in image_id_vec {
log::info!("Deleting IMS image '{}'", image_id);
let image_deleted_value_rslt = ims::image::http_client::delete(
shasta_token,
shasta_base_url,
shasta_root_cert,
image_id,
)
.await;
match image_deleted_value_rslt {
Ok(_) => println!("IMS image deleted: {}", image_id),
Err(e) => {
eprintln!("{e}. Continue");
}
}
}
let bos_session_vec = bos::session::http_client::v2::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
None,
)
.await
.unwrap();
for bos_session in bos_session_vec {
let bos_session_id = &bos_session.name.unwrap();
log::info!("Deleting BOS sesion '{}'", bos_session_id);
if bos_sessiontemplate_name_vec
.contains(&bos_session.template_name.as_str())
{
bos::session::http_client::v2::delete(
shasta_token,
shasta_base_url,
shasta_root_cert,
&bos_session_id,
)
.await
.unwrap();
println!(
"BOS session deleted: {}",
bos_session_id );
} else {
log::debug!("Ignoring BOS session template {}", bos_session_id);
}
}
let max_attempts = 5;
for cfs_session_name in cfs_session_name_vec {
log::info!("Deleting IMS image '{}'", cfs_session_name);
let mut counter = 0;
loop {
let deletion_rslt = cfs::session::http_client::v3::delete(
shasta_token,
shasta_base_url,
shasta_root_cert,
cfs_session_name,
)
.await;
if deletion_rslt.is_err() && counter <= max_attempts {
log::warn!("Could not delete CFS session {} attempt {} of {}, trying again in 2 seconds...", cfs_session_name, counter, max_attempts);
tokio::time::sleep(time::Duration::from_secs(2)).await;
counter += 1;
} else if deletion_rslt.is_err() && counter > max_attempts {
eprintln!(
"ERROR deleting CFS session {}, please delete it manually.",
cfs_session_name,
);
log::debug!("ERROR:\n{:#?}", deletion_rslt.unwrap_err());
break;
} else {
println!("CfS session deleted: {}", cfs_session_name);
break;
}
}
}
let max_attempts = 5;
for bos_sessiontemplate_name in bos_sessiontemplate_name_vec {
log::info!(
"Deleting BOS sessiontemplate '{}'",
bos_sessiontemplate_name
);
let mut counter = 0;
loop {
let deletion_rslt = bos::template::http_client::v2::delete(
shasta_token,
shasta_base_url,
shasta_root_cert,
bos_sessiontemplate_name,
)
.await;
if deletion_rslt.is_err() && counter <= max_attempts {
log::warn!("Could not delete BOS sessiontemplate {} attempt {} of {}, trying again in 2 seconds...", bos_sessiontemplate_name, counter, max_attempts);
tokio::time::sleep(time::Duration::from_secs(2)).await;
counter += 1;
} else if deletion_rslt.is_err() && counter > max_attempts {
eprintln!(
"ERROR deleting BOS sessiontemplate {}, please delete it manually.",
bos_sessiontemplate_name,
);
log::debug!("ERROR:\n{:#?}", deletion_rslt.unwrap_err());
break;
} else {
println!("BOS sessiontemplate deleted: {}", bos_sessiontemplate_name);
break;
}
}
}
let max_attempts = 5;
for cfs_configuration in cfs_configuration_name_vec {
log::info!("Deleting CFS configuration '{}'", cfs_configuration);
let mut counter = 0;
loop {
let deletion_rslt = cfs::configuration::http_client::v3::delete(
shasta_token,
shasta_base_url,
shasta_root_cert,
cfs_configuration,
)
.await;
if deletion_rslt.is_err() && counter <= max_attempts {
log::warn!("Could not delete CFS configuration {} attempt {} of {}, trying again in 2 seconds...", cfs_configuration, counter, max_attempts);
tokio::time::sleep(time::Duration::from_secs(2)).await;
counter += 1;
} else if deletion_rslt.is_err() && counter > max_attempts {
eprintln!(
"ERROR deleting CFS configuration {}, please delete it manually.",
cfs_configuration,
);
log::debug!("ERROR:\n{:#?}", deletion_rslt.unwrap_err());
break;
} else {
println!("CFS configuration deleted: {}", cfs_configuration);
break;
}
}
}
}
pub fn get_node_vec_booting_image(
image_id: &str,
boot_param_vec: &[BootParameters],
) -> Vec<String> {
let mut node_booting_image_vec = boot_param_vec
.iter()
.cloned()
.filter(|boot_param| boot_param.get_boot_image().eq(image_id))
.flat_map(|boot_param| boot_param.hosts)
.collect::<Vec<_>>();
node_booting_image_vec.sort();
node_booting_image_vec
}