From dd9bf238d644f8b6d37ad13503330e1c62bea0f3 Mon Sep 17 00:00:00 2001 From: WoodenMaiden Date: Thu, 23 Feb 2023 11:55:09 +0100 Subject: [PATCH] feat: read both stderr and stdout chore: testing InternalApi.create_workspace() Signed-off-by: WoodenMaiden --- agent/lib/Cargo.toml | 1 - agent/lib/src/external_api/model.rs | 2 +- agent/lib/src/internal_api/model.rs | 4 +- agent/lib/src/internal_api/service.rs | 139 +++++++++++++++++++------- agent/src/main.rs | 10 +- 5 files changed, 114 insertions(+), 42 deletions(-) diff --git a/agent/lib/Cargo.toml b/agent/lib/Cargo.toml index d2605f8..7c1914a 100644 --- a/agent/lib/Cargo.toml +++ b/agent/lib/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" serialport = "4.2.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" -unshare = "0.7.0" log = "0.4.0" anyhow = "1.0.69" env_logger = "0.10.0" \ No newline at end of file diff --git a/agent/lib/src/external_api/model.rs b/agent/lib/src/external_api/model.rs index f64d97e..5120c36 100644 --- a/agent/lib/src/external_api/model.rs +++ b/agent/lib/src/external_api/model.rs @@ -9,5 +9,5 @@ pub struct FileModel { #[derive(Deserialize, Serialize, Debug)] pub struct CodeEntry { pub files: Vec, - pub script: Vec, + pub script: Vec, // All commands to execute at startup } diff --git a/agent/lib/src/internal_api/model.rs b/agent/lib/src/internal_api/model.rs index ae91a1f..f159194 100644 --- a/agent/lib/src/internal_api/model.rs +++ b/agent/lib/src/internal_api/model.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; -use unshare; #[derive(Deserialize, Serialize, Debug)] pub struct FileModel { @@ -39,9 +38,10 @@ impl CodeReturn { #[derive(Debug)] pub enum InternalError { - CmdSpawn(unshare::Error), + CmdSpawn, ChildWait(std::io::Error), ChildExitError(i32), InvalidExitCode, StdoutRead, + StderrRead, } diff --git a/agent/lib/src/internal_api/service.rs b/agent/lib/src/internal_api/service.rs index 3fc04da..81df8b6 100644 --- a/agent/lib/src/internal_api/service.rs +++ b/agent/lib/src/internal_api/service.rs @@ -1,14 +1,9 @@ -use super::model::{CodeReturn, InternalError}; -use crate::{external_api::model::CodeEntry, internal_api::model::FileModel}; +use std::{process::Command, fs::File, path::{PathBuf, Path}}; use anyhow::{anyhow, Result}; use log::{error, info}; +use crate::{external_api::model::CodeEntry, internal_api::model::FileModel}; use std::io::Write; -use std::{ - fs::File, - io::{BufReader, Read}, - path::{Path, PathBuf}, -}; -use unshare::Command; +use super::model::{CodeReturn, InternalError}; const WORKSPACE_PATH: &str = "/tmp"; @@ -104,34 +99,106 @@ impl InternalApi { } pub fn run(&mut self) -> Result { - let code = Command::new("/bin/sh") - .args(&["-c", "echo", "'Hello world'"]) - .spawn() - .map_err(InternalError::CmdSpawn)? - .stdout; - - // .wait().map_err(InternalError::ChildWait)?.fmt(f) - // .wait() - // .map_err(InternalError::ChildWait)? - // .code(); - - if let Some(code) = code { - // if code != 0 { - // return Err(InternalError::ChildExitError(code)); - // } - let mut stdout_reader = BufReader::new(code); - let mut stdout_output = String::new(); - println!("Internal API: Reading stdout"); - stdout_reader - .read_to_string(&mut stdout_output) - .map_err(|_| InternalError::StdoutRead)?; - // println!("{}", stdout_output); - let result = CodeReturn::new(stdout_output, "stderr".to_string(), 0); - - Ok(result) + info!("Running code"); + + // Running the latest command in vector for now + + let child_process = Command::new("/bin/sh") + .args(["-c", + self.code_entry.script.last().ok_or( + InternalError::CmdSpawn + )?.as_str() + ]) + .current_dir(WORKSPACE_PATH) + .output() + .map_err(|_|InternalError::CmdSpawn)?; + + info!("Code execution finished, gathering outputs and exit code"); + + let exit_code = child_process.status.code().ok_or( + InternalError::InvalidExitCode + )?; + let stdout = String::from_utf8(child_process.stdout).map_err( + |_| InternalError::StdoutRead + )?; + let stderr = String::from_utf8(child_process.stderr).map_err( + |_| InternalError::StderrRead + )?; + + Ok(CodeReturn::new(stdout, stderr, exit_code)) + } + +} + +#[cfg(test)] +mod tests { + use std::fs::File; + use std::io::Read; + use crate::external_api::model::FileModel; + use super::*; + + 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 { - println!("No exit code"); - Err(InternalError::InvalidExitCode) + value % max } } -} + + 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 + } + + #[test] + fn workload_runs_correctly() { + let entry = CodeEntry { + files: vec![], + script: vec![String::from("echo 'This is stdout' && echo 'This is stderr' >&2")], + }; + + + let mut api = InternalApi::new(entry); // Empty code entry to avoid borrowing issues + // since the same object is used in the `run` method + + let res = api.run().unwrap(); + + assert_eq!(res.exit_code, 0); + assert_eq!(res.stderr, "This is stderr\n"); + assert_eq!(res.stdout, "This is stdout\n"); + } + + #[test] + fn workspace_created_sucessfully() { + let mut base_dir = PathBuf::from(WORKSPACE_PATH); + base_dir.push(native_rand_string(20)); + base_dir.push("main.sh"); + let path = base_dir.into_os_string().into_string().unwrap(); + + + let entry = CodeEntry { + files: vec![ + FileModel { + filename: path.clone(), + content: "#!/bin/sh\necho -n 'Some outpout'".to_string() + } + ], + script: vec![path.clone()], + }; + + InternalApi::new(entry).create_workspace().unwrap(); + + assert!(Path::new(&path).exists()); + } +} \ No newline at end of file diff --git a/agent/src/main.rs b/agent/src/main.rs index 61085cf..e1196fc 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -1,7 +1,7 @@ use agent_lib::{external_api::service::ExternalApi, internal_api::service::InternalApi}; -use anyhow::Result; +use anyhow::{ Result, anyhow }; -use log::info; +use log::{info, error}; fn main() -> Result<()> { env_logger::init(); @@ -13,6 +13,12 @@ fn main() -> Result<()> { let code_entry = external_api.read_from_serial()?; let mut internal_api = InternalApi::new(code_entry); internal_api.create_workspace()?; + let res = internal_api.run().map_err(|e| anyhow!("{:?}", e)); + + match res { + Err(e) => error!("Error: {:?}", e), + Ok(code) => info!("Code: {:?}", code), + } info!("Stopping agent"); Ok(())