Skip to content

Commit

Permalink
Merge pull request #73 from ByteOtter/dev/link-ip-adresses
Browse files Browse the repository at this point in the history
Implement linking of IP Adresses to newly created device.
  • Loading branch information
ByteOtter authored Sep 5, 2024
2 parents 1c58ffd + bae50c4 commit a972201
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 125 deletions.
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

0 comments on commit a972201

Please sign in to comment.