Added endpoints to get/list interface and system health

This commit is contained in:
Ishan Jain 2022-04-17 21:00:12 +05:30
commit ed5cd865c5
13 changed files with 1932 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1635
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

21
Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "mikrotik"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-process = "1.2.0"
async-recursion = "0.3.2"
base64 = "0.13.0"
color-eyre = "0.6.1"
futures = "0.3.17"
reqwest = { version = "0.11.4", features = ["rustls-tls", "serde_json", "json"] }
serde = { version = "1.0.130", features = ["derive"] }
serde_derive = "1.0.136"
serde_json = "1.0.68"
tokio = { version = "1.10.1", features = ["full"] }
tokio-test = "0.4.2"
tracing = "0.1.34"
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }

51
src/client.rs Normal file
View File

@ -0,0 +1,51 @@
use color_eyre::Report;
use reqwest::{
header::{HeaderMap, HeaderValue},
Request, Url,
};
pub struct Client {
pub client: reqwest::Client,
pub base_url: Url,
pub basic_auth: HeaderValue,
pub self_signed_cert: bool,
}
impl Client {
pub fn new(
base_url: Url,
username: String,
password: String,
self_signed_cert: bool,
) -> Result<Self, Report> {
let value = format!("{}:{}", username, password);
let value = base64::encode(value);
let basic_auth = HeaderValue::from_str(&format!("Basic {}", value))?;
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(self_signed_cert)
.build()?;
Ok(Self {
client,
base_url,
basic_auth,
self_signed_cert,
})
}
pub fn add_credentials(&self, m: &mut HeaderMap<HeaderValue>) {
let token = m
.entry("Authorization")
.or_insert_with(|| self.basic_auth.clone());
*token = self.basic_auth.clone();
}
pub async fn execute(&mut self, mut r: Request) -> Result<reqwest::Response, reqwest::Error> {
self.add_credentials(r.headers_mut());
self.client.execute(r).await
}
}

View File

@ -0,0 +1,28 @@
use super::types::*;
use crate::Client;
use color_eyre::Report;
use reqwest::{Method, Request};
use tracing::debug;
pub async fn list(client: &mut Client) -> Result<Vec<Interface>, Report> {
let url = client.base_url.clone();
let url = url.join(super::BASE)?;
let req = Request::new(Method::GET, url);
let response = client.execute(req).await?.json::<Vec<Interface>>().await?;
Ok(response)
}
pub async fn get(client: &mut Client, ifid: &str) -> Result<Interface, Report> {
let url = client.base_url.clone();
let url = url.join(&format!("{}/{}", super::BASE, ifid))?;
debug!("url {}", url);
let req = Request::new(Method::GET, url);
let response = client.execute(req).await?.json::<Interface>().await?;
Ok(response)
}

6
src/interface/mod.rs Normal file
View File

@ -0,0 +1,6 @@
mod interface;
mod types;
pub use interface::*;
const BASE: &str = "rest/interface";

58
src/interface/types.rs Normal file
View File

@ -0,0 +1,58 @@
use serde_derive::Deserialize;
use serde_derive::Serialize;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Interface {
#[serde(rename = ".id")]
pub id: String,
#[serde(rename = "actual-mtu")]
pub actual_mtu: String,
pub comment: Option<String>,
#[serde(rename = "default-name")]
pub default_name: Option<String>,
pub disabled: String,
#[serde(rename = "fp-rx-byte")]
pub fp_rx_byte: String,
#[serde(rename = "fp-rx-packet")]
pub fp_rx_packet: String,
#[serde(rename = "fp-tx-byte")]
pub fp_tx_byte: String,
#[serde(rename = "fp-tx-packet")]
pub fp_tx_packet: String,
pub l2mtu: Option<String>,
#[serde(rename = "last-link-up-time")]
pub last_link_up_time: Option<String>,
#[serde(rename = "link-downs")]
pub link_downs: String,
#[serde(rename = "mac-address")]
pub mac_address: Option<String>,
#[serde(rename = "max-l2mtu")]
pub max_l2mtu: Option<String>,
pub mtu: String,
pub name: String,
pub running: String,
#[serde(rename = "rx-byte")]
pub rx_byte: String,
#[serde(rename = "rx-drop")]
pub rx_drop: String,
#[serde(rename = "rx-error")]
pub rx_error: String,
#[serde(rename = "rx-packet")]
pub rx_packet: String,
pub slave: Option<String>,
#[serde(rename = "tx-byte")]
pub tx_byte: String,
#[serde(rename = "tx-drop")]
pub tx_drop: String,
#[serde(rename = "tx-error")]
pub tx_error: String,
#[serde(rename = "tx-packet")]
pub tx_packet: String,
#[serde(rename = "tx-queue-drop")]
pub tx_queue_drop: String,
#[serde(rename = "type")]
pub type_field: String,
#[serde(rename = "last-link-down-time")]
pub last_link_down_time: Option<String>,
}

