use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::Error;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Group {
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub members: Option<Member>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "exclusiveGroup"))]
pub exclusive_group: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Member {
#[serde(skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct XnameId {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
impl Group {
pub fn new(label: &str, member_vec_opt: Option<Vec<&str>>) -> Self {
let members_opt = if let Some(member_vec) = member_vec_opt {
Some(Member {
ids: Some(member_vec.iter().map(|&id| id.to_string()).collect()),
})
} else {
None
};
let group = Self {
label: label.to_string(),
description: None,
tags: None,
members: members_opt,
exclusive_group: None,
};
group
}
pub fn get_members(&self) -> Vec<String> {
self.members
.clone()
.map(|member| member.ids.unwrap_or_default())
.unwrap_or_default()
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentArray {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Components"))]
pub components: Option<Vec<Component>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Component {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "ID"))]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Type"))]
pub r#type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "State"))]
pub state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Flag"))]
pub flag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Enabled"))]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "SoftwareStatus"))]
pub software_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Role"))]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "SubRole"))]
pub sub_role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "NID"))]
pub nid: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Subtype"))]
pub subtype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "NetType"))]
pub net_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Arch"))]
pub arch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Class"))]
pub class: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "ReservationDisabled"))]
pub reservation_disabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Locked"))]
pub locked: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentArrayPostQuery {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "ComponentIDs"))]
pub component_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "stateonly"))]
pub state_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "flagonly"))]
pub falg_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "roleonly"))]
pub role_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "nidonly"))]
pub nid_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "softwarestatus"))]
pub software_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subrole: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
arch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub class: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nid_start: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nid_end: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentArrayPostByNidQuery {
#[serde(rename(serialize = "NIDRanges"))]
pub nid_ranges: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "stateonly"))]
pub state_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "flagonly"))]
pub falg_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "roleonly"))]
pub role_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "nidonly"))]
pub nid_only: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentArrayPostArray {
#[serde(rename(serialize = "Components"))]
pub components: Vec<ComponentCreate>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Force"))]
pub force: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentCreate {
#[serde(rename(serialize = "ID"))]
pub id: String,
#[serde(rename(serialize = "State"))]
pub state: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Flag"))]
pub flag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Enabled"))]
pub enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "SoftwareStatus"))]
pub software_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Role"))]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "SubRole"))]
pub sub_role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "NID"))]
pub nid: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Subtype"))]
pub subtype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "NetType"))]
pub net_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Arch"))]
pub arch: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Class"))]
pub class: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ComponentPut {
pub component: ComponentCreate,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename(serialize = "Force"))]
pub force: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct BootParameters {
#[serde(default)]
pub hosts: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub macs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nids: Option<Vec<u32>>,
#[serde(default)]
pub params: String, #[serde(default)]
pub kernel: String,
#[serde(default)]
pub initrd: String,
#[serde(rename = "cloud-init")]
#[serde(skip_serializing_if = "Option::is_none")]
pub cloud_init: Option<Value>,
}
impl BootParameters {
pub fn get_image_id_from_s3_path(s3_path: &str) -> Option<&str> {
s3_path.split("/").skip(3).next()
}
pub fn get_boot_image(&self) -> String {
let params: HashMap<&str, &str> = self
.params
.split_whitespace()
.map(|kernel_param| {
kernel_param
.split_once('=')
.map(|(key, value)| (key.trim(), value.trim()))
.unwrap_or((kernel_param, ""))
})
.collect();
let root_kernel_param_opt = params.get("root");
let metal_server_kernel_param_opt = params.get("metal.server");
let boot_image_id_opt: Option<&str> = if let Some(root_kernel_param) = root_kernel_param_opt
{
Self::get_image_id_from_s3_path(root_kernel_param)
} else if let Some(metal_server_kernel_param) = metal_server_kernel_param_opt {
Self::get_image_id_from_s3_path(metal_server_kernel_param)
} else {
None
};
boot_image_id_opt.unwrap_or("").to_string()
}
pub fn update_boot_image(&mut self, new_image_id: &str) -> Result<bool, Error> {
let mut changed = false;
let mut params: HashMap<&str, &str> = self
.params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.collect();
let root_kernel_param_rslt = params.get("root");
let mut root_kernel_param: Vec<&str> = match root_kernel_param_rslt {
Some(root_kernel_param) => root_kernel_param.split("/").collect::<Vec<&str>>(),
None => {
return Err(Error::Message(
"ERROR - The 'root' kernel param is missing from user input".to_string(),
));
}
};
for current_image_id in &mut root_kernel_param {
if uuid::Uuid::try_parse(current_image_id).is_ok() {
if *current_image_id != new_image_id {
changed = true;
}
*current_image_id = new_image_id;
}
}
let new_root_kernel_param = root_kernel_param.join("/");
params
.entry("root")
.and_modify(|root_param| *root_param = &new_root_kernel_param);
self.update_kernel_param("root", &new_root_kernel_param);
let mut params: HashMap<&str, &str> = self
.params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.map(|(key, value)| (key.trim(), value.trim()))
.collect();
let mut metal_server_kernel_param: Vec<&str>;
if let Some(metal_server_data) = params.get("metal.server") {
metal_server_kernel_param = metal_server_data.split("/").collect();
for substring in &mut metal_server_kernel_param {
if uuid::Uuid::try_parse(substring).is_ok() {
*substring = new_image_id;
changed = true;
}
}
let new_metal_server_kernel_param = metal_server_kernel_param.join("/");
params
.entry("metal.server")
.and_modify(|metal_server_param| {
*metal_server_param = &new_metal_server_kernel_param
});
self.update_kernel_param("metal.server", &new_metal_server_kernel_param);
params = self
.params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.collect();
} else {
};
let mut nmd_kernel_param: Vec<&str>;
if let Some(nmd_data) = params.get("nmd_data") {
nmd_kernel_param = nmd_data.split("/").collect();
for substring in &mut nmd_kernel_param {
if uuid::Uuid::try_parse(substring).is_ok() {
*substring = new_image_id;
changed = true;
}
}
let new_nmd_kernel_param = nmd_kernel_param.join("/");
params
.entry("nmd_data")
.and_modify(|nmd_param| *nmd_param = &new_nmd_kernel_param);
self.update_kernel_param("nmd_data", &new_nmd_kernel_param);
} else {
};
self.kernel = format!("s3://boot-images/{}/kernel", new_image_id);
self.initrd = format!("s3://boot-images/{}/initrd", new_image_id);
Ok(changed)
}
pub fn update_kernel_params(&mut self, new_params: &str) -> bool {
let mut change = false;
let new_params: Vec<(&str, &str)> = new_params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.map(|(key, value)| (key.trim(), value.trim()))
.collect();
let mut params: HashMap<&str, &str> = self
.params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.collect();
for (new_key, new_value) in &new_params {
for (key, value) in params.iter_mut() {
if *key == *new_key {
log::debug!("key '{}' found", key);
if value != new_value {
log::info!("changing key {} from {} to {}", key, value, new_value);
*value = new_value;
change = true
} else {
log::debug!("key '{}' value does not change ({})", key, value);
}
}
}
}
if change == false {
log::debug!("No value change in kernel params. Checking is either new params have been added or removed");
if new_params.len() != params.len() {
log::info!("num kernel parameters have changed");
change = true;
}
}
self.params = params
.iter()
.map(|(key, value)| {
if !value.is_empty() {
format!("{key}={value}")
} else {
key.to_string()
}
})
.collect::<Vec<String>>()
.join(" ");
change
}
pub fn update_kernel_param(&mut self, new_key: &str, new_value: &str) -> bool {
let mut changed = false;
let mut params: HashMap<&str, &str> = self
.params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.map(|(key, value)| (key.trim(), value.trim()))
.collect();
for (current_key, current_value) in params.iter_mut() {
if *current_key == new_key {
log::debug!("key '{}' found", new_key);
if *current_value != new_value {
log::info!(
"changing key {} from {} to {}",
new_key,
current_value,
new_value
);
*current_value = new_value;
changed = true
} else {
log::debug!(
"key '{}' value does not change ({})",
new_key,
current_value
);
}
}
}
self.params = params
.iter()
.map(|(key, value)| {
if !value.is_empty() {
format!("{key}={value}")
} else {
key.to_string()
}
})
.collect::<Vec<String>>()
.join(" ");
changed
}
pub fn add_kernel_params(&mut self, new_kernel_params: &str) -> bool {
let mut changed = false;
let mut params: HashMap<&str, &str> = self
.params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.map(|(key, value)| (key.trim(), value.trim()))
.collect();
let new_kernel_params_tuple: HashMap<&str, &str> = new_kernel_params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.collect();
for (key, new_value) in new_kernel_params_tuple {
if params.contains_key(key) {
log::info!("key '{}' already exists, the new kernel parameter won't be added since it already exists", key);
} else {
log::info!(
"key '{}' not found, adding new kernel param with value '{}'",
key,
new_value
);
params.insert(key, new_value);
changed = true
}
}
self.params = params
.iter()
.map(|(key, value)| {
if !value.is_empty() {
format!("{key}={value}")
} else {
key.to_string()
}
})
.collect::<Vec<String>>()
.join(" ");
changed
}
pub fn delete_kernel_params(&mut self, kernel_params_to_delete: &str) -> bool {
let mut changed = false;
let mut params: HashMap<&str, &str> = self
.params
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.map(|(key, value)| (key.trim(), value.trim()))
.collect();
let kernel_params_to_delete_tuple: HashMap<&str, &str> = kernel_params_to_delete
.split_whitespace()
.map(|kernel_param| kernel_param.split_once('=').unwrap_or((kernel_param, "")))
.collect();
for (key, _value) in kernel_params_to_delete_tuple {
changed = changed | params.remove(key).is_some();
}
self.params = params
.iter()
.map(|(key, value)| {
if !value.is_empty() {
format!("{key}={value}")
} else {
key.to_string()
}
})
.collect::<Vec<String>>()
.join(" ");
changed
}
}