Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement linking of IP Adresses to newly created device. #73

Merged
merged 17 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ Cargo.lock
# output.txt file used for testing
output.txt
output.json

# Test output
test/manual/**/output/
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ 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 = "1.0.0"
thanix_client = "1.2.0"
# Uncomment this line if you are using a custom thanix client implementation.
# Change the path to be relative to this Cargo.toml file.
# The package parameter is the name of your client package. This is needed if you assigned a custom name upon creation.
# thanix_client = { package = "your_package_name", path = "path/to/your/client/crate" }
# thanix_client = { package = "thanix_client", path = "/path/to/your/crate" }

[dev-dependencies]
mockall = "0.11.4"
10 changes: 7 additions & 3 deletions src/collectors/network_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ pub fn collect_network_information(
match network_interfaces_result {
Ok(network_interfaces) => {
if network_interfaces.is_empty() {
return Err(collector_exceptions::NoNetworkInterfacesException {
Err(collector_exceptions::NoNetworkInterfacesException {
message: "\x1b[31m[error]\x1b[0m No network interfaces found!".to_string(),
});
})
} else {
return Ok(network_interfaces);
Ok(network_interfaces)
}
}
Err(_) => {
Expand Down Expand Up @@ -114,6 +114,10 @@ pub fn construct_network_information(
let mut network_information: NetworkInformation;

for network_interface in raw_information {
// Skip loopback device.
if network_interface.name == "lo" {
continue;
}
match Some(&network_interface.addr) {
Some(_v) => {
// Cases where only one set of addresses exist.
Expand Down
85 changes: 82 additions & 3 deletions src/configuration/config_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ use super::config_exceptions::{self, *};
/// # Members
/// - netbox: `NetBoxConfig` - Configuration parameters for the NetBox connection.
/// - system: `SystemConfig` - Parameters abouth the system.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ConfigData {
pub netbox: NetboxConfig,
pub system: SystemConfig,
pub nwi: NwiConfig,
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NetboxConfig {
pub netbox_api_token: String,
pub netbox_uri: String,
Expand Down Expand Up @@ -94,7 +95,7 @@ pub struct NetboxConfig {
/// * tenant: `Option<i64>` - ID of tenant this device belongs to. (e.g: team or individual)
/// * rack: `Option<i64>` - ID of the rack this device is located in.
/// * position: `Option<i64>` - Position of the device within a rack if any.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SystemConfig {
pub name: String,
pub site_id: Option<i64>,
Expand All @@ -119,6 +120,80 @@ pub struct SystemConfig {
pub position: Option<i64>,
}

/// Information about the system's interface.
///
/// # Members
///
/// * name: `String` - The name of the interface.
/// * id: `Option<i64>` - The ID of the interface, if it already exists. Mutually exclusive with
/// name.
/// * vdcs: `Option<Vec<i64>>`
/// * module: `Option<i64>` - The module assigned to this interface.
/// * label: `Option<String>` - The phyiscal label of this device if any.
/// * r#type: `String` - The type of the interface (e.g. "bridge")
/// * enabled: `bool` - Whether this device is enabled or not. Default: `True`.
/// * parent: `Option<i64>` - ID of the parent interface if applicable.
/// * bridge: `Option<i64>` - ID of the bridge device for this interface if applicable.
/// * lag: `Option<i64>`
/// * mtu: `Option<u32>`
/// * duplex: `Option<String>`
/// * wwn: `Option<String>`
/// * mgmt_only: `bool` - Whether this interface may only be used for management. Default: `False`.
/// * description: `Option<String>` - Optional description of the device.
/// * mode: `Option<String>` - The mode this interface operates in.
/// * rf_role: `Option<String>`
/// * rf_channel: `Option<String>`
/// * poe_mode: `Option<String>`
/// * poe_type: `Option<String>`
/// * rf_channel_frequency: `Option<f64>`
/// * rf_channel_width: `Option<f64>`
/// * poe_mode: `Option<String>` - The PoE mode of the interface.
/// * poe_type: `Option<String>`
/// * rf_channel_frequency: `Option<f64>`
/// * rf_channel_width: `Option<f64>`
/// * tx_power: `Option<u8>`
/// * untagged_vlans: `Option<Vec<i64>>` - List of IDs of untagged VLANs assigned to this
/// interface.
/// * tagged_vlans: `Option<Vec<i64>>` - List of IDs of tagged VLANs assigned to this interface.
/// * mark_connected: `bool` - Whether this interface is connected. Default: `True`.
/// * wireless_lans: `Option<Vec<i64>>`
/// * vrf: `Option<i64>`
/// * custom_fields: `Option<Hashmap<String, Value, RandomState>>` - Any Custom fields you wish to
/// add in form a of a Key-Value list.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NwiConfig {
pub name: Option<String>,
pub id: Option<i64>,
pub vdcs: Option<Vec<i64>>,
pub module: Option<i64>,
pub label: Option<String>,
#[serde(rename = "rtype")]
pub r#type: String, // I hate this field name, but that's what the openAPI schema said.
pub enabled: bool,
pub parent: Option<i64>,
pub bridge: Option<i64>,
pub lag: Option<i64>,
pub mtu: Option<u32>,
pub duplex: Option<String>,
pub wwn: Option<String>,
pub mgmt_only: bool,
pub description: Option<String>,
pub mode: Option<String>,
pub rf_role: Option<String>,
pub rf_channel: Option<String>,
pub poe_mode: Option<String>,
pub poe_type: Option<String>,
pub rf_channel_frequency: Option<f64>,
pub rf_channel_width: Option<f64>,
pub tx_power: Option<u8>,
pub untagged_vlans: Option<Vec<i64>>,
pub tagged_vlans: Option<Vec<i64>>,
pub mark_connected: bool,
pub wireless_lans: Option<Vec<i64>>,
pub vrf: Option<i64>,
pub custom_fields: Option<HashMap<String, Value, RandomState>>,
}

/// Set up configuration
///
/// This function reads the configuration file located at `~/.nazara-config.toml`. If no file can be found, a warning is
Expand Down Expand Up @@ -410,6 +485,10 @@ impl ConfigData {
/// # Returns
///
/// * `config: ConfigData` - A `ConfigData` object.
///
/// # Aborts
///
/// This function pay terminate the process if it cannot read the cofnig file.
fn read_config_file() -> ConfigData {
let mut file = match File::open(get_config_dir()) {
Ok(file) => file,
Expand Down
35 changes: 35 additions & 0 deletions src/configuration/config_template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,38 @@ platform = "x86_64"
# collect_cpu_information = true
#
# collect_network_information = true

[nwi]
# name = "" # The name of your interface. Must correspond to an interface name present on this device.
# id = 0 # The ID of the desired interface, if it already exists. (Mutually exclusive with name)
# vdcs = []
# module = 0
# label = "" # The physical label on the interface or device, if applicable.
# r#type = "" # The type of the interface (e.g "bridge")
enabled = true # Whether this interface is enabled or not. Default: True.
# parent = 0 # ID of the parent interface if applicable.
# bridge = 0 # ID of the bridge device associated with this interface if applicable.
# lag = 0
# mtu = 0 # 32-bit unsigned interger!
# duplex = ""
# wwn = ""
mgmt_only = false # Whether this interface shall be used for management only. (Default: False)
# description = "" # Optional description of this interface.
# mode = "" # Mode this interface operates in.
# rf_role = ""
# rf_channel = ""
# poe_mode = ""
# poe_type = ""
# rf_channel_frequency = 0.0 # f64 value.
# rf_channel_width = 0.0 # f64 value.
# tx_power = 0 # u8 value
# untagged_vlans = [] # List of IDs of the untagged VLANs associated to this interface.
# tagged_vlans = [] # List of IDs of the tagged VLANs associated to this interfacce.
mark_connected = true # Whether this interface is currently connected or not.
# wireless_lans = [] # List of IDs of the wireless lans associated with this interface.
# vrf = 0

[nwi.custom_fields]
# Custom fields of the interface go here.


2 changes: 1 addition & 1 deletion src/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ pub mod api_client; // TODO make this non-public
pub mod publisher;
pub mod publisher_exceptions;
pub mod trans_validation;
pub mod translator; // make this not public // TODO make this non-public // TODO: make this non-public
pub mod translator;
148 changes: 139 additions & 9 deletions src/publisher/api_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
//! deserialize our data.
extern crate thanix_client;

use reqwest::{blocking::Client, Error as ReqwestError};
use reqwest::Error as ReqwestError;
use thanix_client::{
paths::{dcim_devices_create, DcimDevicesCreateResponse},
types::WritableDeviceWithConfigContextRequest,
paths::{
dcim_devices_create, dcim_interfaces_create, dcim_interfaces_list,
ipam_ip_addresses_create, DcimDevicesCreateResponse, DcimInterfacesListQuery,
},
types::{
Interface, WritableDeviceWithConfigContextRequest, WritableIPAddressRequest,
WritableInterfaceRequest,
},
util::ThanixClient,
};

use super::{publisher, publisher_exceptions};
use super::publisher_exceptions::{self, NetBoxApiError};

/// Tests connection to the NetBox API.
///
Expand Down Expand Up @@ -50,17 +56,29 @@ pub fn test_connection(client: &ThanixClient) -> Result<(), publisher_exceptions
}
}

pub fn create_device(client: &ThanixClient, payload: &WritableDeviceWithConfigContextRequest) {
println!("Creating Device in NetBox...");
/// Send request to create a new device in NetBox.
///
/// # Parameters
///
/// * client: `&ThanixClient` - The `ThanixClient` instance to use for communication.
/// * payload: `&WritableDeviceWithConfigContextRequest` - The information about the device serving
/// as a request body.
///
pub fn create_device(
client: &ThanixClient,
payload: &WritableDeviceWithConfigContextRequest,
) -> Result<i64, NetBoxApiError> {
println!("Creating device in NetBox...");

match dcim_devices_create(client, payload.clone()) {
Ok(response) => {
match response {
DcimDevicesCreateResponse::Http201(created_device) => {
// TODO
println!(
"\x1b[32m[success] Device creation successful!\x1b[0m \n+++ Your machine can be found under the ID {}. +++", created_device.id
);
"\x1b[32m[success]\x1b[0m Device creation successful! New Device-ID: '{}'.",
created_device.id
);
Ok(created_device.id)
}
DcimDevicesCreateResponse::Other(other_response) => {
// TODO
Expand All @@ -76,3 +94,115 @@ pub fn create_device(client: &ThanixClient, payload: &WritableDeviceWithConfigCo
}
}
}

/// Create an interface object in NetBox.
///
/// # Parameters
///
/// * client: `&ThanixClient` - The client instance necessary for communication.
/// * payload: `&WritableInterfaceRequest` - The payload for the API request.
///
/// # Returns
///
/// * `Ok(i64)` - The ID of the interface object.
/// * `Err(NetBoxApiError)` - Error will be passed back to `publisher`.
///
/// # Panics
///
/// Panics if NetBox become unreachable.
pub fn create_interface(
client: &ThanixClient,
payload: WritableInterfaceRequest,
) -> Result<i64, NetBoxApiError> {
println!("Creating network interface in NetBox...");

match dcim_interfaces_create(client, payload) {
Ok(response) => match response {
thanix_client::paths::DcimInterfacesCreateResponse::Http201(result) => {
println!("\x1b[32m[success]\x1b[0m Interface created successfully. New Interface-ID: '{}'", result.id);
Ok(result.id)
}
thanix_client::paths::DcimInterfacesCreateResponse::Other(other_response) => {
let exc: NetBoxApiError = NetBoxApiError::Other(other_response.text().unwrap());
Err(exc)
}
},
Err(e) => {
eprintln!("\x1b[33m[warning]\x1b[0m Error while decoding NetBox Response while creating network interface. This is probably still fine and a problem with NetBox.\nError: {}", e);
let exc = NetBoxApiError::Other(e.to_string());
Err(exc)
}
}
}

/// Create new IP adress object.
///
/// # Parameters
///
/// * client: `&ThanixClient` - The client instance necessary for communication.
/// * payload: `&`
pub fn create_ip(
client: &ThanixClient,
payload: WritableIPAddressRequest,
) -> Result<i64, NetBoxApiError> {
println!("Creating new IP address object...");

match ipam_ip_addresses_create(client, payload) {
Ok(response) => match response {
thanix_client::paths::IpamIpAddressesCreateResponse::Http201(result) => {
println!(
"\x1b[32m[success]\x1b[0m IP Address created successfully. New IP-ID: '{}'",
result.id
);
Ok(result.id)
}
thanix_client::paths::IpamIpAddressesCreateResponse::Other(other_response) => {
let exc: NetBoxApiError = NetBoxApiError::Other(other_response.text().unwrap());
Err(exc)
}
},
Err(e) => {
eprintln!("\x1b[33m[warning]\x1b[0m Error while decoding NetBox response while creating IP address. This probably is still fine and a problem with NetBox.\nError: {}", e);
let exc = NetBoxApiError::Other(e.to_string());
Err(exc)
}
}
}

pub fn get_interface_by_name(
state: &ThanixClient,
payload: &WritableInterfaceRequest,
) -> Result<Interface, NetBoxApiError> {
println!(
"Trying to retrieve interface by name '{}'...",
payload.name.as_ref().unwrap()
);

match dcim_interfaces_list(state, DcimInterfacesListQuery::default()) {
Ok(response) => {
let interface_list: Vec<Interface> = match response {
thanix_client::paths::DcimInterfacesListResponse::Http200(interfaces) => {
interfaces.results.unwrap()
}
thanix_client::paths::DcimInterfacesListResponse::Other(response) => {
let err: NetBoxApiError = NetBoxApiError::Other(response.text().unwrap());
return Err(err);
}
};

for interface in interface_list {
if interface.name == payload.name {
return Ok(interface);
}
}
Err(NetBoxApiError::Other(format!(
"No Inteface '{}' with name found. Creation possibly failed.",
payload.name.as_ref().unwrap()
)))
}
Err(e) => {
let err: NetBoxApiError = NetBoxApiError::Reqwest(e);
Err(err)
}
}
}
Loading
Loading