Refactor: Move server logic into server module
Add start_ssh_agent Implement Command sending
This commit is contained in:
parent
05d9ea3a54
commit
df1292c9c3
@ -5,3 +5,4 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
lazy-regex = "3.3.0"
|
||||
|
||||
239
src/main.rs
239
src/main.rs
@ -1,14 +1,17 @@
|
||||
mod server;
|
||||
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use lazy_regex::{lazy_regex, Lazy, Regex};
|
||||
use server::{Server, ServerReference};
|
||||
use std::cell::LazyCell;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::str::FromStr;
|
||||
|
||||
const SERVERS_ENV_VAR: &str = "MSSH_SERVERS";
|
||||
type ShellCmd = std::process::Command;
|
||||
|
||||
/// Uploads a file or executes a command on multiple configured servers
|
||||
///
|
||||
@ -42,6 +45,9 @@ enum Command {
|
||||
/// The directory where to upload to
|
||||
#[arg(short = 'p', long, default_value = "plugins")]
|
||||
upload_directory: PathBuf,
|
||||
/// Skip the confirmation dialog
|
||||
#[arg(long, default_value = "false", default_missing_value = "true", num_args = 0..1)]
|
||||
no_confirm: bool,
|
||||
},
|
||||
/// Execute a command on the servers
|
||||
#[command(visible_short_flag_alias = 'c')]
|
||||
@ -62,162 +68,6 @@ enum OldVersionPolicy {
|
||||
Delete,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ServerReference {
|
||||
Resolved(Server),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
impl ServerReference {
|
||||
pub fn get_name(&self) -> &str {
|
||||
match self {
|
||||
ServerReference::Resolved(server) => &server.ssh_name,
|
||||
ServerReference::Name(name) => name,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn resolve(self, configured_servers: &[Server]) -> Option<Server> {
|
||||
match self {
|
||||
ServerReference::Resolved(server) => Some(server),
|
||||
ServerReference::Name(name) => Self::resolve_server_name(&name, configured_servers),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn resolve_lazy<S, F>(self, provider: &mut LazyCell<S, F>) -> Option<Server>
|
||||
where
|
||||
S: Deref<Target = [Server]>,
|
||||
F: FnOnce() -> S,
|
||||
{
|
||||
match self {
|
||||
ServerReference::Resolved(server) => Some(server),
|
||||
ServerReference::Name(name) => Self::resolve_server_name(&name, provider),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_resolve_lazy<S, F, E>(
|
||||
self,
|
||||
provider: &mut LazyCell<Result<S, E>, F>,
|
||||
) -> Result<Option<Server>, E>
|
||||
where
|
||||
S: Deref<Target = [Server]>,
|
||||
F: FnOnce() -> Result<S, E>,
|
||||
E: Clone,
|
||||
{
|
||||
match self {
|
||||
ServerReference::Resolved(server) => Ok(Some(server)),
|
||||
ServerReference::Name(name) => provider
|
||||
.as_ref()
|
||||
.map_err(|e| e.clone())
|
||||
.map(|servers| Self::resolve_server_name(&name, servers)),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_server_name(name: &str, servers: &[Server]) -> Option<Server> {
|
||||
servers
|
||||
.iter()
|
||||
.find(|server| server.ssh_name == name)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ServerReference {
|
||||
type Err = ServerReferenceParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Server::from_str(s)
|
||||
.map(Self::Resolved)
|
||||
.or_else(|_| Ok(Self::Name(s.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ServerReference {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.get_name() == other.get_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ServerReference {}
|
||||
|
||||
impl Hash for ServerReference {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.get_name().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ServerReference {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ServerReference::Resolved(server) => write!(f, "{}", server),
|
||||
ServerReference::Name(name) => write!(f, "{}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ServerReferenceParseError {}
|
||||
|
||||
impl Display for ServerReferenceParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self) //replace that with an actual implementation if there ever are any variants
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ServerReferenceParseError {}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
struct Server {
|
||||
pub ssh_name: String,
|
||||
pub server_directory_path: PathBuf,
|
||||
}
|
||||
|
||||
impl FromStr for Server {
|
||||
type Err = ServerParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.split_once(':')
|
||||
.ok_or(ServerParseError::MissingServerDirectory)
|
||||
.map(|(name, directory)| Self {
|
||||
ssh_name: name.to_string(),
|
||||
server_directory_path: PathBuf::from(directory),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Server {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:{:?}", self.ssh_name, self.server_directory_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ServerParseError {
|
||||
MissingServerDirectory,
|
||||
}
|
||||
|
||||
impl Display for ServerParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ServerParseError::MissingServerDirectory => {
|
||||
write!(
|
||||
f,
|
||||
"String is not specifying a server directory. Please use an empty string after a \
|
||||
double colon to point to the home directory, e.g: 'lobby:'"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ServerParseError {}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
struct PluginInfo {
|
||||
pub name: String,
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let args = Args::parse();
|
||||
dbg!(&args);
|
||||
@ -242,6 +92,56 @@ fn main() -> Result<(), String> {
|
||||
|
||||
dbg!(&servers);
|
||||
|
||||
match args.command {
|
||||
Command::Upload {
|
||||
file,
|
||||
old_version_policy,
|
||||
upload_directory,
|
||||
no_confirm,
|
||||
} => {
|
||||
todo!()
|
||||
}
|
||||
Command::Command { command } => {
|
||||
start_ssh_agent()?;
|
||||
for server in servers {
|
||||
println!("Running command on '{}'...", server.ssh_name);
|
||||
ShellCmd::new("ssh")
|
||||
.arg(server.ssh_name)
|
||||
.arg(format!("cd {:?}; {command}", server.server_directory_path))
|
||||
.spawn()
|
||||
.map_err(|_| "failed to start ssh command".to_string())?
|
||||
.wait()
|
||||
.map_err(|e| format!("failed to wait for ssh command completion: {e}"))?;
|
||||
}
|
||||
println!("Done!")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_ssh_agent() -> Result<(), String> {
|
||||
//start the ssh agent
|
||||
let agent_output = ShellCmd::new("ssh-agent")
|
||||
.arg("-s")
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
.map_err(|e| format!("failed to start ssh agent: {e}"))?;
|
||||
let agent_output = String::from_utf8_lossy(&agent_output.stdout);
|
||||
|
||||
//set the env vars from the agent
|
||||
static ENV_VAR_REGEX: Lazy<Regex> = lazy_regex!("(.+?)=(.+?);");
|
||||
for capture in ENV_VAR_REGEX.captures_iter(&agent_output) {
|
||||
let (_, [env_var, value]) = capture.extract();
|
||||
env::set_var(env_var, value);
|
||||
}
|
||||
|
||||
//add the ssh key
|
||||
ShellCmd::new("ssh-add")
|
||||
.spawn()
|
||||
.map_err(|e| format!("failed to add ssh key: {}", e))?
|
||||
.wait()
|
||||
.expect("failed to wait on ssh-add");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -263,9 +163,9 @@ fn parse_server_configuration(config_str: &str) -> Result<Vec<Server>, String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{parse_server_configuration, Server, ServerReference};
|
||||
use crate::parse_server_configuration;
|
||||
use crate::server::Server;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_parse_server_configuration() {
|
||||
@ -285,19 +185,4 @@ mod test {
|
||||
servers
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_reference_from_str() {
|
||||
assert_eq!(
|
||||
ServerReference::Name("foo".to_string()),
|
||||
ServerReference::from_str("foo").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
ServerReference::Resolved(Server {
|
||||
ssh_name: "crea".to_string(),
|
||||
server_directory_path: PathBuf::from("server/creative2")
|
||||
}),
|
||||
ServerReference::from_str("crea:server/creative2").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
180
src/server.rs
Normal file
180
src/server.rs
Normal file
@ -0,0 +1,180 @@
|
||||
use std::cell::LazyCell;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ServerReference {
|
||||
Resolved(Server),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
impl ServerReference {
|
||||
pub fn get_name(&self) -> &str {
|
||||
match self {
|
||||
ServerReference::Resolved(server) => &server.ssh_name,
|
||||
ServerReference::Name(name) => name,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn resolve(self, configured_servers: &[Server]) -> Option<Server> {
|
||||
match self {
|
||||
ServerReference::Resolved(server) => Some(server),
|
||||
ServerReference::Name(name) => Self::resolve_server_name(&name, configured_servers),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn resolve_lazy<S, F>(self, provider: &mut LazyCell<S, F>) -> Option<Server>
|
||||
where
|
||||
S: Deref<Target = [Server]>,
|
||||
F: FnOnce() -> S,
|
||||
{
|
||||
match self {
|
||||
ServerReference::Resolved(server) => Some(server),
|
||||
ServerReference::Name(name) => Self::resolve_server_name(&name, provider),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_resolve_lazy<S, F, E>(
|
||||
self,
|
||||
provider: &mut LazyCell<Result<S, E>, F>,
|
||||
) -> Result<Option<Server>, E>
|
||||
where
|
||||
S: Deref<Target = [Server]>,
|
||||
F: FnOnce() -> Result<S, E>,
|
||||
E: Clone,
|
||||
{
|
||||
match self {
|
||||
ServerReference::Resolved(server) => Ok(Some(server)),
|
||||
ServerReference::Name(name) => provider
|
||||
.as_ref()
|
||||
.map_err(|e| e.clone())
|
||||
.map(|servers| Self::resolve_server_name(&name, servers)),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_server_name(name: &str, servers: &[Server]) -> Option<Server> {
|
||||
servers
|
||||
.iter()
|
||||
.find(|server| server.ssh_name == name)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ServerReference {
|
||||
type Err = ServerReferenceParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Server::from_str(s)
|
||||
.map(Self::Resolved)
|
||||
.or_else(|_| Ok(Self::Name(s.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ServerReference {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.get_name() == other.get_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ServerReference {}
|
||||
|
||||
impl Hash for ServerReference {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.get_name().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ServerReference {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ServerReference::Resolved(server) => write!(f, "{}", server),
|
||||
ServerReference::Name(name) => write!(f, "{}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerReferenceParseError {}
|
||||
|
||||
impl Display for ServerReferenceParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self) //replace that with an actual implementation if there ever are any variants
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ServerReferenceParseError {}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Server {
|
||||
pub ssh_name: String,
|
||||
pub server_directory_path: PathBuf,
|
||||
}
|
||||
|
||||
impl FromStr for Server {
|
||||
type Err = ServerParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.split_once(':')
|
||||
.ok_or(ServerParseError::MissingServerDirectory)
|
||||
.map(|(name, directory)| Self {
|
||||
ssh_name: name.to_string(),
|
||||
server_directory_path: PathBuf::from(directory),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Server {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:{:?}", self.ssh_name, self.server_directory_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerParseError {
|
||||
MissingServerDirectory,
|
||||
}
|
||||
|
||||
impl Display for ServerParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ServerParseError::MissingServerDirectory => {
|
||||
write!(
|
||||
f,
|
||||
"String is not specifying a server directory. Please use an empty string after a \
|
||||
double colon to point to the home directory, e.g: 'lobby:'"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ServerParseError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_server_reference {
|
||||
use crate::server::{Server, ServerReference};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
assert_eq!(
|
||||
ServerReference::Name("foo".to_string()),
|
||||
ServerReference::from_str("foo").unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
ServerReference::Resolved(Server {
|
||||
ssh_name: "crea".to_string(),
|
||||
server_directory_path: PathBuf::from("server/creative2")
|
||||
}),
|
||||
ServerReference::from_str("crea:server/creative2").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user