5
src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
mod client;
pub mod interface;
pub mod system;
pub use client::*;

14
src/system/health.rs Normal file
View File

@ -0,0 +1,14 @@
use crate::{system::types::Health, Client};
use color_eyre::Report;
use reqwest::{Method, Request};
pub async fn health(client: &mut Client) -> Result<Vec<Health>, Report> {
let url = client.base_url.clone();
let url = url.join(&format!("{}/health", super::BASE))?;
let req = Request::new(Method::GET, url);
let response = client.execute(req).await?.json::<Vec<Health>>().await?;
Ok(response)
}

6
src/system/mod.rs Normal file
View File

@ -0,0 +1,6 @@
mod health;
mod types;
pub use health::*;
const BASE: &str = "rest/system";

13
src/system/types.rs Normal file
View File

@ -0,0 +1,13 @@
use serde_derive::Deserialize;
use serde_derive::Serialize;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Health {
#[serde(rename = ".id")]
pub id: String,
pub name: String,
#[serde(rename = "type")]
pub type_field: String,
pub value: String,
}

54
tests/interfaces_test.rs Normal file
View File

@ -0,0 +1,54 @@
use color_eyre::Report;
use mikrotik::Client;
use reqwest::Url;
use std::sync::Once;
use tracing_subscriber::EnvFilter;
static INIT: Once = Once::new();
fn setup() -> Result<(), Report> {
INIT.call_once(|| {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1")
}
color_eyre::install();
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}
tracing_subscriber::fmt::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
});
Ok(())
}
#[tokio::test]
async fn list_interfaces() -> Result<(), Report> {
setup()?;
let base = Url::parse("https://10.0.10.1")?;
let mut client = Client::new(base, "admin".to_string(), "ifd783far".to_string(), true)?;
let response = mikrotik::interface::list(&mut client).await?;
println!("{:?}", response);
Ok(())
}
#[tokio::test]
async fn get_interface() -> Result<(), Report> {
setup()?;
let base = Url::parse("https://10.0.10.1")?;
let mut client = Client::new(base, "admin".to_string(), "ifd783far".to_string(), true)?;
let response = mikrotik::interface::get(&mut client, "ether5").await?;
println!("{:?}", response);
Ok(())
}

40
tests/system_test.rs Normal file
View File

@ -0,0 +1,40 @@
use color_eyre::Report;
use mikrotik::Client;
use reqwest::Url;
use std::sync::Once;
use tracing_subscriber::EnvFilter;
static INIT: Once = Once::new();
fn setup() -> Result<(), Report> {
INIT.call_once(|| {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1")
}
color_eyre::install();
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}
tracing_subscriber::fmt::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
});
Ok(())
}
#[tokio::test]
async fn health() -> Result<(), Report> {
setup()?;
let base = Url::parse("https://10.0.10.1")?;
let mut client = Client::new(base, "admin".to_string(), "ifd783far".to_string(), true)?;
let response = mikrotik::system::health(&mut client).await?;
println!("{:?}", response);
Ok(())
}