use anyhow::{Context, Error, bail};
use manta_backend_dispatcher::interfaces::hsm::group::GroupTrait;
use manta_backend_dispatcher::types::Group;
use crate::common;
use crate::common::app_context::InfraContext;
use crate::common::authorization::{
get_groups_names_available, validate_target_hsm_members,
};
pub struct GetGroupParams {
pub group_name: Option<String>,
pub settings_hsm_group_name: Option<String>,
}
pub async fn get_groups(
infra: &InfraContext<'_>,
token: &str,
params: &GetGroupParams,
) -> Result<Vec<Group>, Error> {
let target_hsm_group_vec = get_groups_names_available(
infra.backend,
token,
params.group_name.as_deref(),
params.settings_hsm_group_name.as_deref(),
)
.await?;
let group_vec = infra.backend
.get_groups(token, Some(&target_hsm_group_vec))
.await
.context("Failed to fetch HSM groups")?;
Ok(group_vec)
}
pub async fn validate_group_deletion(
infra: &InfraContext<'_>,
token: &str,
label: &str,
) -> Result<(), Error> {
let xname_vec = infra
.backend
.get_member_vec_from_group_name_vec(token, &[label.to_string()])
.await?;
let xname_vec: Vec<&str> = xname_vec.iter().map(String::as_str).collect();
let mut xname_map = infra
.backend
.get_group_map_and_filter_by_group_vec(token, &xname_vec)
.await?;
xname_map.retain(|_xname, group_name_vec| {
group_name_vec.len() == 1
&& group_name_vec.first().is_some_and(|name| name == label)
});
let mut members_orphan_if_group_deleted: Vec<String> =
xname_map.into_keys().collect();
members_orphan_if_group_deleted.sort();
if !members_orphan_if_group_deleted.is_empty() {
bail!(
"The hosts below will become orphan if group '{}' \
gets deleted.\n{}",
label,
members_orphan_if_group_deleted.join(", ")
);
}
Ok(())
}
pub async fn delete_group(
infra: &InfraContext<'_>,
token: &str,
label: &str,
force: bool,
) -> Result<(), Error> {
if !force {
validate_group_deletion(infra, token, label).await?;
}
infra
.backend
.delete_group(token, label)
.await
.with_context(|| format!("Could not delete group '{}'", label))?;
Ok(())
}
pub async fn prepare_add_group(
infra: &InfraContext<'_>,
token: &str,
label: &str,
description: Option<&str>,
hosts_expression_opt: Option<&str>,
) -> Result<(Group, Option<Vec<String>>), Error> {
let xname_vec_opt: Option<Vec<String>> = match hosts_expression_opt {
Some(hosts_expression) => {
let xname_vec = common::node_ops::resolve_hosts_expression(
infra.backend,
token,
hosts_expression,
false,
)
.await?;
Some(xname_vec)
}
None => None,
};
if let Some(xname_vec) = &xname_vec_opt {
validate_target_hsm_members(infra.backend, token, xname_vec).await?;
}
let group = Group::new(
label,
description.map(str::to_string),
xname_vec_opt.clone(),
None,
None,
);
Ok((group, xname_vec_opt))
}
pub async fn create_group(
infra: &InfraContext<'_>,
token: &str,
group: Group,
) -> Result<(), Error> {
infra.backend.add_group(token, group).await?;
Ok(())
}
pub async fn add_nodes_to_group(
infra: &InfraContext<'_>,
token: &str,
target_hsm_name: &str,
hosts_expression: &str,
) -> Result<(Vec<String>, Vec<String>), Error> {
let xname_to_move_vec = common::node_ops::resolve_hosts_expression(
infra.backend,
token,
hosts_expression,
false,
)
.await?;
if xname_to_move_vec.is_empty() {
bail!(
"The list of nodes to move is empty. \
Nothing to do",
);
}
let target_hsm_group =
infra.backend.get_group(token, target_hsm_name).await;
if target_hsm_group.is_err() {
bail!(
"Target HSM group '{}' does not exist. \
Nothing to do",
target_hsm_name
);
}
let xnames_to_move: Vec<&str> = xname_to_move_vec
.iter()
.map(|xname| xname.as_str())
.collect();
let mut updated_members = infra
.backend
.add_members_to_group(token, target_hsm_name, &xnames_to_move)
.await
.with_context(|| {
format!(
"Could not add nodes {:?} \
to HSM group '{}'",
xnames_to_move, target_hsm_name
)
})?;
updated_members.sort();
Ok((xname_to_move_vec, updated_members))
}