#![doc = include_str!("configuration/config_template.toml")]
#[macro_use]
pub mod output;
pub mod collectors;
pub mod configuration;
pub mod constants;
pub mod error;
pub mod investigator;
pub mod publisher;
use clap::{Parser, Subcommand, ValueEnum};
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 investigator::check_environment;
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;
use crate::configuration::parser::ConfigData;
use crate::error::*;
use std::sync::OnceLock;
pub struct Nazara {
args: Args,
config: Option<ConfigData>,
client: Option<ThanixClient>,
}
#[derive(PartialEq, PartialOrd, ValueEnum, Clone, Debug)]
pub enum LogLevelList {
Trace,
Debug,
Info,
Warn,
Error,
Fatal,
}
pub static LOG_LEVEL: OnceLock<LogLevelList> = OnceLock::new();
#[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>>,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum IpAssignmentMode {
Static,
DhcpIgnore,
DhcpObserved,
}
impl Nazara {
pub fn new() -> NazaraResult<Self> {
let args = Args::parse();
Ok(Self {
args,
config: None,
client: None,
})
}
pub fn run(&mut self) -> NazaraResult<()> {
LOG_LEVEL.set(self.args.log_level.clone()).unwrap();
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 BytePaws. (codeberg.org/BytePaws)
Licensed under the terms of the GPL-v3.0 License.
Check https://codeberg.org/nazara-project/Nazara/src/branch/main/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,
tenant,
location,
rack,
platform,
site,
role,
cluster_id,
force,
json,
} => {
status!("Writing configuration file...");
if json.is_some() {
write_config_file(
uri, token, name,
description,
comments,
status,
primary_ip4,
primary_ip6,
device_type,
tenant,
location,
rack,
platform,
role,
site,
cluster_id,
force,
json,
)?;
} else {
write_config_file(
uri,
token,
name,
description,
comments,
status,
primary_ip4,
primary_ip6,
device_type,
tenant,
location,
rack,
platform,
role,
site,
cluster_id,
force,
&None,
)?;
}
success!("Configuration written successfully.");
return Ok(Some(()));
}
Commands::CheckConfig => {
check_config_file()?;
return Ok(Some(()));
}
Commands::ViewConfig => {
view_config_file()?;
return Ok(Some(()));
}
Commands::PrepareEnvironment => {
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(),
};
test_connection(&client)?;
check_environment(&client, &config, true)?;
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(),
};
status!("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 {
ip_mode,
prepare_environment,
} => {
check_environment(client, config, *prepare_environment)?;
let mode = ip_mode.unwrap_or(IpAssignmentMode::Static);
register_machine(client, machine, config.clone(), mode)?
}
Commands::Update {
id,
ip_mode,
prepare_environment,
} => {
check_environment(client, config, *prepare_environment)?;
let mode = ip_mode.unwrap_or(IpAssignmentMode::Static);
update_machine(client, machine, config.clone(), id.to_owned(), mode)?
}
Commands::Auto { ip_mode } => {
warn_auto_deprecated();
let mode = ip_mode.unwrap_or(IpAssignmentMode::Static);
auto_register_or_update_machine(client, machine, config.clone(), mode)?;
}
_ => {}
}
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 {
#[arg(long, value_enum, default_value = "static")]
ip_mode: Option<IpAssignmentMode>,
#[arg(long)]
prepare_environment: bool,
},
Update {
#[arg(long)]
id: i64,
#[arg(long, value_enum, default_value = "static")]
ip_mode: Option<IpAssignmentMode>,
#[arg(long)]
prepare_environment: bool,
},
Auto {
#[arg(long, value_enum, default_value = "static")]
ip_mode: Option<IpAssignmentMode>,
},
WriteConfig {
#[arg(short, long, conflicts_with = "json")]
uri: Option<String>,
#[arg(short, long, 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")]
tenant: Option<i64>,
#[arg(long, conflicts_with = "json")]
platform: Option<i64>,
#[arg(long, conflicts_with = "json")]
location: Option<i64>,
#[arg(long, conflicts_with = "json")]
rack: 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)]
force: bool,
#[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,
PrepareEnvironment,
}
#[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>,
#[arg(long, default_value = "debug", value_enum)]
log_level: LogLevelList,
#[command(subcommand)]
command: Commands,
}