use crate::client;
use crate::config::KnifeConfig;
use colored::Colorize;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt;
#[derive(Debug)]
enum NodeAttribute {
IPAddress,
ChefEnvironment,
Name,
Platform,
Roles,
Recipes,
}
impl std::fmt::Display for NodeAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let display_str = match self {
NodeAttribute::IPAddress => "IP Address",
NodeAttribute::ChefEnvironment => "Chef Environment",
NodeAttribute::Name => "Name",
NodeAttribute::Platform => "Platform",
NodeAttribute::Roles => "Roles",
NodeAttribute::Recipes => "Recipes",
};
write!(f, "{}", display_str)
}
}
impl NodeAttribute {
fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"ipaddress" => Some(Self::IPAddress),
"chef_environment" => Some(Self::ChefEnvironment),
"name" => Some(Self::Name),
"platform" => Some(Self::Platform),
"roles" => Some(Self::Roles),
"recipes" => Some(Self::Recipes),
_ => None,
}
}
fn display(&self, node: &SearchNode) {
match self {
Self::Name => node.display_node_name(),
Self::IPAddress => node.display_ipaddress(),
Self::ChefEnvironment => node.display_chef_environment(),
Self::Roles => node.display_roles(),
Self::Platform => node.display_platform(),
Self::Recipes => node.display_recipes(),
}
}
}
#[derive(Deserialize, Serialize, Debug, Eq, PartialEq)]
pub struct SearchResult {
pub total: u16,
pub start: u16,
pub rows: Vec<SearchNode>,
}
#[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone)]
pub struct SearchNode {
pub chef_environment: String,
pub name: String,
pub run_list: Vec<String>,
#[serde(default)]
pub ipaddress: String,
#[serde(default)]
pub macaddress: String,
#[serde(default)]
pub hostname: String,
#[serde(default)]
pub os: String,
#[serde(default)]
pub os_version: String,
#[serde(default)]
pub machinename: String,
#[serde(default)]
pub fqdn: String,
#[serde(default)]
pub platform: String,
#[serde(default)]
pub platform_family: String,
#[serde(default)]
pub platform_version: String,
#[serde(default)]
pub recipes: Vec<String>,
#[serde(default)]
pub roles: Vec<String>,
}
impl SearchNode {
pub fn display(&self, attributes: &[String]) {
if attributes.is_empty() {
self.display_node_name();
self.display_ipaddress();
self.display_chef_environment();
self.display_roles();
self.display_runlist();
self.display_recipes();
self.display_platform_family();
self.display_platform_version();
println!("\n");
} else {
println!("{}", self.name);
for attribute in attributes {
if let Some(attr) = NodeAttribute::from_str(attribute) {
attr.display(self);
}
} println!("\n");
}
}
fn display_node_name(&self) {
println!("{}: {}", "Node name".green().bold(), self.name);
}
fn display_ipaddress(&self) {
println!("{}: {}", "IP Address".green().bold(), self.ipaddress);
}
fn display_chef_environment(&self) {
println!(
"{}: {}",
"Chef Environment".green().bold(),
self.chef_environment
);
}
fn display_roles(&self) {
println!(
"{}: {}",
"Roles".green().bold(),
self.roles.join(", ")
);
}
fn display_platform(&self) {
println!(
"{}: {}",
"Platform".green().bold(),
self.platform_family
);
}
fn display_runlist(&self) {
println!(
"{}: {}",
"Run List".green().bold(),
self.run_list.join(", ")
);
}
fn display_recipes(&self) {
println!(
"{}: {}",
"Recipes".green().bold(),
self.recipes.join(", ")
);
}
fn display_platform_family(&self) {
println!(
"{}: {}",
"Platform".green().bold(),
self.platform_family
);
}
fn display_platform_version(&self) {
println!(
"{}: {}",
"Platform version".green().bold(),
self.platform_version
);
}
}
#[derive(Serialize, Deserialize)]
pub struct ChefSearchResponseRaw {
#[serde(default)]
pub total: i32,
#[serde(default)]
pub start: i32,
pub rows: Vec<ChefNodeRowRaw>,
}
#[derive(Serialize, Deserialize)]
pub struct ChefNodeRowRaw {
#[serde(default)]
pub url: String,
pub data: NodeDocumentRaw,
}
#[derive(Serialize, Deserialize)]
pub struct NodeDocumentRaw {
#[serde(default)]
pub chef_environment: Option<String>,
#[serde(default)]
pub node_name: Option<String>,
#[serde(default)]
pub run_list: Vec<String>,
#[serde(default)]
pub ipaddress: Option<String>,
#[serde(default)]
pub macaddress: Option<String>,
#[serde(default)]
pub hostname: Option<String>,
#[serde(default)]
pub os: Option<String>,
#[serde(default)]
pub os_version: Option<String>,
#[serde(default)]
pub fqdn: Option<String>,
#[serde(default)]
pub platform: Option<String>,
#[serde(default)]
pub platform_family: Option<String>,
#[serde(default)]
pub platform_version: Option<String>,
#[serde(default)]
pub recipes: Option<Vec<String>>,
#[serde(default)]
pub roles: Option<Vec<String>>,
}
impl From<ChefNodeRowRaw> for SearchNode {
fn from(raw: ChefNodeRowRaw) -> Self {
SearchNode {
chef_environment: raw.data.chef_environment.unwrap_or_default(),
name: raw.data.node_name.unwrap_or_default(), run_list: raw.data.run_list,
ipaddress: raw.data.ipaddress.unwrap_or_default(),
macaddress: raw.data.macaddress.unwrap_or_default(),
hostname: raw.data.hostname.clone().unwrap_or_default(),
os: raw.data.os.unwrap_or_default(),
os_version: raw.data.os_version.unwrap_or_default(),
machinename: raw.data.hostname.unwrap_or_default(), fqdn: raw.data.fqdn.unwrap_or_default(),
platform: raw.data.platform.unwrap_or_default(),
platform_family: raw.data.platform_family.unwrap_or_default(),
platform_version: raw.data.platform_version.unwrap_or_default(),
recipes: raw.data.recipes.unwrap_or_default(),
roles: raw.data.roles.unwrap_or_default(),
}
}
}
pub async fn display_search_nodes(config: &KnifeConfig, query: &str, attributes: &[String]) {
match search_nodes(config, query).await {
Ok(nodes) => nodes.iter().for_each(|n| n.display(attributes)),
Err(e) => {
println!("Error during search: {}", e);
}
}
}
pub async fn search_nodes(
config: &KnifeConfig,
query: &str,
) -> Result<Vec<SearchNode>, Box<dyn Error + Send + Sync>> {
let request_path = format!("/organizations/{}/search/node", config.organization);
match client::request::post(config, &request_path, query).await {
Ok(k) => match k.status {
200 => {
let body: ChefSearchResponseRaw = match serde_json::from_str(&k.body) {
Ok(body) => body,
Err(e) => return Err(format!("parsing return JSON: {}", e).into()),
};
let nodes: Vec<SearchNode> = body.rows.into_iter().map(SearchNode::from).collect();
Ok(nodes)
}
400 => Err(
"HTTP Status: 400 - Request parameters or body have missing or invalid fields"
.into(),
),
401 => Err("HTTP Status: 401 - failed authentication!".into()),
403 => Err("HTTP Code: 403 . Permission denied".into()),
404 => Err("HTTP Code: 404 . Resource does not exist.".into()),
406 => Err("HTTP Code: 406. Accept header does not include application/json".into()),
_ => {
println!("HTTP Status code: {}", k.status);
println!("Body returned: {:#?}", k.body);
Err(format!("HTTP Status: {}", k.status).into())
}
},
Err(e) => Err(format!("search {}: {}", query, e).into()),
}
}