From 73c8a7c91193243c16e9eb4e55c87d3402b441bb Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Sun, 18 Feb 2024 11:08:19 +0100 Subject: [PATCH 01/14] return device id or None in search function implement skeleton for device creation (tmp) --- CONTRIBUTING.md | 11 +- Cargo.toml | 4 +- src/configuration/config_parser.rs | 4 +- src/main.rs | 24 +---- src/publisher/publisher.rs | 155 +++++++++++++++++++++++------ src/publisher/translator.rs | 11 +- 6 files changed, 148 insertions(+), 61 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 528d7ef..6f54dd2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,4 +47,13 @@ If you discover a security vulnerability in our code, please inform us privately If you wish to fix a vulnerability, please also inform us and stand by for our green light. We would still like to investigate the vulnerability for ourselves to get an overview over the severity and scope of the problem. *Please refrain from publishing a fix until we came back to you or have published a security advisory*. -#### Thank you for your contribution! \ No newline at end of file +# Disclaimer + +By contributing to this project you agree to surrender your contribution to the Nazara Project. + +Your contribution to this project will be subject to the same licensing terms as the rest of the project. + +This is a standard practice in open-source projects to ensure that all contributions are compatible with the project's overall license and to maintain consistency in the project's legal framework. + +It's important for contributors to be aware of this agreement to ensure that their contributions are properly integrated into the project without any legal conflicts. +#### Thank you for your contribution! diff --git a/Cargo.toml b/Cargo.toml index 951ae84..05b66f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,10 @@ toml = "0.7.6" # This may not work in your environment. You can get your schema by visiting # https://your.netbox-instance.com/api/schema. # The yaml file will be downloaded and you can generate your client by using https://github.com/The-Nazara-Project/Thanix. -thanix_client = "0.7.0" +thanix_client = "1.0.0" # Uncomment this line if you are using a custom thanix client implementation. # Change the path to be relative to this Cargo.toml file. -# thanix_client = { path = "path/to/your/client/" } +# thanix_client = { path = "../thanix_client/" } [dev-dependencies] mockall = "0.11.4" diff --git a/src/configuration/config_parser.rs b/src/configuration/config_parser.rs index 82013e1..b34d1c2 100644 --- a/src/configuration/config_parser.rs +++ b/src/configuration/config_parser.rs @@ -45,7 +45,7 @@ pub struct ConfigData { /// /// # Panics /// -/// The function panics under these condition: +/// The function panics under these conditions: /// /// * If the initialization of the config file raises an error. /// * When using a default (empty) configuration file and not providing all required CLI arguments. @@ -176,7 +176,7 @@ fn get_config_dir() -> PathBuf { let config_file_path: PathBuf = Path::new(&home_dir).join(".nazara-config.toml"); - return config_file_path; + config_file_path } impl ConfigData { diff --git a/src/main.rs b/src/main.rs index ece2bd4..70753f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -116,7 +116,8 @@ fn main() { let dmi_information: dmi_collector::DmiInformation = dmi_collector::construct_dmi_information(); - let network_information = network_collector::construct_network_information().unwrap(); + let network_information: Vec = + network_collector::construct_network_information().unwrap(); let machine: Machine = Machine { name: args.name, @@ -126,25 +127,10 @@ fn main() { // Passing a name in any way is mandatory for a virtual machine if machine.dmi_information.system_information.is_virtual && machine.name.is_none() { - panic!("[FATAL] No name has been provided for this virtual machine! Providing a name as search parameter is mandatory for virtual machines.") + eprintln!("[FATAL] No name has been provided for this virtual machine! Providing a name as search parameter is mandatory for virtual machines."); + process::exit(1) } + // Register the machine or VM with NetBox let _ = register_machine(&client, machine); - - // println!("{:#?}", network_information); - - // let system_information: SystemData = SystemData { - // dmi_information, - // network_information, - // name: config.name, - // system_location: config.get_system_location().to_string(), - // device_role: config.device_role, - // }; - - // let payload: CreateMachinePayload = CreateMachinePayload { system_information }; - - // match netbox_client.create_machine(&payload) { - // Ok(()) => println!("Machine created!"), - // Err(err) => eprintln!("Error: {:?}", err), - // } } diff --git a/src/publisher/publisher.rs b/src/publisher/publisher.rs index be8b33e..df116fc 100644 --- a/src/publisher/publisher.rs +++ b/src/publisher/publisher.rs @@ -7,25 +7,23 @@ //! The actual request logic will be provided by the `thanix_client` crate. //! //! The `api_client` module will provide the actual client and request logic. -use std::io::{self, Write}; +//! +use std::process; -use serde::{Deserialize, Serialize}; +/// TODO: 1. Implement Creation/update logic 2. Denest by splitting query logic off 3. Do not panic upon request fail use thanix_client::{ - paths::{self, DcimDevicesListQuery, VirtualizationVirtualMachinesListQuery}, + paths::{ + self, DcimDevicesListQuery, DcimDevicesListResponse, + VirtualizationVirtualMachinesListQuery, VirtualizationVirtualMachinesListResponse, + }, types::{ DeviceWithConfigContext, PaginatedDeviceWithConfigContextList, - PaginatedVirtualMachineWithConfigContextList, VirtualMachineWithConfigContext, - WritableDeviceWithConfigContextRequest, + VirtualMachineWithConfigContext, WritableDeviceWithConfigContextRequest, }, util::ThanixClient, }; -use crate::{ - collectors::{dmi_collector::DmiInformation, network_collector::NetworkInformation}, - publisher::api_client::test_connection, - publisher::translator, - Machine, -}; +use crate::{publisher::api_client::test_connection, publisher::translator, Machine}; use super::publisher_exceptions::NetBoxApiError; @@ -57,7 +55,7 @@ pub fn probe(client: &ThanixClient) -> Result<(), NetBoxApiError> { } } -/// Register this machine in NetBox. +/// Register this machine or VM in NetBox. /// /// # Parameters /// @@ -71,8 +69,92 @@ pub fn register_machine(client: &ThanixClient, machine: Machine) -> Result<(), N let nb_devices: DeviceListOrVMList = get_machines(client, &machine); - search_for_matches(&machine, &nb_devices); + if machine.dmi_information.system_information.is_virtual { + todo!() // TODO: VM Creation / Update + } else { + let payload: WritableDeviceWithConfigContextRequest = + translator::information_to_device(&machine); + + match search_for_matches(&machine, &nb_devices) { + Some(device_id) => { + todo!() // TODO Implement machine update + } + None => { + match paths::dcim_devices_create(&client, payload) { + Ok(response) => { + todo!() // Check response code 201, handle other. (Should not happen) + } + Err(err) => { + panic!("{}", err) // Handle failure correctly + } + } + } + } + } + + // check if virtual machine, create or update virtual machine. + // if machine.dmi_information.system_information.is_virtual { + // match search_for_matches(&machine, &nb_devices) { + // Some(vm_id) => { + // match paths::virtualization_virtual_machines_update( + // &client, + // VirtualizationVirtualMachinesUpdateQuery::default(), + // vm_id, + // ) { + // Ok(response) => { + // todo!() + // } + // Err(err) => { + // panic!("{}", err) + // } + // } + // } + // None => { + // match paths::virtualization_virtual_machines_create( + // &client, + // VirtualizationVirtualMachinesCreateQuery::default(), + // ) { + // Ok(response) => { + // todo!() + // } + // Err(err) => { + // panic!("{}", err) + // } + // } + // } + // } + // } else { + // // proper physical machines + // match search_for_matches(&machine, &nb_devices) { + // Some(id) => { + // match paths::dcim_devices_update(&client, DcimDevicesUpdateQuery::default(), id) { + // Ok(response) => { + // todo!() + // } + // Err(err) => { + // panic!("{}", err) + // } + // } + // } + // None => match paths::dcim_devices_create(&client, DcimDevicesCreateQuery::default()) { + // Ok(response) => { + // todo!() + // } + // Err(err) => { + // panic!("{}", err) + // } + // }, + // } + // } + + Ok(()) +} +/// Creates a new machine in NetBox by calling the `translator` module to translate the `machine` parameter +/// struct into the correct data type required by the API. +fn create_machine(client: &ThanixClient, machine: &Machine) -> Result<(), NetBoxApiError> { + println!("Creating new machine in NetBox..."); + let payload = translator::information_to_device(machine); Ok(()) } @@ -108,12 +190,15 @@ fn get_machines(client: &ThanixClient, machine: &Machine) -> DeviceListOrVMList ) { Ok(response) => { println!("List received. Analyzing..."); - let debug_json = response.text().unwrap(); - - let response_content: PaginatedVirtualMachineWithConfigContextList = - serde_json::from_str(&debug_json).unwrap(); - let vm_list: Vec = response_content.results; + let vm_list: Vec = match response { + VirtualizationVirtualMachinesListResponse::Http200(virtual_machines) => { + virtual_machines.results + } + _ => { + todo!(); + } + }; DeviceListOrVMList::VmList(vm_list) } @@ -125,21 +210,25 @@ fn get_machines(client: &ThanixClient, machine: &Machine) -> DeviceListOrVMList match paths::dcim_devices_list(client, DcimDevicesListQuery::default()) { Ok(response) => { println!("List received. Analyzing..."); - let debug_json = response.text().unwrap(); - - let response_content: PaginatedDeviceWithConfigContextList = - serde_json::from_str(&debug_json).unwrap(); - let device_list: Vec = response_content.results; + let device_list: Vec = match response { + DcimDevicesListResponse::Http200(devices) => devices.results, + _ => { + todo!(); + } + }; DeviceListOrVMList::DeviceList(device_list) } - Err(err) => panic!("{}", err), + Err(err) => { + eprintln!("\x1b[31m[error]\x1b[0m Failure while retrieving list of devices. Please make sure your NetBox database is set up correctly.\n{}", err); + process::exit(1); + } } } } -/// Searches for matching device in list of machines. +/// Searches for matching device in list of machines and returns the device id in case of a match. /// /// Primary search parameters are the device's **serial number** and **UUID** acquired by `dmidecode`. /// @@ -153,8 +242,8 @@ fn get_machines(client: &ThanixClient, machine: &Machine) -> DeviceListOrVMList /// /// # Returns /// -/// - `bool` - Depending on if the device has been found or not. -fn search_for_matches(machine: &Machine, device_list: &DeviceListOrVMList) -> bool { +/// - `Option` - Either returns the id of the device or Vm found, or None. +fn search_for_matches(machine: &Machine, device_list: &DeviceListOrVMList) -> Option { match device_list { DeviceListOrVMList::DeviceList(devices) => { if machine.name.is_none() { @@ -162,30 +251,30 @@ fn search_for_matches(machine: &Machine, device_list: &DeviceListOrVMList) -> bo for device in devices { if machine.dmi_information.system_information.serial == device.serial { println!("\x1b[32m[success]\x1b[0m Machine found using serial number!"); - return true; + return Some(device.id); } } println!("\x1b[36m[info]\x1b[0m Machine not found using serial number."); - return false; + return None; } for device in devices { if device.name == machine.name { println!("\x1b[32m[success]\x1b[0m Machine found using name!"); - return true; + return Some(device.id); } } println!("\x1b[36m[info]\x1b[0m Machine not found in registered machines using name."); - false + None } DeviceListOrVMList::VmList(virtual_machines) => { for vm in virtual_machines { if machine.name.as_ref().unwrap() == &vm.name { println!("\x1b[32m[success]\x1b[0m VM found found using serial number!"); - return true; + return Some(vm.id); } } println!("\x1b[36m[info]\x1b[0m VM not found in registered machines."); - false + None } } } diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index afd1249..194c63f 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -1,15 +1,18 @@ //! # Translator Module //! //! This module handles the translation and processing of the data sent to or received from NetBox. -use thanix_client::types::{DeviceWithConfigContext, WritableDeviceWithConfigContextRequest}; +use thanix_client::types::{ + WritableDeviceWithConfigContextRequest, WritableVirtualMachineWithConfigContextRequest, +}; use crate::Machine; -/// Translates a Request into a Vector of Objects. -pub fn translate_response_to_vec() -> Vec { +/// Translate the machine information to a `WritableDeviceWithConfigContextRequest` required by +/// NetBox's API. +pub fn information_to_device(machine: &Machine) -> WritableDeviceWithConfigContextRequest { todo!() } -pub fn information_to_device(machine: &Machine) -> WritableDeviceWithConfigContextRequest { +pub fn information_to_vm(machine: &Machine) -> WritableVirtualMachineWithConfigContextRequest { todo!() } From 910c0fbb1d14c89c010834d2b048151ff08c192b Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Wed, 6 Mar 2024 22:00:54 +0100 Subject: [PATCH 02/14] rewrite config file structure and config parser struct --- src/configuration/config_parser.rs | 328 ++++++++++--------------- src/configuration/config_template.toml | 45 ++++ src/main.rs | 18 +- src/publisher/publisher.rs | 55 +++-- src/publisher/translator.rs | 10 +- 5 files changed, 229 insertions(+), 227 deletions(-) create mode 100644 src/configuration/config_template.toml diff --git a/src/configuration/config_parser.rs b/src/configuration/config_parser.rs index b34d1c2..65909ff 100644 --- a/src/configuration/config_parser.rs +++ b/src/configuration/config_parser.rs @@ -9,27 +9,69 @@ //! netbox_api_token = "" //! //! [system] -//! location = "" +//! name = "some_name" # Required for virtual machines! +//! site_id = 0 +//! description = "" +//! comments = "Automatically registered using Nazara." +//! device_type = 0 +//! role = 0 +//! +//! +//! # These will be parsed a singl HashMap with no further checking. +//! # Make sure that these custom fields line up with the +//! # Custom fields of your NetBox instance. +//! [[system.custom]] +//! cpu_count = 1 +//! platform = "x86_64" # Overriden by collector +//! collect_cpu_information = true +//! collect_network_information = true +//! primary_network_interface = "eth0" +//! config_template = 0 # integer of the config_template ID //! ``` //! //! It will be created at ` ~/.nazara-config.toml`. -use serde::Deserialize; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; use std::fs::File; +use std::hash::RandomState; use std::io::prelude::*; use std::path::Path; use std::{fs, path::PathBuf}; -use toml::Value; use super::config_exceptions::{self, *}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct ConfigData { - netbox_api_token: String, - netbox_uri: String, - name: String, - system_location: String, - device_role: String, + pub netbox: NetboxConfig, + pub system: SystemConfig, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct NetboxConfig { + pub netbox_api_token: String, + pub netbox_uri: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SystemConfig { + pub name: String, + pub site_id: i64, + pub description: String, + pub comments: String, + pub device_type: i64, + pub device_role: i64, + pub face: String, + pub status: String, + pub airflow: String, + pub primary_network_interface: Option, + pub custom_fields: Option>, + // optional System information + pub tenant_group: Option, + pub tenant: Option, + pub rack: Option, + pub position: Option, } /// Set up configuration @@ -54,8 +96,6 @@ pub fn set_up_configuration( uri: Option, token: Option, name: Option, - location: Option, - device_role: Option, ) -> Result { let mut conf_data: ConfigData; @@ -70,23 +110,15 @@ pub fn set_up_configuration( conf_data = ConfigData::read_config_file(); if uri.is_some() { - conf_data.netbox_uri = uri.unwrap(); + conf_data.netbox.netbox_uri = uri.unwrap(); } if token.is_some() { - conf_data.netbox_api_token = token.unwrap(); + conf_data.netbox.netbox_api_token = token.unwrap(); } if name.is_some() { - conf_data.name = name.unwrap(); - } - - if location.is_some() { - conf_data.system_location = location.unwrap(); - } - - if device_role.is_some() { - conf_data.device_role = device_role.unwrap(); + conf_data.system.name = name.unwrap(); } return Ok(conf_data); @@ -97,7 +129,7 @@ pub fn set_up_configuration( println!("\x1b[36m[info]\x1b[0m No config file found. Creating default..."); - match ConfigData::initialize_config_file() { + match ConfigData::initialize_config_file(&uri, &token, &name) { Ok(_) => { println!("\x1b[32m[success]\x1b[0m Default configuration file created successfully.") } @@ -119,12 +151,10 @@ pub fn set_up_configuration( conf_data = ConfigData::read_config_file(); - if uri.is_some() && token.is_some() && location.is_some() { - conf_data.netbox_uri = uri.unwrap(); - conf_data.netbox_api_token = token.unwrap(); - conf_data.name = name.unwrap(); - conf_data.system_location = location.unwrap(); - conf_data.device_role = device_role.unwrap(); + if uri.is_some() && token.is_some() && name.is_some() { + conf_data.netbox.netbox_uri = uri.unwrap(); + conf_data.netbox.netbox_api_token = token.unwrap(); + conf_data.system.name = name.unwrap(); } println!("\x1b[32m[success]\x1b[0m Configuration loaded.\x1b[0m"); @@ -190,70 +220,55 @@ impl ConfigData { /// /// If it is not able to create a new config file at `~/.nazara-config.toml` or if it cannot write the defaults /// to the file, the function panics as this is the main method of configuring the program. - fn initialize_config_file() -> std::io::Result<()> { - // Create new toml table - let mut config: toml::map::Map = toml::value::Table::new(); - - // Create netbox section - let netbox_section: toml::map::Map = { - let mut netbox_config_table: toml::map::Map = toml::value::Table::new(); - netbox_config_table.insert("netbox_uri".to_string(), Value::String("".to_string())); - netbox_config_table.insert( - "netbox_api_token".to_string(), - Value::String("".to_string()), - ); - netbox_config_table - }; - - let system_section: toml::map::Map = { - let mut system_config_table: toml::map::Map = toml::value::Table::new(); - system_config_table.insert("name".to_string(), Value::String("".to_string())); - system_config_table - .insert("system_location".to_string(), Value::String("".to_string())); - system_config_table.insert("device_role".to_string(), Value::String("".to_string())); - system_config_table + fn initialize_config_file( + uri: &Option, + token: &Option, + name: &Option, + ) -> std::io::Result<()> { + let template_path: &Path = Path::new("src/configuration/config_template.toml"); + let mut file: File = match File::open(&template_path) { + Ok(file) => file, + Err(err) => { + let exc = config_exceptions::UnableToReadConfigError { + message: format!("\x1b[31m[error]\x1b[0m An Error occurred while attempting to read template file! {}", err) + }; + exc.abort(1); + } }; - - // Insert the netbox section as value under the header "netbox" - config.insert("netbox".to_string(), Value::Table(netbox_section)); - - config.insert("system".to_string(), Value::Table(system_section)); - - let toml_string: String = match toml::to_string(&Value::Table(config)) { - Ok(result) => result, + let mut contents: String = String::new(); + match file.read_to_string(&mut contents) { + Ok(x) => x, Err(err) => { - println!("{}", err); - String::new() + panic!("{}", err); } }; - // Create a new File - let mut file: File = match File::create(get_config_dir()) { + // Replace placeholders with actual values if exist. + if let Some(uri) = uri { + contents = contents.replace("{NETBOX_URI}", &uri); + } + if let Some(token) = token { + contents = contents.replace("{NETBOX_TOKEN}", &token); + } + if let Some(name) = name { + contents = contents.replace("{SYSTEM_NAME}", &name); + } + + // Path to the output file + let output_path = get_config_dir(); + let mut output_file = match File::create(&output_path) { Ok(file) => file, Err(err) => { - let exc: UnableToCreateConfigError = config_exceptions::UnableToCreateConfigError { - message: format!( - "\x1b[31mFATAL:\x1b[0m Unable to create config file! ({})", - err - ), - }; - exc.abort(10); + panic!("{}", err) } }; - - // Write default contents to file - match file.write_all(toml_string.as_bytes()) { - Ok(_) => {} + match output_file.write_all(contents.as_bytes()) { + Ok(()) => {} Err(err) => { - let exc: UnableToCreateConfigError = config_exceptions::UnableToCreateConfigError { - message: format!( - "\x1b[31mFATAL:\x1b[0m Unable to write defaults to config file! ({})", - err - ), - }; - exc.abort(13); + panic!("{}", err) } - } + }; + Ok(()) } @@ -272,89 +287,36 @@ impl ConfigData { /// * not able to read the config file. /// * the config file does not have valid TOML syntax. fn validate_config_file() -> Result<(), String> { - let file_contents: String = match fs::read_to_string(get_config_dir()) { - Ok(contents) => contents, - Err(err) => { - let exc: UnableToReadConfigError = config_exceptions::UnableToReadConfigError { - message: format!("x1b[31m[FATAL]x1b[0m Unable to open config file! {}", err), - }; - exc.abort(14) - } - }; - - let config_contents: Value = match toml::from_str(&file_contents) { - Ok(config) => config, - Err(err) => { - let exc: InvalidConfigFileError = config_exceptions::InvalidConfigFileError { - message: format!("\x1b[31m[FATAL]\x1b[0m Invalid config file syntax! Make sure the configuration file has valid TOML syntax. ({})", err), - }; - exc.abort(15) - } - }; + let mut file = File::open(get_config_dir()) + .map_err(|e| format!("[error] Failed to open config file! {}", e))?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|e| format!("[error] Failed to read config file! {}", e))?; - let config_parameters: ConfigData = ConfigData { - netbox_api_token: config_contents["netbox"]["netbox_uri"] - .as_str() - .unwrap() - .trim() - .to_string(), - netbox_uri: config_contents["netbox"]["netbox_api_token"] - .as_str() - .unwrap() - .trim() - .to_string(), - name: config_contents["system"]["name"] - .as_str() - .unwrap() - .trim() - .to_string(), - system_location: config_contents["system"]["system_location"] - .as_str() - .unwrap() - .trim() - .to_string(), - device_role: config_contents["system"]["device_role"] - .as_str() - .unwrap() - .trim() - .to_string(), - }; + let config_data: ConfigData = toml::from_str(&contents) + .map_err(|e| format!("[error] Failed to deserialize toml parameters! {}", e))?; - if config_parameters.netbox_uri.is_empty() { + if config_data.netbox.netbox_uri.is_empty() { return Err( "\x1b[31m[error]\x1b[0m Validation Error: Config parameter 'netbox_uri' is empty! This parameter is mandatory." .to_string(), ); } - if config_parameters.netbox_api_token.is_empty() { + if config_data.netbox.netbox_api_token.is_empty() { return Err( "\x1b[31m[error]\x1b[0m Validation Error: Config parameter 'netbox_api_token' is empty! This parameter is mandatory." .to_string(), ); } - if config_parameters.name.is_empty() { + if config_data.system.name.is_empty() { return Err( "\x1b[31m[error]\x1b[0m Validation Error: Config parameter 'name' is empty! This parameter is mandatory." .to_string(), ); } - if config_parameters.system_location.is_empty() { - return Err( - "\x1b[31m[error]\x1b[0m Validation Error: Config parameter 'system_location' is empty! This parameter is mandatory." - .to_string(), - ); - } - - if config_parameters.device_role.is_empty() { - return Err( - "\x1b[31m[error]\x1b[0m Validation Error: Config parameter 'device_role' is empty! This parameter is mandatory." - .to_string(), - ); - } - return Ok(()); } @@ -365,55 +327,44 @@ impl ConfigData { /// /// * `config: ConfigData` - A `ConfigData` object. fn read_config_file() -> ConfigData { - let file_contents: String = match fs::read_to_string(get_config_dir()) { - Ok(contents) => contents, + let mut file = match File::open(get_config_dir()) { + Ok(file) => file, Err(err) => { - let exc: UnableToReadConfigError = config_exceptions::UnableToReadConfigError { - message: format!("\x1b[31m[FATAL]\x1b[0m Unable to open config file! {}", err), + let exc = config_exceptions::UnableToReadConfigError { + message: format!( + "[error] An error occurred while attempting to read the config file: {}", + err + ), }; - exc.abort(14) + exc.abort(1); } }; - let config_content: Value = match toml::from_str(&file_contents) { - Ok(config) => config, + let mut contents = String::new(); + match file.read_to_string(&mut contents) { + Ok(u) => u, Err(err) => { - let exc: InvalidConfigFileError = config_exceptions::InvalidConfigFileError { - message: format!("\x1b[31m[FATAL]\x1b[0m Invalid config file syntax! Make sure the configuration file has valid TOML syntax. ({})", err), + let exc = config_exceptions::UnableToReadConfigError { + message: format!("[error] Unable to read config file to buffer! {}", err), }; - exc.abort(15) + exc.abort(1); } }; - let config_parameters: ConfigData = ConfigData { - netbox_api_token: config_content["netbox"]["netbox_api_token"] - .as_str() - .unwrap() - .trim() - .to_string(), - netbox_uri: config_content["netbox"]["netbox_uri"] - .as_str() - .unwrap() - .trim() - .to_string(), - name: config_content["system"]["name"] - .as_str() - .unwrap() - .trim() - .to_string(), - system_location: config_content["system"]["system_location"] - .as_str() - .unwrap() - .trim() - .to_string(), - device_role: config_content["system"]["device_role"] - .as_str() - .unwrap() - .trim() - .to_string(), + let config_data: ConfigData = match toml::from_str(&contents) { + Ok(t) => t, + Err(err) => { + let exc = config_exceptions::UnableToCreateConfigError { + message: format!( + "[error] An error occured while trying to parse the toml: {}", + err + ), + }; + exc.abort(1); + } }; - return config_parameters; + config_data } /// Return NetBox URL. Necessary for payload generation. @@ -422,7 +373,7 @@ impl ConfigData { /// /// * `system_location: &str` - The location of the system to be created/updated as read from the config file. pub fn get_netbox_uri(&self) -> &str { - &self.netbox_uri + &self.netbox.netbox_uri } /// Return API auth token. Necessary for payload generation. @@ -431,15 +382,6 @@ impl ConfigData { /// /// * `system_location: String` - The location of the system to be created/updated as read from the config file. pub fn get_api_token(&self) -> &str { - &self.netbox_api_token - } - - /// Return system location. Necessary for payload generation. - /// - /// # Returns - /// - /// * `system_location: String` - The location of the system to be created/updated as read from the config file. - pub fn get_system_location(&self) -> &str { - &self.system_location + &self.netbox.netbox_api_token } } diff --git a/src/configuration/config_template.toml b/src/configuration/config_template.toml new file mode 100644 index 0000000..6b8de63 --- /dev/null +++ b/src/configuration/config_template.toml @@ -0,0 +1,45 @@ +# Template nazara-config.toml file for v0.1.0 + +# Configuration parameters for the NetBox connection +[netbox] +netbox_api_token = "" +netbox_uri = "https://demo.netbox.dev" + +# Mandatory information about the system +[system] +name = "" # Name of the machine or VM. **Required** when device is a VM +site_id = 0 # The id of the site this device is located at. (Stored in NetBox) +description = "" +comments = "Automatically registered by Nazara." +device_type = 0 # ID of the type of the Device Type in NetBox +device_role = 0 # ID of the device role in NetBox +# Name of the network interface to set. (e.g eth0, etc) +# If not set, the first active interface will be selected. +primary_network_interface = "" +face = "" # Direction this device may face in (e.g front or rear) +status = "active" # status of the device. default: active +airflow = "Front to rear" # Direction of airflow + +# Optional data of your device. +# This section may be empty +[[system.optional]] +# tenant_group = 0 # ID of the department this device belongs to +# tenant = 0 # ID of the team or individual this device belongs to +# location = 0 # ID of the location of the device +# rack = 0 # ID of the rack the device is mounted in if any +# position = 0 # Position of the device within the rack if any + + +# TODO + +# Custom parameters about the system, which fall under the "custom_fields" section. +# Basically anything can go here, these are example values Nazara collects. +[[system.custom]] +cpu_count = 1 # overriden by collector +platform = "x86_64" # overriden bei collector +config_template = 0 +## ... +# TODO: Does this make sense if this information is collected anyway? +# TODO: Maybe something like this would be better? +collect_cpu_information = true +collect_network_information = true diff --git a/src/main.rs b/src/main.rs index 70753f7..f6e0788 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,14 +59,6 @@ struct Args { /// The name of the device #[arg(short, long)] name: Option, - - /// The location of the machine (must be one of the locations you have set as available in your Netbox instance) - #[arg(short, long)] - location: Option, - - /// The role of the machine (switch, server, router, etc.) - #[arg(short, long)] - device_role: Option, } fn main() { @@ -89,13 +81,7 @@ fn main() { ascii_art ); - let config = match set_up_configuration( - args.uri, - args.token, - args.name.clone(), - args.location, - args.device_role, - ) { + let config = match set_up_configuration(args.uri, args.token, args.name.clone()) { Ok(conf) => conf, Err(err) => { println!("{}", err); @@ -132,5 +118,5 @@ fn main() { } // Register the machine or VM with NetBox - let _ = register_machine(&client, machine); + let _ = register_machine(&client, machine, config); } diff --git a/src/publisher/publisher.rs b/src/publisher/publisher.rs index df116fc..3f64203 100644 --- a/src/publisher/publisher.rs +++ b/src/publisher/publisher.rs @@ -13,17 +13,21 @@ use std::process; /// TODO: 1. Implement Creation/update logic 2. Denest by splitting query logic off 3. Do not panic upon request fail use thanix_client::{ paths::{ - self, DcimDevicesListQuery, DcimDevicesListResponse, + self, DcimDevicesCreateResponse, DcimDevicesListQuery, DcimDevicesListResponse, VirtualizationVirtualMachinesListQuery, VirtualizationVirtualMachinesListResponse, }, types::{ - DeviceWithConfigContext, PaginatedDeviceWithConfigContextList, - VirtualMachineWithConfigContext, WritableDeviceWithConfigContextRequest, + DeviceWithConfigContext, VirtualMachineWithConfigContext, + WritableDeviceWithConfigContextRequest, }, util::ThanixClient, }; -use crate::{publisher::api_client::test_connection, publisher::translator, Machine}; +use crate::{ + configuration::config_parser::ConfigData, + publisher::{api_client::test_connection, translator}, + Machine, +}; use super::publisher_exceptions::NetBoxApiError; @@ -63,26 +67,42 @@ pub fn probe(client: &ThanixClient) -> Result<(), NetBoxApiError> { /// /// # Returns /// -/// TODO -pub fn register_machine(client: &ThanixClient, machine: Machine) -> Result<(), NetBoxApiError> { +/// Empty Result object upon successful completeion. Otherwise a `NetBoxApiError`. +pub fn register_machine( + client: &ThanixClient, + machine: Machine, + config_data: ConfigData, +) -> Result<(), NetBoxApiError> { println!("Starting registration process. This may take a while..."); let nb_devices: DeviceListOrVMList = get_machines(client, &machine); if machine.dmi_information.system_information.is_virtual { - todo!() // TODO: VM Creation / Update + todo!("Virtual machine creation not yet implemented!") // TODO: VM Creation / Update } else { let payload: WritableDeviceWithConfigContextRequest = - translator::information_to_device(&machine); + translator::information_to_device(&machine, config_data); match search_for_matches(&machine, &nb_devices) { Some(device_id) => { - todo!() // TODO Implement machine update + todo!("Device update not yet implemented.") // TODO Implement machine update } None => { match paths::dcim_devices_create(&client, payload) { Ok(response) => { - todo!() // Check response code 201, handle other. (Should not happen) + // Check response code 201, handle other. (Should not happen) + match response { + DcimDevicesCreateResponse::Http201(created_device) => { + // TODO + todo!("Device creation not yet implemented!") + } + DcimDevicesCreateResponse::Other(other_response) => { + // TODO + todo!( + "Unexpected response code on device creation not handled yet!" + ) + } + } } Err(err) => { panic!("{}", err) // Handle failure correctly @@ -154,7 +174,7 @@ pub fn register_machine(client: &ThanixClient, machine: Machine) -> Result<(), N /// struct into the correct data type required by the API. fn create_machine(client: &ThanixClient, machine: &Machine) -> Result<(), NetBoxApiError> { println!("Creating new machine in NetBox..."); - let payload = translator::information_to_device(machine); + // let payload = translator::information_to_device(machine); Ok(()) } @@ -196,13 +216,15 @@ fn get_machines(client: &ThanixClient, machine: &Machine) -> DeviceListOrVMList virtual_machines.results } _ => { - todo!(); + // TODO change the way Nazara exits here + eprintln!("\x1b[31m[error]\x1b[0m Failure while retrieving list of virtual machines. Please make sure your NetBox database is set up correctly."); + process::exit(1); } }; DeviceListOrVMList::VmList(vm_list) } - Err(err) => panic!("{}", err), + Err(e) => panic!("{}", e), } } else { println!("Retrieving list of machines..."); @@ -214,14 +236,15 @@ fn get_machines(client: &ThanixClient, machine: &Machine) -> DeviceListOrVMList let device_list: Vec = match response { DcimDevicesListResponse::Http200(devices) => devices.results, _ => { - todo!(); + todo!("Handling of non 200 Response code when getting machines not implemented yet!"); } }; DeviceListOrVMList::DeviceList(device_list) } - Err(err) => { - eprintln!("\x1b[31m[error]\x1b[0m Failure while retrieving list of devices. Please make sure your NetBox database is set up correctly.\n{}", err); + Err(e) => { + // TODO change the way Nazara exits here + eprintln!("\x1b[31m[error]\x1b[0m Failure while retrieving list of devices. Please make sure your NetBox database is set up correctly.\n{}", e); process::exit(1); } } diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 194c63f..0891ed7 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -1,15 +1,21 @@ //! # Translator Module //! //! This module handles the translation and processing of the data sent to or received from NetBox. +//! +//! TODO: +//! - Identify primary IPv4 or IPv6 using the primary_network_interface field from `ConfigData`. use thanix_client::types::{ WritableDeviceWithConfigContextRequest, WritableVirtualMachineWithConfigContextRequest, }; -use crate::Machine; +use crate::{configuration::config_parser::ConfigData, Machine}; /// Translate the machine information to a `WritableDeviceWithConfigContextRequest` required by /// NetBox's API. -pub fn information_to_device(machine: &Machine) -> WritableDeviceWithConfigContextRequest { +pub fn information_to_device( + machine: &Machine, + config_data: ConfigData, +) -> WritableDeviceWithConfigContextRequest { todo!() } From 2367b6c557eb69832876e90fa2966c47cb12a310 Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Thu, 7 Mar 2024 08:17:15 +0100 Subject: [PATCH 03/14] implement machine info to device translation --- src/configuration/config_template.toml | 1 - src/publisher/translator.rs | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/configuration/config_template.toml b/src/configuration/config_template.toml index 6b8de63..4ebfea4 100644 --- a/src/configuration/config_template.toml +++ b/src/configuration/config_template.toml @@ -29,7 +29,6 @@ airflow = "Front to rear" # Direction of airflow # rack = 0 # ID of the rack the device is mounted in if any # position = 0 # Position of the device within the rack if any - # TODO # Custom parameters about the system, which fall under the "custom_fields" section. diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 0891ed7..8882fec 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -12,13 +12,34 @@ use crate::{configuration::config_parser::ConfigData, Machine}; /// Translate the machine information to a `WritableDeviceWithConfigContextRequest` required by /// NetBox's API. +/// +/// # Parameters +/// +/// - `machine: &Machine` - Collected information about the device +/// - `config_data: ConfigData` - Additional information about the device provided by config file +/// or CLI +/// +/// # Returns +/// +/// - `device: WritableDeviceWithConfigContextRequest` - Payload for machine creation request pub fn information_to_device( machine: &Machine, config_data: ConfigData, ) -> WritableDeviceWithConfigContextRequest { + // let device: WritableDeviceWithConfigContextRequest = WritableDeviceWithConfigContextRequest { + // name: Some(config_data.system.name), + // device_type: config_data.system.device_type, + // role: config_data.system.device_role, + // tenant: config_data.system.tenant, + // }; + // device todo!() } +fn get_platform_id(machine: &Machine) -> Option { + todo!("Get platform by name an return platform id!") +} + pub fn information_to_vm(machine: &Machine) -> WritableVirtualMachineWithConfigContextRequest { todo!() } From 6b337050093a1ad08e926476ff0d6938a536695a Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Thu, 7 Mar 2024 10:47:07 +0100 Subject: [PATCH 04/14] Add system platform configuration via config file --- src/configuration/config_parser.rs | 1 + src/configuration/config_template.toml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/configuration/config_parser.rs b/src/configuration/config_parser.rs index 65909ff..d5a4e03 100644 --- a/src/configuration/config_parser.rs +++ b/src/configuration/config_parser.rs @@ -67,6 +67,7 @@ pub struct SystemConfig { pub airflow: String, pub primary_network_interface: Option, pub custom_fields: Option>, + pub platform_name: Option, // optional System information pub tenant_group: Option, pub tenant: Option, diff --git a/src/configuration/config_template.toml b/src/configuration/config_template.toml index 4ebfea4..e9fbc64 100644 --- a/src/configuration/config_template.toml +++ b/src/configuration/config_template.toml @@ -2,12 +2,12 @@ # Configuration parameters for the NetBox connection [netbox] -netbox_api_token = "" -netbox_uri = "https://demo.netbox.dev" +netbox_api_token = "{NETBOX_TOKEN}" +netbox_uri = "{NETBOX_URI}" # Mandatory information about the system [system] -name = "" # Name of the machine or VM. **Required** when device is a VM +name = "{SYSTEM_NAME}" # Name of the machine or VM. **Required** when device is a VM site_id = 0 # The id of the site this device is located at. (Stored in NetBox) description = "" comments = "Automatically registered by Nazara." @@ -28,6 +28,7 @@ airflow = "Front to rear" # Direction of airflow # location = 0 # ID of the location of the device # rack = 0 # ID of the rack the device is mounted in if any # position = 0 # Position of the device within the rack if any +platform = "x86_64" # TODO @@ -35,7 +36,6 @@ airflow = "Front to rear" # Direction of airflow # Basically anything can go here, these are example values Nazara collects. [[system.custom]] cpu_count = 1 # overriden by collector -platform = "x86_64" # overriden bei collector config_template = 0 ## ... # TODO: Does this make sense if this information is collected anyway? From d252309e7fa082b094e6856e24032b75365525aa Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Thu, 7 Mar 2024 20:44:33 +0100 Subject: [PATCH 05/14] Add cpu platform gathering --- src/collectors/dmi_collector.rs | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/collectors/dmi_collector.rs b/src/collectors/dmi_collector.rs index 697281f..7ea5188 100644 --- a/src/collectors/dmi_collector.rs +++ b/src/collectors/dmi_collector.rs @@ -10,6 +10,8 @@ * 3. REMOVE DEBUG PRINT STATEMENTS. * */ +use crate::collectors::collector_exceptions; + use super::collector_exceptions::UnableToCollectDataError; use super::util::{find_table, split_output}; use serde::{Deserialize, Serialize}; @@ -81,6 +83,7 @@ pub struct ChassisInformation { /// * max_speed: `String`- The maximum speed of the CPU. /// * voltage: `String` - The voltage the CPU runs at. /// * status: `String` - Shows if the socket is enabled/disabled and populated/empty. +/// * arch: `String` - The architecture of the CPU (x86_64, etc). #[derive(Serialize, Debug)] pub struct CpuInformation { pub version: String, @@ -90,6 +93,7 @@ pub struct CpuInformation { pub max_speed: String, pub voltage: String, pub status: String, + pub arch: Option, } /// List of possible system parameters to collect dmi information from. @@ -361,6 +365,7 @@ fn dmidecode_cpu(_param: T) -> CpuInformation { max_speed: String::new(), voltage: String::new(), status: String::new(), + arch: None, }; let mut table_found: bool = false; @@ -419,10 +424,41 @@ fn dmidecode_cpu(_param: T) -> CpuInformation { } } } + cpu_information.arch = match get_architecture() { + Ok(arch) => Some(arch), + Err(e) => { + eprintln!("\x1b[31m[error]\x1b[0m Failure to get cpu information via `uname`!\n{}", e); + None + } + }; println!("\x1b[32m[success]\x1b[0m CPU information collection completed."); return cpu_information; } +/// Gets the architecture name through `uname`. +/// +/// *This will override any information entered in the config file's `platform` field!* +/// +/// # Returns +/// - Ok(arch_name): `string` - Name of the CPU architecture. +/// - Err(UnableToCollectDataError) +fn get_architecture() -> Result { + println!("Running uname to collect CPU architecture..."); + let output = match Command::new("uname").arg("-p").output() { + Ok(output) => { + output + }, + Err(e) => { + let exc: UnableToCollectDataError = UnableToCollectDataError { + message: String::from(format!("\x1b[31m[error]\x1b[0m An error occured while attempting to execute `uname -p`! {}", e)) + }; + return Err(exc); + } + }; + + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) +} + #[cfg(test)] pub mod dmi_collector_tests { use super::*; @@ -533,6 +569,7 @@ pub mod dmi_collector_tests { max_speed: "4000 MHz".to_string(), voltage: "1.2 V".to_string(), status: "Populated, Enabled".to_string(), + arch: Some("x86_64".to_string()), }; let result = dmidecode_cpu(MockDmiDecodeTable {}); @@ -544,5 +581,6 @@ pub mod dmi_collector_tests { assert_eq!(expected.max_speed, result.max_speed); assert_eq!(expected.voltage, result.voltage); assert_eq!(expected.status, result.status); + assert_eq!(expected.arch, result.arch); } } From dc509ba2b295a23fd46e2a91cee65c8f17f7732b Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Sun, 17 Mar 2024 09:52:06 +0100 Subject: [PATCH 06/14] Add non-collectible fields to config and translator split off device creation request --- Cargo.toml | 2 +- src/collectors/dmi_collector.rs | 9 +- src/configuration/config_parser.rs | 31 ++++++- src/configuration/config_template.toml | 2 +- src/publisher/api_client.rs | 58 ++++++------- src/publisher/publisher.rs | 28 +------ src/publisher/translator.rs | 111 ++++++++++++++++++++++--- 7 files changed, 164 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 05b66f8..1a894b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ toml = "0.7.6" thanix_client = "1.0.0" # Uncomment this line if you are using a custom thanix client implementation. # Change the path to be relative to this Cargo.toml file. -# thanix_client = { path = "../thanix_client/" } +# thanix_client = { path = "path/to/your/client/crate" } [dev-dependencies] mockall = "0.11.4" diff --git a/src/collectors/dmi_collector.rs b/src/collectors/dmi_collector.rs index 7ea5188..bf81c26 100644 --- a/src/collectors/dmi_collector.rs +++ b/src/collectors/dmi_collector.rs @@ -427,7 +427,10 @@ fn dmidecode_cpu(_param: T) -> CpuInformation { cpu_information.arch = match get_architecture() { Ok(arch) => Some(arch), Err(e) => { - eprintln!("\x1b[31m[error]\x1b[0m Failure to get cpu information via `uname`!\n{}", e); + eprintln!( + "\x1b[31m[error]\x1b[0m Failure to get cpu information via `uname`!\n{}", + e + ); None } }; @@ -445,9 +448,7 @@ fn dmidecode_cpu(_param: T) -> CpuInformation { fn get_architecture() -> Result { println!("Running uname to collect CPU architecture..."); let output = match Command::new("uname").arg("-p").output() { - Ok(output) => { - output - }, + Ok(output) => output, Err(e) => { let exc: UnableToCollectDataError = UnableToCollectDataError { message: String::from(format!("\x1b[31m[error]\x1b[0m An error occured while attempting to execute `uname -p`! {}", e)) diff --git a/src/configuration/config_parser.rs b/src/configuration/config_parser.rs index d5a4e03..a2b77b1 100644 --- a/src/configuration/config_parser.rs +++ b/src/configuration/config_parser.rs @@ -42,6 +42,11 @@ use std::{fs, path::PathBuf}; use super::config_exceptions::{self, *}; +/// Configuration State set by the configuration file. +/// +/// # Members +/// - netbox: `NetBoxConfig` - Configuration parameters for the NetBox connection. +/// - system: `SystemConfig` - Parameters abouth the system. #[derive(Debug, Serialize, Deserialize)] pub struct ConfigData { pub netbox: NetboxConfig, @@ -54,6 +59,29 @@ pub struct NetboxConfig { pub netbox_uri: String, } +/// Additional information about the system. +/// +/// # Members +/// +/// * name: `String` - Name of the device. *Required for virtual machines! Must be unique!* +/// * site_id: `i64` - ID of the site the device is located in. +/// * description: `String` - Short description of the device. +/// * comments: `String` - Comment field. +/// * device_type: `i64` - ID of the device type. +/// * device_role: `i64` - ID of the device role. +/// * face: `String` - Tag of the orientation of the device. +/// * status: `String` - Status of the device. (Default: `active`) +/// * airflow `String` - Airflow orientation of the device. +/// * primary_network_interface: `Option` - Name of the network interface you want to set as +/// primary. +/// * custom_fields: `Option>` - Unsorted, unfiltered list of +/// information that will be handed to NetBox as-is. +/// * platform_name: `Option` - Name of the processor architecture used as a fallback if collection by `uname` +/// fails. *Must be present in your NetBox instance!* +/// * tenant_group: `Option` - ID of the tenant group this device belongs to. (e.g: department) +/// * tenant: `Option` - ID of tenant this device belongs to. (e.g: team or individual) +/// * rack: `Option` - ID of the rack this device is located in. +/// * position: `Option` - Position of the device within a rack if any. #[derive(Debug, Serialize, Deserialize)] pub struct SystemConfig { pub name: String, @@ -71,6 +99,7 @@ pub struct SystemConfig { // optional System information pub tenant_group: Option, pub tenant: Option, + pub location: Option, pub rack: Option, pub position: Option, } @@ -318,7 +347,7 @@ impl ConfigData { ); } - return Ok(()); + Ok(()) } /// Opens and reads the config file and writes the set parameters into a [`ConfigData`](struct.ConfigData) Object diff --git a/src/configuration/config_template.toml b/src/configuration/config_template.toml index e9fbc64..4dda218 100644 --- a/src/configuration/config_template.toml +++ b/src/configuration/config_template.toml @@ -15,7 +15,7 @@ device_type = 0 # ID of the type of the Device Type in NetBox device_role = 0 # ID of the device role in NetBox # Name of the network interface to set. (e.g eth0, etc) # If not set, the first active interface will be selected. -primary_network_interface = "" +primary_network_interface = "eth0" face = "" # Direction this device may face in (e.g front or rear) status = "active" # status of the device. default: active airflow = "Front to rear" # Direction of airflow diff --git a/src/publisher/api_client.rs b/src/publisher/api_client.rs index e250541..7423b27 100644 --- a/src/publisher/api_client.rs +++ b/src/publisher/api_client.rs @@ -8,42 +8,10 @@ extern crate thanix_client; use reqwest::{blocking::Client, Error as ReqwestError}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json; -use thanix_client::util::ThanixClient; - -use crate::collectors::{dmi_collector::DmiInformation, network_collector::NetworkInformation}; +use thanix_client::{paths::{dcim_devices_create, DcimDevicesCreateResponse}, types::WritableDeviceWithConfigContextRequest, util::ThanixClient}; use super::{publisher, publisher_exceptions}; -/// Represents the combined system information required to create a new machine or update an existing one. -/// -/// # Members -/// -/// * `dmi_information: DmiInformation` - DMI (Desktop Management Interface) details of the system such as model and manufacturer. -/// * `network_information: NetworkInformation` - A list of network interface details. -/// * `system_location: String` A string representing the location or office the system is located at. Read from the config file. -#[derive(Serialize)] -pub struct SystemData { - pub dmi_information: DmiInformation, - pub network_information: Vec, - pub system_name: String, - pub system_location: String, - pub device_role: String, -} - -/// Encapsulates the payload required to create a new machine in NetBox. -/// -/// This struct is serialized into JSON format and sent as the body of the HTTP request to create a new machine. -/// -/// # Members -/// -/// * `system_information: SystemData` - System Information to include in the request. -#[derive(Serialize)] -pub struct CreateMachinePayload { - pub system_information: SystemData, -} - /// Tests connection to the NetBox API. /// /// This method attempts to retrieve the API root from the NetBox API to affirm that it is reachable. @@ -77,3 +45,27 @@ pub fn test_connection(client: &ThanixClient) -> Result<(), publisher_exceptions Err(e) => Err(publisher_exceptions::NetBoxApiError::Reqwest(e)), } } + +pub fn create_device(client: &ThanixClient, payload: &WritableDeviceWithConfigContextRequest) { + println!("Creating Device in NetBox..."); + + match dcim_devices_create(client, payload.clone()) { + Ok(response) => { + match response { + DcimDevicesCreateResponse::Http201(created_device) => { + // TODO + println!( + "[success] Device creation successful!\nYour machine can be found under the ID {}.", created_device.id + ); + } + DcimDevicesCreateResponse::Other(other_response) => { + // TODO + todo!("Other Response codes from creation not yet implemented! {}", other_response.text().unwrap()); + } + } + } + Err(err) => { + panic!("{}", err); // Handle this better + } + } +} diff --git a/src/publisher/publisher.rs b/src/publisher/publisher.rs index 3f64203..1a1fdf1 100644 --- a/src/publisher/publisher.rs +++ b/src/publisher/publisher.rs @@ -5,9 +5,6 @@ //! machine or update an existing one. //! //! The actual request logic will be provided by the `thanix_client` crate. -//! -//! The `api_client` module will provide the actual client and request logic. -//! use std::process; /// TODO: 1. Implement Creation/update logic 2. Denest by splitting query logic off 3. Do not panic upon request fail @@ -25,7 +22,7 @@ use thanix_client::{ use crate::{ configuration::config_parser::ConfigData, - publisher::{api_client::test_connection, translator}, + publisher::{api_client::{create_device, test_connection}, translator}, Machine, }; @@ -81,33 +78,14 @@ pub fn register_machine( todo!("Virtual machine creation not yet implemented!") // TODO: VM Creation / Update } else { let payload: WritableDeviceWithConfigContextRequest = - translator::information_to_device(&machine, config_data); + translator::information_to_device(&client, &machine, config_data); match search_for_matches(&machine, &nb_devices) { Some(device_id) => { todo!("Device update not yet implemented.") // TODO Implement machine update } None => { - match paths::dcim_devices_create(&client, payload) { - Ok(response) => { - // Check response code 201, handle other. (Should not happen) - match response { - DcimDevicesCreateResponse::Http201(created_device) => { - // TODO - todo!("Device creation not yet implemented!") - } - DcimDevicesCreateResponse::Other(other_response) => { - // TODO - todo!( - "Unexpected response code on device creation not handled yet!" - ) - } - } - } - Err(err) => { - panic!("{}", err) // Handle failure correctly - } - } + create_device(client, &payload); } } } diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 8882fec..03a8573 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -4,15 +4,24 @@ //! //! TODO: //! - Identify primary IPv4 or IPv6 using the primary_network_interface field from `ConfigData`. +use std::process; +use thanix_client::paths::{self, DcimPlatformsListQuery}; use thanix_client::types::{ - WritableDeviceWithConfigContextRequest, WritableVirtualMachineWithConfigContextRequest, + Platform, WritableDeviceWithConfigContextRequest, + WritableVirtualMachineWithConfigContextRequest, }; +use thanix_client::util::ThanixClient; use crate::{configuration::config_parser::ConfigData, Machine}; +use super::publisher_exceptions::NetBoxApiError; + /// Translate the machine information to a `WritableDeviceWithConfigContextRequest` required by /// NetBox's API. /// +/// *Certain information provided in the config file, like the CPU platform, will be overwritten +/// if another one is detected by the collector!* +/// /// # Parameters /// /// - `machine: &Machine` - Collected information about the device @@ -23,23 +32,101 @@ use crate::{configuration::config_parser::ConfigData, Machine}; /// /// - `device: WritableDeviceWithConfigContextRequest` - Payload for machine creation request pub fn information_to_device( + state: &ThanixClient, machine: &Machine, config_data: ConfigData, ) -> WritableDeviceWithConfigContextRequest { - // let device: WritableDeviceWithConfigContextRequest = WritableDeviceWithConfigContextRequest { - // name: Some(config_data.system.name), - // device_type: config_data.system.device_type, - // role: config_data.system.device_role, - // tenant: config_data.system.tenant, - // }; - // device - todo!() + let wanted_platform: Option = if let Some(arch) = + machine.dmi_information.cpu_information.arch.as_ref() + { + println!("[info] CPU architecture was collected. Used by default, overriding possible config options..."); + Some(arch.clone()) + } else if let Some(config_value) = config_data.system.platform_name.as_ref() { + println!("[info] Architecture was not collected. Using config specifications..."); + Some(config_value.clone()) + } else { + println!("[warning] No cpu architecture specified. Proceeding with 'none'..."); + None + }; + + let device: WritableDeviceWithConfigContextRequest = WritableDeviceWithConfigContextRequest { + name: Some(config_data.system.name), + device_type: config_data.system.device_type, + role: config_data.system.device_role, + tenant: config_data.system.tenant, + platform: match wanted_platform { + Some(platform_name) => get_platform_id(&state, platform_name), + None => None, + }, + serial: machine.dmi_information.system_information.serial, + asset_tag: todo!(), + site: todo!("Implement search logic for site id"), + location: todo!(), + rack: todo!(), + face: todo!(), + + }; + device } -fn get_platform_id(machine: &Machine) -> Option { - todo!("Get platform by name an return platform id!") +/// Returns the ID of the platform this machine uses. +/// +/// # Parameters +/// +/// * state: `&ThanixClient` - The client required for searching for the platform. +fn get_platform_id(state: &ThanixClient, platform_name: String) -> Option { + let platform_list: Vec; + + match paths::dcim_platforms_list(&state, DcimPlatformsListQuery::default()) { + Ok(response) => { + println!("List received. Analyzing..."); + + platform_list = match response { + paths::DcimPlatformsListResponse::Http200(platforms) => platforms.results, + _ => { + todo!("Handling of non 200 Response code when getting platforms not implemented yet.") + } + }; + } + Err(e) => { + eprintln!( + "\x1b[31m[error]\x1b[0m Failure while receiving list of platforms.\n{}", + e + ); + process::exit(1); + } + }; + + for platform in platform_list { + if platform.name == platform_name { + return Some(platform.id); + } + } + + None } -pub fn information_to_vm(machine: &Machine) -> WritableVirtualMachineWithConfigContextRequest { +/// Translate gathered information about the virtual machine into a usable Payload. +pub fn information_to_vm( + state: &ThanixClient, + machine: &Machine, + config_data: &ConfigData, +) -> WritableVirtualMachineWithConfigContextRequest { todo!() } + +/// Validate the `Device` Payload. +/// +/// # Parameters +/// * payload: `WritableDeviceWithConfigContextRequest` - The struct to validate. +/// +/// # Returns +/// +/// - Ok(()) +fn validate_device_payload(payload: WritableDeviceWithConfigContextRequest) -> bool { + println!("Validating device payload..."); + + todo!("Device payload validation not implemented yet!"); + + false +} From eb41bab98cc542e1eef8c711caaba4d1475cd23a Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Wed, 17 Apr 2024 14:52:19 +0200 Subject: [PATCH 07/14] create working device creation state --- src/publisher/translator.rs | 147 +++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 43 deletions(-) diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 03a8573..7068fa0 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -4,18 +4,16 @@ //! //! TODO: //! - Identify primary IPv4 or IPv6 using the primary_network_interface field from `ConfigData`. +use std::collections::HashMap; use std::process; -use thanix_client::paths::{self, DcimPlatformsListQuery}; +use thanix_client::paths::{self, DcimPlatformsListQuery, IpamIpAddressesListQuery}; use thanix_client::types::{ - Platform, WritableDeviceWithConfigContextRequest, - WritableVirtualMachineWithConfigContextRequest, + IPAddress, Platform, WritableDeviceWithConfigContextRequest, WritableVirtualMachineWithConfigContextRequest }; use thanix_client::util::ThanixClient; use crate::{configuration::config_parser::ConfigData, Machine}; -use super::publisher_exceptions::NetBoxApiError; - /// Translate the machine information to a `WritableDeviceWithConfigContextRequest` required by /// NetBox's API. /// @@ -24,9 +22,10 @@ use super::publisher_exceptions::NetBoxApiError; /// /// # Parameters /// -/// - `machine: &Machine` - Collected information about the device +/// - `state: &ThanixClient` - API Client instance used for search and validation. +/// - `machine: &Machine` - Collected information about the device. /// - `config_data: ConfigData` - Additional information about the device provided by config file -/// or CLI +/// or CLI. /// /// # Returns /// @@ -49,24 +48,59 @@ pub fn information_to_device( None }; - let device: WritableDeviceWithConfigContextRequest = WritableDeviceWithConfigContextRequest { - name: Some(config_data.system.name), - device_type: config_data.system.device_type, - role: config_data.system.device_role, - tenant: config_data.system.tenant, - platform: match wanted_platform { - Some(platform_name) => get_platform_id(&state, platform_name), - None => None, - }, - serial: machine.dmi_information.system_information.serial, - asset_tag: todo!(), - site: todo!("Implement search logic for site id"), - location: todo!(), - rack: todo!(), - face: todo!(), - + let mut payload: WritableDeviceWithConfigContextRequest = WritableDeviceWithConfigContextRequest::default(); + payload.name = Some(config_data.system.name); + payload.device_type = config_data.system.device_type; + payload.role = config_data.system.device_role; + payload.tenant = config_data.system.tenant; + payload.platform = match wanted_platform { + Some(platform_name) => get_platform_id(&state, platform_name), + None => None, }; - device + payload.serial = machine.dmi_information.system_information.serial.clone(); + // payload.asset_tag = todo!(); + payload.site = config_data.system.site_id; // TODO: Check if this exists. + payload.rack = config_data.system.rack; + payload.face = config_data.system.face; + // payload.position = todo!(); + // payload.longitude = todo!(); + // payload.latitude = todo!(); + payload.status = config_data.system.status; + payload.airflow = config_data.system.airflow; + payload.comments = config_data.system.comments; + // payload.config_template = todo!(); + payload.custom_fields = Some(HashMap::new()); + // payload.description = todo!(); + // payload.local_context_data = todo!(); + // payload.oob_ip = todo!(); + // TODO payload.primary_ip4 = todo!(); + // TODO payload.primary_ip6 = todo!(); + // payload.tags = todo!(); + // payload.virtual_chassis = todo!(); + // payload.vc_position = todo!(); + // payload.vc_priority = todo!(); + + payload +} + +/// Translate gathered information about the virtual machine into a usable Payload. +/// +/// # Parameters +/// +/// * state: `&ThanixClient` - The client instance to be used for communication. +/// * machine: `&Machine` - The collected information about the virtual machine. +/// * config_data: `&ConfigData` - Data parsed from the `nazar-config.toml`. +/// +/// # Returns +/// +/// * payload: `WritableVirtualMachineWithConfigContextRequest` - Payload for the VM POST or UPDATE +/// request. +pub fn information_to_vm( + state: &ThanixClient, + machine: &Machine, + config_data: &ConfigData, +) -> WritableVirtualMachineWithConfigContextRequest { + todo!("Translation of collected information to VM not implemented yet!") } /// Returns the ID of the platform this machine uses. @@ -102,31 +136,58 @@ fn get_platform_id(state: &ThanixClient, platform_name: String) -> Option { return Some(platform.id); } } - None } -/// Translate gathered information about the virtual machine into a usable Payload. -pub fn information_to_vm( - state: &ThanixClient, - machine: &Machine, - config_data: &ConfigData, -) -> WritableVirtualMachineWithConfigContextRequest { - todo!() -} - -/// Validate the `Device` Payload. +/// Returns the ID of the ipv4 Adress linked to this device if it exists. +/// If not, a new IPv4 Adress object will be created in NetBox. /// -/// # Parameters -/// * payload: `WritableDeviceWithConfigContextRequest` - The struct to validate. +/// The function will retrieve a list of IPv4 Adresses from NetBox, +/// then search this list for the IP Adress Nazara collected. /// -/// # Returns +/// The "primary_network_interface" paramter specified in the `nazara_config.toml` +/// will be used to specify which adress to search for. +/// +/// # Parameters /// -/// - Ok(()) -fn validate_device_payload(payload: WritableDeviceWithConfigContextRequest) -> bool { - println!("Validating device payload..."); +/// * state: `&ThanixClient` - The client required for making API requests. +/// * machine: `&Machine` - The collected machine information. +fn get_primary_ip4(state: &ThanixClient, machine: &Machine, preferred_nwi: String) -> i64 { + println!("Retrieving list of IPv4 adresses..."); + let ip4_list: Vec; - todo!("Device payload validation not implemented yet!"); + match paths::ipam_ip_addresses_list(&state, IpamIpAddressesListQuery::default()) { + Ok(response) => { + println!("List received. Analyzing..."); + + ip4_list = match response { + paths::IpamIpAddressesListResponse::Http200(adresses) => adresses.results, + _ => { + todo!("Handling of non 200 Response code for Ipv4 retrieval not yet implemented.") + } + }; + } + Err(e) => { + eprintln!( + "\x1b[31m[error]\x1b[0m Failure while retrieving list of IPv4 Adresses.\n{}", + e + ); + process::exit(1); + } + } - false + for address in ip4_list { + // TODO search for fitting IP Adress + } + return 0; } + +/// Get the id of the location provided by the config file. +/// +/// Parameters +/// +/// * state: `&ThanixClient` - The client required for searching for the location. +/// * location_id: `i64` - The id of the location the system is located at. +fn get_location_id(state: &ThanixClient, location_id: i64) -> Option { + todo!("Getting device location not implemented yet.") +} From 77024cb22e77e41b90f95bf6f93b8c6b5e0437df Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Wed, 17 Apr 2024 16:01:12 +0200 Subject: [PATCH 08/14] change way custom_fields are handled --- src/configuration/config_parser.rs | 34 +++++++++++++++++--------- src/configuration/config_template.toml | 15 ++++++------ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/configuration/config_parser.rs b/src/configuration/config_parser.rs index a2b77b1..f8aae17 100644 --- a/src/configuration/config_parser.rs +++ b/src/configuration/config_parser.rs @@ -10,23 +10,35 @@ //! //! [system] //! name = "some_name" # Required for virtual machines! -//! site_id = 0 +//! site_id = 0 # The ID of the site this device is located at. //! description = "" //! comments = "Automatically registered using Nazara." //! device_type = 0 //! role = 0 +//! # Name of the network interface to set. (e.g eth0, etc) +//! # If not set, the first active interface will be selected. +//! primary_network_interface = "" +//! face = "" # Direction this device may face (e.g front or rear) +//! status = "active" # Status of the device. 'active' by default. +//! airflow = "front-to-rear" # Direction of airflow. //! +//! # Optional data of your device +//! # This section may be empty +//! [[system.optional]] +//! # tenant_group = 0 # The ID of the department this device belongs to. +//! # tenant = 0 # ID of the team or individual this device blongs to. +//! # location = 0 # ID of the location of the device. +//! # rack = 0 # ID of the Rack this device sits in. +//! # position = 0 # Position of the device within the Rack. +//! platform = "x86_64" # Name of the paltform of this device. //! -//! # These will be parsed a singl HashMap with no further checking. -//! # Make sure that these custom fields line up with the -//! # Custom fields of your NetBox instance. -//! [[system.custom]] -//! cpu_count = 1 -//! platform = "x86_64" # Overriden by collector -//! collect_cpu_information = true -//! collect_network_information = true -//! primary_network_interface = "eth0" -//! config_template = 0 # integer of the config_template ID +//! # These will be parsed into a single HashMap. You must provide +//! # the correct field labels as there is no way for Nazara to know. +//! +//! # These values are purely exemplary. +//! [system.custom_fields] +//! # cpu_count = 1 +//! # config_template = 0 //! ``` //! //! It will be created at ` ~/.nazara-config.toml`. diff --git a/src/configuration/config_template.toml b/src/configuration/config_template.toml index 4dda218..6bd751e 100644 --- a/src/configuration/config_template.toml +++ b/src/configuration/config_template.toml @@ -18,7 +18,7 @@ device_role = 0 # ID of the device role in NetBox primary_network_interface = "eth0" face = "" # Direction this device may face in (e.g front or rear) status = "active" # status of the device. default: active -airflow = "Front to rear" # Direction of airflow +airflow = "front-to-rear" # Direction of airflow # Optional data of your device. # This section may be empty @@ -30,15 +30,14 @@ airflow = "Front to rear" # Direction of airflow # position = 0 # Position of the device within the rack if any platform = "x86_64" -# TODO - # Custom parameters about the system, which fall under the "custom_fields" section. # Basically anything can go here, these are example values Nazara collects. -[[system.custom]] -cpu_count = 1 # overriden by collector -config_template = 0 +[system.custom] +# cpu_count = 1 # overriden by collector +# config_template = 0 ## ... # TODO: Does this make sense if this information is collected anyway? # TODO: Maybe something like this would be better? -collect_cpu_information = true -collect_network_information = true +# collect_cpu_information = true +# +# collect_network_information = true From 7250df6607b684772f325d99e107ac4c4beb96d6 Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Wed, 17 Apr 2024 16:10:44 +0200 Subject: [PATCH 09/14] add custom fields to payload --- .github/labeler.yml | 6 +++++- src/publisher.rs | 3 ++- src/publisher/api_client.rs | 11 +++++++++-- src/publisher/publisher.rs | 5 ++++- src/publisher/translator.rs | 23 +++++++++++++++-------- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index f94a3fc..9ef5a6f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -19,6 +19,10 @@ publisher: api-client: - src/publisher/api_client/* +translator: +- src/publisher/translator.rs +- src/publisher/trans_validation.rs + dependencies: - Cargo.toml - Cargo.lock @@ -37,4 +41,4 @@ code-quality: - .pre-commit-config.yaml maintenance: -- .github/* \ No newline at end of file +- .github/* diff --git a/src/publisher.rs b/src/publisher.rs index 697f5dc..cc6442a 100644 --- a/src/publisher.rs +++ b/src/publisher.rs @@ -1,4 +1,5 @@ pub mod api_client; // TODO make this non-public pub mod publisher; pub mod publisher_exceptions; -pub mod translator; // make this not public // TODO make this non-public +pub mod trans_validation; +pub mod translator; // make this not public // TODO make this non-public // TODO: make this non-public diff --git a/src/publisher/api_client.rs b/src/publisher/api_client.rs index 7423b27..2696a4e 100644 --- a/src/publisher/api_client.rs +++ b/src/publisher/api_client.rs @@ -8,7 +8,11 @@ extern crate thanix_client; use reqwest::{blocking::Client, Error as ReqwestError}; -use thanix_client::{paths::{dcim_devices_create, DcimDevicesCreateResponse}, types::WritableDeviceWithConfigContextRequest, util::ThanixClient}; +use thanix_client::{ + paths::{dcim_devices_create, DcimDevicesCreateResponse}, + types::WritableDeviceWithConfigContextRequest, + util::ThanixClient, +}; use super::{publisher, publisher_exceptions}; @@ -60,7 +64,10 @@ pub fn create_device(client: &ThanixClient, payload: &WritableDeviceWithConfigCo } DcimDevicesCreateResponse::Other(other_response) => { // TODO - todo!("Other Response codes from creation not yet implemented! {}", other_response.text().unwrap()); + todo!( + "Other Response codes from creation not yet implemented! {}", + other_response.text().unwrap() + ); } } } diff --git a/src/publisher/publisher.rs b/src/publisher/publisher.rs index 1a1fdf1..44c5248 100644 --- a/src/publisher/publisher.rs +++ b/src/publisher/publisher.rs @@ -22,7 +22,10 @@ use thanix_client::{ use crate::{ configuration::config_parser::ConfigData, - publisher::{api_client::{create_device, test_connection}, translator}, + publisher::{ + api_client::{create_device, test_connection}, + translator, + }, Machine, }; diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 7068fa0..2f4b524 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -8,7 +8,8 @@ use std::collections::HashMap; use std::process; use thanix_client::paths::{self, DcimPlatformsListQuery, IpamIpAddressesListQuery}; use thanix_client::types::{ - IPAddress, Platform, WritableDeviceWithConfigContextRequest, WritableVirtualMachineWithConfigContextRequest + IPAddress, Platform, WritableDeviceWithConfigContextRequest, + WritableVirtualMachineWithConfigContextRequest, }; use thanix_client::util::ThanixClient; @@ -22,9 +23,9 @@ use crate::{configuration::config_parser::ConfigData, Machine}; /// /// # Parameters /// -/// - `state: &ThanixClient` - API Client instance used for search and validation. -/// - `machine: &Machine` - Collected information about the device. -/// - `config_data: ConfigData` - Additional information about the device provided by config file +/// - state: `&ThanixClient` - API Client instance used for search and validation. +/// - machine: `&Machine` - Collected information about the device. +/// - config_data: `ConfigData` - Additional information about the device provided by config file /// or CLI. /// /// # Returns @@ -48,7 +49,9 @@ pub fn information_to_device( None }; - let mut payload: WritableDeviceWithConfigContextRequest = WritableDeviceWithConfigContextRequest::default(); + let mut payload: WritableDeviceWithConfigContextRequest = + WritableDeviceWithConfigContextRequest::default(); + payload.name = Some(config_data.system.name); payload.device_type = config_data.system.device_type; payload.role = config_data.system.device_role; @@ -69,7 +72,7 @@ pub fn information_to_device( payload.airflow = config_data.system.airflow; payload.comments = config_data.system.comments; // payload.config_template = todo!(); - payload.custom_fields = Some(HashMap::new()); + payload.custom_fields = config_data.system.custom_fields; // payload.description = todo!(); // payload.local_context_data = todo!(); // payload.oob_ip = todo!(); @@ -79,6 +82,7 @@ pub fn information_to_device( // payload.virtual_chassis = todo!(); // payload.vc_position = todo!(); // payload.vc_priority = todo!(); + // payload.tenant = todo!(); payload } @@ -109,6 +113,7 @@ pub fn information_to_vm( /// /// * state: `&ThanixClient` - The client required for searching for the platform. fn get_platform_id(state: &ThanixClient, platform_name: String) -> Option { + println!("Searching for id of platform '{}' ... ", platform_name); let platform_list: Vec; match paths::dcim_platforms_list(&state, DcimPlatformsListQuery::default()) { @@ -163,7 +168,9 @@ fn get_primary_ip4(state: &ThanixClient, machine: &Machine, preferred_nwi: Strin ip4_list = match response { paths::IpamIpAddressesListResponse::Http200(adresses) => adresses.results, _ => { - todo!("Handling of non 200 Response code for Ipv4 retrieval not yet implemented.") + todo!( + "Handling of non 200 Response code for Ipv4 retrieval not yet implemented." + ) } }; } @@ -190,4 +197,4 @@ fn get_primary_ip4(state: &ThanixClient, machine: &Machine, preferred_nwi: Strin /// * location_id: `i64` - The id of the location the system is located at. fn get_location_id(state: &ThanixClient, location_id: i64) -> Option { todo!("Getting device location not implemented yet.") -} +} From a2f6056100aa320ce3d210530d3e44a004ff1c12 Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Wed, 17 Apr 2024 16:15:05 +0200 Subject: [PATCH 10/14] create validation module make network information fields public --- src/collectors/network_collector.rs | 24 ++++++------- src/publisher/trans_validation.rs | 55 +++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 src/publisher/trans_validation.rs diff --git a/src/collectors/network_collector.rs b/src/collectors/network_collector.rs index 276f7f2..97826e0 100644 --- a/src/collectors/network_collector.rs +++ b/src/collectors/network_collector.rs @@ -31,18 +31,18 @@ use super::collector_exceptions; /// * interface_speed - `u32` the speed of the interface. #[derive(Serialize, Debug)] pub struct NetworkInformation { - name: String, - interface_speed: Option, - v4ip: Option, - v4broadcast: Option, - v4netmask: Option, - v6ip: Option, - v6broadcast: Option, - v6netmask: Option, - mac_addr: Option, - index: Option, - is_physical: bool, - is_connected: bool, + pub name: String, + pub interface_speed: Option, + pub v4ip: Option, + pub v4broadcast: Option, + pub v4netmask: Option, + pub v6ip: Option, + pub v6broadcast: Option, + pub v6netmask: Option, + pub mac_addr: Option, + pub index: Option, + pub is_physical: bool, + pub is_connected: bool, } /// Collect information about all network interfaces. diff --git a/src/publisher/trans_validation.rs b/src/publisher/trans_validation.rs new file mode 100644 index 0000000..179f7c7 --- /dev/null +++ b/src/publisher/trans_validation.rs @@ -0,0 +1,55 @@ +//! # Translator Validation Module +//! +//! This module is responsible for validating all system parameters before +//! a creation or update request is sent to NetBox. +//! +//! This includes: +//! +//! * Confirming certain API objects (like IP Adresses) are in fact present in NetBox. +//! * Validating that the Names of certain API objects are valid and correspond to an ID. +//! +//! > Also, given the filename here is room for a statement: +//! > Trans rights are human rights! + +use thanix_client::{types::WritableDeviceWithConfigContextRequest, util::ThanixClient}; + +use crate::{configuration::config_parser::ConfigData, Machine}; + +/// Validate the `Device` Payload. +/// +/// # Parameters +/// * payload: `WritableDeviceWithConfigContextRequest` - The struct to validate. +/// +/// # Returns +/// +/// - Ok(()) +fn validate_device_payload(payload: WritableDeviceWithConfigContextRequest) -> bool { + println!("Validating device payload..."); + + todo!("Device payload validation not implemented yet!"); + + false +} + +pub fn get_ip_adresses(state: &ThanixClient, config_data: &ConfigData) { + todo!("getting ip adresses is still todo"); +} + +// Create a new IP-Adress object in NetBox if the collected IP Adresses for the preferred interface +// do not exist yet. +// +// # Parameters +// +// * state: `&ThanixClient` - The `ThanixClient` object used for API connection. +// * config_data: `&ConfigData` - The config information which identifies the preferred network +// interface. +// * sys_info: `&Machine` - Collected system information which contains the IP Adresses to create. +// +// # Returns +// +// * Ok() +// +// # Panics +// +// This function panics if the creation if the IP Adresses return an error code other than "Ok". +fn create_ip_adresses(state: &ThanixClient, config_data: &ConfigData, sys_info: &Machine) {} From fb9de45b48d03d16e1183d8dea1c3679ea0ddfc3 Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Thu, 18 Apr 2024 13:00:24 +0200 Subject: [PATCH 11/14] make tenant and site passable by string or ID --- src/configuration/config_parser.rs | 44 +++++++++++++++++++++++++- src/configuration/config_template.toml | 3 ++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/configuration/config_parser.rs b/src/configuration/config_parser.rs index f8aae17..620b78e 100644 --- a/src/configuration/config_parser.rs +++ b/src/configuration/config_parser.rs @@ -97,7 +97,8 @@ pub struct NetboxConfig { #[derive(Debug, Serialize, Deserialize)] pub struct SystemConfig { pub name: String, - pub site_id: i64, + pub site_id: Option, + pub site_name: Option, pub description: String, pub comments: String, pub device_type: i64, @@ -110,7 +111,9 @@ pub struct SystemConfig { pub platform_name: Option, // optional System information pub tenant_group: Option, + pub tenant_group_name: Option, pub tenant: Option, + pub tenant_name: Option, pub location: Option, pub rack: Option, pub position: Option, @@ -359,6 +362,45 @@ impl ConfigData { ); } + if config_data.system.site_id.is_none() && config_data.system.site_name.is_none() { + return Err( + "\x1b[31m[error]\x1b[0m Validation Error: Config parameters 'site_id' and 'site_name' empty. One of these is mandatory!" + .to_string(), + ); + } + + if config_data.system.tenant.is_none() || config_data.system.tenant_name.is_none() { + println!("\x1b[36m[info]\x1b[0m One of the parameters 'tenant' or 'tenant_name' or both not set."); + } + + if config_data.system.tenant_group.is_none() + || config_data.system.tenant_group_name.is_none() + { + println!("x1b[36m[info]\x1b[0m One of the config parameters 'tenant_group' or 'tenant_group_name' or both not set."); + } + + if config_data.system.site_id.is_some() && config_data.system.site_name.is_some() { + return Err( + "\x1b[31m[error]\x1b[0m Validation Error: Parameters 'site_id' and 'site_name' are exclusive." + .to_string(), + ); + } + + if config_data.system.tenant_group.is_some() + && config_data.system.tenant_group_name.is_some() + { + return Err( + "\x1b[31m[error]\x1b[0m Validation Error: Parameters 'tenant_group' and 'tenant_group_name' are exclusive." + .to_string(), + ); + } + + if config_data.system.tenant.is_some() && config_data.system.tenant_name.is_some() { + return Err( + "\x1b[31m[error]\x1b[0m Validation Error: Parameters 'tenant' and 'tenant_name' are exclusive." + .to_string(), + ); + } Ok(()) } diff --git a/src/configuration/config_template.toml b/src/configuration/config_template.toml index 6bd751e..f390be5 100644 --- a/src/configuration/config_template.toml +++ b/src/configuration/config_template.toml @@ -9,6 +9,7 @@ netbox_uri = "{NETBOX_URI}" [system] name = "{SYSTEM_NAME}" # Name of the machine or VM. **Required** when device is a VM site_id = 0 # The id of the site this device is located at. (Stored in NetBox) +# site_name = "" # Name of the site this device is located at. (May take longer to search for) (Mutually exclusive with ID!) description = "" comments = "Automatically registered by Nazara." device_type = 0 # ID of the type of the Device Type in NetBox @@ -24,7 +25,9 @@ airflow = "front-to-rear" # Direction of airflow # This section may be empty [[system.optional]] # tenant_group = 0 # ID of the department this device belongs to +# tenant_group_name = "" # Name of the department this device belongs to (mutually exclusive with ID!) # tenant = 0 # ID of the team or individual this device belongs to +# tenant_name = "" # Name of the individual or team this device belongs to (mutually exclusive with ID!) # location = 0 # ID of the location of the device # rack = 0 # ID of the rack the device is mounted in if any # position = 0 # Position of the device within the rack if any From 5e909f2db3c878d241cff731626ff42ce33bd33d Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Thu, 18 Apr 2024 13:20:05 +0200 Subject: [PATCH 12/14] lay groundwork for ip retrieval --- src/publisher/api_client.rs | 2 +- src/publisher/trans_validation.rs | 46 ++++++------- src/publisher/translator.rs | 109 ++++++++++++++++++++++++++---- 3 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/publisher/api_client.rs b/src/publisher/api_client.rs index 2696a4e..6b080e3 100644 --- a/src/publisher/api_client.rs +++ b/src/publisher/api_client.rs @@ -59,7 +59,7 @@ pub fn create_device(client: &ThanixClient, payload: &WritableDeviceWithConfigCo DcimDevicesCreateResponse::Http201(created_device) => { // TODO println!( - "[success] Device creation successful!\nYour machine can be found under the ID {}.", created_device.id + "\x1b[32m[success] Device creation successful!\x1b[0m \n+++ Your machine can be found under the ID {}. +++", created_device.id ); } DcimDevicesCreateResponse::Other(other_response) => { diff --git a/src/publisher/trans_validation.rs b/src/publisher/trans_validation.rs index 179f7c7..dfc48d7 100644 --- a/src/publisher/trans_validation.rs +++ b/src/publisher/trans_validation.rs @@ -15,6 +15,29 @@ use thanix_client::{types::WritableDeviceWithConfigContextRequest, util::ThanixC use crate::{configuration::config_parser::ConfigData, Machine}; +/// Validate the system information found in the config file. +/// +/// Checks that parameters such as IDs and other system parameters entered in the config file +/// correspond to an existing NetBox object. +/// Otherwise, return an Error. +/// +/// # Parameters +/// +/// * state: `&ThanixClient` - The API client instance to use for communication. +/// * config_data: `&ConfigData` - The configuration file contents. +/// +/// # Returns +/// +/// Returns `Ok(())` when all relevant config parameters correspond to existing objects. Otherwise +/// return `Error`. +/// +/// # Panics +/// +/// This function panics if connection to NetBox fails. +// pub fn validate_config_data(state: &ThanixClient, config_data: &ConfigData) -> Result<(), Error> { +// Ok(()) +// } + /// Validate the `Device` Payload. /// /// # Parameters @@ -30,26 +53,3 @@ fn validate_device_payload(payload: WritableDeviceWithConfigContextRequest) -> b false } - -pub fn get_ip_adresses(state: &ThanixClient, config_data: &ConfigData) { - todo!("getting ip adresses is still todo"); -} - -// Create a new IP-Adress object in NetBox if the collected IP Adresses for the preferred interface -// do not exist yet. -// -// # Parameters -// -// * state: `&ThanixClient` - The `ThanixClient` object used for API connection. -// * config_data: `&ConfigData` - The config information which identifies the preferred network -// interface. -// * sys_info: `&Machine` - Collected system information which contains the IP Adresses to create. -// -// # Returns -// -// * Ok() -// -// # Panics -// -// This function panics if the creation if the IP Adresses return an error code other than "Ok". -fn create_ip_adresses(state: &ThanixClient, config_data: &ConfigData, sys_info: &Machine) {} diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 2f4b524..414e778 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -4,7 +4,6 @@ //! //! TODO: //! - Identify primary IPv4 or IPv6 using the primary_network_interface field from `ConfigData`. -use std::collections::HashMap; use std::process; use thanix_client::paths::{self, DcimPlatformsListQuery, IpamIpAddressesListQuery}; use thanix_client::types::{ @@ -13,8 +12,11 @@ use thanix_client::types::{ }; use thanix_client::util::ThanixClient; +use crate::collectors::network_collector::NetworkInformation; use crate::{configuration::config_parser::ConfigData, Machine}; +use super::publisher_exceptions::NetBoxApiError; + /// Translate the machine information to a `WritableDeviceWithConfigContextRequest` required by /// NetBox's API. /// @@ -30,22 +32,28 @@ use crate::{configuration::config_parser::ConfigData, Machine}; /// /// # Returns /// -/// - `device: WritableDeviceWithConfigContextRequest` - Payload for machine creation request +/// - device: `WritableDeviceWithConfigContextRequest` - Payload for machine creation request pub fn information_to_device( state: &ThanixClient, machine: &Machine, config_data: ConfigData, ) -> WritableDeviceWithConfigContextRequest { + println!("Creating Device object..."); + let wanted_platform: Option = if let Some(arch) = machine.dmi_information.cpu_information.arch.as_ref() { - println!("[info] CPU architecture was collected. Used by default, overriding possible config options..."); + println!("\x1b[36m[info]\x1b[0m CPU architecture was collected. Used by default, overriding possible config options..."); Some(arch.clone()) } else if let Some(config_value) = config_data.system.platform_name.as_ref() { - println!("[info] Architecture was not collected. Using config specifications..."); + println!( + "\x1b[36m[info]\x1b[0m Architecture was not collected. Using config specifications..." + ); Some(config_value.clone()) } else { - println!("[warning] No cpu architecture specified. Proceeding with 'none'..."); + println!( + "[\x1b[33m[warning]\x1b[0m No cpu architecture specified. Proceeding with 'none'..." + ); None }; @@ -62,7 +70,7 @@ pub fn information_to_device( }; payload.serial = machine.dmi_information.system_information.serial.clone(); // payload.asset_tag = todo!(); - payload.site = config_data.system.site_id; // TODO: Check if this exists. + payload.site = config_data.system.site_id.unwrap(); // TODO: Check if this exists. payload.rack = config_data.system.rack; payload.face = config_data.system.face; // payload.position = todo!(); @@ -129,7 +137,7 @@ fn get_platform_id(state: &ThanixClient, platform_name: String) -> Option { } Err(e) => { eprintln!( - "\x1b[31m[error]\x1b[0m Failure while receiving list of platforms.\n{}", + "[\x1b[31m[error]\x1b[0m Failure while receiving list of platforms.\n{}", e ); process::exit(1); @@ -157,26 +165,42 @@ fn get_platform_id(state: &ThanixClient, platform_name: String) -> Option { /// /// * state: `&ThanixClient` - The client required for making API requests. /// * machine: `&Machine` - The collected machine information. -fn get_primary_ip4(state: &ThanixClient, machine: &Machine, preferred_nwi: String) -> i64 { +fn get_primary_addresses(state: &ThanixClient, machine: &Machine, preferred_nwi: String) -> i64 { println!("Retrieving list of IPv4 adresses..."); let ip4_list: Vec; + let key_nwi: &NetworkInformation; + + if let Some(nwi_match) = machine + .network_information + .iter() + .find(|nwi| nwi.name == preferred_nwi) + { + key_nwi = nwi_match; + } else { + eprintln!( + "\x1b[31m[error] Specified Network Interface '{}' not found!", + preferred_nwi + ); + process::exit(1); + }; match paths::ipam_ip_addresses_list(&state, IpamIpAddressesListQuery::default()) { Ok(response) => { - println!("List received. Analyzing..."); + println!("IPAddress list received. Analyzing..."); ip4_list = match response { paths::IpamIpAddressesListResponse::Http200(adresses) => adresses.results, - _ => { - todo!( - "Handling of non 200 Response code for Ipv4 retrieval not yet implemented." - ) + paths::IpamIpAddressesListResponse::Other(response) => { + eprintln!("\x1b[31m[error]\x1b[0m Failure while trying to retrieve list of IPAddresses. \n --- Unexpected response: {}", + response.text().unwrap() + ); + process::exit(1); } }; } Err(e) => { eprintln!( - "\x1b[31m[error]\x1b[0m Failure while retrieving list of IPv4 Adresses.\n{}", + "\x1b[31m[error]\x1b[0m Failure while retrieving list of IPv4 Adresses.\n --- Unexpected response: {} ---", e ); process::exit(1); @@ -184,7 +208,7 @@ fn get_primary_ip4(state: &ThanixClient, machine: &Machine, preferred_nwi: Strin } for address in ip4_list { - // TODO search for fitting IP Adress + // TODO search for IP Address(es) in List. } return 0; } @@ -198,3 +222,58 @@ fn get_primary_ip4(state: &ThanixClient, machine: &Machine, preferred_nwi: Strin fn get_location_id(state: &ThanixClient, location_id: i64) -> Option { todo!("Getting device location not implemented yet.") } + +/// Search for the site specified in the config file by ID or by name. +fn get_site_id(state: &ThanixClient, config_data: &ConfigData) -> i64 { + println!("Searching for site..."); + if config_data.system.site_id.is_some() { + // Check if site with given ID exists. + match paths::dcim_sites_retrieve(state, config_data.system.site_id.unwrap()) { + Ok(response) => match response { + paths::DcimSitesRetrieveResponse::Http200(site) => site.id, + paths::DcimSitesRetrieveResponse::Other(response) => { + eprintln!( + "\x1b[31m[error]\x1b[0m Error while searching for site by site_id.\n--- Unexpected response: {} ---", + response.text().unwrap() + ); + process::exit(1); + } + }, + Err(e) => { + eprintln!( + "\x1b[31m[error]\x1b[0m Error while searching for site.\n{}", + e + ); + process::exit(1); + } + } + } else { + return 0; // Search for site using site_name + } +} + +// Create a new IP-Adress object in NetBox if the collected IP Adresses for the preferred interface +// do not exist yet. +// +// # Parameters +// +// * state: `&ThanixClient` - The `ThanixClient` object used for API connection. +// * config_data: `&ConfigData` - The config information which identifies the preferred network +// interface. +// * sys_info: `&Machine` - Collected system information which contains the IP Adresses to create. +// +// # Returns +// +// Return `Ok(i64)` containing the ID of the created IP Adress entry if the creation was +// successful. Otherwise, return `NetBoxApiError`. +// +// # Panics +// +// This function panics if the connection to NetBox fails. +fn create_ip_adresses( + state: &ThanixClient, + config_data: &ConfigData, + sys_info: &Machine, +) -> Result { + todo!(); +} From dd48d657eed760035fd3539ce0291e447d4fcf51 Mon Sep 17 00:00:00 2001 From: ByteOtter Date: Thu, 18 Apr 2024 14:47:17 +0200 Subject: [PATCH 13/14] search for site --- src/configuration/config_parser.rs | 2 +- src/configuration/config_template.toml | 2 +- src/publisher/translator.rs | 37 +++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/configuration/config_parser.rs b/src/configuration/config_parser.rs index 620b78e..1834bab 100644 --- a/src/configuration/config_parser.rs +++ b/src/configuration/config_parser.rs @@ -376,7 +376,7 @@ impl ConfigData { if config_data.system.tenant_group.is_none() || config_data.system.tenant_group_name.is_none() { - println!("x1b[36m[info]\x1b[0m One of the config parameters 'tenant_group' or 'tenant_group_name' or both not set."); + println!("\x1b[36m[info]\x1b[0m One of the config parameters 'tenant_group' or 'tenant_group_name' or both not set."); } if config_data.system.site_id.is_some() && config_data.system.site_name.is_some() { diff --git a/src/configuration/config_template.toml b/src/configuration/config_template.toml index f390be5..ca9f028 100644 --- a/src/configuration/config_template.toml +++ b/src/configuration/config_template.toml @@ -35,7 +35,7 @@ platform = "x86_64" # Custom parameters about the system, which fall under the "custom_fields" section. # Basically anything can go here, these are example values Nazara collects. -[system.custom] +[system.custom_fields] # cpu_count = 1 # overriden by collector # config_template = 0 ## ... diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 414e778..395fe53 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -5,9 +5,11 @@ //! TODO: //! - Identify primary IPv4 or IPv6 using the primary_network_interface field from `ConfigData`. use std::process; -use thanix_client::paths::{self, DcimPlatformsListQuery, IpamIpAddressesListQuery}; +use thanix_client::paths::{ + self, DcimPlatformsListQuery, DcimSitesListQuery, IpamIpAddressesListQuery, +}; use thanix_client::types::{ - IPAddress, Platform, WritableDeviceWithConfigContextRequest, + IPAddress, Platform, Site, WritableDeviceWithConfigContextRequest, WritableVirtualMachineWithConfigContextRequest, }; use thanix_client::util::ThanixClient; @@ -246,10 +248,37 @@ fn get_site_id(state: &ThanixClient, config_data: &ConfigData) -> i64 { ); process::exit(1); } - } + }; } else { - return 0; // Search for site using site_name + println!("\x1b[36m[info]\x1b[0m No 'site_id' specified. Searching by name..."); + let site_list: Vec; + match paths::dcim_sites_list(state, DcimSitesListQuery::default()) { + Ok(response) => match response { + paths::DcimSitesListResponse::Http200(sites) => site_list = sites.results, + paths::DcimSitesListResponse::Other(response) => { + eprintln!("\x1b[31[error] Error while retrieving site list.\n--- Unexpected response: {} ---", + response.text().unwrap() + ); + process::exit(1); + } + }, + Err(e) => { + eprintln!( + "\x1b[31m[error]\x1b[0m Error while performing site list query.\n{}", + e + ); + process::exit(1); + } + } + let target: String = config_data.system.site_name.clone().unwrap(); + + return site_list + .iter() + .find(|site| &site.name == &target) + .unwrap() + .id; } + 0 } // Create a new IP-Adress object in NetBox if the collected IP Adresses for the preferred interface From 44d9968846f8a95d487d040d9be48583384744d3 Mon Sep 17 00:00:00 2001 From: Marvin Date: Fri, 19 Apr 2024 15:31:48 +0200 Subject: [PATCH 14/14] Add IpAddr matching add site id search --- src/publisher/translator.rs | 118 +++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 30 deletions(-) diff --git a/src/publisher/translator.rs b/src/publisher/translator.rs index 395fe53..49dcb22 100644 --- a/src/publisher/translator.rs +++ b/src/publisher/translator.rs @@ -4,7 +4,10 @@ //! //! TODO: //! - Identify primary IPv4 or IPv6 using the primary_network_interface field from `ConfigData`. +use core::net::IpAddr; +use std::net::Ipv4Addr; use std::process; +use std::str::FromStr; use thanix_client::paths::{ self, DcimPlatformsListQuery, DcimSitesListQuery, IpamIpAddressesListQuery, }; @@ -62,7 +65,7 @@ pub fn information_to_device( let mut payload: WritableDeviceWithConfigContextRequest = WritableDeviceWithConfigContextRequest::default(); - payload.name = Some(config_data.system.name); + payload.name = Some(config_data.system.name.clone()); payload.device_type = config_data.system.device_type; payload.role = config_data.system.device_role; payload.tenant = config_data.system.tenant; @@ -72,7 +75,15 @@ pub fn information_to_device( }; payload.serial = machine.dmi_information.system_information.serial.clone(); // payload.asset_tag = todo!(); - payload.site = config_data.system.site_id.unwrap(); // TODO: Check if this exists. + payload.site = match get_site_id(state, &config_data) { + Some(site_id) => site_id, + None => { + eprintln!( + "\x1b[31m[error]\x1b[0m An Error occured while validating the site ID or name." + ); + process::exit(1); + } + }; payload.rack = config_data.system.rack; payload.face = config_data.system.face; // payload.position = todo!(); @@ -86,13 +97,25 @@ pub fn information_to_device( // payload.description = todo!(); // payload.local_context_data = todo!(); // payload.oob_ip = todo!(); - // TODO payload.primary_ip4 = todo!(); - // TODO payload.primary_ip6 = todo!(); + payload.primary_ip4 = get_primary_addresses( + state, + machine, + config_data + .system + .primary_network_interface + .clone() + .unwrap(), + ); + payload.primary_ip6 = get_primary_addresses( + state, + machine, + config_data.system.primary_network_interface.unwrap(), + ); // payload.tags = todo!(); // payload.virtual_chassis = todo!(); // payload.vc_position = todo!(); // payload.vc_priority = todo!(); - // payload.tenant = todo!(); + payload.location = config_data.system.location; payload } @@ -148,6 +171,7 @@ fn get_platform_id(state: &ThanixClient, platform_name: String) -> Option { for platform in platform_list { if platform.name == platform_name { + println!("\x1b[32m[success]\x1b[0m Platform ID found. Proceeding..."); return Some(platform.id); } } @@ -167,9 +191,13 @@ fn get_platform_id(state: &ThanixClient, platform_name: String) -> Option { /// /// * state: `&ThanixClient` - The client required for making API requests. /// * machine: `&Machine` - The collected machine information. -fn get_primary_addresses(state: &ThanixClient, machine: &Machine, preferred_nwi: String) -> i64 { - println!("Retrieving list of IPv4 adresses..."); - let ip4_list: Vec; +fn get_primary_addresses( + state: &ThanixClient, + machine: &Machine, + preferred_nwi: String, +) -> Option { + println!("Retrieving list of Addresses..."); + let ip_list: Vec; let key_nwi: &NetworkInformation; if let Some(nwi_match) = machine @@ -186,14 +214,15 @@ fn get_primary_addresses(state: &ThanixClient, machine: &Machine, preferred_nwi: process::exit(1); }; + // TODO: Split this API call off so it is only done once. match paths::ipam_ip_addresses_list(&state, IpamIpAddressesListQuery::default()) { Ok(response) => { println!("IPAddress list received. Analyzing..."); - ip4_list = match response { + ip_list = match response { paths::IpamIpAddressesListResponse::Http200(adresses) => adresses.results, paths::IpamIpAddressesListResponse::Other(response) => { - eprintln!("\x1b[31m[error]\x1b[0m Failure while trying to retrieve list of IPAddresses. \n --- Unexpected response: {}", + eprintln!("\x1b[31m[error]\x1b[0m Failure while trying to retrieve list of IPAddresses. \n --- Unexpected response: {} ---", response.text().unwrap() ); process::exit(1); @@ -209,28 +238,53 @@ fn get_primary_addresses(state: &ThanixClient, machine: &Machine, preferred_nwi: } } - for address in ip4_list { - // TODO search for IP Address(es) in List. + let mut result: Option = None; + + for (idx, addr) in ip_list.iter().enumerate() { + print! {"Searching for matching IP Adress... ({:?}/{:?})\r", idx+1, ip_list.len()}; + let ip = IpAddr::from_str(addr.address.clone().split("/").next().unwrap()).unwrap(); // TODO: Errorhandling + match ip { + IpAddr::V4(x) => match key_nwi.v4ip { + Some(y) => { + if x == y { + result = Some(addr.id); + } + } + None => todo!(), + }, + IpAddr::V6(x) => match key_nwi.v6ip { + Some(y) => { + if x == y { + result = Some(addr.id); + } + } + None => todo!(), + }, + } } - return 0; + println!(); + result } -/// Get the id of the location provided by the config file. +/// Search for the site specified in the config file by ID or by name. /// -/// Parameters +/// # Parameters /// -/// * state: `&ThanixClient` - The client required for searching for the location. -/// * location_id: `i64` - The id of the location the system is located at. -fn get_location_id(state: &ThanixClient, location_id: i64) -> Option { - todo!("Getting device location not implemented yet.") -} - -/// Search for the site specified in the config file by ID or by name. -fn get_site_id(state: &ThanixClient, config_data: &ConfigData) -> i64 { +/// * state: `&ThanixClient` - The client required for performing API requests. +/// * config_data: `&ConfigData` - The configuration data found in the config file. +/// +/// # Returns +/// +/// * site_id: `i64` - The ID of the site if found. If not found, returns 0. +/// +/// # Aborts +/// +/// Unexpected API responses may terminate the process. +fn get_site_id(state: &ThanixClient, config_data: &ConfigData) -> Option { println!("Searching for site..."); if config_data.system.site_id.is_some() { // Check if site with given ID exists. - match paths::dcim_sites_retrieve(state, config_data.system.site_id.unwrap()) { + let target = match paths::dcim_sites_retrieve(state, config_data.system.site_id.unwrap()) { Ok(response) => match response { paths::DcimSitesRetrieveResponse::Http200(site) => site.id, paths::DcimSitesRetrieveResponse::Other(response) => { @@ -249,6 +303,8 @@ fn get_site_id(state: &ThanixClient, config_data: &ConfigData) -> i64 { process::exit(1); } }; + println!("\x1b[32m[success]\x1b[0m Valid site ID. Proceeding..."); + return Some(target); } else { println!("\x1b[36m[info]\x1b[0m No 'site_id' specified. Searching by name..."); let site_list: Vec; @@ -272,13 +328,15 @@ fn get_site_id(state: &ThanixClient, config_data: &ConfigData) -> i64 { } let target: String = config_data.system.site_name.clone().unwrap(); - return site_list - .iter() - .find(|site| &site.name == &target) - .unwrap() - .id; + return Some( + site_list + .iter() + .find(|site| &site.name == &target) + .unwrap() + .id, + ); } - 0 + None } // Create a new IP-Adress object in NetBox if the collected IP Adresses for the preferred interface