From 2ee2be95315d3c6b14fb75387f58642e26cfc113 Mon Sep 17 00:00:00 2001 From: Alexis-Bernard Date: Wed, 22 Mar 2023 15:43:39 +0100 Subject: [PATCH] fix: fix compatibilities issues w/ api and docs * fix: Implement new communication protocol * docs: internal_api & main * fix: Add carriage return on send * chore: renamed crate's models * docs: documented api module * fix: parse json with serialize_optionnal_string & docs * fix: serde mapping & fix reading Signed-off-by: Alexis-Bernard Signed-off-by: WoodenMaiden --- agent/Cargo.toml | 3 + agent/lib/src/api/comms.rs | 67 +++++++ agent/lib/src/{external_api => api}/mod.rs | 0 agent/lib/src/api/model.rs | 188 ++++++++++++++++++ .../lib/src/{external_api => api}/service.rs | 102 ++++------ agent/lib/src/config.rs | 66 ++++++ agent/lib/src/external_api/model.rs | 139 ------------- agent/lib/src/internal/mod.rs | 2 + .../src/{internal_api => internal}/model.rs | 14 ++ .../src/{internal_api => internal}/service.rs | 75 ++++--- agent/lib/src/lib.rs | 4 +- agent/src/main.rs | 28 ++- 12 files changed, 459 insertions(+), 229 deletions(-) create mode 100644 agent/lib/src/api/comms.rs rename agent/lib/src/{external_api => api}/mod.rs (100%) create mode 100644 agent/lib/src/api/model.rs rename agent/lib/src/{external_api => api}/service.rs (75%) create mode 100644 agent/lib/src/config.rs delete mode 100644 agent/lib/src/external_api/model.rs create mode 100644 agent/lib/src/internal/mod.rs rename agent/lib/src/{internal_api => internal}/model.rs (63%) rename agent/lib/src/{internal_api => internal}/service.rs (83%) diff --git a/agent/Cargo.toml b/agent/Cargo.toml index 2cab3ec..50a6ecf 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -20,3 +20,6 @@ thiserror = "1.0.32" [lib] name = "agent_lib" path = "lib/src/lib.rs" + +[dev-dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/agent/lib/src/api/comms.rs b/agent/lib/src/api/comms.rs new file mode 100644 index 0000000..04a5114 --- /dev/null +++ b/agent/lib/src/api/comms.rs @@ -0,0 +1,67 @@ +// This message is sent to the API server to indicate wether +// the agent is ready or not to receive messages. + +pub const MESSAGE_SIZE_NB_BYTES: usize = 8; + +/// Represents a message sent by the agent +pub struct Message { + /// These are characters e.g. 00002048 + pub message_size: [u8; MESSAGE_SIZE_NB_BYTES], + /// stringified json, vec because size is unknown + pub message: Vec +} + +impl Message { + pub fn new(message_to_send: String) -> Self { + let mut message_size = [0; MESSAGE_SIZE_NB_BYTES]; + let message = message_to_send.as_bytes().to_vec(); + + let string_size = format!("{:0>8}", message.len()); + //We can't call directly as bytes as both &str and String sizes are not known at + //compile time unlike message_size + + for (i, c) in string_size.chars().enumerate() { + message_size[i] = c as u8; + } + + Self { + message_size, + message + } + } + + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&self.message_size); + bytes.extend_from_slice(&self.message); + bytes + } + + +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_message_well_encoded() { + let message_data = "Hello world".to_string(); + let message = Message::new(message_data); + assert_eq!(message.message, [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]); + assert_eq!(message.message_size, [48, 48, 48, 48, 48, 48, 49, 49]); + + assert_eq!(message.to_bytes(), [48, 48, 48, 48, 48, 48, 49, 49, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]); + } + + + #[test] + fn message_size_badly_encoded() { + let message_data = "Hello world".to_string(); + let message = Message::new(message_data); + assert_eq!(message.message, [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]); + assert_ne!(message.message_size, [48, 48, 48, 48, 48, 48, 49, 50]); // should be 11, is 12 + } + +} + diff --git a/agent/lib/src/external_api/mod.rs b/agent/lib/src/api/mod.rs similarity index 100% rename from agent/lib/src/external_api/mod.rs rename to agent/lib/src/api/mod.rs diff --git a/agent/lib/src/api/model.rs b/agent/lib/src/api/model.rs new file mode 100644 index 0000000..53efa5a --- /dev/null +++ b/agent/lib/src/api/model.rs @@ -0,0 +1,188 @@ +use serde::{Deserialize, Serialize}; + + +/// Represents a file to be included in the workspace +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] +pub struct FileModel { + /// Name of the file, paths relative to the workspace + pub filename: String, + /// Content of the file + pub content: String, +} + +/// Identifies the type of the message +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] +pub enum Type { + /// Status message to indicate that the agent is ready + #[serde(rename = "status")] + Status, + /// Request message + #[serde(rename = "request")] + Request, + /// Response message answering to a request message + #[serde(rename = "response")] + Response, +} + +/// Code to tell what the Request/Response message is about +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] +pub enum Code { + /// Represents a request to run the code or a response to such request + #[serde(rename = "run")] + Run, + /// Agent is ready to communicate + #[serde(rename = "ready")] + Ready, +} + +/// Represents a Status message +#[derive(Deserialize, Serialize, Debug)] +pub struct StatusMessage { + /// Type of the message + pub r#type: Type, + /// Code of the message + pub code: Code, +} + +impl StatusMessage { + pub fn new(code: Code) -> StatusMessage { + StatusMessage { + // r#type is a reserved keyword in Rust, so we need to use the raw identifier syntax + r#type: Type::Status, + code + } + } +} + +impl Default for StatusMessage { + fn default() -> Self { + Self::new(Code::Ready) + } +} + +/// Serializes an Option as a String by returning an empty string if the Option is None +fn serialize_optionnal_string(value: &Option, serializer: S) -> Result +where + S: serde::Serializer, +{ + match value { + Some(v) => serializer.serialize_str(v), + None => serializer.serialize_str(""), + } +} + +/// Represents the output of a step +#[derive(Deserialize, Serialize, Debug)] +pub struct ResponseStep { + /// Command that was run + pub command: String, + /// Exit code of the command + #[serde(alias = "exitCode")] + pub exit_code: i32, + /// Stdout of the command. If it is None, it will be serialized as an empty string + /// to avoid api crashes + #[serde(serialize_with = "serialize_optionnal_string")] + pub stdout: Option, + /// Stderr of the command + pub stderr: String, +} + +impl ResponseStep { + pub fn new( + command: String, + exit_code: i32, + stdout: Option, + stderr: String, + ) -> ResponseStep { + ResponseStep { + command, + exit_code, + stdout, + stderr, + } + } +} + +/// Contains the id of the request and the result of all steps +#[derive(Deserialize, Serialize, Debug)] +pub struct ResponseData { + /// Id of the request (UUID) + pub id: String, + /// Result of all steps + pub steps: Vec, +} + +impl ResponseData { + pub fn new(id: String, steps: Vec) -> ResponseData { + ResponseData { id, steps } + } +} + +/// Represents a Response message with code Type::Run, meaning that it is a response to a run code request +#[derive(Deserialize, Serialize, Debug)] +pub struct ResponseMessage { + /// Type of the message + pub r#type: Type, + /// Code of the message + pub code: Code, + /// Data of the message + pub data: ResponseData, +} + +impl ResponseMessage { + pub fn new(data: ResponseData) -> ResponseMessage { + ResponseMessage { + r#type: Type::Response, + code: Code::Run, + data, + } + } +} + +/// Represent a step in the request with type Type::Run +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct RequestStep { + /// Command to run + pub command: String, + /// Whether the stdout should be returned or not (stderr will alaways be) + #[serde(alias = "enableOutput")] + pub enable_output: bool, +} + +/// Represents the data of a request message with type Type::Run +#[derive(Deserialize, Serialize, Debug)] +pub struct RequestData { + /// Id of the request (UUID) + pub id: String, + /// Files to be included in the workspace, paths relative to the workspace + pub files: Vec, + /// Steps to be executed + pub steps: Vec, +} + +impl RequestData { + pub fn new(id: String, files: Vec, steps: Vec) -> RequestData { + RequestData { id, files, steps } + } +} + +/// Represents a Request message with type Type::Run +#[derive(Deserialize, Serialize, Debug)] +pub struct RequestMessage { + /// Type of the message + pub r#type: Type, + /// Code of the message + pub code: Code, + /// Data of the message + pub data: RequestData, +} + +impl RequestMessage { + pub fn new(data: RequestData) -> RequestMessage { + RequestMessage { + r#type: Type::Request, + code: Code::Run, + data, + } + } +} diff --git a/agent/lib/src/external_api/service.rs b/agent/lib/src/api/service.rs similarity index 75% rename from agent/lib/src/external_api/service.rs rename to agent/lib/src/api/service.rs index eadeb09..28056d7 100644 --- a/agent/lib/src/external_api/service.rs +++ b/agent/lib/src/api/service.rs @@ -1,34 +1,34 @@ use anyhow::{anyhow, Result}; -use log::{error, info, debug, trace}; +use log::{debug, error, info, trace}; + +use serialport::SerialPort; -use super::model::{RequestMessage, ResponseMessage, StatusMessage}; use super::comms::{Message, MESSAGE_SIZE_NB_BYTES}; +use super::model::{RequestMessage, ResponseMessage, StatusMessage, Code}; pub struct ExternalApi { serial_path: String, serial_baud_rate: u32, + + serial_port: Box, // So we don't open it multiple times } impl ExternalApi { pub fn new(serial_path: String, serial_baud_rate: u32) -> Self { Self { - serial_path, + serial_path: serial_path.clone(), serial_baud_rate, + serial_port: serialport::new(serial_path, serial_baud_rate) + .open() + .unwrap(), } } pub fn read_from_serial(&mut self) -> Result { info!("Reading from serial port: {}", self.serial_path); - // Open the serial port - let mut serial = serialport::new(&self.serial_path, self.serial_baud_rate) - .open() - .map_err(|e| anyhow!("Failed to open serial port: {}", e))?; - // Create a buffer to hold the data - let mut data_size: usize = 0; - let mut size_buffer: [u8; MESSAGE_SIZE_NB_BYTES] = [0; MESSAGE_SIZE_NB_BYTES]; - let mut got_size = false; + let mut size_buffer = [0u8; MESSAGE_SIZE_NB_BYTES]; let mut buf = [0; 128]; let mut bytes_read: usize = 0; @@ -36,36 +36,14 @@ impl ExternalApi { // Create the final vector to hold the data let mut data_received: Vec = Vec::new(); - //we read the buffer and retrieve the first 8 bytes which are the size of the message - while !got_size { - match serial.read(&mut size_buffer) { + //we read the buffer and retrieve the first 8 bytes which are the size of the message + while bytes_read < MESSAGE_SIZE_NB_BYTES { + match self.serial_port.read(&mut size_buffer) { Ok(t) => { - if t == 0 { - break; - } - - bytes_read += t; - data_received.extend_from_slice(&size_buffer[..t]); - - trace!("Received {} bytes", t); - trace!("Size buffer: {:?}", data_received.clone()); - - if bytes_read >= MESSAGE_SIZE_NB_BYTES { - got_size = true; - - let size_string = String::from_utf8( - data_received.clone() - ).map_err( - |e| anyhow!("Failed to get message size as string: {}", e) - )?; - - trace!("Size string: {}", size_string); - - data_size = size_string.parse::().map_err( - |e| anyhow!("Failed to parse length of message: {}", e) - )?; - - data_received.drain(..MESSAGE_SIZE_NB_BYTES); + if t > 0 { + bytes_read += t; + data_received.extend_from_slice(&size_buffer[..t]); + debug!("Received {} bytes", t); } } Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (), @@ -73,10 +51,22 @@ impl ExternalApi { } } + let size_string = String::from_utf8(data_received.clone()) + .map_err(|e| anyhow!("Failed to get message size as string: {}", e))?; + + trace!("Size string: {}", size_string); + + let data_size = size_string + .parse::() + .map_err(|e| anyhow!("Failed to parse length of message: {}", e))?; + + // We clean up the vector to only keep the message + data_received.drain(..MESSAGE_SIZE_NB_BYTES); + bytes_read = 0; while bytes_read < data_size { - match serial.read(&mut buf) { + match self.serial_port.read(&mut buf) { Ok(t) => { if t > 0 { bytes_read += t; @@ -96,16 +86,11 @@ impl ExternalApi { info!("Code entry: {:?}", code_entry); - // Flush the serial port - serial - .flush() - .map_err(|e| anyhow!("Failed to flush serial port: {}", e))?; - Ok(code_entry) } pub fn parse_json_payload(&mut self, data: &[u8]) -> Result { - // Convert the data vector to a codeEntry struct + // Convert the data vector to a RequestMessage struct let request_message: RequestMessage = serde_json::from_slice(data) .map_err(|e| anyhow!("Failed to parse JSON payload: {}", e))?; @@ -115,7 +100,7 @@ impl ExternalApi { } pub fn send_status_message(&mut self) -> Result<()> { - let status_message: StatusMessage = StatusMessage::new(); + let status_message: StatusMessage = StatusMessage::new(Code::Ready); let status_message_json = serde_json::to_string(&status_message) .map_err(|e| anyhow!("Failed to serialize status message: {}", e))?; self.write_to_serial(&status_message_json)?; @@ -137,24 +122,23 @@ impl ExternalApi { } pub fn write_to_serial(&mut self, data: &str) -> Result<()> { - // Open the serial port - let mut serial = serialport::new(&self.serial_path, self.serial_baud_rate) - .open() - .map_err(|e| anyhow!("Failed to open serial port: {}", e))?; + info!("Writing to serial port: {}", self.serial_path); - // Conver the string to a byte array + // Convert the string to a byte array let message = Message::new(data.to_string()).to_bytes(); let buf = message.as_slice(); // Write the byte array to the serial port - serial + self.serial_port .write_all(buf) .map_err(|e| anyhow!("Failed to write to serial port: {}", e))?; - // Flush the serial port - serial - .flush() - .map_err(|e| anyhow!("Failed to flush serial port: {}", e))?; + // In order to still be readable by ``readline`` on the api side, we add a carriage return + // (not included in the message size) + self.serial_port + .write("\r\n".as_bytes()) + .map_err(|e| anyhow!("Failed to write to serial port: {}", e))?; + Ok(()) } } @@ -164,7 +148,7 @@ mod tests { use anyhow::Result; - use crate::external_api::model::{Code, FileModel, RequestStep, Type}; + use crate::api::model::{Code, FileModel, RequestStep, Type}; use super::ExternalApi; diff --git a/agent/lib/src/config.rs b/agent/lib/src/config.rs new file mode 100644 index 0000000..3f9e042 --- /dev/null +++ b/agent/lib/src/config.rs @@ -0,0 +1,66 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use std::{ + fs::File, + io::{self, BufReader}, +}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AgentConfigError { + #[error("cannot load config file")] + Load(#[from] io::Error), + #[error("cannot parse config file")] + Parse(#[from] serde_yaml::Error), + #[error("unsupported config kind")] + KindNotSupported, + #[error("unsupported config api version")] + VersionNotSupported, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +#[allow(non_snake_case)] +pub struct AgentConfig { + /// The api version of the agent config file + pub apiVersion: String, + /// The kind of the agent config file + pub kind: String, + /// The serial configuration + pub serial: SerialConfig, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct SerialConfig { + /// The path to the serial port + pub path: String, + /// The baud rate to use for the serial port + pub baud_rate: u32, +} + +impl AgentConfig { + /// Load a AgentConfig from a file. + /// + /// Arguments: + /// + /// * `path`: The path to the config file. + /// + /// Returns: + /// + /// A Result + pub fn load(path: &str) -> Result { + let file = File::open(path).map_err(AgentConfigError::Load)?; + let reader = BufReader::new(file); + let config: AgentConfig = + serde_yaml::from_reader(reader).map_err(AgentConfigError::Parse)?; + + if config.kind != "AgentConfig" { + return Err(AgentConfigError::KindNotSupported.into()); + } + + if config.apiVersion != "lambdo.io/v1alpha1" { + return Err(AgentConfigError::VersionNotSupported.into()); + } + + Ok(config) + } +} \ No newline at end of file diff --git a/agent/lib/src/external_api/model.rs b/agent/lib/src/external_api/model.rs deleted file mode 100644 index b78410c..0000000 --- a/agent/lib/src/external_api/model.rs +++ /dev/null @@ -1,139 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] -pub struct FileModel { - pub filename: String, - pub content: String, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct CodeEntry { - pub files: Vec, - pub script: Vec, // All commands to execute at startup -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] -pub enum Type { - Status, - Request, - Response, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] -pub enum Code { - Run, - Ok, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct StatusMessage { - pub r#type: Type, - pub code: Code, -} - -impl StatusMessage { - pub fn new() -> StatusMessage { - StatusMessage { - r#type: Type::Status, - code: Code::Ok, - } - } -} - -impl Default for StatusMessage { - fn default() -> Self { - Self::new() - } -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct ResponseStep { - pub command: String, - pub result: i32, - pub stdout: String, - pub stderr: String, - pub enable_output: bool, -} - -impl ResponseStep { - pub fn new( - command: String, - result: i32, - stdout: String, - stderr: String, - enable_output: bool, - ) -> ResponseStep { - ResponseStep { - command, - result, - stdout, - stderr, - enable_output, - } - } -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct ResponseData { - pub id: String, - pub steps: Vec, -} - -impl ResponseData { - pub fn new(id: String, steps: Vec) -> ResponseData { - ResponseData { id, steps } - } -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct ResponseMessage { - pub r#type: Type, - pub code: Code, - pub data: ResponseData, -} - -impl ResponseMessage { - pub fn new(data: ResponseData) -> ResponseMessage { - ResponseMessage { - r#type: Type::Response, - code: Code::Run, - data, - } - } -} - -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] -pub struct RequestStep { - pub command: String, - pub enable_output: bool, -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct RequestData { - pub id: String, - pub files: Vec, - pub steps: Vec, -} - -impl RequestData { - pub fn new(id: String, files: Vec, steps: Vec) -> RequestData { - RequestData { id, files, steps } - } -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct RequestMessage { - pub r#type: Type, - pub code: Code, - pub data: RequestData, -} - -impl RequestMessage { - pub fn new(data: RequestData) -> RequestMessage { - RequestMessage { - r#type: Type::Request, - code: Code::Run, - data, - } - } -} diff --git a/agent/lib/src/internal/mod.rs b/agent/lib/src/internal/mod.rs new file mode 100644 index 0000000..0948cae --- /dev/null +++ b/agent/lib/src/internal/mod.rs @@ -0,0 +1,2 @@ +pub mod model; +pub mod service; \ No newline at end of file diff --git a/agent/lib/src/internal_api/model.rs b/agent/lib/src/internal/model.rs similarity index 63% rename from agent/lib/src/internal_api/model.rs rename to agent/lib/src/internal/model.rs index 0ea8bf9..47358be 100644 --- a/agent/lib/src/internal_api/model.rs +++ b/agent/lib/src/internal/model.rs @@ -2,6 +2,13 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; +/// A struct to represent a file in the request message +/// +/// # Attributes +/// +/// * `path` - The path of the file +/// * `file_name` - The name of the file +/// * `content` - The content of the file #[derive(Deserialize, Serialize, Debug)] pub struct FileModel { pub path: PathBuf, @@ -19,6 +26,13 @@ impl FileModel { } } +/// A struct to represent the result of a command +/// +/// # Attributes +/// +/// * `stdout` - The stdout of the command +/// * `stderr` - The stderr of the command +/// * `exit_code` - The exit code of the command #[derive(Deserialize, Serialize, Debug)] pub struct CodeReturn { pub stdout: String, diff --git a/agent/lib/src/internal_api/service.rs b/agent/lib/src/internal/service.rs similarity index 83% rename from agent/lib/src/internal_api/service.rs rename to agent/lib/src/internal/service.rs index 662d04a..d488cc5 100644 --- a/agent/lib/src/internal_api/service.rs +++ b/agent/lib/src/internal/service.rs @@ -1,7 +1,7 @@ use super::model::CodeReturn; use crate::{ - external_api::model::{RequestMessage, ResponseData, ResponseMessage, ResponseStep}, - internal_api::model::FileModel, + api::model::{RequestMessage, ResponseData, ResponseMessage, ResponseStep}, + internal::model::FileModel, }; use anyhow::{anyhow, Ok, Result}; use log::{error, info}; @@ -12,17 +12,33 @@ use std::{ process::Command, }; +/// The path where the workspace will be created const WORKSPACE_PATH: &str = "/tmp"; +/// The internal API pub struct InternalApi { pub request_message: RequestMessage, } impl InternalApi { + /// Create a new instance of InternalApi + /// + /// # Arguments + /// + /// * `request_message` - The request message + /// + /// # Returns + /// + /// * `Self` - The new instance of InternalApi pub fn new(request_message: RequestMessage) -> Self { Self { request_message } } + /// Create the workspace for the code execution + /// + /// # Returns + /// + /// * `Result<()>` - Nothing or an error pub fn create_workspace(&mut self) -> Result<()> { info!("Creating workspace for code execution"); @@ -101,6 +117,11 @@ impl InternalApi { Ok(()) } + /// Run all the steps of the request message + /// + /// # Returns + /// + /// * `Result` - The response message or an error pub fn run(&mut self) -> Result { info!("Running all steps"); let mut steps: Vec = Vec::new(); @@ -114,16 +135,15 @@ impl InternalApi { // Hide Stdout if enable_output is false let stdout = if step.enable_output { - code_return.stdout + Some(code_return.stdout) } else { - "".to_string() + None }; let response_step = ResponseStep::new( command.to_string(), code_return.exit_code, stdout, code_return.stderr, - step.enable_output, ); steps.push(response_step); @@ -135,6 +155,15 @@ impl InternalApi { Ok(response_message) } + /// Run a command + /// + /// # Arguments + /// + /// * `command` - The command to run + /// + /// # Returns + /// + /// * `Result` - The code return or an error pub fn run_one(&mut self, command: &str) -> Result { info!("Running command : {}", command); @@ -163,34 +192,32 @@ impl InternalApi { #[cfg(test)] mod tests { use super::*; - use crate::external_api::model::{FileModel, RequestData, RequestStep}; + use crate::api::model::{FileModel, RequestData, RequestStep}; use std::fs::File; use std::io::Read; - - fn random_usize(max: usize) -> usize { - let mut f = File::open("/dev/urandom").unwrap(); - let mut buf = [0u8; 1]; - f.read_exact(&mut buf).unwrap(); - let value = buf[0] as usize; - - if value < max { - max - } else { - value % max - } - } - + use rand::random; + + /// Generate a random string + /// + /// # Arguments + /// + /// * `len` - The length of the string + /// + /// # Returns + /// + /// * `String` - The random string fn native_rand_string(len: usize) -> String { let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; let mut string = String::new(); for _ in 0..len { - string.push(chars.chars().nth(random_usize(chars.len() - 1)).unwrap()); + string.push(chars.chars().nth(random::() % (chars.len() - 1)).unwrap()); } string } + /// Test the creation of a file #[test] fn workload_runs_correctly() { let files: Vec = Vec::new(); @@ -211,13 +238,13 @@ mod tests { let res = api.run().unwrap(); - assert_eq!(res.data.steps[0].result, 0); + assert_eq!(res.data.steps[0].exit_code, 0); assert_eq!(res.data.steps[0].stderr, "This is stderr\n"); - assert_eq!(res.data.steps[0].stdout, "This is stdout\n"); + assert_eq!(res.data.steps[0].stdout.as_ref().unwrap(), "This is stdout\n"); assert_eq!(res.data.id, "4bf68974-c315-4c41-aee2-3dc2920e76e9"); - assert!(res.data.steps[0].enable_output); } + /// Test the execution of a command with a workspace #[test] fn workspace_created_sucessfully() { let mut base_dir = PathBuf::from(WORKSPACE_PATH); diff --git a/agent/lib/src/lib.rs b/agent/lib/src/lib.rs index 428bc93..32c6c97 100644 --- a/agent/lib/src/lib.rs +++ b/agent/lib/src/lib.rs @@ -1,3 +1,3 @@ pub mod config; -pub mod external_api; -pub mod internal_api; +pub mod api; +pub mod internal; diff --git a/agent/src/main.rs b/agent/src/main.rs index 5ca0bc6..cbd71dd 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -1,10 +1,11 @@ use agent_lib::{ - config::AgentConfig, external_api::service::ExternalApi, internal_api::service::InternalApi, + config::AgentConfig, api::service::ExternalApi, internal::service::InternalApi, }; use anyhow::{anyhow, Result}; use clap::Parser; use log::{debug, info, trace}; +/// Agent CLI options #[derive(Parser)] #[clap( version = "0.1", @@ -17,13 +18,19 @@ pub struct AgentOpts { config: String, } +/// Main function fn main() -> Result<()> { + // Initialize logger env_logger::init(); + info!("Starting agent"); + // Parse CLI options let options = AgentOpts::parse(); debug!("loading config file at {}", options.config); + + // Load config file let config = AgentConfig::load(options.config.as_str())?; trace!( @@ -31,15 +38,26 @@ fn main() -> Result<()> { config ); - let mut external_api = ExternalApi::new(config.serial.path, config.serial.baud_rate); + // Initialize external API + let mut api = ExternalApi::new(config.serial.path, config.serial.baud_rate); + + // Send status message to serial port + api.send_status_message()?; - external_api.send_status_message()?; + // Read request message from serial port + let request_message = api.read_from_serial()?; - let request_message = external_api.read_from_serial()?; + // Initialize internal API let mut internal_api = InternalApi::new(request_message); + + // Create the workspace internal_api.create_workspace()?; + + // Run the steps of the request message let response_message = internal_api.run().map_err(|e| anyhow!("{:?}", e))?; - external_api.send_response_message(response_message)?; + + // Send response message to serial port + api.send_response_message(response_message)?; info!("Stopping agent"); Ok(())