summaryrefslogtreecommitdiffstats
path: root/crates/core/plugin_sm
diff options
context:
space:
mode:
authorDidier Wenzek <didier.wenzek@acidalie.com>2022-04-14 15:21:57 +0100
committerGitHub <noreply@github.com>2022-04-14 15:21:57 +0100
commitc65512084af039ce17ef2869f9534878975c8a8f (patch)
treef480c0336da822d76f3ac3d8fceb0c96a1d55d94 /crates/core/plugin_sm
parentc3dde3e670bd80450978c8a907e2a16082eebc74 (diff)
parentea4faf4bdd7ec872146b72ce9ad06d2749603bba (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.toml1
-rw-r--r--crates/core/plugin_sm/src/lib.rs1
-rw-r--r--crates/core/plugin_sm/src/logged_command.rs231
-rw-r--r--crates/core/plugin_sm/src/plugin.rs2
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};