Added ssh functionality and improved hostnames
This commit is contained in:
parent
e33b503159
commit
ac28a37cd6
|
@ -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"] }
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>;
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue