diff --git a/crates/core/tedge_api/src/lib.rs b/crates/core/tedge_api/src/lib.rs index d35d8b7539..16d1672501 100644 --- a/crates/core/tedge_api/src/lib.rs +++ b/crates/core/tedge_api/src/lib.rs @@ -8,6 +8,7 @@ pub mod health; pub mod measurement; pub mod mqtt_topics; pub mod path; +pub mod script; mod software; mod store; pub mod workflow; diff --git a/crates/core/tedge_api/src/script.rs b/crates/core/tedge_api/src/script.rs new file mode 100644 index 0000000000..dff5eeea48 --- /dev/null +++ b/crates/core/tedge_api/src/script.rs @@ -0,0 +1,80 @@ +use serde::de::Error; +use serde::Deserialize; +use serde::Deserializer; +use serde::Serialize; +use serde::Serializer; +use std::fmt::Display; +use std::fmt::Formatter; +use std::str::FromStr; + +/// A parsed Unix command line +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ShellScript { + pub command: String, + pub args: Vec, +} + +/// Deserialize an Unix command line +impl<'de> Deserialize<'de> for ShellScript { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let command_line = String::deserialize(deserializer)?; + command_line.parse().map_err(Error::custom) + } +} + +impl FromStr for ShellScript { + type Err = String; + + fn from_str(command_line: &str) -> Result { + let mut args = shell_words::split(command_line) + .map_err(|err| format!("invalid script: {command_line}: {err}"))?; + if args.is_empty() { + Err("invalid script: empty".to_string()) + } else { + let script = args.remove(0); + Ok(ShellScript { + command: script, + args, + }) + } + } +} + +/// Serialize an Unix command line, using appropriate quotes +impl Serialize for ShellScript { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl Display for ShellScript { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut args = vec![self.command.clone()]; + args.append(&mut self.args.clone()); + f.write_str(&shell_words::join(args)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn script_parse_and_display() { + let script: ShellScript = "sh -c 'sleep 10'".parse().unwrap(); + assert_eq!( + script, + ShellScript { + command: "sh".to_string(), + args: vec!["-c".to_string(), "sleep 10".to_string()] + } + ); + assert_eq!(format!("{script}"), "sh -c 'sleep 10'"); + } +} diff --git a/crates/core/tedge_api/src/workflow/mod.rs b/crates/core/tedge_api/src/workflow/mod.rs index f005ba275c..4c71ff4f7d 100644 --- a/crates/core/tedge_api/src/workflow/mod.rs +++ b/crates/core/tedge_api/src/workflow/mod.rs @@ -9,6 +9,7 @@ mod toml_config; use crate::mqtt_topics::EntityTopicId; use crate::mqtt_topics::MqttSchema; use crate::mqtt_topics::OperationType; +use crate::script::ShellScript; use ::log::info; pub use error::*; use mqtt_channel::MqttMessage; diff --git a/crates/core/tedge_api/src/workflow/script.rs b/crates/core/tedge_api/src/workflow/script.rs index 3fa4a2a80e..4f81d4f69a 100644 --- a/crates/core/tedge_api/src/workflow/script.rs +++ b/crates/core/tedge_api/src/workflow/script.rs @@ -1,72 +1,11 @@ use crate::workflow::GenericStateUpdate; use crate::workflow::ScriptDefinitionError; -use serde::de::Error; -use serde::Deserialize; -use serde::Deserializer; -use serde::Serialize; -use serde::Serializer; use serde_json::Value; use std::cmp::max; use std::fmt::Display; -use std::fmt::Formatter; use std::os::unix::prelude::ExitStatusExt; -use std::str::FromStr; use std::time::Duration; -/// A parsed Unix command line -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ShellScript { - pub command: String, - pub args: Vec, -} - -/// Deserialize an Unix command line -impl<'de> Deserialize<'de> for ShellScript { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let command_line = String::deserialize(deserializer)?; - command_line.parse().map_err(Error::custom) - } -} - -impl FromStr for ShellScript { - type Err = String; - - fn from_str(command_line: &str) -> Result { - let mut args = shell_words::split(command_line) - .map_err(|err| format!("invalid script: {command_line}: {err}"))?; - if args.is_empty() { - Err("invalid script: empty".to_string()) - } else { - let script = args.remove(0); - Ok(ShellScript { - command: script, - args, - }) - } - } -} - -/// Serialize an Unix command line, using appropriate quotes -impl Serialize for ShellScript { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl Display for ShellScript { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut args = vec![self.command.clone()]; - args.append(&mut self.args.clone()); - f.write_str(&shell_words::join(args)) - } -} - /// Define how to interpret the exit code of a script as the next state for a command #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ExitHandlers { @@ -396,23 +335,12 @@ impl Default for DefaultHandlers { #[cfg(test)] mod tests { use super::*; + use crate::script::ShellScript; use crate::workflow::OperationAction; use crate::workflow::OperationWorkflow; use serde_json::json; use std::process::Command; - - #[test] - fn script_parse_and_display() { - let script: ShellScript = "sh -c 'sleep 10'".parse().unwrap(); - assert_eq!( - script, - ShellScript { - command: "sh".to_string(), - args: vec!["-c".to_string(), "sleep 10".to_string()] - } - ); - assert_eq!(format!("{script}"), "sh -c 'sleep 10'"); - } + use std::str::FromStr; #[test] fn successful_exit_code_determines_next_state() { diff --git a/crates/core/tedge_api/src/workflow/toml_config.rs b/crates/core/tedge_api/src/workflow/toml_config.rs index d9ae45f210..df648eb1b8 100644 --- a/crates/core/tedge_api/src/workflow/toml_config.rs +++ b/crates/core/tedge_api/src/workflow/toml_config.rs @@ -1,4 +1,5 @@ use crate::mqtt_topics::OperationType; +use crate::script::ShellScript; use crate::workflow::AwaitHandlers; use crate::workflow::DefaultHandlers; use crate::workflow::ExecHandlers; @@ -9,7 +10,6 @@ use crate::workflow::IterateHandlers; use crate::workflow::OperationAction; use crate::workflow::OperationWorkflow; use crate::workflow::ScriptDefinitionError; -use crate::workflow::ShellScript; use crate::workflow::WorkflowDefinitionError; use serde::de::Error; use serde::Deserialize; diff --git a/crates/extensions/c8y_mapper_ext/src/converter.rs b/crates/extensions/c8y_mapper_ext/src/converter.rs index 75710d77d4..41d7421181 100644 --- a/crates/extensions/c8y_mapper_ext/src/converter.rs +++ b/crates/extensions/c8y_mapper_ext/src/converter.rs @@ -79,8 +79,8 @@ use tedge_api::mqtt_topics::IdGenerator; use tedge_api::mqtt_topics::MqttSchema; use tedge_api::mqtt_topics::OperationType; use tedge_api::pending_entity_store::PendingEntityData; +use tedge_api::script::ShellScript; use tedge_api::workflow::GenericCommandState; -use tedge_api::workflow::ShellScript; use tedge_api::CommandLog; use tedge_api::DownloadInfo; use tedge_api::EntityStore; @@ -1655,7 +1655,7 @@ pub(crate) mod tests { use tedge_api::mqtt_topics::MqttSchema; use tedge_api::mqtt_topics::OperationType; use tedge_api::pending_entity_store::PendingEntityData; - use tedge_api::workflow::ShellScript; + use tedge_api::script::ShellScript; use tedge_api::SoftwareUpdateCommand; use tedge_config::AutoLogUpload; use tedge_config::SoftwareManagementApiFlag;