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]
|
[dependencies]
|
||||||
clap = { version = "4.5.23", features = ["derive"] }
|
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 clap::{Parser, Subcommand, ValueEnum};
|
||||||
|
use lazy_regex::{lazy_regex, Lazy, Regex};
|
||||||
|
use server::{Server, ServerReference};
|
||||||
use std::cell::LazyCell;
|
use std::cell::LazyCell;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error::Error;
|
use std::hash::Hash;
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Stdio;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
const SERVERS_ENV_VAR: &str = "MSSH_SERVERS";
|
const SERVERS_ENV_VAR: &str = "MSSH_SERVERS";
|
||||||
|
type ShellCmd = std::process::Command;
|
||||||
|
|
||||||
/// Uploads a file or executes a command on multiple configured servers
|
/// Uploads a file or executes a command on multiple configured servers
|
||||||
///
|
///
|
||||||
@ -42,6 +45,9 @@ enum Command {
|
|||||||
/// The directory where to upload to
|
/// The directory where to upload to
|
||||||
#[arg(short = 'p', long, default_value = "plugins")]
|
#[arg(short = 'p', long, default_value = "plugins")]
|
||||||
upload_directory: PathBuf,
|
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
|
/// Execute a command on the servers
|
||||||
#[command(visible_short_flag_alias = 'c')]
|
#[command(visible_short_flag_alias = 'c')]
|
||||||
@ -62,162 +68,6 @@ enum OldVersionPolicy {
|
|||||||
Delete,
|
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> {
|
fn main() -> Result<(), String> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
dbg!(&args);
|
dbg!(&args);
|
||||||
@ -242,6 +92,56 @@ fn main() -> Result<(), String> {
|
|||||||
|
|
||||||
dbg!(&servers);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,9 +163,9 @@ fn parse_server_configuration(config_str: &str) -> Result<Vec<Server>, String> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod 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::path::PathBuf;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_server_configuration() {
|
fn test_parse_server_configuration() {
|
||||||
@ -285,19 +185,4 @@ mod test {
|
|||||||
servers
|
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