use std::collections::{BTreeMap, HashMap};
use crate::{
bos::{
self,
session::http_client::v2::types::{BosSession, Operation},
template::http_client::v2::types::{BootSet, BosSessionTemplate, Cfs},
},
cfs::{
self,
configuration::http_client::v2::types::{
cfs_configuration_request::CfsConfigurationRequest,
cfs_configuration_response::CfsConfigurationResponse,
},
session::http_client::v2::types::CfsSessionPostRequest,
},
common,
error::Error,
hsm,
ims::{self, image::http_client::types::Link},
node::utils::validate_target_hsm_members,
};
use image::Image;
use serde::{Deserialize, Serialize};
use serde_json::Map;
use serde_yaml::Value;
use uuid::Uuid;
use self::sessiontemplate::SessionTemplate;
#[derive(Deserialize, Serialize, Debug)]
pub struct SatFile {
#[serde(skip_serializing_if = "Option::is_none")]
pub configurations: Option<Vec<configuration::Configuration>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub images: Option<Vec<image::Image>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_templates: Option<Vec<sessiontemplate::SessionTemplate>>,
}
impl SatFile {
pub fn filter(
&mut self,
image_only: bool,
session_template_only: bool,
) -> Result<(), Error> {
if image_only {
let image_vec_opt: Option<&Vec<Image>> = self.images.as_ref();
let configuration_name_image_vec: Vec<String> = match image_vec_opt {
Some(image_vec) => image_vec
.iter()
.filter_map(|sat_template_image| {
sat_template_image.configuration.clone()
})
.collect(),
None => {
return Err(Error::Message(
"ERROR - 'images' section missing in SAT file".to_string(),
));
}
};
self
.configurations
.as_mut()
.unwrap_or(&mut Vec::new())
.retain(|configuration| {
configuration_name_image_vec.contains(&configuration.name)
});
self.session_templates = None;
}
if session_template_only {
let sessiontemplate_vec_opt: Option<&Vec<SessionTemplate>> =
self.session_templates.as_ref();
let configuration_name_sessiontemplate_vec: Vec<String> =
match sessiontemplate_vec_opt {
Some(sessiontemplate_vec) => sessiontemplate_vec
.iter()
.map(|sat_sessiontemplate| {
sat_sessiontemplate.configuration.clone()
})
.collect(),
None => {
return Err(Error::Message(
"ERROR - 'session_templates' section not defined in SAT file"
.to_string(),
));
}
};
if let Some(&[_]) = self.configurations.as_deref() {
self
.configurations
.as_mut()
.unwrap_or(&mut Vec::new())
.retain(|configuration| {
configuration_name_sessiontemplate_vec.contains(&configuration.name)
})
} else {
self.configurations = None;
}
let image_name_sessiontemplate_vec: Vec<String> = self
.session_templates
.as_ref()
.unwrap_or(&Vec::new())
.iter()
.filter_map(|sessiontemplate| match &sessiontemplate.image {
sessiontemplate::Image::ImageRef(name) => Some(name),
sessiontemplate::Image::Ims { ims } => match ims {
sessiontemplate::ImsDetails::Name { name } => Some(name),
sessiontemplate::ImsDetails::Id { .. } => None,
},
})
.cloned()
.collect();
self
.images
.as_mut()
.unwrap_or(&mut Vec::new())
.retain(|image| image_name_sessiontemplate_vec.contains(&image.name));
if self.images.as_ref().is_some_and(|images| images.is_empty()) {
self.images = None;
}
}
Ok(())
}
}
pub mod sessiontemplate {
use std::collections::HashMap;
use strum_macros::Display;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
pub struct SessionTemplate {
pub name: String,
pub image: Image,
pub configuration: String,
pub bos_parameters: BosParamters,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum ImsDetails {
Name { name: String },
Id { id: String },
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum Image {
Ims { ims: ImsDetails },
ImageRef(String),
}
#[derive(Deserialize, Serialize, Debug)]
pub struct BosParamters {
pub boot_sets: HashMap<String, BootSet>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct BootSet {
#[serde(skip_serializing_if = "Option::is_none")]
pub arch: Option<Arch>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kernel_parameters: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub network: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub node_list: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub node_roles_group: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub node_groups: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rootfs_provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rootfs_provider_passthrough: Option<String>,
}
#[derive(Deserialize, Serialize, Debug, Display)]
pub enum Arch {
X86,
ARM,
Other,
Unknown,
}
}
impl TryFrom<SessionTemplate> for BosSessionTemplate {
type Error = ();
fn try_from(
value: SessionTemplate,
) -> Result<BosSessionTemplate, Self::Error> {
let b_st_cfs = Cfs {
configuration: Some(value.configuration),
};
let mut boot_set_map: HashMap<String, BootSet> = HashMap::new();
for (property, boot_set) in value.bos_parameters.boot_sets {
let boot_set = BootSet {
name: Some(format!(
"Boot set property '{}' created by manta from SAT file",
property
)),
path: None,
r#type: None,
etag: None,
kernel_parameters: None,
node_list: boot_set.node_list,
node_roles_groups: boot_set.node_roles_group,
node_groups: boot_set.node_groups,
rootfs_provider: boot_set.rootfs_provider,
rootfs_provider_passthrough: boot_set.rootfs_provider_passthrough,
cfs: Some(b_st_cfs.clone()),
arch: boot_set.arch.map(|value| value.to_string()),
};
boot_set_map.insert(property, boot_set);
}
let b_st = BosSessionTemplate {
name: Some(value.name),
description: Some(format!(
"BOS sessiontemplate created by manta from SAT file"
)),
enable_cfs: Some(true),
cfs: Some(b_st_cfs),
boot_sets: Some(boot_set_map),
links: None,
tenant: None,
};
Ok(b_st)
}
}
pub mod image {
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum Arch {
#[serde(rename(serialize = "aarch64", deserialize = "aarch64"))]
Aarch64,
#[serde(rename(serialize = "x86_64", deserialize = "x86_64"))]
X86_64,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum ImageIms {
NameIsRecipe { name: String, is_recipe: bool },
IdIsRecipe { id: String, is_recipe: bool },
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum ImageBaseIms {
NameType { name: String, r#type: String },
IdType { id: String, r#type: String },
BackwardCompatible { is_recipe: Option<bool>, id: String },
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum Filter {
Prefix { prefix: String },
Wildcard { wildcard: String },
Arch { arch: Arch },
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Product {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
version: Option<String>,
r#type: String,
filter: Filter,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum Base {
Ims { ims: ImageBaseIms },
Product { product: Product },
ImageRef { image_ref: String },
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum BaseOrIms {
Base { base: Base },
Ims { ims: ImageIms },
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Image {
pub name: String,
#[serde(flatten)]
pub base_or_ims: BaseOrIms,
#[serde(skip_serializing_if = "Option::is_none")]
pub configuration: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub configuration_group_names: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ref_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
}
pub mod configuration {
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum Product {
ProductVersionBranch {
name: String,
version: Option<String>,
branch: String,
},
ProductVersionCommit {
name: String,
version: Option<String>,
commit: String,
},
ProductVersion {
name: String,
version: String,
},
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum Git {
GitCommit { url: String, commit: String },
GitBranch { url: String, branch: String },
GitTag { url: String, tag: String },
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum LayerType {
Git { git: Git },
Product { product: Product },
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Layer {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default = "default_playbook")]
pub playbook: String, #[serde(flatten)]
pub layer_type: LayerType,
}
fn default_playbook() -> String {
"site.yml".to_string()
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)] pub enum Inventory {
InventoryCommit {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
url: String,
commit: String,
},
InventoryBranch {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
url: String,
branch: String,
},
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Configuration {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub layers: Vec<Layer>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_inventory: Option<Inventory>,
}
}
pub mod sat_file_image_old {
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
pub struct Ims {
is_recipe: bool,
id: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Product {
name: String,
version: String,
r#type: String,
}
}
pub async fn create_cfs_configuration_from_sat_file(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
gitea_base_url: &str,
gitea_token: &str,
cray_product_catalog: &BTreeMap<String, String>,
sat_file_configuration_yaml: &serde_yaml::Value,
dry_run: bool,
site_name: &str,
overwrite: bool,
) -> Result<CfsConfigurationResponse, Error> {
log::debug!(
"Convert CFS configuration in SAT file (yaml):\n{:#?}",
sat_file_configuration_yaml
);
let (cfs_configuration_name, cfs_configuration) =
CfsConfigurationRequest::from_sat_file_serde_yaml(
shasta_root_cert,
gitea_base_url,
gitea_token,
sat_file_configuration_yaml,
cray_product_catalog,
site_name,
)
.await?;
if dry_run {
println!(
"Dry run mode: Create CFS configuration:\n{}",
serde_json::to_string_pretty(&cfs_configuration)?
);
let cfs_configuration = CfsConfigurationResponse {
name: cfs_configuration_name,
last_updated: "".to_string(),
layers: Vec::new(),
additional_inventory: None,
};
Ok(cfs_configuration)
} else {
cfs::configuration::utils::create_new_configuration(
shasta_token,
shasta_base_url,
shasta_root_cert,
&cfs_configuration,
&cfs_configuration_name,
overwrite,
)
.await
}
}
pub fn get_next_image_in_sat_file_to_process(
image_yaml_vec: &[serde_yaml::Value],
ref_name_processed_vec: &[String],
) -> Option<serde_yaml::Value> {
image_yaml_vec
.iter()
.find(|image_yaml| {
let ref_name: &str = &get_image_name_or_ref_name_to_process(image_yaml);
let image_base_image_ref_opt: Option<&str> =
image_yaml.get("base").and_then(|image_base_yaml| {
image_base_yaml.get("image_ref").and_then(
|image_base_image_ref_yaml| image_base_image_ref_yaml.as_str(),
)
});
!ref_name_processed_vec.contains(&ref_name.to_string())
&& (image_base_image_ref_opt.is_none()
|| image_base_image_ref_opt.is_some_and(|image_base_image_ref| {
ref_name_processed_vec.contains(&image_base_image_ref.to_string())
}))
})
.cloned()
}
pub fn get_image_name_or_ref_name_to_process(
image_yaml: &serde_yaml::Value,
) -> String {
if image_yaml.get("ref_name").is_some() {
image_yaml["ref_name"].as_str().unwrap().to_string()
} else {
image_yaml["name"].as_str().unwrap().to_string()
}
}
#[deprecated(
since = "v0.86.2",
note = "this function prints cfs session logs to stdout"
)]
pub async fn i_import_images_section_in_sat_file(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
vault_base_url: &str,
site_name: &str,
k8s_api_url: &str,
ref_name_processed_hashmap: &mut HashMap<String, String>,
image_yaml_vec: &[serde_yaml::Value],
cray_product_catalog: &BTreeMap<String, String>,
ansible_verbosity_opt: Option<u8>,
ansible_passthrough_opt: Option<&str>,
debug_on_failure: bool, dry_run: bool,
watch_logs: bool,
timestamps: bool,
) -> Result<HashMap<String, serde_yaml::Value>, Error> {
if image_yaml_vec.is_empty() {
log::warn!("No images found in SAT file. Nothing to process.");
return Ok(HashMap::new());
}
let mut next_image_to_process_opt: Option<serde_yaml::Value> =
get_next_image_in_sat_file_to_process(
&image_yaml_vec,
&ref_name_processed_hashmap
.keys()
.cloned()
.collect::<Vec<String>>(),
);
log::info!("Processing image '{:?}'", next_image_to_process_opt);
let mut image_processed_hashmap: HashMap<String, serde_yaml::Value> =
HashMap::new();
while let Some(image_yaml) = &next_image_to_process_opt {
let image_id = i_create_image_from_sat_file_serde_yaml(
shasta_token,
shasta_base_url,
shasta_root_cert,
vault_base_url,
site_name,
k8s_api_url,
image_yaml,
cray_product_catalog,
ansible_verbosity_opt,
ansible_passthrough_opt,
ref_name_processed_hashmap,
debug_on_failure,
dry_run,
watch_logs,
timestamps,
)
.await?;
image_processed_hashmap.insert(image_id.clone(), image_yaml.clone());
ref_name_processed_hashmap.insert(
get_image_name_or_ref_name_to_process(image_yaml),
image_id.clone(),
);
next_image_to_process_opt = get_next_image_in_sat_file_to_process(
&image_yaml_vec,
&ref_name_processed_hashmap
.keys()
.cloned()
.collect::<Vec<String>>(),
);
}
Ok(image_processed_hashmap)
}
#[deprecated(
since = "v0.86.2",
note = "this function prints cfs session logs to stdout"
)]
pub async fn i_create_image_from_sat_file_serde_yaml(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
vault_base_url: &str,
site_name: &str,
k8s_api_url: &str,
image_yaml: &serde_yaml::Value, cray_product_catalog: &BTreeMap<String, String>,
ansible_verbosity_opt: Option<u8>,
ansible_passthrough_opt: Option<&str>,
ref_name_image_id_hashmap: &HashMap<String, String>,
_debug_on_failure: bool,
dry_run: bool,
watch_logs: bool,
timestamps: bool,
) -> Result<String, Error> {
let image_name = image_yaml["name"].as_str().unwrap().to_string();
log::info!(
"Creating CFS session related to build image '{}'",
image_name
);
let configuration_name: String = image_yaml["configuration"]
.as_str()
.unwrap_or_default()
.to_string();
let groups_name: Vec<&str> = image_yaml["configuration_group_names"]
.as_sequence()
.and_then(|group_name_vec| {
group_name_vec
.iter()
.map(|group_name| group_name.as_str())
.collect::<Option<Vec<&str>>>()
})
.unwrap_or(vec![]);
let invalid_groups: Vec<String> =
hsm::group::hacks::validate_groups_auth_token(&groups_name, shasta_token)?;
if !invalid_groups.is_empty() {
log::debug!("CFS session group validation - failed");
return Err(Error::Message(format!(
"Please fix 'images' section in SAT file.\nInvalid groups: {:?}",
invalid_groups
)));
} else {
log::debug!("CFS session group validation - passed");
}
let base_image_id: String;
if let Some(sat_file_image_ims_value_yaml) = image_yaml.get("ims") {
log::info!("SAT file - 'image.ims' job ('images' section in SAT file is outdated - switching to backward compatibility)");
base_image_id =
process_sat_file_image_old_version(sat_file_image_ims_value_yaml)
.unwrap();
} else if let Some(sat_file_image_base_value_yaml) = image_yaml.get("base") {
if let Some(sat_file_image_base_image_ref_value_yaml) =
sat_file_image_base_value_yaml.get("image_ref")
{
log::info!("SAT file - 'image.base.image_ref' job");
base_image_id = process_sat_file_image_ref_name(
sat_file_image_base_image_ref_value_yaml,
ref_name_image_id_hashmap,
)
.unwrap();
} else if let Some(sat_file_image_base_ims_value_yaml) =
sat_file_image_base_value_yaml.get("ims")
{
log::info!("SAT file - 'image.base.ims' job");
let ims_job_type =
sat_file_image_base_ims_value_yaml["type"].as_str().unwrap();
if ims_job_type == "recipe" {
log::info!("SAT file - 'image.base.ims' job of type 'recipe'");
base_image_id = process_sat_file_image_ims_type_recipe(
shasta_token,
shasta_base_url,
shasta_root_cert,
sat_file_image_base_ims_value_yaml,
&image_name,
dry_run,
)
.await
.unwrap();
} else if ims_job_type == "image" {
log::info!("SAT file - 'image.base.ims' job of type 'image'");
base_image_id = sat_file_image_base_ims_value_yaml["id"]
.as_str()
.unwrap()
.to_string();
} else {
return Err(Error::Message(
"Can't process SAT file 'images.base.ims' is missing. Exit"
.to_string(),
));
}
} else if let Some(sat_file_image_base_product_value_yaml) =
sat_file_image_base_value_yaml.get("product")
{
log::info!("SAT file - 'image.base.product' job");
let product_name = sat_file_image_base_product_value_yaml["name"]
.as_str()
.unwrap();
let product_version = sat_file_image_base_product_value_yaml["version"]
.as_str()
.unwrap();
let product_type = sat_file_image_base_product_value_yaml["type"]
.as_str()
.unwrap()
.to_string()
+ "s";
let product_image_map = &serde_yaml::from_str::<serde_json::Value>(
&cray_product_catalog[product_name],
)
.unwrap()[product_version][product_type.clone()]
.as_object()
.unwrap()
.clone();
let image_id = if let Some(filter) =
sat_file_image_base_product_value_yaml.get("filter")
{
filter_product_catalog_images(
filter,
product_image_map.clone(),
&image_name,
)
.unwrap()
} else {
log::info!("No 'image.product.filter' defined in SAT file. Checking Cray product catalog only/must have 1 image");
product_image_map
.values()
.next()
.and_then(|value| value.get("id"))
.unwrap()
.as_str()
.unwrap()
.to_string()
};
base_image_id = if product_type == "recipes" {
log::info!("SAT file - 'image.base.product' job based on IMS recipes");
let product_recipe_id = image_id.clone();
process_sat_file_image_product_type_ims_recipe(
shasta_token,
shasta_base_url,
shasta_root_cert,
&product_recipe_id,
&image_name,
dry_run,
)
.await
.unwrap()
} else if product_type == "images" {
log::info!("SAT file - 'image.base.product' job based on IMS images");
log::info!("Getting base image id from Cray product catalog");
let product_image_id = image_id;
product_image_id
} else {
return Err(Error::Message(
"Can't process SAT file, field 'images.base.product.type' must be either 'images' or 'recipes'. Exit".to_string(),
));
}
} else {
return Err(Error::Message(
"Can't process SAT file 'images.base.product' is missing. Exit"
.to_string(),
));
}
} else {
return Err(Error::Message(
"Can't process SAT file 'images.base' is missing. Exit".to_string(),
));
}
if configuration_name.is_empty() {
log::info!("No CFS session needs to be created since there is no CFS configuration assigned to this image");
println!(
"Image '{}' imported image_id '{}'",
image_name, base_image_id
);
Ok(base_image_id)
} else {
log::info!("Creating CFS session");
let session_name = image_name.clone();
let cfs_session = CfsSessionPostRequest::new(
session_name,
&configuration_name,
None,
ansible_verbosity_opt,
ansible_passthrough_opt,
true,
Some(&groups_name),
Some(&base_image_id),
);
if !dry_run {
let cfs_session_rslt = cfs::session::i_post_sync(
shasta_token,
shasta_base_url,
shasta_root_cert,
vault_base_url,
site_name,
k8s_api_url,
&cfs_session,
watch_logs,
timestamps,
)
.await;
let cfs_session = match cfs_session_rslt {
Ok(cfs_session) => cfs_session,
Err(e) => {
return Err(Error::Message(format!(
"Could not create Image. Reason:\n{}",
e
)));
}
};
if !cfs_session.is_success() {
return Err(Error::Message(format!(
"CFS session '{}' failed. Exit",
cfs_session.name.unwrap()
)));
}
let image_id = cfs_session.first_result_id().unwrap_or_default();
println!("Image '{}' ({}) created", image_name, image_id);
Ok(image_id.to_string())
} else {
println!(
"Dry run mode: Create CFS session:\n{}",
serde_json::to_string_pretty(&cfs_session)?
);
let image_id = Uuid::new_v4().to_string();
println!(
"Dry run mode: Image '{}' ({}) created",
image_name, image_id
);
Ok(image_id)
}
}
}
async fn process_sat_file_image_product_type_ims_recipe(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
recipe_id: &str,
image_name: &str,
dry_run: bool,
) -> Result<String, Error> {
let root_public_ssh_key_value: serde_json::Value =
ims::public_keys::http_client::v3::get_single(
shasta_token,
shasta_base_url,
shasta_root_cert,
"mgmt root key",
)
.await
.unwrap();
let root_public_ssh_key = root_public_ssh_key_value["id"].as_str().unwrap();
let ims_job = ims::job::types::Job {
job_type: "create".to_string(),
image_root_archive_name: image_name.to_string(),
kernel_file_name: Some("vmlinuz".to_string()),
initrd_file_name: Some("initrd".to_string()),
kernel_parameters_file_name: Some("kernel-parameters".to_string()),
artifact_id: recipe_id.to_string(),
public_key_id: root_public_ssh_key.to_string(),
ssh_containers: None, enable_debug: Some(false),
build_env_size: Some(15),
require_dkms: None, id: None,
created: None,
status: None,
kubernetes_job: None,
kubernetes_service: None,
kubernetes_configmap: None,
resultant_image_id: None,
kubernetes_namespace: None,
arch: None,
};
let ims_job = if dry_run {
println!(
"Dry run mode: Create IMS job:\n{}",
serde_json::to_string_pretty(&ims_job)?
);
let mut dry_run_ims_job = ims_job;
dry_run_ims_job.resultant_image_id = Some(Uuid::new_v4().to_string());
dry_run_ims_job
} else {
ims::job::http_client::post_sync(
shasta_token,
shasta_base_url,
shasta_root_cert,
&ims_job,
)
.await
.unwrap()
};
Ok(ims_job.resultant_image_id.unwrap())
}
async fn process_sat_file_image_ims_type_recipe(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
sat_file_image_base_ims_value_yaml: &serde_yaml::Value,
image_name: &String,
dry_run: bool,
) -> Result<String, Error> {
let recipe_name =
sat_file_image_base_ims_value_yaml["name"].as_str().unwrap();
let recipe_detail_vec: Vec<ims::recipe::types::RecipeGetResponse> =
ims::recipe::http_client::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
None,
)
.await
.unwrap();
let recipe_detail_opt = recipe_detail_vec
.iter()
.find(|recipe| recipe.name == recipe_name);
log::info!("IMS recipe details:\n{:#?}", recipe_detail_opt);
let recipe_id = if let Some(recipe_detail) = recipe_detail_opt {
recipe_detail.id.as_ref().unwrap()
} else {
return Err(Error::Message(format!(
"IMS recipe with name '{}' - not found. Exit",
recipe_name
)));
};
log::info!("IMS recipe id found '{}'", recipe_id);
let root_public_ssh_key_value: serde_json::Value =
ims::public_keys::http_client::v3::get_single(
shasta_token,
shasta_base_url,
shasta_root_cert,
"mgmt root key",
)
.await
.unwrap();
let root_public_ssh_key = root_public_ssh_key_value["id"].as_str().unwrap();
let ims_job = ims::job::types::Job {
job_type: "create".to_string(),
image_root_archive_name: image_name.to_string(),
kernel_file_name: Some("vmlinuz".to_string()),
initrd_file_name: Some("initrd".to_string()),
kernel_parameters_file_name: Some("kernel-parameters".to_string()),
artifact_id: recipe_id.to_string(),
public_key_id: root_public_ssh_key.to_string(),
ssh_containers: None, enable_debug: Some(false),
build_env_size: Some(15),
require_dkms: None, id: None,
created: None,
status: None,
kubernetes_job: None,
kubernetes_service: None,
kubernetes_configmap: None,
resultant_image_id: None,
kubernetes_namespace: None,
arch: None,
};
let ims_job = if dry_run {
println!(
"Dry run mode: Create IMS job:\n{}",
serde_json::to_string_pretty(&ims_job)?
);
ims_job.into()
} else {
ims::job::http_client::post_sync(
shasta_token,
shasta_base_url,
shasta_root_cert,
&ims_job,
)
.await
.unwrap()
};
log::info!("IMS job response:\n{:#?}", ims_job);
Ok(ims_job.resultant_image_id.unwrap())
}
fn process_sat_file_image_old_version(
sat_file_image_ims_value_yaml: &serde_yaml::Value,
) -> Result<String, Error> {
if sat_file_image_ims_value_yaml
.get("is_recipe")
.is_some_and(|is_recipe_value| is_recipe_value.as_bool().unwrap() == false)
&& sat_file_image_ims_value_yaml.get("id").is_some()
{
Ok(
sat_file_image_ims_value_yaml["id"]
.as_str()
.unwrap()
.to_string(),
)
} else {
Err(Error::Message("Functionality not built. Exit".to_string()))
}
}
fn process_sat_file_image_ref_name(
sat_file_image_base_image_ref_value_yaml: &serde_yaml::Value,
ref_name_image_id_hashmap: &HashMap<String, String>,
) -> Result<String, Error> {
let image_ref: String = sat_file_image_base_image_ref_value_yaml
.as_str()
.unwrap()
.to_string();
Ok(
ref_name_image_id_hashmap
.get(&image_ref)
.unwrap()
.to_string(),
)
}
pub fn filter_product_catalog_images(
filter: &Value,
image_map: Map<String, serde_json::Value>,
image_name: &str,
) -> Result<String, Error> {
if let Some(arch) = filter.get("arch") {
let image_key_vec = image_map
.keys()
.collect::<Vec<_>>()
.into_iter()
.filter(|product| {
product
.split(".")
.last()
.unwrap()
.eq(arch.as_str().unwrap())
})
.collect::<Vec<_>>();
if image_key_vec.is_empty() {
Err(Error::Message(format!(
"Product catalog for image '{}' not found. Exit",
image_name
)))
} else if image_key_vec.len() > 1 {
Err(Error::Message(format!(
"Product catalog for image '{}' multiple items found. Exit",
image_name
)))
} else {
let image_key = image_key_vec.first().cloned().unwrap();
Ok(
image_map.get(image_key).unwrap()["id"]
.as_str()
.unwrap()
.to_string(),
)
}
} else if let Some(wildcard) = filter.get("wildcard") {
let image_key_vec = image_map
.keys()
.filter(|product| product.contains(wildcard.as_str().unwrap()))
.collect::<Vec<_>>();
if image_key_vec.is_empty() {
Err(Error::Message(format!(
"Product catalog for image '{}' not found. Exit",
image_name
)))
} else if image_key_vec.len() > 1 {
Err(Error::Message(format!(
"Product catalog for image '{}' multiple items found. Exit",
image_name
)))
} else {
let image_key = image_key_vec.first().cloned().unwrap();
Ok(
image_map.get(image_key).unwrap()["id"]
.as_str()
.unwrap()
.to_string(),
)
}
} else if let Some(prefix) = filter.get("prefix") {
let image_key_vec = image_map
.keys()
.filter(|product| {
product
.strip_prefix(&prefix.as_str().unwrap().to_string())
.is_some()
})
.collect::<Vec<_>>();
if image_key_vec.is_empty() {
Err(Error::Message(format!(
"Product catalog for image '{}' not found. Exit",
image_name
)))
} else if image_key_vec.len() > 1 {
Err(Error::Message(format!(
"Product catalog for image '{}' multiple items found. Exit",
image_name
)))
} else {
let image_key = image_key_vec.first().cloned().unwrap();
Ok(
image_map.get(image_key).unwrap()["id"]
.as_str()
.unwrap()
.to_string(),
)
}
} else {
Err(Error::Message(format!(
"Product catalog for image '{}' not found. Exit",
image_name
)))
}
}
pub fn validate_sat_file_images_section(
image_yaml_vec: &Vec<Value>,
configuration_yaml_vec: &Vec<Value>,
hsm_group_available_vec: &[&str],
cray_product_catalog: &BTreeMap<String, String>,
image_vec: Vec<ims::image::http_client::types::Image>,
configuration_vec: Vec<CfsConfigurationResponse>,
ims_recipe_vec: Vec<ims::recipe::types::RecipeGetResponse>,
) -> Result<(), Error> {
for image_yaml in image_yaml_vec {
let image_name = image_yaml["name"].as_str().unwrap();
log::info!("Validate 'image' '{}'", image_name);
if let Some(image_ims_id_to_find) = image_yaml
.get("ims")
.and_then(|ims| ims.get("id").and_then(|id| id.as_str()))
{
log::info!(
"Validate 'image' '{}' base image '{}'",
image_name,
image_ims_id_to_find
);
log::info!(
"Searching image.ims.id (old format - backward compatibility) '{}' in CSM",
image_ims_id_to_find,
);
let is_image_base_id_in_csm = image_vec.iter().any(
|image: &ims::image::http_client::types::Image| {
let image_id = image.id.as_ref().unwrap();
image_id.eq(image_ims_id_to_find)
},
);
if !is_image_base_id_in_csm {
return Err(Error::Message(format!(
"Could not find base image id '{}' in image '{}'. Exit",
image_ims_id_to_find,
image_yaml["name"].as_str().unwrap()
)));
}
} else if image_yaml.get("base").is_some() {
if let Some(image_ref_to_find) = image_yaml["base"].get("image_ref") {
log::info!(
"Validate 'image' '{}' base image '{}'",
image_name,
image_ref_to_find.clone().as_str().unwrap()
);
let image_found = image_yaml_vec.iter().any(|image_yaml| {
image_yaml
.get("ref_name")
.is_some_and(|ref_name| ref_name.eq(image_ref_to_find))
});
if !image_found {
return Err(Error::Message(format!(
"Could not find image with ref name '{}' in SAT file. Cancelling image build proccess. Exit",
image_ref_to_find.as_str().unwrap(),
)));
}
} else if let Some(image_base_product) = image_yaml["base"].get("product")
{
log::info!("Image '{}' base.base.product", image_name);
log::info!("SAT file - 'image.base.product' job");
let product_name = image_base_product["name"].as_str().unwrap();
let product_version = image_base_product["version"].as_str().unwrap();
let product_type =
image_base_product["type"].as_str().unwrap().to_string() + "s";
let product_catalog_rslt = &serde_yaml::from_str::<serde_json::Value>(
&cray_product_catalog
.get(product_name)
.unwrap_or(&"".to_string()),
);
let product_catalog = if let Ok(product_catalog) = product_catalog_rslt
{
product_catalog
} else {
return Err(Error::Message(format!(
"Product catalog for image '{}' not found. Exit",
image_name
)));
};
let product_type_opt = product_catalog
.get(product_version)
.and_then(|product_version| product_version.get(product_type.clone()))
.cloned();
let product_type_opt = if let Some(product_type) = product_type_opt {
product_type.as_object().cloned()
} else {
return Err(Error::Message(format!(
"Product catalog for image '{}' not found. Exit",
image_name
)));
};
let image_map: Map<String, serde_json::Value> =
if let Some(product_type) = &product_type_opt {
product_type.clone()
} else {
return Err(Error::Message(format!(
"Product catalog for image '{}' not found. Exit",
image_name
)));
};
log::debug!("CRAY product catalog items related to product name '{}', product version '{}' and product type '{}':\n{:#?}", product_name, product_version, product_type, product_type_opt);
if let Some(filter) = image_base_product.get("filter") {
let image_recipe_id =
filter_product_catalog_images(filter, image_map, image_name);
image_recipe_id.is_ok()
} else {
log::info!("No 'image.product.filter' defined in SAT file. Checking Cray product catalog only/must have 1 image");
image_map
.values()
.next()
.is_some_and(|value| value.get("id").is_some())
};
} else if let Some(image_base_ims_yaml) = image_yaml["base"].get("ims") {
log::info!("Image '{}' base.base.ims", image_name);
if let Some(image_base_ims_name_yaml) = image_base_ims_yaml.get("name")
{
let image_base_ims_name_to_find =
image_base_ims_name_yaml.as_str().unwrap();
log::info!(
"Searching base image '{}' related to image '{}' in SAT file",
image_base_ims_name_to_find,
image_name
);
let mut image_found = image_yaml_vec
.iter()
.any(|image_yaml| image_yaml["name"].eq(image_base_ims_name_yaml));
if !image_found {
log::warn!(
"Base image '{}' not found in SAT file, looking in CSM",
image_base_ims_name_to_find
);
if let Some(image_base_ims_type_yaml) =
image_base_ims_yaml.get("type")
{
let image_base_ims_type =
image_base_ims_type_yaml.as_str().unwrap();
if image_base_ims_type.eq("recipe") {
log::info!(
"Searching base image recipe '{}' related to image '{}' in CSM",
image_base_ims_name_to_find,
image_name
);
image_found = ims_recipe_vec
.iter()
.any(|recipe| recipe.name.eq(image_base_ims_name_to_find));
if !image_found {
return Err(Error::Message(format!(
"Could not find IMS recipe '{}' in CSM. Cancelling image build proccess. Exit",
image_base_ims_name_to_find,
)));
}
} else {
log::info!(
"Searching base image '{}' related to image '{}' in CSM",
image_base_ims_name_to_find,
image_name
);
image_found = image_vec.iter().any(|image| {
image.name.contains(image_base_ims_name_to_find)
});
if !image_found {
return Err(Error::Message(format!(
"Could not find image base '{}' in image '{}'. Cancelling image build proccess. Exit",
image_base_ims_name_to_find,
image_name
)));
}
}
} else {
return Err(Error::Message(format!(
"Image '{}' is missing the field base.ims.type. Cancelling image build proccess. Exit",
image_base_ims_name_to_find,
)));
}
}
} else {
eprintln!(
"Image '{}' is missing the field 'base.ims.name'. Exit",
image_name
);
};
} else {
return Err(Error::Message(format!(
"Image '{}' yaml not recognised. Exit",
image_name
)));
}
} else {
return Err(Error::Message(format!(
"Image '{}' neither have 'ims' nor 'base' value. Exit",
image_name
)));
}
log::info!("Validate 'image' '{}' configuration", image_name);
if let Some(configuration_yaml) = image_yaml.get("configuration") {
let configuration_name_to_find = configuration_yaml.as_str().unwrap();
log::info!(
"Searching configuration name '{}' related to image '{}' in SAT file",
configuration_name_to_find,
image_name
);
let mut configuration_found =
configuration_yaml_vec.iter().any(|configuration_yaml| {
configuration_yaml["name"]
.as_str()
.unwrap()
.eq(configuration_name_to_find)
});
if !configuration_found {
log::warn!(
"Configuration '{}' not found in SAT file, looking in CSM",
configuration_name_to_find
);
log::info!(
"Searching configuration name '{}' related to image '{}' in CSM",
configuration_name_to_find,
image_yaml["name"].as_str().unwrap()
);
configuration_found = configuration_vec.iter().any(|configuration| {
configuration.name.eq(configuration_name_to_find)
});
if !configuration_found {
return Err(Error::Message(format!(
"Could not find configuration '{}' in image '{}'. Cancelling image build proccess. Exit",
configuration_name_to_find,
image_name
)));
}
}
log::info!("Validate 'image' '{}' HSM groups", image_name);
let configuration_group_names_vec: Vec<String> =
serde_yaml::from_value(image_yaml["configuration_group_names"].clone())
.unwrap_or(Vec::new());
let configuration_group_names_vec =
hsm::group::hacks::filter_system_hsm_group_names(
configuration_group_names_vec,
);
if configuration_group_names_vec.is_empty() {
return Err(Error::Message(format!("Image '{}' must have group name values assigned to it. Canceling image build process. Exit", image_name)));
} else {
for hsm_group in
configuration_group_names_vec.iter().filter(|&hsm_group| {
!hsm_group.eq_ignore_ascii_case("Compute")
&& !hsm_group.eq_ignore_ascii_case("Application")
&& !hsm_group.eq_ignore_ascii_case("Application_UAN")
})
{
if !hsm_group_available_vec.contains(&hsm_group.as_str()) {
return Err(Error::Message(format!
(
"HSM group '{}' in image '{}' not allowed, List of HSM groups available:\n{:?}. Exit",
hsm_group,
image_yaml["name"].as_str().unwrap(),
hsm_group_available_vec
)));
}
}
};
}
}
Ok(())
}
pub fn validate_sat_file_configurations_section(
configuration_yaml_vec_opt: Option<&Vec<Value>>,
image_yaml_vec_opt: Option<&Vec<Value>>,
sessiontemplate_yaml_vec_opt: Option<&Vec<Value>>,
) -> Result<(), Error> {
if configuration_yaml_vec_opt.is_some()
&& !configuration_yaml_vec_opt.unwrap().is_empty()
{
if !(image_yaml_vec_opt.is_some()
&& !image_yaml_vec_opt.unwrap().is_empty())
&& !(sessiontemplate_yaml_vec_opt.is_some()
&& !sessiontemplate_yaml_vec_opt.unwrap().is_empty())
{
return Err(Error::Message(
"Incorrect SAT file. Please define either an 'image' or a 'session template'. Exit"
.to_string(),
));
}
}
Ok(())
}
pub async fn validate_sat_file_session_template_section(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
image_yaml_vec_opt: Option<&Vec<Value>>,
configuration_yaml_vec_opt: Option<&Vec<Value>>,
session_template_yaml_vec_opt: Option<&Vec<Value>>,
hsm_group_available_vec: &[&str],
) -> Result<(), Error> {
log::info!("Validate 'session_template' section in SAT file");
for session_template_yaml in session_template_yaml_vec_opt.unwrap_or(&vec![])
{
let session_template_name = session_template_yaml["name"].as_str().unwrap();
log::info!("Validate 'session_template' '{}'", session_template_name);
log::info!(
"Validate 'session_template' '{}' HSM groups",
session_template_name
);
let bos_session_template_hsm_groups: Vec<String> =
if let Some(boot_sets_compute) =
session_template_yaml["bos_parameters"]["boot_sets"].get("compute")
{
boot_sets_compute["node_groups"]
.as_sequence()
.unwrap_or(&vec![])
.iter()
.map(|node| node.as_str().unwrap().to_string())
.collect()
} else if let Some(boot_sets_compute) =
session_template_yaml["bos_parameters"]["boot_sets"].get("uan")
{
boot_sets_compute["node_groups"]
.as_sequence()
.unwrap_or(&vec![])
.iter()
.map(|node| node.as_str().unwrap().to_string())
.collect()
} else {
return Err(Error::Message(format!(
"No HSM group found in session_templates section in SAT file"
)));
};
for hsm_group in bos_session_template_hsm_groups {
if !hsm_group_available_vec.contains(&hsm_group.as_str()) {
return Err(Error::Message(format!(
"HSM group '{}' in session_templates {} not allowed, List of HSM groups available {:?}. Exit",
hsm_group,
session_template_yaml["name"].as_str().unwrap(),
hsm_group_available_vec
)));
}
}
log::info!(
"Validate 'session_template' '{}' boot image",
session_template_name
);
if let Some(ref_name_to_find) = session_template_yaml
.get("image")
.and_then(|image| image.get("image_ref"))
{
log::info!(
"Searching ref_name '{}' in SAT file",
ref_name_to_find.as_str().unwrap(),
);
let image_ref_name_found = image_yaml_vec_opt.is_some_and(|image_vec| {
image_vec.iter().any(|image| {
image
.get("ref_name")
.is_some_and(|ref_name| ref_name.eq(ref_name_to_find))
})
});
if !image_ref_name_found {
return Err(Error::Message(format!(
"Could not find image ref '{}' in SAT file. Exit",
ref_name_to_find.as_str().unwrap()
)));
}
} else if let Some(image_name_substr_to_find) = session_template_yaml
.get("image")
.and_then(|image| image.get("ims").and_then(|ims| ims.get("name")))
{
log::info!(
"Searching image name '{}' related to session template '{}' in SAT file",
image_name_substr_to_find.as_str().unwrap(),
session_template_yaml["name"].as_str().unwrap()
);
let mut image_found = image_yaml_vec_opt.is_some_and(|image_vec| {
image_vec.iter().any(|image| {
image
.get("name")
.is_some_and(|name| name.eq(image_name_substr_to_find))
})
});
if !image_found {
log::warn!(
"Image name '{}' not found in SAT file, looking in CSM",
image_name_substr_to_find.as_str().unwrap()
);
log::info!(
"Searching image name '{}' related to session template '{}' in CSM",
image_name_substr_to_find.as_str().unwrap(),
session_template_yaml["name"].as_str().unwrap()
);
image_found = ims::image::utils::get_fuzzy(
shasta_token,
shasta_base_url,
shasta_root_cert,
hsm_group_available_vec,
image_name_substr_to_find.as_str(),
Some(&1),
)
.await
.is_ok();
}
if !image_found {
return Err(Error::Message(format!(
"Could not find image name '{}' in session_template '{}'. Exit",
image_name_substr_to_find.as_str().unwrap(),
session_template_yaml["name"].as_str().unwrap()
)));
}
} else if let Some(image_id) = session_template_yaml
.get("image")
.and_then(|image| image.get("ims").and_then(|ims| ims.get("id")))
{
log::info!(
"Searching image id '{}' related to session template '{}' in CSM",
image_id.as_str().unwrap(),
session_template_yaml["name"].as_str().unwrap()
);
let image_found = ims::image::http_client::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
image_id.as_str(),
)
.await
.is_ok();
if !image_found {
return Err(Error::Message(format!(
"Could not find image id '{}' in session_template '{}'. Exit",
image_id.as_str().unwrap(),
session_template_yaml["name"].as_str().unwrap()
)));
}
} else if let Some(image_name_substr_to_find) =
session_template_yaml.get("image")
{
log::info!(
"Searching image name '{}' related to session template '{}' in CSM - ('sessiontemplate' section in SAT file is outdated - switching to backward compatibility)",
image_name_substr_to_find.as_str().unwrap(),
session_template_yaml["name"].as_str().unwrap()
);
let image_found = ims::image::utils::get_fuzzy(
shasta_token,
shasta_base_url,
shasta_root_cert,
hsm_group_available_vec,
image_name_substr_to_find.as_str(),
Some(&1),
)
.await
.is_ok();
if !image_found {
return Err(Error::Message(format!(
"Image name '{}' not found in CSM. Exit",
image_name_substr_to_find.as_str().unwrap()
)));
}
} else {
return Err(Error::Message(format!(
"Session template '{}' must have one of these entries 'image.ref_name', 'image.ims.name' or 'image.ims.id' values. Exit",
session_template_yaml["name"].as_str().unwrap(),
)));
}
log::info!(
"Validate 'session_template' '{}' configuration",
session_template_name
);
if let Some(configuration_to_find_value) =
session_template_yaml.get("configuration")
{
let configuration_to_find = configuration_to_find_value.as_str().unwrap();
log::info!(
"Searching configuration name '{}' related to session template '{}' in CSM in SAT file",
configuration_to_find,
session_template_yaml["name"].as_str().unwrap()
);
let mut configuration_found =
configuration_yaml_vec_opt.is_some_and(|configuration_yaml_vec| {
configuration_yaml_vec.iter().any(|configuration_yaml| {
configuration_yaml["name"].eq(configuration_to_find_value)
})
});
if !configuration_found {
log::warn!("Configuration not found in SAT file, looking in CSM");
log::info!(
"Searching configuration name '{}' related to session_template '{}' in CSM",
configuration_to_find,
session_template_yaml["name"].as_str().unwrap()
);
configuration_found = cfs::configuration::http_client::v3::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
Some(configuration_to_find),
)
.await
.is_ok();
if !configuration_found {
return Err(Error::Message(format!(
"Could not find configuration '{}' in session_template '{}'. Exit",
configuration_to_find,
session_template_yaml["name"].as_str().unwrap(),
)));
}
}
} else {
return Err(Error::Message(format!(
"Session template '{}' does not have 'configuration' value. Exit",
session_template_yaml["name"].as_str().unwrap(),
)));
}
}
Ok(())
}
pub async fn process_session_template_section_in_sat_file(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
ref_name_processed_hashmap: HashMap<String, String>,
hsm_group_available_vec: &[&str],
sat_file_yaml: Value,
do_not_reboot: bool,
dry_run: bool,
) -> Result<(), Error> {
let empty_vec = Vec::new();
let bos_session_template_list_yaml = sat_file_yaml["session_templates"]
.as_sequence()
.unwrap_or(&empty_vec);
if bos_session_template_list_yaml.is_empty() {
log::warn!("No 'session_templates' section found in SAT file. Skipping session template processing");
return Ok(());
}
let mut bos_st_created_vec: Vec<String> = Vec::new();
for bos_sessiontemplate_yaml in bos_session_template_list_yaml {
let _bos_sessiontemplate: BosSessionTemplate =
serde_yaml::from_value(bos_sessiontemplate_yaml.clone())
.map_err(|e| Error::Message(e.to_string()))?;
let image_details: ims::image::http_client::types::Image =
if let Some(bos_sessiontemplate_image) =
bos_sessiontemplate_yaml.get("image")
{
let (image_reference, is_image_id) =
get_image_reference_from_bos_sessiontemplate_yaml(
bos_sessiontemplate_image,
&ref_name_processed_hashmap,
)?;
if dry_run {
let dry_run_mock_image =
get_image_details_from_bos_sessiontemplate_yaml(
shasta_token,
shasta_base_url,
shasta_root_cert,
&hsm_group_available_vec,
&image_reference,
is_image_id,
)
.await
.unwrap_or_else(|_| {
let dry_run_mock_image = if is_image_id {
ims::image::http_client::types::Image {
id: Some(image_reference.to_string()),
created: None,
name: "dryrun_image".to_string(),
link: Some(Link {
path: "dryrun_path".to_string(),
etag: Some("dryrun_etag".to_string()),
r#type: "dryrun_type".to_string(),
}),
arch: None,
metadata: None,
}
} else {
ims::image::http_client::types::Image {
id: None,
created: None,
name: image_reference.to_string(),
link: Some(Link {
path: "dryrun_path".to_string(),
etag: Some("dryrun_etag".to_string()),
r#type: "dryrun_type".to_string(),
}),
arch: None,
metadata: None,
}
};
dry_run_mock_image
});
println!(
"Dry run mode: Generate mock Image\n{}",
serde_json::to_string_pretty(&dry_run_mock_image)?
);
dry_run_mock_image
} else {
get_image_details_from_bos_sessiontemplate_yaml(
shasta_token,
shasta_base_url,
shasta_root_cert,
&hsm_group_available_vec,
&image_reference,
is_image_id,
)
.await?
}
} else {
return Err(Error::Message(
"ERROR: no 'image' section in session_template.\nExit".to_string(),
));
};
log::info!("Image with name '{}' found", image_details.name);
let bos_session_template_configuration_name = bos_sessiontemplate_yaml
["configuration"]
.as_str()
.unwrap()
.to_string();
log::info!(
"Looking for CFS configuration with name: {}",
bos_session_template_configuration_name
);
if dry_run {
println!(
"Dry run mode: CFS configuration '{}' found in CSM.",
bos_session_template_configuration_name
);
} else {
cfs::configuration::http_client::v3::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
Some(&bos_session_template_configuration_name),
)
.await?;
};
let ims_image_etag =
image_details.link.as_ref().unwrap().etag.as_ref().unwrap();
let ims_image_path = &image_details.link.as_ref().unwrap().path;
let ims_image_type = &image_details.link.as_ref().unwrap().r#type;
let bos_sessiontemplate_name = bos_sessiontemplate_yaml["name"]
.as_str()
.unwrap_or("")
.to_string();
let mut boot_set_vec: HashMap<String, BootSet> = HashMap::new();
for (parameter, boot_set) in bos_sessiontemplate_yaml["bos_parameters"]
["boot_sets"]
.as_mapping()
.unwrap()
{
let kernel_parameters = boot_set["kernel_parameters"].as_str().unwrap();
let arch_opt = boot_set["arch"].as_str().map(|value| value.to_string());
let node_roles_groups_opt: Option<Vec<String>> = boot_set
.get("node_roles_groups")
.and_then(|node_roles_groups| {
node_roles_groups
.as_sequence()
.and_then(|node_role_groups| {
node_role_groups
.iter()
.map(|hsm_group_value| {
hsm_group_value
.as_str()
.map(|hsm_group| hsm_group.to_string())
})
.collect()
})
});
if !hsm_group_available_vec.is_empty()
&& node_roles_groups_opt
.clone()
.is_some_and(|node_roles_groups| !node_roles_groups.is_empty())
{
return Err(Error::Message(
"User type tenant can't user node roles in BOS sessiontemplate. Exit"
.to_string(),
));
}
let node_groups_opt: Option<Vec<String>> =
boot_set.get("node_groups").and_then(|node_groups_value| {
node_groups_value.as_sequence().and_then(|node_group| {
node_group
.iter()
.map(|hsm_group_value| {
hsm_group_value
.as_str()
.map(|hsm_group| hsm_group.to_string())
})
.collect()
})
});
let node_groups_opt =
Some(hsm::group::hacks::filter_system_hsm_group_names(
node_groups_opt.unwrap_or_default(),
));
for node_group in node_groups_opt.clone().unwrap_or_default() {
if !hsm_group_available_vec.contains(&node_group.as_str()) {
return Err(Error::Message(format!("User does not have access to HSM group '{}' in SAT file under session_templates.bos_parameters.boot_sets.compute.node_groups section. Exit", node_group)));
}
}
let node_list_opt: Option<Vec<String>> =
boot_set.get("node_list").and_then(|node_list_value| {
node_list_value.as_sequence().and_then(|node_list| {
node_list
.into_iter()
.map(|node_value_value| {
node_value_value
.as_str()
.map(|node_value| node_value.to_string())
})
.collect()
})
});
if let Some(node_list) = &node_list_opt {
validate_target_hsm_members(
shasta_token,
shasta_base_url,
shasta_root_cert,
&node_list.iter().map(|s| s.as_str()).collect::<Vec<&str>>(),
)
.await?;
}
let cfs = Cfs {
configuration: Some(bos_session_template_configuration_name.clone()),
};
let rootfs_provider = boot_set["rootfs_provider"]
.as_str()
.map(|value| value.to_string());
let rootfs_provider_passthrough = boot_set["rootfs_provider_passthrough"]
.as_str()
.map(|value| value.to_string());
let boot_set = BootSet {
name: None,
path: Some(ims_image_path.to_string()),
r#type: Some(ims_image_type.to_string()),
etag: Some(ims_image_etag.to_string()),
kernel_parameters: Some(kernel_parameters.to_string()),
node_list: node_list_opt,
node_roles_groups: node_roles_groups_opt, node_groups: node_groups_opt,
rootfs_provider,
rootfs_provider_passthrough,
cfs: Some(cfs),
arch: arch_opt,
};
boot_set_vec.insert(parameter.as_str().unwrap().to_string(), boot_set);
}
let cfs = Cfs {
configuration: Some(bos_session_template_configuration_name),
};
let create_bos_session_template_payload = BosSessionTemplate {
name: None,
description: None,
enable_cfs: Some(true),
cfs: Some(cfs),
boot_sets: Some(boot_set_vec),
links: None,
tenant: None,
};
if dry_run {
println!(
"Dry run mode: Create BOS sessiontemplate:\n{}",
serde_json::to_string_pretty(&create_bos_session_template_payload)?
);
let dry_run_bos_sessiontemplate_name =
format!("DRYRUN_{}", Uuid::new_v4().to_string());
println!(
"Dry Run Mode: BOS sessiontemplate name '{}' created",
dry_run_bos_sessiontemplate_name
);
bos_st_created_vec.push(dry_run_bos_sessiontemplate_name);
} else {
let bos_sessiontemplate = bos::template::http_client::v2::put(
shasta_token,
shasta_base_url,
shasta_root_cert,
&create_bos_session_template_payload,
&bos_sessiontemplate_name,
)
.await?;
println!(
"BOS sessiontemplate name '{}' created",
bos_sessiontemplate_name
);
bos_st_created_vec.push(bos_sessiontemplate.name.unwrap())
}
}
if do_not_reboot {
log::info!("Reboot canceled by user");
} else {
log::info!("Rebooting");
for bos_st_name in bos_st_created_vec {
log::info!(
"Creating BOS session for BOS sessiontemplate '{}' with action 'reboot'",
bos_st_name
);
let bos_session = BosSession {
name: None,
tenant: None,
operation: Some(Operation::Reboot),
template_name: bos_st_name.clone(),
limit: None,
stage: None,
include_disabled: None,
status: None,
components: None,
};
if dry_run {
println!(
"Dry run mode: Create BOS session:\n{}",
serde_json::to_string_pretty(&bos_session)?
);
} else {
bos::session::http_client::v2::post(
shasta_token,
shasta_base_url,
shasta_root_cert,
bos_session,
)
.await?;
}
}
}
let user = common::jwt_ops::get_name(shasta_token).unwrap();
let username = common::jwt_ops::get_preferred_username(shasta_token).unwrap();
log::info!(target: "app::audit", "User: {} ({}) ; Operation: Apply cluster", user, username);
Ok(())
}
fn get_image_reference_from_bos_sessiontemplate_yaml(
bos_sessiontemplate_image: &Value,
ref_name_processed_hashmap: &HashMap<String, String>,
) -> Result<(String, bool), Error> {
if let Some(bos_sessiontemplate_image_ims) =
bos_sessiontemplate_image.get("ims")
{
if let Some(bos_session_template_image_ims_name) =
bos_sessiontemplate_image_ims.get("name")
{
let image_name = bos_session_template_image_ims_name
.as_str()
.unwrap()
.to_string();
Ok((image_name, false))
} else if let Some(bos_session_template_image_ims_id) =
bos_sessiontemplate_image_ims.get("id")
{
let image_id = bos_session_template_image_ims_id
.as_str()
.unwrap()
.to_string();
Ok((image_id, true))
} else {
return Err(Error::Message("ERROR: neither 'image.ims.name' nor 'image.ims.id' fields defined in session_template.".to_string()));
}
} else if let Some(bos_session_template_image_image_ref) =
bos_sessiontemplate_image.get("image_ref")
{
let image_ref = bos_session_template_image_image_ref
.as_str()
.unwrap()
.to_string();
let image_id = ref_name_processed_hashmap
.get(&image_ref)
.unwrap()
.to_string();
Ok((image_id, true))
} else if let Some(image_name_substring) = bos_sessiontemplate_image.as_str()
{
let image_name = image_name_substring;
Ok((image_name.to_string(), false))
} else {
return Err(Error::Message("ERROR: neither 'image.ims' nor 'image.image_ref' nor 'image.<image id>' sections found in session_template.image.\nExit".to_string()));
}
}
async fn get_image_details_from_bos_sessiontemplate_yaml(
shasta_token: &str,
shasta_base_url: &str,
shasta_root_cert: &[u8],
hsm_group_available_vec: &[&str],
image_reference: &str,
is_image_id: bool,
) -> Result<ims::image::http_client::types::Image, Error> {
let image = if is_image_id {
ims::image::http_client::get(
shasta_token,
shasta_base_url,
shasta_root_cert,
Some(&image_reference),
)
.await
.map(|image_vec| image_vec.first().cloned().unwrap())
} else {
ims::image::utils::get_fuzzy(
shasta_token,
shasta_base_url,
shasta_root_cert,
hsm_group_available_vec,
Some(&image_reference),
Some(&1),
)
.await
.map(|image_vec| image_vec.first().cloned().unwrap())
};
image
}