Added ssh functionality and improved hostnames

This commit is contained in:
Sugui 2024-07-07 19:01:11 +02:00
parent e33b503159
commit ac28a37cd6
7 changed files with 85 additions and 58 deletions

View File

@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
hostname-validator = "1.1.1"
openssh = "0.10.4"
ping = "0.5.2" ping = "0.5.2"
regex = "1.10.5"
thiserror = "1.0.61" thiserror = "1.0.61"
tokio = { version = "1.38.0", features = ["rt-multi-thread"] }

View File

@ -1,35 +1,26 @@
use ::thiserror::Error; use ::thiserror::Error;
use regex::Regex;
const VALID_IP_ADDRESS_PATTERN: &str = r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$";
const VALID_HOSTNAME_PATTERN: &str = r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$";
#[derive(Clone, Debug, Error)] #[derive(Clone, Debug, Error)]
#[error("The hostname \"{0}\" is invalid")] #[error("The hostname \"{0}\" is invalid")]
pub struct InvalidHostnameError(String); pub struct InvalidHostnameError(String);
pub struct Hostname { pub struct Hostname {
hostname: String, pub value: String,
} }
impl Hostname { impl Hostname {
pub fn new(hostname: &str) -> Result<Hostname, InvalidHostnameError> { pub fn new(hostname: &str) -> Result<Hostname, InvalidHostnameError> {
if Self::is_valid_ip(&hostname) || Self::is_valid_hostname(&hostname) { if Self::is_valid_hostname(&hostname) {
Ok(Hostname { Ok(Hostname {
hostname: hostname.to_string(), value: hostname.to_string(),
}) })
} else { } else {
Err(InvalidHostnameError(hostname.to_string())) Err(InvalidHostnameError(hostname.to_string()))
} }
} }
fn is_valid_ip(ip: &str) -> bool {
let re = Regex::new(VALID_IP_ADDRESS_PATTERN).unwrap();
re.is_match(ip)
}
fn is_valid_hostname(hostname: &str) -> bool { fn is_valid_hostname(hostname: &str) -> bool {
let re = Regex::new(VALID_HOSTNAME_PATTERN).unwrap(); hostname_validator::is_valid(hostname)
re.is_match(hostname)
} }
} }
@ -57,12 +48,24 @@ mod tests {
assert!(Hostname::new("is-42-the-response").is_ok()); assert!(Hostname::new("is-42-the-response").is_ok());
assert!(Hostname::new("GoB.NaSa-42.eu").is_ok()); assert!(Hostname::new("GoB.NaSa-42.eu").is_ok());
assert!(Hostname::new("256.127.63.31").is_ok()); assert!(Hostname::new("256.127.63.31").is_ok());
assert!(Hostname::new(
"es.this-large-hostname-is-destined-to-work.so-you-dont-need-to-worry-for-anything.com"
)
.is_ok());
} }
#[test] #[test]
fn invalid_hostnames() { fn invalid_hostnames() {
assert!(Hostname::new("no..double.dots").is_err()); assert!(Hostname::new("no..double.dots").is_err());
assert!(Hostname::new("#illegal-character").is_err()); // We don't support IPv6 yet assert!(Hostname::new("#illegal-character").is_err());
assert!(Hostname::new(
"es.this-large-hostname-is-destined-to-panic-so-hard-that-it-will-break-computer.com"
)
.is_err());
assert!(Hostname::new(
"too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname.too-large-hostname"
)
.is_err());
assert!(Hostname::new("").is_err()); assert!(Hostname::new("").is_err());
assert!(Hostname::new("not-end-with-hyphen-").is_err()); assert!(Hostname::new("not-end-with-hyphen-").is_err());
} }

View File

@ -1,15 +1,8 @@
use crate::hostname::Hostname; use crate::hostname::Hostname;
use std::collections::HashMap;
pub struct Machine { pub struct Machine {
name: String, pub name: String,
hostname: Hostname, pub hostname: Hostname,
}
pub enum Status {
Unreachable,
Forbidden,
Success(HashMap<String, String>),
} }
impl Machine { impl Machine {
@ -20,16 +13,3 @@ impl Machine {
} }
} }
} }
pub trait Monitor {
fn monitor(machine: &Machine) -> Status;
}
#[derive(Debug)]
struct SshMonitor {}
impl Monitor for SshMonitor {
fn monitor(machine: &Machine) -> Status {
todo!()
}
}

View File

@ -1,8 +1,26 @@
use hostname::Hostname;
use machine::Machine;
use monitor::Monitor;
use ssh_monitor::SshMonitor;
mod hostname; mod hostname;
mod machine; mod machine;
mod ping_monitor; mod monitor;
use machine::Machine; mod ssh_monitor;
fn main() { #[tokio::main]
async fn main() {
println!("Hello, world!"); println!("Hello, world!");
let machine = Machine::new("kogasa", Hostname::new("kogasa").unwrap());
let status = SshMonitor::monitor(&machine).await;
println!("{}:", machine.name);
if let Ok(commands) = status {
for (command, output) in commands.iter() {
println!("{}: {}", command, output);
}
}
} }

7
src/monitor.rs Normal file
View File

@ -0,0 +1,7 @@
use std::{collections::HashMap, error::Error};
use crate::machine::Machine;
pub trait Monitor {
async fn monitor(machine: &Machine) -> Result<HashMap<String, String>, Box<dyn Error>>;
}

View File

@ -1,18 +0,0 @@
use crate::machine::{Machine, Monitor, Status};
#[derive(Debug)]
struct PingMonitor {}
impl Monitor for PingMonitor {
fn monitor(machine: &Machine) -> Status {
todo!()
}
}
#[cfg(test)]
mod test {
#[test]
fn monitor_works() {
assert!(true)
}
}

35
src/ssh_monitor.rs Normal file
View File

@ -0,0 +1,35 @@
use std::{collections::HashMap, error::Error};
use openssh::{KnownHosts, Session};
use crate::{machine::Machine, monitor::Monitor};
#[derive(Debug)]
pub struct SshMonitor {}
impl Monitor for SshMonitor {
async fn monitor(machine: &Machine) -> Result<HashMap<String, String>, Box<dyn Error>> {
let result = Self::monitor_with_ssh(&machine).await;
match result {
Ok(commands) => Ok(commands),
Err(ssh_error) => Err(Box::new(ssh_error)),
}
}
}
impl SshMonitor {
async fn monitor_with_ssh(
machine: &Machine,
) -> Result<HashMap<String, String>, openssh::Error> {
let uri = format!("ssh://{}", &machine.hostname.value);
let session = Session::connect(uri, KnownHosts::Accept).await?;
let output = session.command("neofetch").output().await?;
let mut commands = HashMap::new();
commands.insert(
"neofetch".to_string(),
String::from_utf8(output.stdout).unwrap(),
);
Ok(commands)
}
}