#![doc = include_str!("configuration/config_template.toml")]
pub mod collectors;
pub mod configuration;
pub mod error;
pub mod output;
pub mod publisher;
use clap::{Parser, Subcommand};
use collectors::{
dmi::{self, DmiInformation},
network::{self, NetworkInformation},
plugin::execute,
};
use configuration::parser::{
check_config_file, set_up_configuration, view_config_file, write_config_file,
};
use publisher::{
auto_register_or_update_machine, register_machine, test_connection, update_machine,
};
use reqwest::blocking::Client;
use serde_json::Value;
use std::collections::HashMap;
use thanix_client::util::ThanixClient;
pub use error::NazaraError;
use crate::configuration::parser::ConfigData;
#[cfg(target_os = "linux")]
use crate::error::NazaraResult;
pub struct Nazara {
args: Args,
config: Option<ConfigData>,
client: Option<ThanixClient>,
}
#[derive(Debug)]
pub struct Machine {
pub name: Option<String>,
pub dmi_information: DmiInformation,
pub network_information: Vec<NetworkInformation>,
pub custom_information: Option<HashMap<String, Value>>,
}
impl Nazara {
pub fn new() -> NazaraResult<Self> {
let args = Args::parse();
Ok(Self {
args,
config: None,
client: None,
})
}
pub fn run(&mut self) -> NazaraResult<()> {
Self::print_banner();
if let Some(_) = self.handle_config_commands()? {
return Ok(());
}
let machine = self.prepare_machine()?;
self.config = Some(set_up_configuration(
self.args.uri.as_deref(),
self.args.token.as_deref(),
)?);
self.client = Some(self.prepare_client()?);
self.execute_operation(machine)?;
success!("All done, have a nice day!");
Ok(())
}
fn print_banner() -> () {
const ASCII_ART: &str = r#"
███╗ ██╗ █████╗ ███████╗ █████╗ ██████╗ █████╗
████╗ ██║██╔══██╗╚══███╔╝██╔══██╗██╔══██╗██╔══██╗
██╔██╗ ██║███████║ ███╔╝ ███████║██████╔╝███████║
██║╚██╗██║██╔══██║ ███╔╝ ██╔══██║██╔══██╗██╔══██║
██║ ╚████║██║ ██║███████╗██║ ██║██║ ██║██║ ██║
╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
(c) Tiara Hock aka ByteOtter. (github.com/ByteOtter)
Licensed under the terms of the GPL-v3.0 License.
Check github.com/The-Nazara-Project/Nazara/LICENSE for more info.
"#;
println!("{ASCII_ART}");
}
fn handle_config_commands(&self) -> NazaraResult<Option<()>> {
match &self.args.command {
Commands::WriteConfig {
uri,
token,
name,
description,
comments,
status,
primary_ip4,
primary_ip6,
device_type,
site,
role,
cluster_id,
json,
} => {
println!("Writing configuration file...");
if json.is_some() {
write_config_file(
"", "", name,
description,
comments,
status,
primary_ip4,
primary_ip6,
device_type,
role,
site,
cluster_id,
json,
)?;
} else {
let uri = uri.as_deref().ok_or_else(|| {
NazaraError::Other("Missing required argument: --uri".into())
})?;
let token = token.as_deref().ok_or_else(|| {
NazaraError::Other("Missing required argument: --token".into())
})?;
write_config_file(
uri,
token,
name,
description,
comments,
status,
primary_ip4,
primary_ip6,
device_type,
role,
site,
cluster_id,
&None,
)?;
}
success!("Configuration written successfully.");
return Ok(Some(()));
}
Commands::CheckConfig => {
check_config_file()?;
return Ok(Some(()));
}
Commands::ViewConfig => {
view_config_file()?;
return Ok(Some(()));
}
_ => Ok(None),
}
}
fn prepare_machine(&self) -> NazaraResult<Machine> {
let machine = start_collection(self.args.plugin.clone())?;
if machine.dmi_information.system_information.is_virtual && machine.name.is_none() {
return Err(NazaraError::Other(
"No name has been provided for this virtual machine! Providing a name as search parameter is mandatory for virtual machines.".into(),
));
}
if self.args.dry_run {
println!("Dry run results:");
dbg!(&machine);
std::process::exit(0)
}
Ok(machine)
}
fn prepare_client(&self) -> NazaraResult<ThanixClient> {
let config = set_up_configuration(self.args.uri.as_deref(), self.args.token.as_deref())?;
let client = ThanixClient {
base_url: config.get_netbox_uri().to_string(),
authentication_token: config.get_api_token().to_string(),
client: Client::new(),
};
println!("Testing connection...");
test_connection(&client)?;
Ok(client)
}
fn execute_operation(&self, machine: Machine) -> NazaraResult<()> {
let client = self
.client
.as_ref()
.ok_or_else(|| NazaraError::Other("Client not initialized".into()))?;
let config = self
.config
.as_ref()
.ok_or_else(|| NazaraError::Other("Configuration not initialized".into()))?;
match &self.args.command {
Commands::Register => register_machine(client, machine, config.clone())?,
Commands::Update { id } => {
update_machine(client, machine, config.clone(), id.to_owned())?
}
Commands::Auto {} => {
warn_auto_deprecated();
auto_register_or_update_machine(client, machine, config.clone())?;
}
_ => {}
}
Ok(())
}
}
pub fn warn_auto_deprecated() {
let msg = "
\x1b[33m[WARNING] +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ [WARNING]\x1b[0m
\x1b[33m[WARNING] Running Nazara in 'Auto' mode is deprecated. Please use 'register' or 'update' subcommands instead. [WARNING]\x1b[0m
\x1b[33m[WARNING] +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ [WARNING]\x1b[0m
";
println!("{}", msg);
}
pub fn start_collection(plugin: Option<String>) -> NazaraResult<Machine> {
Ok(Machine {
name: None,
dmi_information: dmi::construct_dmi_information()?,
network_information: network::construct_network_information()?,
custom_information: Some(execute(plugin)?),
})
}
#[derive(Debug, Subcommand)]
enum Commands {
Register,
Update {
#[arg(long)]
id: i64,
},
Auto,
WriteConfig {
#[arg(short, long, required_unless_present = "json", conflicts_with = "json")]
uri: Option<String>,
#[arg(short, long, required_unless_present = "json", conflicts_with = "json")]
token: Option<String>,
#[arg(short, long, conflicts_with = "json")]
name: Option<String>,
#[arg(short, long, conflicts_with = "json")]
description: Option<String>,
#[arg(short, long, conflicts_with = "json")]
comments: Option<String>,
#[arg(short, long, conflicts_with = "json")]
status: Option<String>,
#[arg(long, conflicts_with = "json")]
device_type: Option<i64>,
#[arg(long, conflicts_with = "json")]
role: Option<i64>,
#[arg(long, conflicts_with = "json")]
site: Option<i64>,
#[arg(long, conflicts_with = "json")]
cluster_id: Option<i64>,
#[arg(long, conflicts_with = "json")]
primary_ip4: Option<String>,
#[arg(long, conflicts_with = "json")]
primary_ip6: Option<String>,
#[arg(long, conflicts_with_all = &[
"uri", "token", "name", "description", "comments",
"status", "primary_ipv4", "primary_ipv6", "device_type", "role", "site", "cluster_id"
])]
json: Option<String>,
},
CheckConfig,
ViewConfig,
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about=None)]
struct Args {
#[arg(short, long)]
dry_run: bool,
#[arg(short, long)]
uri: Option<String>,
#[arg(short, long)]
token: Option<String>,
#[arg(short, long)]
plugin: Option<String>,
#[command(subcommand)]
command: Commands,
}