Use new ShellCommand interface in main application
This commit is contained in:
parent
85a9cd9ae5
commit
7c64383d72
@ -1,3 +1,5 @@
|
||||
#[deprecated]
|
||||
|
||||
use crate::log;
|
||||
use crate::logger::{LogLevel, Logger};
|
||||
use crate::shell_interface::command_to_string;
|
||||
|
||||
242
src/main.rs
242
src/main.rs
@ -9,14 +9,13 @@ mod server;
|
||||
mod shell_interface;
|
||||
|
||||
use crate::action::{Action, FileAction, ServerActions};
|
||||
use crate::command::LogRunnable;
|
||||
use crate::environment::{Environment, Prod};
|
||||
use crate::file::{FileMatcher, FileNameInfo};
|
||||
use crate::logger::{LogLevel, Logger};
|
||||
use crate::os_str_extension::OsStrExtension;
|
||||
use crate::os_string_builder::ReplaceWithOsStr;
|
||||
use crate::server::{RelativeLocalPathAnker, ServerAddress};
|
||||
use crate::shell_interface::os_string_from_ssh_output;
|
||||
use crate::shell_interface::{ScpParam, ServerCommand, ShellCommand, ShellInterface};
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use lazy_regex::{lazy_regex, Lazy, Regex};
|
||||
use server::{Server, ServerReference};
|
||||
@ -32,8 +31,6 @@ const SERVERS_ENV_VAR: &str = "MSSH_SERVERS";
|
||||
const EDITOR_ENV_VAR: &str = "MSSH_EDITOR";
|
||||
const FILE_PLACEHOLDER: &str = "<file>";
|
||||
|
||||
type ShellCmd = std::process::Command;
|
||||
|
||||
/// Uploads a file or executes a command on multiple configured servers
|
||||
///
|
||||
/// Servers must either be configured via environment variable or denote their server directory with
|
||||
@ -155,7 +152,11 @@ where
|
||||
}
|
||||
|
||||
pub fn run_with_args(&mut self, args: Args) -> Result<(), String> {
|
||||
let _env = &mut self.environment;
|
||||
macro_rules! env {
|
||||
() => {
|
||||
&mut self.environment
|
||||
};
|
||||
}
|
||||
|
||||
let logger = Logger {
|
||||
//all the below options are conflicting with each other so an if else is fine
|
||||
@ -231,23 +232,29 @@ where
|
||||
files = files
|
||||
.iter()
|
||||
.map(|file| {
|
||||
let output = ShellCmd::new("ssh")
|
||||
.arg(ssh_address)
|
||||
.arg(osf!("realpath -e ") + file_server.server_directory_path.join(file))
|
||||
.collect_full_output()
|
||||
.map_err(|e| format!("Failed to canonicalize files: {e}"))?;
|
||||
let output = ShellCommand::Ssh {
|
||||
address: ssh_address.to_string(),
|
||||
server_command: ServerCommand::Realpath {
|
||||
path: file_server.server_directory_path.join(file),
|
||||
},
|
||||
}
|
||||
.in_env(env!())
|
||||
.output()
|
||||
.into_result()
|
||||
.map_err(|e| format!("Failed to canonicalize files: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
if !output.status.success {
|
||||
Err(format!(
|
||||
"Path doesn't match any files on file-server: {}",
|
||||
file.to_string_lossy()
|
||||
))?;
|
||||
}
|
||||
|
||||
let denoted_files = os_string_from_ssh_output(output.stdout)
|
||||
let denoted_files = output
|
||||
.stdout
|
||||
.split(b'\n') //split at line breaks
|
||||
.into_iter()
|
||||
.filter(|bytes| !bytes.is_empty()) //needed since realpath sometimes gives us empty lines
|
||||
.filter(|file_name| !file_name.is_empty()) //needed since realpath sometimes gives us empty lines
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@ -305,19 +312,23 @@ where
|
||||
server,
|
||||
actions: {
|
||||
let present_file_names: Vec<OsString> = match &server.address {
|
||||
ServerAddress::Ssh { ssh_address } => os_string_from_ssh_output(
|
||||
ShellCmd::new("ssh")
|
||||
.arg(ssh_address)
|
||||
.arg(osf!("ls ") + &working_directory)
|
||||
.collect_output()
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Failed to query present files on server {}: {e}",
|
||||
server.get_name()
|
||||
)
|
||||
})?
|
||||
.stdout,
|
||||
)
|
||||
ServerAddress::Ssh { ssh_address } => ShellCommand::Ssh {
|
||||
address: ssh_address.to_string(),
|
||||
server_command: ServerCommand::Ls {
|
||||
dir: working_directory.clone(),
|
||||
},
|
||||
}
|
||||
.in_env(env!())
|
||||
.output()
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"Failed to query present files on server {}: {e}",
|
||||
server.get_name()
|
||||
)
|
||||
})?
|
||||
.stdout
|
||||
.split(b'\n')
|
||||
.into_iter()
|
||||
.map(OsString::from)
|
||||
@ -452,32 +463,37 @@ where
|
||||
for file_action in server_actions.actions {
|
||||
match file_action.kind {
|
||||
Action::Add | Action::Replace => {
|
||||
let scp_source = match &file_server {
|
||||
Some(file_server) => {
|
||||
osf!(match &file_server.address {
|
||||
ServerAddress::Ssh { ssh_address } => format!("{ssh_address}:"),
|
||||
ServerAddress::Localhost => "".to_string(),
|
||||
}) + file_server.server_directory_path.join(&file_action.file)
|
||||
}
|
||||
None => osf!(&file_action.file),
|
||||
let source = match &file_server {
|
||||
Some(file_server) => ScpParam::from((
|
||||
file_server,
|
||||
file_server.server_directory_path.join(&file_action.file),
|
||||
)),
|
||||
None => ScpParam::from(file_action.file.as_path()),
|
||||
};
|
||||
let scp_target = osf!(match &server.address {
|
||||
ServerAddress::Ssh { ssh_address } => format!("{ssh_address}:"),
|
||||
ServerAddress::Localhost => "".to_string(),
|
||||
}) + &server_actions.working_directory;
|
||||
ShellCmd::new("scp")
|
||||
.arg(scp_source)
|
||||
.arg(scp_target)
|
||||
.run(&logger)
|
||||
.map_err(|e| format!("upload failure: {e}"))?;
|
||||
let destination = ScpParam::from((server, &server_actions.working_directory));
|
||||
ShellCommand::Scp {
|
||||
source,
|
||||
destination,
|
||||
}
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| format!("upload failure: {e}"))?;
|
||||
}
|
||||
Action::Delete => match &server.address {
|
||||
ServerAddress::Ssh { ssh_address } => {
|
||||
ShellCmd::new("ssh")
|
||||
.arg(ssh_address)
|
||||
.arg(osf!("rm ") + server_actions.working_directory.join(&file_action.file))
|
||||
.run(&logger)
|
||||
.map_err(|e| format!("failed to delete old version: {e}"))?;
|
||||
ShellCommand::Ssh {
|
||||
address: ssh_address.to_string(),
|
||||
server_command: ServerCommand::Rm {
|
||||
file: server_actions.working_directory.join(&file_action.file),
|
||||
},
|
||||
}
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| format!("failed to delete old version: {e}"))?;
|
||||
}
|
||||
ServerAddress::Localhost => {
|
||||
fs::remove_file(server_actions.working_directory.join(&file_action.file))
|
||||
@ -486,16 +502,18 @@ where
|
||||
},
|
||||
Action::Rename { new_name } => match &server.address {
|
||||
ServerAddress::Ssh { ssh_address } => {
|
||||
ShellCmd::new("ssh")
|
||||
.arg(ssh_address)
|
||||
.arg(
|
||||
osf!("mv ")
|
||||
+ server_actions.working_directory.join(&file_action.file)
|
||||
+ " "
|
||||
+ server_actions.working_directory.join(&new_name),
|
||||
)
|
||||
.run(&logger)
|
||||
.map_err(|e| format!("failed to rename: {e}"))?;
|
||||
ShellCommand::Ssh {
|
||||
address: ssh_address.to_string(),
|
||||
server_command: ServerCommand::Mv {
|
||||
source: server_actions.working_directory.join(&file_action.file),
|
||||
destination: server_actions.working_directory.join(&new_name),
|
||||
},
|
||||
}
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| format!("failed to rename: {e}"))?;
|
||||
}
|
||||
ServerAddress::Localhost => {
|
||||
let dir = &server_actions.working_directory;
|
||||
@ -516,20 +534,33 @@ where
|
||||
log!(logger, "Running command on '{}'...", server.get_name());
|
||||
match &server.address {
|
||||
ServerAddress::Ssh { ssh_address } => {
|
||||
ShellCmd::new("ssh")
|
||||
.arg(ssh_address)
|
||||
.arg(osf!("cd ") + server.server_directory_path + "; " + &command)
|
||||
.run(&logger)
|
||||
.map_err(|e| format!("{e}"))?;
|
||||
ShellCommand::Ssh {
|
||||
address: ssh_address.to_string(),
|
||||
server_command: ServerCommand::Execute {
|
||||
working_directory: server.server_directory_path.clone(),
|
||||
command: OsString::from(&command),
|
||||
},
|
||||
}
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| format!("{e}"))?;
|
||||
}
|
||||
ServerAddress::Localhost => {
|
||||
let mut command_args = shell_words::split(&command)
|
||||
.map_err(|e| format!("failed to parse command: {e}"))?;
|
||||
ShellCmd::new(command_args.remove(0))
|
||||
.args(&command_args)
|
||||
.current_dir(&server.server_directory_path)
|
||||
.run(&logger)
|
||||
.map_err(|e| format!("{e}"))?;
|
||||
let command = shell_words::split(&command)
|
||||
.map_err(|e| format!("failed to parse command: {e}"))?
|
||||
.into_iter()
|
||||
.map(OsString::from)
|
||||
.collect();
|
||||
ShellCommand::Execute {
|
||||
working_directory: server.server_directory_path.clone(),
|
||||
command,
|
||||
}
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -597,18 +628,19 @@ where
|
||||
|
||||
for server in servers {
|
||||
log!(logger, "Getting file from {}...", server.get_name());
|
||||
let file_source = osf!(match &server.address {
|
||||
ServerAddress::Ssh { ssh_address } => format!("{ssh_address}:"),
|
||||
ServerAddress::Localhost => "".to_string(),
|
||||
}) + server.server_directory_path.join(&file);
|
||||
ShellCmd::new("scp")
|
||||
.arg(&file_source)
|
||||
.arg(&download_directory)
|
||||
.run(&logger)
|
||||
.map_err(|e| format!("download failure: {e}"))?;
|
||||
let source = ScpParam::from((&server, server.server_directory_path.join(&file)));
|
||||
ShellCommand::Scp {
|
||||
source: source.clone(),
|
||||
destination: ScpParam::from(download_directory.as_path()),
|
||||
}
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| format!("download failure: {e}"))?;
|
||||
|
||||
//open file in editor
|
||||
let mut editor_command_args = shell_words::split(&editor)
|
||||
let editor_command = shell_words::split(&editor)
|
||||
.map_err(|e| format!("failed to parse editor command: {e}"))?
|
||||
.into_iter()
|
||||
.map(|part| {
|
||||
@ -616,18 +648,23 @@ where
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let command = editor_command_args.remove(0);
|
||||
ShellCmd::new(command)
|
||||
.args(editor_command_args)
|
||||
.run(&logger)
|
||||
ShellCommand::Editor(editor_command)
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| format!("failed to open file in editor: {e}"))?;
|
||||
|
||||
//upload file again
|
||||
ShellCmd::new("scp")
|
||||
.arg(download_directory.join(file_name))
|
||||
.arg(&file_source)
|
||||
.run(&logger)
|
||||
.map_err(|e| format!("failed to re-upload file: {e}"))?;
|
||||
ShellCommand::Scp {
|
||||
source: ScpParam::from(download_directory.join(file_name).as_path()),
|
||||
destination: source,
|
||||
}
|
||||
.in_env(env!())
|
||||
.run_logged(&logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(&logger)
|
||||
.map_err(|e| format!("failed to re-upload file: {e}"))?;
|
||||
}
|
||||
|
||||
log!(logger, "Done!");
|
||||
@ -669,25 +706,30 @@ where
|
||||
let env = &mut self.environment;
|
||||
|
||||
//start the ssh agent
|
||||
let agent_output = ShellCmd::new("ssh-agent")
|
||||
.arg("-s")
|
||||
.collect_output()
|
||||
.map_err(|e| format!("failed to start ssh agent: {e}"))?;
|
||||
let agent_stdout = String::from_utf8_lossy(&agent_output.stdout);
|
||||
if !agent_output.status.success() {
|
||||
return Err("failed to start ssh agent; maybe try to run ssh-agent manually?".to_string());
|
||||
}
|
||||
let agent_output = ShellCommand::SshAgent
|
||||
.in_env(env)
|
||||
.output()
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(logger)
|
||||
.map_err(|e| format!("Failed to start ssh agent: {e}"))?;
|
||||
let agent_stdout = &agent_output
|
||||
.stdout
|
||||
.into_string()
|
||||
.map_err(|_| "ssh-agent returned invalid utf-8 - how did this even happen?")?;
|
||||
|
||||
//set the env vars from the agent
|
||||
static ENV_VAR_REGEX: Lazy<Regex> = lazy_regex!("(.+?)=(.+?);");
|
||||
for capture in ENV_VAR_REGEX.captures_iter(&agent_stdout) {
|
||||
for capture in ENV_VAR_REGEX.captures_iter(agent_stdout) {
|
||||
let (_, [env_var, value]) = capture.extract();
|
||||
env.set_var(env_var, value);
|
||||
}
|
||||
|
||||
//add the ssh key
|
||||
ShellCmd::new("ssh-add")
|
||||
.run(logger)
|
||||
ShellCommand::ShhAdd
|
||||
.in_env(env)
|
||||
.run_logged(logger)
|
||||
.and_expect_success()
|
||||
.into_result_with_error_logging(logger)
|
||||
.map_err(|e| format!("failed to add ssh-key: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
use crate::logger::{LogLevel, Logger};
|
||||
use crate::server::{Server, ServerAddress};
|
||||
use crate::{log, osf};
|
||||
use std::error::Error;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::iter::once;
|
||||
use std::path::PathBuf;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output};
|
||||
use std::{io, process};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnvCommand<'a, E> {
|
||||
command: ShellCommand,
|
||||
phantom_data: PhantomData<&'a E>,
|
||||
#[cfg(test)]
|
||||
environment: &'a mut E,
|
||||
}
|
||||
|
||||
@ -34,10 +38,12 @@ pub enum ShellCommand {
|
||||
}
|
||||
|
||||
impl ShellCommand {
|
||||
pub fn in_env<E>(self, environment: &mut E) -> EnvCommand<E> {
|
||||
pub fn in_env<E>(self, _environment: &mut E) -> EnvCommand<E> {
|
||||
EnvCommand {
|
||||
command: self,
|
||||
environment,
|
||||
phantom_data: Default::default(),
|
||||
#[cfg(test)]
|
||||
environment: _environment,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,6 +75,30 @@ pub struct ScpParam {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl<P> From<(&Server, P)> for ScpParam
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
fn from((server, path): (&Server, P)) -> Self {
|
||||
Self {
|
||||
server: match &server.address {
|
||||
ServerAddress::Ssh { ssh_address } => Some(ssh_address.into()),
|
||||
ServerAddress::Localhost => None,
|
||||
},
|
||||
path: PathBuf::from(path.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Path> for ScpParam {
|
||||
fn from(value: &Path) -> Self {
|
||||
Self {
|
||||
server: None,
|
||||
path: PathBuf::from(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ScpParam> for OsString {
|
||||
fn from(value: &ScpParam) -> Self {
|
||||
let mut builder = osf!();
|
||||
@ -327,8 +357,7 @@ impl From<Output> for CommandOutput {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO remove super visibility once it is not needed anymore
|
||||
pub(super) fn os_string_from_ssh_output(output: Vec<u8>) -> OsString {
|
||||
pub fn os_string_from_ssh_output(output: Vec<u8>) -> OsString {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
@ -352,6 +381,7 @@ impl AsRef<ExitStatus> for CommandOutput {
|
||||
pub struct ExitStatus {
|
||||
pub success: bool,
|
||||
pub string_form: String,
|
||||
#[allow(dead_code)]
|
||||
pub code: Option<i32>,
|
||||
}
|
||||
|
||||
@ -383,6 +413,15 @@ pub struct CommandError<E> {
|
||||
pub error: E,
|
||||
}
|
||||
|
||||
impl<E> From<CommandError<E>> for String
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
fn from(value: CommandError<E>) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Display for CommandError<E>
|
||||
where
|
||||
E: Display,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user