diff options
author | Didier Wenzek <didier.wenzek@acidalie.com> | 2022-04-14 15:21:57 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-14 15:21:57 +0100 |
commit | c65512084af039ce17ef2869f9534878975c8a8f (patch) | |
tree | f480c0336da822d76f3ac3d8fceb0c96a1d55d94 /crates/core/plugin_sm | |
parent | c3dde3e670bd80450978c8a907e2a16082eebc74 (diff) | |
parent | ea4faf4bdd7ec872146b72ce9ad06d2749603bba (diff) |
Merge pull request #1063 from makr11st/feature/759_logged_command_to_crate
#759 Move 'logged_command' to a crate as it going to be used by other components
Diffstat (limited to 'crates/core/plugin_sm')
-rw-r--r-- | crates/core/plugin_sm/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/core/plugin_sm/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/core/plugin_sm/src/logged_command.rs | 231 | ||||
-rw-r--r-- | crates/core/plugin_sm/src/plugin.rs | 2 |
4 files changed, 2 insertions, 233 deletions
diff --git a/crates/core/plugin_sm/Cargo.toml b/crates/core/plugin_sm/Cargo.toml index 21a8d003..e54837a1 100644 --- a/crates/core/plugin_sm/Cargo.toml +++ b/crates/core/plugin_sm/Cargo.toml @@ -10,6 +10,7 @@ agent_interface = { path = "../agent_interface" } async-trait = "0.1" csv = "1.1" download = { path = "../../common/download" } +logged_command = { path = "../../common/logged_command" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tedge_utils = { path = "../../common/tedge_utils" } diff --git a/crates/core/plugin_sm/src/lib.rs b/crates/core/plugin_sm/src/lib.rs index 79800999..c124bbbc 100644 --- a/crates/core/plugin_sm/src/lib.rs +++ b/crates/core/plugin_sm/src/lib.rs @@ -1,4 +1,3 @@ pub mod log_file; -pub mod logged_command; pub mod plugin; pub mod plugin_manager; diff --git a/crates/core/plugin_sm/src/logged_command.rs b/crates/core/plugin_sm/src/logged_command.rs deleted file mode 100644 index 11739ce0..00000000 --- a/crates/core/plugin_sm/src/logged_command.rs +++ /dev/null @@ -1,231 +0,0 @@ -use std::ffi::OsStr; -use std::process::{Output, Stdio}; -use tokio::fs::File; -use tokio::io::{AsyncWriteExt, BufWriter}; -use tokio::process::{Child, Command}; - -pub struct LoggingChild { - command_line: String, - pub inner_child: Child, -} - -impl LoggingChild { - pub async fn wait_with_output( - self, - logger: &mut BufWriter<File>, - ) -> Result<Output, std::io::Error> { - let outcome = self.inner_child.wait_with_output().await; - if let Err(err) = LoggedCommand::log_outcome(&self.command_line, &outcome, logger).await { - tracing::log::error!("Fail to log the command execution: {}", err); - } - - outcome - } -} - -/// A command which execution is logged. -/// -/// This struct wraps the main command with a nice representation of that command. -/// This `command_line` field is only required because the -/// [`Command::get_program()`](https://doc.rust-lang.org/std/process/struct.Command.html#method.get_program) -/// and -/// [`Command::get_args()`](https://doc.rust-lang.org/std/process/struct.Command.html#method.get_args) -/// are nightly-only experimental APIs. -pub struct LoggedCommand { - command_line: String, - command: Command, -} - -impl std::fmt::Display for LoggedCommand { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.command_line.fmt(f) - } -} - -impl LoggedCommand { - pub fn new(program: impl AsRef<OsStr>) -> LoggedCommand { - let command_line = match program.as_ref().to_str() { - None => format!("{:?}", program.as_ref()), - Some(cmd) => cmd.to_string(), - }; - - let mut command = Command::new(program); - command - .current_dir("/tmp") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - LoggedCommand { - command_line, - command, - } - } - - pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut LoggedCommand { - // The arguments are displayed as debug, to be properly quoted and distinguished from each other. - self.command_line.push_str(&format!(" {:?}", arg.as_ref())); - self.command.arg(arg); - self - } - - /// Execute the command and log its exit status, stdout and stderr - /// - /// If the command has been executed the outcome is returned (successful or not). - /// If the command fails to execute (say not found or not executable) an `std::io::Error` is returned. - /// - /// If the function fails to log the execution of the command, - /// this is logged with `log::error!` without changing the return value. - pub async fn execute(mut self, logger: &mut BufWriter<File>) -> Result<Output, std::io::Error> { - let outcome = self.command.output().await; - - if let Err(err) = LoggedCommand::log_outcome(&self.command_line, &outcome, logger).await { - tracing::log::error!("Fail to log the command execution: {}", err); - } - - outcome - } - - pub fn spawn(&mut self) -> Result<LoggingChild, std::io::Error> { - let child = self.command.spawn()?; - Ok(LoggingChild { - command_line: self.command_line.clone(), - inner_child: child, - }) - } - - async fn log_outcome( - command_line: &str, - result: &Result<Output, std::io::Error>, - logger: &mut BufWriter<File>, - ) -> Result<(), std::io::Error> { - logger - .write_all(format!("----- $ {}\n", command_line).as_bytes()) - .await?; - - match result.as_ref() { - Ok(output) => { - match &output.status.code() { - None => logger.write_all(b"exit status: unknown\n\n").await?, - Some(code) => { - logger - .write_all(format!("exit status: {}\n\n", code).as_bytes()) - .await? - } - }; - logger.write_all(b"stdout <<EOF\n").await?; - logger.write_all(&output.stdout).await?; - logger.write_all(b"EOF\n\n").await?; - logger.write_all(b"stderr <<EOF\n").await?; - logger.write_all(&output.stderr).await?; - logger.write_all(b"EOF\n").await?; - } - Err(err) => { - logger - .write_all(format!("error: {}\n", &err).as_bytes()) - .await?; - } - } - - logger.flush().await?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::*; - use tokio::fs::File; - - #[tokio::test] - async fn on_execute_are_logged_command_line_exit_status_stdout_and_stderr( - ) -> Result<(), anyhow::Error> { - // Prepare a log file - let tmp_dir = TempDir::new()?; - let log_file_path = tmp_dir.path().join("operation.log"); - let log_file = File::create(log_file_path.clone()).await?; - let mut logger = BufWriter::new(log_file); - - // Prepare a command - let mut command = LoggedCommand::new("echo"); - command.arg("Hello").arg("World!"); - - // Execute the command with logging - let _ = command.execute(&mut logger).await; - - let log_content = String::from_utf8(std::fs::read(&log_file_path)?)?; - assert_eq!( - log_content, - r#"----- $ echo "Hello" "World!" -exit status: 0 - -stdout <<EOF -Hello World! -EOF - -stderr <<EOF -EOF -"# - ); - Ok(()) - } - - #[tokio::test] - async fn on_execute_with_error_stderr_is_logged() -> Result<(), anyhow::Error> { - // Prepare a log file - let tmp_dir = TempDir::new()?; - let log_file_path = tmp_dir.path().join("operation.log"); - let log_file = File::create(log_file_path.clone()).await?; - let mut logger = BufWriter::new(log_file); - - // Prepare a command that triggers some content on stderr - let mut command = LoggedCommand::new("ls"); - command.arg("dummy-file"); - - // Execute the command with logging - let _ = command.execute(&mut logger).await; - - // On expect the errors to be logged - let log_content = String::from_utf8(std::fs::read(&log_file_path)?)?; - assert_eq!( - log_content, - r#"----- $ ls "dummy-file" -exit status: 2 - -stdout <<EOF -EOF - -stderr <<EOF -ls: cannot access 'dummy-file': No such file or directory -EOF -"# - ); - Ok(()) - } - - #[tokio::test] - async fn on_execution_error_are_logged_command_line_and_error() -> Result<(), anyhow::Error> { - // Prepare a log file - let tmp_dir = TempDir::new()?; - let log_file_path = tmp_dir.path().join("operation.log"); - let log_file = File::create(log_file_path.clone()).await?; - let mut logger = BufWriter::new(log_file); - - // Prepare a command that cannot be executed - let command = LoggedCommand::new("dummy-command"); - - // Execute the command with logging - let _ = command.execute(&mut logger).await; - - // The fact that the command cannot be executed must be logged - let log_content = String::from_utf8(std::fs::read(&log_file_path)?)?; - assert_eq!( - log_content, - r#"----- $ dummy-command -error: No such file or directory (os error 2) -"# - ); - Ok(()) - } -} diff --git a/crates/core/plugin_sm/src/plugin.rs b/crates/core/plugin_sm/src/plugin.rs index f7df5449..95ab2988 100644 --- a/crates/core/plugin_sm/src/plugin.rs +++ b/crates/core/plugin_sm/src/plugin.rs @@ -1,8 +1,8 @@ -use crate::logged_command::LoggedCommand; use agent_interface::*; use async_trait::async_trait; use csv::ReaderBuilder; use download::Downloader; +use logged_command::LoggedCommand; use serde::Deserialize; use std::path::Path; use std::{path::PathBuf, process::Output}; |