diff --git a/Cargo.toml b/Cargo.toml index b2a058d..aea6496 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] +hostname-validator = "1.1.1" +openssh = "0.10.4" ping = "0.5.2" -regex = "1.10.5" thiserror = "1.0.61" +tokio = { version = "1.38.0", features = ["rt-multi-thread"] } diff --git a/src/hostname.rs b/src/hostname.rs index a04fe6e..4a343f8 100644 --- a/src/hostname.rs +++ b/src/hostname.rs @@ -1,35 +1,26 @@ 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)] #[error("The hostname \"{0}\" is invalid")] pub struct InvalidHostnameError(String); pub struct Hostname { - hostname: String, + pub value: String, } impl Hostname { pub fn new(hostname: &str) -> Result { - if Self::is_valid_ip(&hostname) || Self::is_valid_hostname(&hostname) { + if Self::is_valid_hostname(&hostname) { Ok(Hostname { - hostname: hostname.to_string(), + value: hostname.to_string(), }) } else { 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 { - let re = Regex::new(VALID_HOSTNAME_PATTERN).unwrap(); - re.is_match(hostname) + hostname_validator::is_valid(hostname) } } @@ -57,12 +48,24 @@ mod tests { assert!(Hostname::new("is-42-the-response").is_ok()); assert!(Hostname::new("GoB.NaSa-42.eu").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] fn invalid_hostnames() { 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("not-end-with-hyphen-").is_err()); } diff --git a/src/machine.rs b/src/machine.rs index 3b6284a..37853a8 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -1,15 +1,8 @@ use crate::hostname::Hostname; -use std::collections::HashMap; pub struct Machine { - name: String, - hostname: Hostname, -} - -pub enum Status { - Unreachable, - Forbidden, - Success(HashMap), + pub name: String, + pub hostname: Hostname, } 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!() - } -} diff --git a/src/main.rs b/src/main.rs index 56152c9..5fb1a46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,26 @@ +use hostname::Hostname; +use machine::Machine; +use monitor::Monitor; +use ssh_monitor::SshMonitor; + mod hostname; mod machine; -mod ping_monitor; -use machine::Machine; +mod monitor; +mod ssh_monitor; -fn main() { +#[tokio::main] +async fn main() { 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); + } + } } diff --git a/src/monitor.rs b/src/monitor.rs new file mode 100644 index 0000000..bcfaed0 --- /dev/null +++ b/src/monitor.rs @@ -0,0 +1,7 @@ +use std::{collections::HashMap, error::Error}; + +use crate::machine::Machine; + +pub trait Monitor { + async fn monitor(machine: &Machine) -> Result, Box>; +} diff --git a/src/ping_monitor.rs b/src/ping_monitor.rs deleted file mode 100644 index cbc7a25..0000000 --- a/src/ping_monitor.rs +++ /dev/null @@ -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) - } -} diff --git a/src/ssh_monitor.rs b/src/ssh_monitor.rs new file mode 100644 index 0000000..01817cb --- /dev/null +++ b/src/ssh_monitor.rs @@ -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, Box> { + 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, 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) + } +}