diff --git a/bin/u_agent/src/lib.rs b/bin/u_agent/src/lib.rs index 74bb353..876a1b6 100644 --- a/bin/u_agent/src/lib.rs +++ b/bin/u_agent/src/lib.rs @@ -77,11 +77,11 @@ pub async fn run_forever() { info!("Connecting to the server"); loop { let job_requests: Vec = - retry_until_ok!(instance.get_agent_jobs(Some(*UID)).await); + retry_until_ok!(instance.get_agent_jobs(Some(*UID)).await).into_builtin_vec(); process_request(job_requests, &instance).await; let result: Vec = pop_completed().await.into_iter().collect(); if result.len() > 0 { - retry_until_ok!(instance.report(&result).await) + retry_until_ok!(instance.report(&result).await); } sleep(Duration::from_secs(5)).await; } diff --git a/bin/u_panel/Cargo.toml b/bin/u_panel/Cargo.toml index d712325..1f15786 100644 --- a/bin/u_panel/Cargo.toml +++ b/bin/u_panel/Cargo.toml @@ -15,3 +15,5 @@ uuid = "0.6.5" reqwest = { version = "0.11", features = ["json"] } openssl = "*" u_lib = { version = "*", path = "../../lib/u_lib" } +serde_json = "1.0.4" +serde = { version = "1.0.114", features = ["derive"] } diff --git a/bin/u_panel/src/main.rs b/bin/u_panel/src/main.rs index d072d56..99cfed0 100644 --- a/bin/u_panel/src/main.rs +++ b/bin/u_panel/src/main.rs @@ -1,14 +1,18 @@ +use serde::Serialize; use std::env; +use std::fmt; use structopt::StructOpt; -use u_lib::{api::ClientHandler, models::JobMeta, utils::init_env, UError}; +use u_lib::{ + api::ClientHandler, messaging::AsMsg, models::JobMeta, utils::init_env, UError, UResult, +}; use uuid::Uuid; -const DELIM: &'static str = "*************\n"; - #[derive(StructOpt, Debug)] struct Args { #[structopt(subcommand)] cmd: Cmd, + #[structopt(long)] + json: bool, } #[derive(StructOpt, Debug)] @@ -72,60 +76,63 @@ fn parse_uuid(src: &str) -> Result { Uuid::parse_str(src).map_err(|e| e.to_string()) } -async fn process_cmd(cmd: Cmd) -> Result<(), UError> { - let token = env::var("ADMIN_AUTH_TOKEN").unwrap(); +async fn process_cmd(args: Args) { + fn printer(data: UResult, json: bool) { + if json { + #[derive(Serialize)] + #[serde(rename_all = "lowercase")] + #[serde(tag = "status", content = "data")] + enum DataResult { + Ok(M), + Err(UError), + } + + let data = match data { + Ok(r) => DataResult::Ok(r), + Err(e) => DataResult::Err(e), + }; + println!("{}", serde_json::to_string_pretty(&data).unwrap()); + } else { + match data { + Ok(r) => println!("{}", r), + Err(e) => eprintln!("Error: {}", e), + } + } + } + + let token = env::var("ADMIN_AUTH_TOKEN").expect("Authentication token is not set"); let cli_handler = ClientHandler::new(None).password(token); - match cmd { + let json = args.json; + match args.cmd { Cmd::Agents(action) => match action { - LD::List { uid } => cli_handler - .get_agents(uid) - .await? - .into_iter() - .for_each(|r| println!("{}{}", DELIM, r)), - LD::Delete { uid } => { - println!("{}", cli_handler.del(Some(uid)).await?); - } + LD::List { uid } => printer(cli_handler.get_agents(uid).await, json), + LD::Delete { uid } => printer(cli_handler.del(Some(uid)).await, json), }, Cmd::Jobs(action) => match action { JobALD::Add { cmd: JobCmd::Cmd(cmd), - agent, + agent: _agent, } => { - let job = JobMeta::from_shell(cmd.join(" "))?; - let job_uid = job.id; - cli_handler.upload_jobs(&vec![job]).await?; - if agent.is_some() { - cli_handler.set_jobs(agent, &vec![job_uid]).await? - } - } - JobALD::LD(LD::List { uid }) => cli_handler - .get_jobs(uid) - .await? - .into_iter() - .for_each(|r| println!("{}{}", DELIM, r)), - JobALD::LD(LD::Delete { uid }) => { - println!("{}", cli_handler.del(Some(uid)).await?) + let job = JobMeta::from_shell(cmd.join(" ")).unwrap(); + printer(cli_handler.upload_jobs(&[job]).await, json); } + JobALD::LD(LD::List { uid }) => printer(cli_handler.get_jobs(uid).await, json), + JobALD::LD(LD::Delete { uid }) => printer(cli_handler.del(Some(uid)).await, json), }, Cmd::Jobmap(action) => match action { JmALD::Add { agent_uid, job_uids, - } => cli_handler.set_jobs(Some(agent_uid), &job_uids).await?, - JmALD::List { uid } => cli_handler - .get_agent_jobs(uid) - .await? - .into_iter() - .for_each(|r| println!("{}{}", DELIM, r)), - JmALD::Delete { uid } => println!("{}", cli_handler.del(Some(uid)).await?), + } => printer(cli_handler.set_jobs(Some(agent_uid), &job_uids).await, json), + JmALD::List { uid } => printer(cli_handler.get_agent_jobs(uid).await, json), + JmALD::Delete { uid } => printer(cli_handler.del(Some(uid)).await, json), }, } - Ok(()) } #[tokio::main] -async fn main() -> Result<(), UError> { +async fn main() { init_env(); let args: Args = Args::from_args(); - process_cmd(args.cmd).await + process_cmd(args).await; } diff --git a/bin/u_server/src/lib.rs b/bin/u_server/src/lib.rs index c17717d..1f6b6bb 100644 --- a/bin/u_server/src/lib.rs +++ b/bin/u_server/src/lib.rs @@ -109,11 +109,11 @@ fn make_filters() -> impl Filter + C .and(get_content::>().and_then(Endpoints::report)); let auth_token = format!("Bearer {}", env::var("ADMIN_AUTH_TOKEN").unwrap()).into_boxed_str(); - let authenticated = warp::header::exact("authorization", Box::leak(auth_token)); + let auth_header = warp::header::exact("authorization", Box::leak(auth_token)); let agent_zone = get_jobs.clone().or(get_personal_jobs).or(report); - let auth_zone = authenticated.and( + let auth_zone = auth_header.and( get_agents .or(get_jobs) .or(upload_jobs) diff --git a/integration/Cargo.toml b/integration/Cargo.toml index fb5000f..dfccd4c 100644 --- a/integration/Cargo.toml +++ b/integration/Cargo.toml @@ -15,6 +15,7 @@ reqwest = { version = "0.11", features = ["json"] } serde_json = "1.0" serde = { version = "1.0.114", features = ["derive"] } futures = "0.3.5" +shlex = "1.0.0" [[test]] diff --git a/integration/docker-compose.yml b/integration/docker-compose.yml index 27d7530..997f1bf 100644 --- a/integration/docker-compose.yml +++ b/integration/docker-compose.yml @@ -75,6 +75,8 @@ services: - ~/.cargo/registry:/root/.cargo/registry working_dir: /tests/ + env_file: + - ../.env depends_on: u_agent_1: condition: service_started @@ -83,4 +85,5 @@ services: u_server: condition: service_healthy environment: - RUST_BACKTRACE: 1 \ No newline at end of file + RUST_BACKTRACE: 1 + U_SERVER: u_server \ No newline at end of file diff --git a/integration/images/u_agent.Dockerfile b/integration/images/u_agent.Dockerfile index 0a2a76f..3058997 100644 --- a/integration/images/u_agent.Dockerfile +++ b/integration/images/u_agent.Dockerfile @@ -1,3 +1,3 @@ FROM centos:7 -CMD yum update \ No newline at end of file +RUN yum update -y \ No newline at end of file diff --git a/integration/tests/behaviour.rs b/integration/tests/behaviour.rs index 617f329..8b13789 100644 --- a/integration/tests/behaviour.rs +++ b/integration/tests/behaviour.rs @@ -1,38 +1 @@ -use crate::helpers::client::AgentClient; -use serde_json::json; -use uuid::Uuid; -type TestResult = Result>; - -async fn register_agent() -> Uuid { - let cli = AgentClient::new(); - let agent_uid = Uuid::new_v4(); - let resp = cli.get(format!("get_agent_jobs/{}", agent_uid)).await; - let job_id = &resp["job_id"]; - let resp = cli.get(format!("get_jobs/{}", job_id)).await; - assert_eq!(&resp["alias"], "agent_hello"); - let agent_data = json! { - {"id": &agent_uid,"inner":[ - {"Agent": - {"alias":null, - "hostname":"3b1030fa6324", - "id":&agent_uid, - "is_root":false, - "is_root_allowed":false, - "last_active":{"secs_since_epoch":1625271265,"nanos_since_epoch":92814921}, - "platform":"x86_64-unknown-linux-gnu", - "regtime":{"secs_since_epoch":1625271265,"nanos_since_epoch":92814945}, - "state":"New", - "token":null, - "username":"root"} - }]} - }; - cli.post("report", &agent_data).await; - agent_uid -} - -#[tokio::test] -async fn test_first_connection() -> TestResult { - register_agent().await; - Ok(()) -} diff --git a/integration/tests/helpers/mod.rs b/integration/tests/helpers/mod.rs index b9babe5..0957ad9 100644 --- a/integration/tests/helpers/mod.rs +++ b/integration/tests/helpers/mod.rs @@ -1 +1,5 @@ pub mod client; +pub mod panel; + +pub use client::AgentClient; +pub use panel::Panel; diff --git a/integration/tests/helpers/panel.rs b/integration/tests/helpers/panel.rs index fe7eeab..1d616c7 100644 --- a/integration/tests/helpers/panel.rs +++ b/integration/tests/helpers/panel.rs @@ -1,3 +1,40 @@ -const BINARY: &str = "/u_panel"; +use serde_json::{from_slice, Value}; +use shlex::split; +use std::process::{Command, Output}; + +const PANEL_BINARY: &str = "/u_panel"; pub struct Panel; + +impl Panel { + fn run(args: &[&str]) -> Output { + Command::new(PANEL_BINARY) + .arg("--json") + .args(args) + .output() + .unwrap() + } + + pub fn output_argv(args: &[&str]) -> Value { + let result = Self::run(args); + assert!(result.status.success()); + from_slice(&result.stdout).unwrap() + } + + pub fn output(args: &str) -> Value { + let splitted = split(args).unwrap(); + Self::output_argv( + splitted + .iter() + .map(|s| s.as_ref()) + .collect::>() + .as_ref(), + ) + } + + pub fn check_output(args: &str) -> Vec { + let result = Self::output(args); + assert_eq!(result["status"], "ok"); + result["data"].as_array().unwrap().clone() + } +} diff --git a/integration/tests/tests.rs b/integration/tests/tests.rs index bb44d7e..5b92396 100644 --- a/integration/tests/tests.rs +++ b/integration/tests/tests.rs @@ -1,2 +1,54 @@ -mod behaviour; mod helpers; + +use helpers::{AgentClient, Panel}; + +use serde_json::json; +use uuid::Uuid; + +type TestResult = Result>; + +async fn register_agent() -> Uuid { + let cli = AgentClient::new(); + let agent_uid = Uuid::new_v4(); + let resp = cli.get(format!("get_agent_jobs/{}", agent_uid)).await; + let job_id = &resp["job_id"]; + let resp = cli.get(format!("get_jobs/{}", job_id)).await; + assert_eq!(&resp["alias"], "agent_hello"); + let agent_data = json! { + {"id": &agent_uid,"inner":[ + {"Agent": + {"alias":null, + "hostname":"3b1030fa6324", + "id":&agent_uid, + "is_root":false, + "is_root_allowed":false, + "last_active":{"secs_since_epoch":1625271265,"nanos_since_epoch":92814921}, + "platform":"x86_64-unknown-linux-gnu", + "regtime":{"secs_since_epoch":1625271265,"nanos_since_epoch":92814945}, + "state":"New", + "token":null, + "username":"root"} + }]} + }; + cli.post("report", &agent_data).await; + agent_uid +} + +#[tokio::test] +async fn test_first_connection() -> TestResult { + let uid = register_agent().await; + let agents = Panel::check_output("agents list"); + dbg!(&agents); + assert_eq!(agents.len(), 2); + let found = agents + .iter() + .find(|v| v["id"].as_str().unwrap() == uid.to_string()); + assert!(found.is_some()); + Ok(()) +} + +#[tokio::test] +async fn test_setup_tasks() -> TestResult { + register_agent().await; + Ok(()) +} diff --git a/lib/u_lib/src/api.rs b/lib/u_lib/src/api.rs index 136861d..1481ff6 100644 --- a/lib/u_lib/src/api.rs +++ b/lib/u_lib/src/api.rs @@ -3,10 +3,11 @@ use crate::{ config::{MASTER_PORT, MASTER_SERVER}, messaging::{AsMsg, BaseMessage}, models::*, - utils::opt_to_string, + utils::{opt_to_string, VecDisplay}, UError, UResult, }; use reqwest::{Client, RequestBuilder, Url}; +use std::env; use u_api_proc_macro::api_route; use uuid::Uuid; @@ -18,9 +19,8 @@ pub struct ClientHandler { impl ClientHandler { pub fn new(server: Option<&str>) -> Self { - let master_server = server - //.map(|s| Ipv4Addr::from_str(&s).unwrap()) - .unwrap_or(MASTER_SERVER); + let env_server = env::var("U_SERVER").unwrap_or(String::from(MASTER_SERVER)); + let master_server = server.unwrap_or(env_server.as_str()); Self { client: Client::new(), base_url: Url::parse(&format!("http://{}:{}", master_server, MASTER_PORT)).unwrap(), @@ -52,24 +52,24 @@ impl ClientHandler { // // get jobs for client #[api_route("GET")] - fn get_agent_jobs(&self, url_param: Option) -> Vec {} + fn get_agent_jobs(&self, url_param: Option) -> VecDisplay {} // // send something to server #[api_route("POST")] - fn report(&self, payload: &M) {} + fn report(&self, payload: &M) -> Empty {} //##########// Admin area //##########// /// client listing #[api_route("GET")] - fn get_agents(&self, url_param: Option) -> Vec {} + fn get_agents(&self, url_param: Option) -> VecDisplay {} // // get all available jobs #[api_route("GET")] - fn get_jobs(&self, url_param: Option) -> Vec {} + fn get_jobs(&self, url_param: Option) -> VecDisplay {} // // create and upload job #[api_route("POST")] - fn upload_jobs(&self, payload: &Vec) {} + fn upload_jobs(&self, payload: &[JobMeta]) -> Empty {} // // delete something #[api_route("GET")] @@ -77,5 +77,5 @@ impl ClientHandler { // // set jobs for client #[api_route("POST")] - fn set_jobs(&self, url_param: Option, payload: &Vec) {} + fn set_jobs(&self, url_param: Option, payload: &[Uuid]) -> Empty {} } diff --git a/lib/u_lib/src/builder.rs b/lib/u_lib/src/builder.rs index 2c60c8a..14b172d 100644 --- a/lib/u_lib/src/builder.rs +++ b/lib/u_lib/src/builder.rs @@ -2,7 +2,7 @@ use crate::{ cache::JobCache, executor::{FutRes, Waiter, DynFut}, models::{Agent, AssignedJob, JobMeta, JobType}, - utils::{CombinedResult, OneOrMany}, + utils::{CombinedResult, OneOrVec}, UError, }; use guess_host_triple::guess_host_triple; @@ -13,7 +13,7 @@ pub struct JobBuilder { } impl JobBuilder { - pub fn from_request>(job_requests: J) -> CombinedResult { + pub fn from_request>(job_requests: J) -> CombinedResult { let job_requests = job_requests.into_vec(); let mut prepared: Vec = vec![]; let mut result = CombinedResult::new(); @@ -52,7 +52,7 @@ impl JobBuilder { result } - pub fn from_meta>(job_metas: J) -> CombinedResult { + pub fn from_meta>(job_metas: J) -> CombinedResult { let job_requests = job_metas .into_vec() .into_iter() @@ -90,7 +90,7 @@ pub struct NamedJobBuilder { } impl NamedJobBuilder { - pub fn from_shell>( + pub fn from_shell>( named_jobs: J, ) -> CombinedResult { let mut result = CombinedResult::new(); @@ -111,7 +111,7 @@ impl NamedJobBuilder { result } - pub fn from_meta>(named_jobs: J) -> Self { + pub fn from_meta>(named_jobs: J) -> Self { let mut job_names = vec![]; let job_metas: Vec = named_jobs .into_vec() diff --git a/lib/u_lib/src/executor.rs b/lib/u_lib/src/executor.rs index c4d6492..95ab8aa 100644 --- a/lib/u_lib/src/executor.rs +++ b/lib/u_lib/src/executor.rs @@ -1,7 +1,7 @@ -use crate::{utils::OneOrMany, models::ExecResult}; +use crate::{models::ExecResult, utils::OneOrVec}; use futures::{future::BoxFuture, lock::Mutex}; use lazy_static::lazy_static; -use std::{collections::HashMap}; +use std::collections::HashMap; use tokio::{ spawn, sync::mpsc::{channel, Receiver, Sender}, @@ -37,7 +37,7 @@ pub struct Waiter { } impl Waiter { - pub fn new>(tasks: S) -> Self { + pub fn new>(tasks: S) -> Self { Self { tasks: tasks.into_vec(), fids: vec![], diff --git a/lib/u_lib/src/messaging.rs b/lib/u_lib/src/messaging.rs index b227c34..3af1933 100644 --- a/lib/u_lib/src/messaging.rs +++ b/lib/u_lib/src/messaging.rs @@ -1,6 +1,8 @@ +use crate::utils::VecDisplay; use crate::UID; use serde::{Deserialize, Serialize}; use std::borrow::Cow; +use std::fmt::Display; use uuid::Uuid; pub struct Moo<'cow, T: AsMsg + Clone>(pub Cow<'cow, T>); @@ -29,6 +31,8 @@ impl<'cow, M: AsMsg> From<&'cow M> for Moo<'cow, M> { } impl AsMsg for Vec {} +impl AsMsg for VecDisplay {} +impl<'msg, M: AsMsg> AsMsg for &'msg [M] {} #[derive(Serialize, Deserialize, Debug)] pub struct BaseMessage<'cow, I: AsMsg> { diff --git a/lib/u_lib/src/models/jobs/misc.rs b/lib/u_lib/src/models/jobs/misc.rs index 1fc3648..7451548 100644 --- a/lib/u_lib/src/models/jobs/misc.rs +++ b/lib/u_lib/src/models/jobs/misc.rs @@ -139,7 +139,7 @@ impl JobOutput { #[cfg(test)] mod tests { - use crate::{models::JobOutput, utils::vec_to_string}; + use crate::{models::JobOutput, utils::bytes_to_string}; use test_case::test_case; const STDOUT: &str = "<***STDOUT***>"; @@ -161,7 +161,7 @@ mod tests { let output = JobOutput::new() .stdout(stdout.as_bytes().to_vec()) .stderr(stderr.as_bytes().to_vec()); - assert_eq!(&vec_to_string(&output.into_combined()), result) + assert_eq!(&bytes_to_string(&output.into_combined()), result) } #[test_case( @@ -181,6 +181,6 @@ mod tests { )] fn test_from_combined(src: &str, result: &str) { let output = JobOutput::from_combined(src.as_bytes()).unwrap(); - assert_eq!(vec_to_string(&output.to_appropriate()).trim(), result); + assert_eq!(bytes_to_string(&output.to_appropriate()).trim(), result); } } diff --git a/lib/u_lib/src/models/mod.rs b/lib/u_lib/src/models/mod.rs index 9406e9a..d30a33a 100644 --- a/lib/u_lib/src/models/mod.rs +++ b/lib/u_lib/src/models/mod.rs @@ -6,6 +6,8 @@ pub mod schema; use crate::messaging::AsMsg; pub use crate::models::result::ExecResult; pub use crate::models::{agent::*, jobs::*}; +use serde::{Deserialize, Serialize}; +use std::fmt; use uuid::Uuid; impl AsMsg for Agent {} @@ -14,4 +16,13 @@ impl AsMsg for ExecResult {} impl AsMsg for JobMeta {} impl AsMsg for String {} impl AsMsg for Uuid {} -impl AsMsg for () {} +impl AsMsg for Empty {} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +pub struct Empty; + +impl fmt::Display for Empty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "") + } +} diff --git a/lib/u_lib/src/utils.rs b/lib/u_lib/src/utils.rs deleted file mode 100644 index b43c968..0000000 --- a/lib/u_lib/src/utils.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::UError; -use chrono::{offset::Local, DateTime}; -use nix::{ - sys::signal::{signal, SigHandler, Signal}, - unistd::{chdir, close as fdclose, fork, getppid, setsid, ForkResult}, -}; -use std::{ - env::temp_dir, fs, ops::Drop, os::unix::fs::PermissionsExt, path::PathBuf, process::exit, - time::SystemTime, -}; -use uuid::Uuid; - -pub trait OneOrMany { - fn into_vec(self) -> Vec; -} - -impl OneOrMany for T { - fn into_vec(self) -> Vec { - vec![self] - } -} - -impl OneOrMany for Vec { - fn into_vec(self) -> Vec { - self - } -} - -pub struct TempFile { - path: PathBuf, -} - -impl TempFile { - pub fn get_path(&self) -> String { - self.path.to_string_lossy().to_string() - } - - pub fn new() -> Self { - let name = Uuid::simple(&Uuid::new_v4()).to_string(); - let mut path = temp_dir(); - path.push(name); - Self { path } - } - - pub fn write_all(&self, data: &[u8]) -> Result<(), String> { - fs::write(&self.path, data).map_err(|e| e.to_string()) - } - - pub fn write_exec(data: &[u8]) -> Result { - let this = Self::new(); - let path = this.get_path(); - this.write_all(data).map_err(|e| (path.clone(), e))?; - let perms = fs::Permissions::from_mode(0o555); - fs::set_permissions(&path, perms).map_err(|e| (path, e.to_string()))?; - Ok(this) - } -} - -impl Drop for TempFile { - fn drop(&mut self) { - fs::remove_file(&self.path).ok(); - } -} - -pub struct CombinedResult { - ok: Vec, - err: Vec, -} - -impl CombinedResult { - pub fn new() -> Self { - Self { - ok: vec![], - err: vec![], - } - } - - pub fn ok>(&mut self, result: I) { - self.ok.extend(result.into_vec()); - } - - pub fn err>(&mut self, err: I) { - self.err.extend(err.into_vec()); - } - - pub fn unwrap(self) -> Vec { - let err_len = self.err.len(); - if err_len > 0 { - panic!("CombinedResult has {} errors", err_len); - } - self.ok - } - - pub fn unwrap_one(self) -> T { - self.unwrap().pop().unwrap() - } - - pub fn pop_errors(&mut self) -> Vec { - self.err.drain(..).collect() - } -} - -#[macro_export] -macro_rules! unwrap_enum { - ($src:ident, $t:path) => { - if let $t(result) = $src { - result - } else { - panic!("wrong type") - } - }; -} - -pub fn daemonize() { - if getppid().as_raw() != 1 { - setsig(Signal::SIGTTOU, SigHandler::SigIgn); - setsig(Signal::SIGTTIN, SigHandler::SigIgn); - setsig(Signal::SIGTSTP, SigHandler::SigIgn); - } - for fd in 0..=2 { - match fdclose(fd) { - _ => (), - } - } - match chdir("/") { - _ => (), - }; - - match fork() { - Ok(ForkResult::Parent { .. }) => { - exit(0); - } - Ok(ForkResult::Child) => match setsid() { - _ => (), - }, - Err(_) => exit(255), - } -} - -pub fn setsig(sig: Signal, hnd: SigHandler) { - unsafe { - signal(sig, hnd).unwrap(); - } -} - -pub fn vec_to_string(v: &[u8]) -> String { - String::from_utf8_lossy(v).to_string() -} - -pub fn opt_to_string(item: Option) -> String { - match item { - Some(s) => s.to_string(), - None => String::new(), - } -} - -pub fn systime_to_string(time: &SystemTime) -> String { - DateTime::::from(*time) - .format("%d/%m/%Y %T") - .to_string() -} - -pub fn init_env() { - let envs = [".env"]; - for envfile in &envs { - dotenv::from_filename(envfile).ok(); - } -} diff --git a/lib/u_lib/src/utils/combined_result.rs b/lib/u_lib/src/utils/combined_result.rs new file mode 100644 index 0000000..687f541 --- /dev/null +++ b/lib/u_lib/src/utils/combined_result.rs @@ -0,0 +1,40 @@ +use crate::utils::OneOrVec; +use crate::UError; + +pub struct CombinedResult { + ok: Vec, + err: Vec, +} + +impl CombinedResult { + pub fn new() -> Self { + Self { + ok: vec![], + err: vec![], + } + } + + pub fn ok>(&mut self, result: I) { + self.ok.extend(result.into_vec()); + } + + pub fn err>(&mut self, err: I) { + self.err.extend(err.into_vec()); + } + + pub fn unwrap(self) -> Vec { + let err_len = self.err.len(); + if err_len > 0 { + panic!("CombinedResult has {} errors", err_len); + } + self.ok + } + + pub fn unwrap_one(self) -> T { + self.unwrap().pop().unwrap() + } + + pub fn pop_errors(&mut self) -> Vec { + self.err.drain(..).collect() + } +} diff --git a/lib/u_lib/src/utils/conv.rs b/lib/u_lib/src/utils/conv.rs new file mode 100644 index 0000000..9cb2166 --- /dev/null +++ b/lib/u_lib/src/utils/conv.rs @@ -0,0 +1,19 @@ +use chrono::{offset::Local, DateTime}; +use std::time::SystemTime; + +pub fn bytes_to_string(v: &[u8]) -> String { + String::from_utf8_lossy(v).to_string() +} + +pub fn opt_to_string(item: Option) -> String { + match item { + Some(s) => s.to_string(), + None => String::new(), + } +} + +pub fn systime_to_string(time: &SystemTime) -> String { + DateTime::::from(*time) + .format("%d/%m/%Y %T") + .to_string() +} diff --git a/lib/u_lib/src/utils/misc.rs b/lib/u_lib/src/utils/misc.rs new file mode 100644 index 0000000..4f93a85 --- /dev/null +++ b/lib/u_lib/src/utils/misc.rs @@ -0,0 +1,71 @@ +use nix::{ + sys::signal::{signal, SigHandler, Signal}, + unistd::{chdir, close as fdclose, fork, getppid, setsid, ForkResult}, +}; +use std::process::exit; + +pub trait OneOrVec { + fn into_vec(self) -> Vec; +} + +impl OneOrVec for T { + fn into_vec(self) -> Vec { + vec![self] + } +} + +impl OneOrVec for Vec { + fn into_vec(self) -> Vec { + self + } +} + +#[macro_export] +macro_rules! unwrap_enum { + ($src:ident, $t:path) => { + if let $t(result) = $src { + result + } else { + panic!("wrong type") + } + }; +} + +pub fn daemonize() { + if getppid().as_raw() != 1 { + setsig(Signal::SIGTTOU, SigHandler::SigIgn); + setsig(Signal::SIGTTIN, SigHandler::SigIgn); + setsig(Signal::SIGTSTP, SigHandler::SigIgn); + } + for fd in 0..=2 { + match fdclose(fd) { + _ => (), + } + } + match chdir("/") { + _ => (), + }; + + match fork() { + Ok(ForkResult::Parent { .. }) => { + exit(0); + } + Ok(ForkResult::Child) => match setsid() { + _ => (), + }, + Err(_) => exit(255), + } +} + +pub fn setsig(sig: Signal, hnd: SigHandler) { + unsafe { + signal(sig, hnd).unwrap(); + } +} + +pub fn init_env() { + let envs = [".env"]; + for envfile in &envs { + dotenv::from_filename(envfile).ok(); + } +} diff --git a/lib/u_lib/src/utils/mod.rs b/lib/u_lib/src/utils/mod.rs new file mode 100644 index 0000000..13f94d3 --- /dev/null +++ b/lib/u_lib/src/utils/mod.rs @@ -0,0 +1,11 @@ +pub mod combined_result; +pub mod conv; +pub mod misc; +pub mod tempfile; +pub mod vec_display; + +pub use combined_result::*; +pub use conv::*; +pub use misc::*; +pub use tempfile::*; +pub use vec_display::*; diff --git a/lib/u_lib/src/utils/tempfile.rs b/lib/u_lib/src/utils/tempfile.rs new file mode 100644 index 0000000..e76baa8 --- /dev/null +++ b/lib/u_lib/src/utils/tempfile.rs @@ -0,0 +1,38 @@ +use std::{env::temp_dir, fs, ops::Drop, os::unix::fs::PermissionsExt, path::PathBuf}; +use uuid::Uuid; + +pub struct TempFile { + path: PathBuf, +} + +impl TempFile { + pub fn get_path(&self) -> String { + self.path.to_string_lossy().to_string() + } + + pub fn new() -> Self { + let name = Uuid::simple(&Uuid::new_v4()).to_string(); + let mut path = temp_dir(); + path.push(name); + Self { path } + } + + pub fn write_all(&self, data: &[u8]) -> Result<(), String> { + fs::write(&self.path, data).map_err(|e| e.to_string()) + } + + pub fn write_exec(data: &[u8]) -> Result { + let this = Self::new(); + let path = this.get_path(); + this.write_all(data).map_err(|e| (path.clone(), e))?; + let perms = fs::Permissions::from_mode(0o555); + fs::set_permissions(&path, perms).map_err(|e| (path, e.to_string()))?; + Ok(this) + } +} + +impl Drop for TempFile { + fn drop(&mut self) { + fs::remove_file(&self.path).ok(); + } +} diff --git a/lib/u_lib/src/utils/vec_display.rs b/lib/u_lib/src/utils/vec_display.rs new file mode 100644 index 0000000..117ee44 --- /dev/null +++ b/lib/u_lib/src/utils/vec_display.rs @@ -0,0 +1,40 @@ +use crate::{messaging::AsMsg, utils::OneOrVec}; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; +use std::ops::{Deref, DerefMut}; + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct VecDisplay(pub Vec); + +impl VecDisplay { + pub fn new>(inner: I) -> Self { + VecDisplay(inner.into_vec()) + } + + pub fn into_builtin_vec(self) -> Vec { + self.0 + } +} + +impl Deref for VecDisplay { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for VecDisplay { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Display for VecDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for (i, itm) in self.0.iter().enumerate() { + writeln!(f, "### {}:\n{}\n", i, itm)?; + } + Ok(()) + } +} diff --git a/scripts/cargo_musl.sh b/scripts/cargo_musl.sh index 5e1395f..c33121b 100755 --- a/scripts/cargo_musl.sh +++ b/scripts/cargo_musl.sh @@ -1,6 +1,7 @@ #!/bin/bash set -e source $(dirname $0)/rootdir.sh #set ROOTDIR +umask 002 docker run \ -v $ROOTDIR:/volume \ -v cargo-cache:/root/.cargo/registry \ @@ -8,4 +9,3 @@ docker run \ -it \ unki/musllibs \ cargo $@ -