use std::collections::{HashMap, HashSet};
use serde_json::Value;
use crate::{
cfs::session::http_client::v3::types::CfsSessionGetResponse,
error::Error,
hsm::{self, group::types::Group},
node::utils::validate_xnames_format_and_membership_agaisnt_single_hsm,
};
use super::{
http_client::{self, delete_member, post_member},
types::Member,
};
pub async fn get_group_name_available(
shasta_auth_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
) -> Result<Vec<String>, Error> {
log::debug!("Get HSM names available from JWT or all");
const ADMIN_ROLE_NAME: &str = "pa_admin";
let realm_access_role_vec =
crate::common::jwt_ops::get_roles(shasta_auth_token)?;
if !realm_access_role_vec.contains(&ADMIN_ROLE_NAME.to_string()) {
log::debug!("User is not admin, getting HSM groups available from JWT");
let realm_access_role_vec = hsm::group::hacks::filter_keycloak_roles(
realm_access_role_vec
.iter()
.map(String::as_str)
.collect::<Vec<&str>>()
.as_slice(),
);
let mut realm_access_role_filtered_vec =
hsm::group::hacks::filter_system_hsm_group_names(
realm_access_role_vec.clone(),
);
realm_access_role_filtered_vec.sort();
Ok(realm_access_role_vec)
} else {
log::debug!("User is admin, getting all HSM groups in the system");
let all_hsm_groups_rslt = hsm::group::http_client::get_all(
shasta_auth_token,
shasta_base_url,
shasta_root_cert,
)
.await;
let mut all_hsm_groups = all_hsm_groups_rslt?
.iter()
.map(|hsm_value| hsm_value.label.clone())
.collect::<Vec<String>>();
all_hsm_groups.sort();
Ok(all_hsm_groups)
}
}
pub async fn add_member(
auth_token: &str,
base_url: &str,
root_cert: &[u8],
group_label: &str,
new_member: &str,
) -> Result<Vec<String>, Error> {
let group_vec = hsm::group::http_client::get(
auth_token,
base_url,
root_cert,
Some(&[&group_label]),
None,
)
.await?;
if let Some(group) = group_vec.first().cloned().as_mut() {
let new_member = new_member.to_string();
let member = crate::hsm::group::types::Member {
id: Some(new_member.clone()),
};
let _ = crate::hsm::group::http_client::post_member(
auth_token,
base_url,
root_cert,
group_label,
member,
)
.await?;
group.get_members().push(new_member);
Ok(group.get_members())
} else {
Err(Error::Message(format!(
"No HSM group '{}' found",
group_label
)))
}
}
pub async fn remove_hsm_members(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
target_hsm_group_name: &str,
new_target_hsm_members: Vec<&str>,
dryrun: bool,
) -> Result<Vec<String>, Error> {
if !validate_xnames_format_and_membership_agaisnt_single_hsm(
shasta_token,
shasta_base_url,
shasta_root_cert,
new_target_hsm_members.as_slice(),
Some(&target_hsm_group_name.to_string()),
)
.await
{
let error_msg =
format!("Nodes '{}' not valid", new_target_hsm_members.join(", "));
return Err(Error::Message(error_msg));
}
let mut target_hsm_group_member_vec: Vec<String> =
get_member_vec_from_hsm_group_name(
shasta_token,
shasta_base_url,
shasta_root_cert,
target_hsm_group_name,
)
.await;
target_hsm_group_member_vec.retain(|parent_member| {
!new_target_hsm_members.contains(&parent_member.as_str())
});
target_hsm_group_member_vec.sort();
target_hsm_group_member_vec.dedup();
if dryrun {
println!(
"Remove following nodes from HSM group {}:\n{:?}",
target_hsm_group_name, new_target_hsm_members
);
println!("dry-run enabled, changes not persisted.");
} else {
for xname in new_target_hsm_members {
let _ = delete_member(
shasta_token,
shasta_base_url,
shasta_root_cert,
target_hsm_group_name,
xname,
)
.await;
}
}
Ok(target_hsm_group_member_vec)
}
pub async fn migrate_hsm_members(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
target_hsm_group_name: &str,
parent_hsm_group_name: &str,
new_target_hsm_members: &[&str],
nodryrun: bool,
) -> Result<(Vec<String>, Vec<String>), Error> {
if !validate_xnames_format_and_membership_agaisnt_single_hsm(
shasta_token,
shasta_base_url,
shasta_root_cert,
new_target_hsm_members,
Some(&parent_hsm_group_name.to_string()),
)
.await
{
let error_msg =
format!("Nodes '{}' not valid", new_target_hsm_members.join(", "));
return Err(Error::Message(error_msg));
}
let mut target_hsm_group_member_vec: Vec<String> =
get_member_vec_from_hsm_group_name(
shasta_token,
shasta_base_url,
shasta_root_cert,
target_hsm_group_name,
)
.await;
target_hsm_group_member_vec
.extend(new_target_hsm_members.iter().map(|xname| xname.to_string()));
target_hsm_group_member_vec.sort();
target_hsm_group_member_vec.dedup();
let mut parent_hsm_group_member_vec: Vec<String> =
get_member_vec_from_hsm_group_name(
shasta_token,
shasta_base_url,
shasta_root_cert,
parent_hsm_group_name,
)
.await;
parent_hsm_group_member_vec.retain(|parent_member| {
!target_hsm_group_member_vec.contains(parent_member)
});
parent_hsm_group_member_vec.sort();
parent_hsm_group_member_vec.dedup();
if !nodryrun {
let target_hsm_group = serde_json::json!({
"label": target_hsm_group_name,
"decription": "",
"members": target_hsm_group_member_vec,
"tags": []
});
println!(
"Target HSM group:\n{}",
serde_json::to_string_pretty(&target_hsm_group).unwrap()
);
let parent_hsm_group = serde_json::json!({
"label": parent_hsm_group_name,
"decription": "",
"members": parent_hsm_group_member_vec,
"tags": []
});
println!(
"Parent HSM group:\n{}",
serde_json::to_string_pretty(&parent_hsm_group).unwrap()
);
println!("dry-run enabled, changes not persisted.");
} else {
for xname in new_target_hsm_members {
let member = Member {
id: Some(xname.to_string()),
};
let _ = post_member(
shasta_token,
shasta_base_url,
shasta_root_cert,
target_hsm_group_name,
member,
)
.await;
let _ = delete_member(
shasta_token,
shasta_base_url,
shasta_root_cert,
parent_hsm_group_name,
xname,
)
.await;
}
}
Ok((target_hsm_group_member_vec, parent_hsm_group_member_vec))
}
pub async fn update_hsm_group_members(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_group_name: &str,
old_target_hsm_group_members: &[&str],
new_target_hsm_group_members: &[&str],
) -> Result<(), Error> {
for old_member in old_target_hsm_group_members {
if !new_target_hsm_group_members.contains(old_member) {
let _ = delete_member(
shasta_token,
shasta_base_url,
shasta_root_cert,
hsm_group_name,
old_member,
)
.await;
}
}
for new_member in new_target_hsm_group_members {
if !old_target_hsm_group_members.contains(new_member) {
let member = Member {
id: Some(new_member.to_string()),
};
let _ = post_member(
shasta_token,
shasta_base_url,
shasta_root_cert,
hsm_group_name,
member,
)
.await;
}
}
Ok(())
}
pub async fn get_xname_map_and_filter_by_xname_vec(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
xname_vec: Vec<&str>,
) -> Result<HashMap<String, Vec<String>>, Error> {
let hsm_group_vec =
http_client::get_all(shasta_token, shasta_base_url, shasta_root_cert)
.await?;
let mut xname_map: HashMap<String, Vec<String>> = HashMap::new();
for hsm_group in hsm_group_vec {
for xname in hsm_group.get_members() {
if xname_vec.contains(&xname.as_str()) {
xname_map
.entry(xname)
.and_modify(|group_vec| group_vec.push(hsm_group.label.clone()))
.or_insert(vec![hsm_group.label.clone()]);
}
}
}
Ok(xname_map)
}
pub async fn get_hsm_map_and_filter_by_hsm_name_vec(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_name_vec: &[&str],
) -> Result<HashMap<String, Vec<String>>, Error> {
let hsm_group_vec =
http_client::get_all(shasta_token, shasta_base_url, shasta_root_cert)
.await?;
Ok(filter_by_hsm_group_name_and_convert_to_map(
hsm_name_vec,
&hsm_group_vec.iter().collect::<Vec<&Group>>(),
))
}
pub async fn get_hsm_group_map_and_filter_by_hsm_group_member_vec(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
member_vec: &[&str],
) -> Result<HashMap<String, Vec<String>>, Error> {
let hsm_group_vec =
http_client::get_all(shasta_token, shasta_base_url, shasta_root_cert)
.await?;
Ok(filter_by_hsm_group_members_and_convert_to_map(
member_vec,
hsm_group_vec,
))
}
pub fn filter_by_hsm_group_name_and_convert_to_map(
hsm_name_vec: &[&str],
hsm_group_vec: &[&Group],
) -> HashMap<String, Vec<String>> {
let mut hsm_group_map: HashMap<String, Vec<String>> = HashMap::new();
for hsm_group in hsm_group_vec {
if hsm_name_vec.contains(&hsm_group.label.as_str()) {
hsm_group_map.entry(hsm_group.label.clone()).or_insert(
hsm_group
.members
.clone()
.and_then(|members| members.ids)
.unwrap(),
);
}
}
hsm_group_map
}
pub fn filter_by_hsm_group_members_and_convert_to_map(
member_vec: &[&str],
hsm_group_vec: Vec<Group>,
) -> HashMap<String, Vec<String>> {
let mut hsm_group_map: HashMap<String, Vec<String>> = HashMap::new();
for hsm_group in hsm_group_vec {
if hsm_group
.get_members()
.iter()
.any(|member| member_vec.contains(&member.as_str()))
{
hsm_group_map
.entry(hsm_group.label)
.or_insert(hsm_group.members.and_then(|members| members.ids).unwrap());
}
}
hsm_group_map
}
pub fn get_member_vec_from_hsm_group_value(hsm_group: &Value) -> Vec<String> {
hsm_group["members"]["ids"]
.as_array()
.unwrap_or(&Vec::new())
.iter()
.map(|xname| xname.as_str().unwrap().to_string())
.collect()
}
pub fn get_member_vec_from_hsm_group(hsm_group: &Group) -> Vec<String> {
hsm_group.get_members()
}
pub async fn get_member_vec_from_hsm_name_vec(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_name_vec: &[&str],
) -> Result<Vec<String>, Error> {
log::info!("Get xnames from HSM groups");
log::debug!("Get xnames from HSM groups: {:?}", hsm_name_vec);
let hsm_group_vec = hsm::group::http_client::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
Some(
hsm_name_vec
.iter()
.map(|c| &**c)
.collect::<Vec<&str>>()
.as_slice(),
),
None,
)
.await?;
let mut hsm_group_member_vec: Vec<String> = Vec::new();
for hsm_group in hsm_group_vec {
hsm_group_member_vec.append(&mut hsm_group.get_members());
}
Ok(hsm_group_member_vec)
}
pub fn get_member_vec_from_hsm_group_value_vec(
hsm_groups: &[Value],
) -> HashSet<String> {
hsm_groups
.iter()
.flat_map(get_member_vec_from_hsm_group_value)
.collect()
}
pub fn get_member_vec_from_hsm_group_vec(
hsm_groups: &[Group],
) -> HashSet<String> {
hsm_groups
.iter()
.flat_map(get_member_vec_from_hsm_group)
.collect()
}
pub fn group_members_by_hsm_group_from_hsm_groups_value(
hsm_groups: &Vec<Value>,
) -> HashMap<String, Vec<String>> {
let mut member_hsm_map: HashMap<String, Vec<String>> = HashMap::new();
for hsm_group_value in hsm_groups {
let hsm_group_name = hsm_group_value["label"].as_str().unwrap().to_string();
for member in get_member_vec_from_hsm_group_value(hsm_group_value) {
member_hsm_map
.entry(member)
.and_modify(|hsm_groups| hsm_groups.push(hsm_group_name.clone()))
.or_insert_with(|| vec![hsm_group_name.clone()]);
}
}
member_hsm_map
}
pub async fn get_member_vec_from_hsm_group_name_opt(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_group: &str,
) -> Option<Vec<String>> {
http_client::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
Some(&[hsm_group]),
None,
)
.await
.unwrap()
.first()
.unwrap()
.get_members_opt()
}
pub async fn get_member_vec_from_hsm_group_name(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_group: &str,
) -> Vec<String> {
http_client::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
Some(&[hsm_group]),
None,
)
.await
.unwrap()
.first()
.unwrap()
.get_members()
}
pub async fn get_hsm_group_from_xname(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
xname: &String,
) -> Option<Vec<String>> {
let mut hsm_group_vec =
http_client::get_all(shasta_token, shasta_base_url, shasta_root_cert)
.await
.unwrap();
hsm_group_vec.retain(|hsm_group| {
hsm_group
.get_members()
.iter()
.any(|hsm_group_member| hsm_group_member == xname)
});
if hsm_group_vec.is_empty() {
None
} else {
Some(
hsm_group_vec
.iter()
.flat_map(|hsm_group| hsm_group.get_members())
.collect(),
)
}
}
pub async fn get_hsm_group_name_vec_from_xname_vec(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
xname_vec: &[&str],
) -> Vec<String> {
let mut hsm_group_vec =
http_client::get_all(shasta_token, shasta_base_url, shasta_root_cert)
.await
.unwrap();
hsm_group_vec.retain(|hsm_group_value| {
hsm_group_value
.get_members()
.iter()
.any(|hsm_group_member| xname_vec.contains(&hsm_group_member.as_str()))
});
hsm_group_vec
.iter()
.map(|hsm_group_value| hsm_group_value.label.clone())
.collect::<Vec<String>>()
}
pub async fn get_hsm_group_vec_from_xname_vec(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
xname_vec: &[&str],
) -> Vec<Group> {
let mut hsm_group_vec =
http_client::get_all(shasta_token, shasta_base_url, shasta_root_cert)
.await
.unwrap();
hsm_group_vec.retain(|hsm_group_value| {
hsm_group_value
.get_members()
.iter()
.any(|hsm_group_member| xname_vec.contains(&hsm_group_member.as_str()))
});
hsm_group_vec
}
pub fn get_hsm_group_from_cfs_session_related_to_cfs_configuration(
cfs_session_value_vec: &[Value],
cfs_configuration: &str,
) -> Vec<String> {
let mut hsm_group_from_cfs_session_vec = cfs_session_value_vec
.iter()
.filter(|cfs_session| {
cfs_session
.pointer("/configuration/name")
.unwrap()
.eq(cfs_configuration)
})
.flat_map(|cfs_session| {
cfs_session
.pointer("/target/groups")
.unwrap()
.as_array()
.unwrap()
.iter()
.map(|group| group["name"].as_str().unwrap().to_string())
})
.collect::<Vec<String>>();
hsm_group_from_cfs_session_vec.sort();
hsm_group_from_cfs_session_vec.dedup();
hsm_group_from_cfs_session_vec
}
pub fn get_hsm_group_from_bos_sessiontimplate_related_to_cfs_configuration(
bos_sessiontemplate_value_vec: &[Value],
cfs_configuration: &str,
) -> Vec<String> {
let hsm_group_from_bos_sessiontemplate_computer_related_to_cfs_configuration =
bos_sessiontemplate_value_vec
.iter()
.filter(|bos_sessiontemplate| {
bos_sessiontemplate
.pointer("/cfs/configuration")
.unwrap()
.eq(cfs_configuration)
})
.flat_map(|bos_sessiontemplate| {
bos_sessiontemplate
.pointer("/boot_sets/compute/node_groups")
.unwrap()
.as_array()
.unwrap()
.iter()
.map(|node_group| node_group.as_str().unwrap().to_string())
});
let hsm_group_from_bos_sessiontemplate_uan_related_to_cfs_configuration =
bos_sessiontemplate_value_vec
.iter()
.filter(|bos_sessiontemplate| {
bos_sessiontemplate
.pointer("/cfs/configuration")
.unwrap()
.eq(cfs_configuration)
&& bos_sessiontemplate
.pointer("/boot_sets/uan/node_groups")
.is_some()
})
.flat_map(|bos_sessiontemplate| {
bos_sessiontemplate
.pointer("/boot_sets/uan/node_groups")
.unwrap()
.as_array()
.unwrap()
.iter()
.map(|node_group| node_group.as_str().unwrap().to_string())
});
let mut hsm_group_from_bos_sessiontemplate_vec =
hsm_group_from_bos_sessiontemplate_computer_related_to_cfs_configuration
.chain(
hsm_group_from_bos_sessiontemplate_uan_related_to_cfs_configuration,
)
.collect::<Vec<String>>();
hsm_group_from_bos_sessiontemplate_vec.sort();
hsm_group_from_bos_sessiontemplate_vec.dedup();
hsm_group_from_bos_sessiontemplate_vec
}
pub async fn validate_config_hsm_group_and_hsm_group_accessed(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_group: Option<&String>,
session_name: Option<&String>,
cfs_sessions: &[CfsSessionGetResponse],
) -> Result<(), Error> {
if let Some(hsm_group_name) = hsm_group {
let hsm_group_details = crate::hsm::group::http_client::get_hsm_group_vec(
shasta_token,
shasta_base_url,
shasta_root_cert,
hsm_group,
)
.await
.unwrap();
let hsm_group_members =
get_member_vec_from_hsm_group_vec(&hsm_group_details);
let cfs_session_hsm_groups: Vec<String> = cfs_sessions
.last()
.unwrap()
.target
.as_ref()
.unwrap()
.groups
.as_ref()
.unwrap_or(&Vec::new())
.iter()
.map(|group| group.name.clone())
.collect();
let cfs_session_members: Vec<String> = cfs_sessions
.last()
.unwrap()
.ansible
.as_ref()
.unwrap()
.limit
.clone()
.unwrap_or_default()
.split(',')
.map(|xname| xname.to_string())
.collect();
if !cfs_session_hsm_groups.contains(hsm_group_name)
&& !cfs_session_members.iter().all(|cfs_session_member| {
hsm_group_members.contains(cfs_session_member)
})
{
return Err(Error::Message(format!(
"CFS session {} does not apply to HSM group {}",
session_name.unwrap(),
hsm_group_name
)));
}
}
Ok(())
}