Compare commits
No commits in common. "effe1418eb2b6d274cff4ed93a65df5c27bf9c9c" and "bdd1652595cf50d37d10008cccc87a6e6a71ac29" have entirely different histories.
effe1418eb
...
bdd1652595
@ -78,7 +78,3 @@ Once you have that installed, just run
|
|||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
and you will find an executable in `target/release`.
|
and you will find an executable in `target/release`.
|
||||||
|
|
||||||
### Unit tests
|
|
||||||
|
|
||||||
In order for the unit tests to pass, you will need `python3`
|
|
||||||
|
|||||||
156
src/command.rs
156
src/command.rs
@ -1,156 +0,0 @@
|
|||||||
use crate::log;
|
|
||||||
use crate::logger::{LogLevel, Logger};
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::io;
|
|
||||||
use std::iter::once;
|
|
||||||
use std::process::{Command, ExitStatus, Output};
|
|
||||||
|
|
||||||
pub trait LogRunnable {
|
|
||||||
fn run(&mut self, logger: &Logger) -> Result<(), SpecificExecutionError>;
|
|
||||||
fn collect_output(&mut self) -> Result<Output, SpecificExecutionError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LogRunnable for Command {
|
|
||||||
fn run(&mut self, logger: &Logger) -> Result<(), SpecificExecutionError> {
|
|
||||||
run(self, logger).map_err(|error| SpecificExecutionError {
|
|
||||||
command: self,
|
|
||||||
error,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_output(&mut self) -> Result<Output, SpecificExecutionError> {
|
|
||||||
collect_output(self, None).map_err(|error| SpecificExecutionError {
|
|
||||||
command: self,
|
|
||||||
error,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(command: &mut Command, logger: &Logger) -> Result<(), ExecutionError> {
|
|
||||||
match logger.level {
|
|
||||||
LogLevel::Debug | LogLevel::Info => {
|
|
||||||
let status = command.status()?;
|
|
||||||
if !status.success() {
|
|
||||||
Err(status)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogLevel::Error => {
|
|
||||||
collect_output(command, Some(logger))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_output(
|
|
||||||
command: &mut Command,
|
|
||||||
logger: Option<&Logger>,
|
|
||||||
) -> Result<Output, ExecutionError> {
|
|
||||||
let output = command.output()?; //pipes stdout and stderr automatically
|
|
||||||
if !output.status.success() {
|
|
||||||
if let Some(logger) = logger {
|
|
||||||
log!(logger, error, "{}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
}
|
|
||||||
Err(output.status)?;
|
|
||||||
}
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SpecificExecutionError<'a> {
|
|
||||||
pub command: &'a Command,
|
|
||||||
pub error: ExecutionError,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SpecificExecutionError<'_> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Failed to execute command '{}': {}",
|
|
||||||
command_to_string(self.command),
|
|
||||||
self.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command_to_string(command: &Command) -> String {
|
|
||||||
once(command.get_program().to_string_lossy())
|
|
||||||
.chain(command.get_args().map(|arg| arg.to_string_lossy()))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for SpecificExecutionError<'_> {}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ExecutionError {
|
|
||||||
StartError(io::Error),
|
|
||||||
BadExitStatus(ExitStatus),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<io::Error> for ExecutionError {
|
|
||||||
fn from(value: io::Error) -> Self {
|
|
||||||
Self::StartError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExitStatus> for ExecutionError {
|
|
||||||
fn from(value: ExitStatus) -> Self {
|
|
||||||
Self::BadExitStatus(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ExecutionError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
ExecutionError::StartError(e) => write!(f, "Failed to start command: {}", e),
|
|
||||||
ExecutionError::BadExitStatus(status) => {
|
|
||||||
write!(f, "Command failed with {}", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ExecutionError {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::command::{ExecutionError, LogRunnable, SpecificExecutionError};
|
|
||||||
use crate::logger::Logger;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unknown_command() {
|
|
||||||
let mut command = Command::new("python7");
|
|
||||||
let Err(
|
|
||||||
e @ SpecificExecutionError {
|
|
||||||
error: ExecutionError::StartError(_),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) = command
|
|
||||||
.args([PathBuf::from("test-ressources/python/exit_1.py")])
|
|
||||||
.run(&Logger::default())
|
|
||||||
else {
|
|
||||||
panic!("command shouldn't exist");
|
|
||||||
};
|
|
||||||
assert_eq!(e.to_string(), "Failed to execute command 'python7': Failed to start command: No such file or directory (os error 2)");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_error() {
|
|
||||||
let mut command = Command::new("python3");
|
|
||||||
let Err(
|
|
||||||
e @ SpecificExecutionError {
|
|
||||||
error: ExecutionError::BadExitStatus(_),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) = command
|
|
||||||
.arg("test-ressources/python/exit_1.py")
|
|
||||||
.run(&Logger::default())
|
|
||||||
else {
|
|
||||||
panic!("command should return exit-code 1")
|
|
||||||
};
|
|
||||||
assert_eq!(e.to_string(), "Failed to execute command 'python3 test-ressources/python/exit_1.py': Command failed with exit status: 1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -39,7 +39,7 @@ macro_rules! log {
|
|||||||
$logger.$level(format!($($args)*));
|
$logger.$level(format!($($args)*));
|
||||||
};
|
};
|
||||||
($logger:expr, $($args:tt)*) => {
|
($logger:expr, $($args:tt)*) => {
|
||||||
log!($logger, info, $($args)*); //TODO better use default level with log function instead of assuming info as default
|
log!($logger, info, $($args)*);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
src/main.rs
59
src/main.rs
@ -1,12 +1,10 @@
|
|||||||
mod action;
|
mod action;
|
||||||
mod command;
|
|
||||||
mod file;
|
mod file;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod os_string_builder;
|
mod os_string_builder;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
use crate::action::{Action, FileAction, ServerActions};
|
use crate::action::{Action, FileAction, ServerActions};
|
||||||
use crate::command::LogRunnable;
|
|
||||||
use crate::file::{FileMatcher, FileNameInfo};
|
use crate::file::{FileMatcher, FileNameInfo};
|
||||||
use crate::logger::{LogLevel, Logger};
|
use crate::logger::{LogLevel, Logger};
|
||||||
use crate::os_string_builder::ReplaceWithOsStr;
|
use crate::os_string_builder::ReplaceWithOsStr;
|
||||||
@ -18,6 +16,7 @@ use std::hash::Hash;
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::process::Stdio;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
@ -160,7 +159,7 @@ fn main() -> Result<(), String> {
|
|||||||
file_name,
|
file_name,
|
||||||
} => {
|
} => {
|
||||||
require_non_empty_servers(&servers)?;
|
require_non_empty_servers(&servers)?;
|
||||||
start_ssh_agent(&logger)?;
|
start_ssh_agent()?;
|
||||||
|
|
||||||
let file_name_info =
|
let file_name_info =
|
||||||
FileNameInfo::try_from(file.clone()).map_err(|e| format!("bad file: {e}"))?;
|
FileNameInfo::try_from(file.clone()).map_err(|e| format!("bad file: {e}"))?;
|
||||||
@ -176,8 +175,9 @@ fn main() -> Result<(), String> {
|
|||||||
let output = ShellCmd::new("ssh")
|
let output = ShellCmd::new("ssh")
|
||||||
.arg(&server.ssh_name)
|
.arg(&server.ssh_name)
|
||||||
.arg(osf!("ls ") + &working_directory)
|
.arg(osf!("ls ") + &working_directory)
|
||||||
.collect_output()
|
.stdout(Stdio::piped())
|
||||||
.map_err(|e| format!("failed to query files: {e}"))?;
|
.output()
|
||||||
|
.map_err(|e| format!("failed to query files via ssh: {e}"))?;
|
||||||
let output = String::from_utf8_lossy(&output.stdout);
|
let output = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
let mut file_matcher =
|
let mut file_matcher =
|
||||||
@ -274,15 +274,19 @@ fn main() -> Result<(), String> {
|
|||||||
ShellCmd::new("scp")
|
ShellCmd::new("scp")
|
||||||
.arg(file.clone())
|
.arg(file.clone())
|
||||||
.arg(osf!(&server.ssh_name) + ":" + &server_actions.working_directory)
|
.arg(osf!(&server.ssh_name) + ":" + &server_actions.working_directory)
|
||||||
.run(&logger)
|
.spawn()
|
||||||
.map_err(|e| format!("upload failure: {e}"))?;
|
.map_err(|e| format!("failed to upload file: {e}"))?
|
||||||
|
.wait()
|
||||||
|
.map_err(|e| format!("failed to wait for upload: {e}"))?;
|
||||||
}
|
}
|
||||||
Action::Delete => {
|
Action::Delete => {
|
||||||
ShellCmd::new("ssh")
|
ShellCmd::new("ssh")
|
||||||
.arg(&server.ssh_name)
|
.arg(&server.ssh_name)
|
||||||
.arg(osf!("cd ") + &server_actions.working_directory + "; rm " + &file_action.file)
|
.arg(osf!("cd ") + &server_actions.working_directory + "; rm " + &file_action.file)
|
||||||
.run(&logger)
|
.spawn()
|
||||||
.map_err(|e| format!("failed to delete old version: {e}"))?;
|
.map_err(|e| format!("failed to send delete command: {e}"))?
|
||||||
|
.wait()
|
||||||
|
.map_err(|e| format!("failed to wait for delete command: {e}"))?;
|
||||||
}
|
}
|
||||||
Action::Rename { new_name } => {
|
Action::Rename { new_name } => {
|
||||||
ShellCmd::new("ssh")
|
ShellCmd::new("ssh")
|
||||||
@ -295,8 +299,10 @@ fn main() -> Result<(), String> {
|
|||||||
+ " "
|
+ " "
|
||||||
+ new_name,
|
+ new_name,
|
||||||
)
|
)
|
||||||
.run(&logger)
|
.spawn()
|
||||||
.map_err(|e| format!("failed to rename: {e}"))?;
|
.map_err(|e| format!("failed to send rename command: {e}"))?
|
||||||
|
.wait()
|
||||||
|
.map_err(|e| format!("failed to wait for rename command: {e}"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,15 +311,17 @@ fn main() -> Result<(), String> {
|
|||||||
log!(logger, "Done!");
|
log!(logger, "Done!");
|
||||||
}
|
}
|
||||||
Command::Command { command } => {
|
Command::Command { command } => {
|
||||||
start_ssh_agent(&logger)?;
|
start_ssh_agent()?;
|
||||||
require_non_empty_servers(&servers)?;
|
require_non_empty_servers(&servers)?;
|
||||||
for server in servers {
|
for server in servers {
|
||||||
log!(logger, "Running command on '{}'...", server.ssh_name);
|
log!(logger, "Running command on '{}'...", server.ssh_name);
|
||||||
ShellCmd::new("ssh")
|
ShellCmd::new("ssh")
|
||||||
.arg(server.ssh_name)
|
.arg(server.ssh_name)
|
||||||
.arg(osf!("cd ") + server.server_directory_path + "; " + &command)
|
.arg(osf!("cd ") + server.server_directory_path + "; " + &command)
|
||||||
.run(&logger)
|
.spawn()
|
||||||
.map_err(|e| format!("{e}"))?;
|
.map_err(|_| "failed to start ssh command".to_string())?
|
||||||
|
.wait()
|
||||||
|
.map_err(|e| format!("failed to wait for ssh command completion: {e}"))?;
|
||||||
}
|
}
|
||||||
log!(logger, "Done!");
|
log!(logger, "Done!");
|
||||||
}
|
}
|
||||||
@ -349,15 +357,15 @@ fn main() -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require_non_empty_servers(&servers)?;
|
require_non_empty_servers(&servers)?;
|
||||||
start_ssh_agent(&logger)?;
|
start_ssh_agent()?;
|
||||||
|
|
||||||
for server in servers {
|
for server in servers {
|
||||||
log!(logger, "Downloading file from {}...", server.ssh_name);
|
log!(logger, "Downloading file from {}...", server.ssh_name);
|
||||||
ShellCmd::new("scp")
|
ShellCmd::new("scp")
|
||||||
.arg(osf!(&server.ssh_name) + ":" + server.server_directory_path.join(&file))
|
.arg(osf!(&server.ssh_name) + ":" + server.server_directory_path.join(&file))
|
||||||
.arg(&working_directory)
|
.arg(&working_directory)
|
||||||
.run(&logger)
|
.status()
|
||||||
.map_err(|e| format!("download failure: {e}"))?;
|
.map_err(|e| format!("failed to download file: {e}"))?;
|
||||||
|
|
||||||
//open file in editor
|
//open file in editor
|
||||||
let mut shell_args = shell_words::split(&editor)
|
let mut shell_args = shell_words::split(&editor)
|
||||||
@ -369,15 +377,15 @@ fn main() -> Result<(), String> {
|
|||||||
let command = shell_args.remove(0);
|
let command = shell_args.remove(0);
|
||||||
ShellCmd::new(command)
|
ShellCmd::new(command)
|
||||||
.args(shell_args)
|
.args(shell_args)
|
||||||
.run(&logger)
|
.status()
|
||||||
.map_err(|e| format!("failed to open file in editor: {e}"))?;
|
.map_err(|e| format!("failed to open file in editor: {e}"))?;
|
||||||
|
|
||||||
//upload file again
|
//upload file again
|
||||||
ShellCmd::new("scp")
|
ShellCmd::new("scp")
|
||||||
.arg(working_directory.join(file_name))
|
.arg(working_directory.join(file_name))
|
||||||
.arg(osf!(&server.ssh_name) + ":" + server.server_directory_path.join(&file))
|
.arg(osf!(&server.ssh_name) + ":" + server.server_directory_path.join(&file))
|
||||||
.run(&logger)
|
.status()
|
||||||
.map_err(|e| format!("failed to re-upload file: {e}"))?;
|
.map_err(|e| format!("failed to upload file again: {e}"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
log!(logger, "Done!");
|
log!(logger, "Done!");
|
||||||
@ -395,11 +403,12 @@ fn require_non_empty_servers(servers: &[Server]) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_ssh_agent(logger: &Logger) -> Result<(), String> {
|
fn start_ssh_agent() -> Result<(), String> {
|
||||||
//start the ssh agent
|
//start the ssh agent
|
||||||
let agent_output = ShellCmd::new("ssh-agent")
|
let agent_output = ShellCmd::new("ssh-agent")
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
.collect_output()
|
.stdout(Stdio::piped())
|
||||||
|
.output()
|
||||||
.map_err(|e| format!("failed to start ssh agent: {e}"))?;
|
.map_err(|e| format!("failed to start ssh agent: {e}"))?;
|
||||||
let agent_stdout = String::from_utf8_lossy(&agent_output.stdout);
|
let agent_stdout = String::from_utf8_lossy(&agent_output.stdout);
|
||||||
if !agent_output.status.success() {
|
if !agent_output.status.success() {
|
||||||
@ -415,8 +424,10 @@ fn start_ssh_agent(logger: &Logger) -> Result<(), String> {
|
|||||||
|
|
||||||
//add the ssh key
|
//add the ssh key
|
||||||
ShellCmd::new("ssh-add")
|
ShellCmd::new("ssh-add")
|
||||||
.run(logger)
|
.spawn()
|
||||||
.map_err(|e| format!("failed to add ssh-key: {e}"))?;
|
.map_err(|e| format!("failed to add ssh key: {}", e))?
|
||||||
|
.wait()
|
||||||
|
.expect("failed to wait on ssh-add");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
exit(1)
|
|
||||||
Loading…
Reference in New Issue
Block a user