|
|
|
@ -1,7 +1,7 @@ |
|
|
|
|
use std::{ |
|
|
|
|
process::Command, |
|
|
|
|
time::SystemTime, |
|
|
|
|
cmp::PartialEq |
|
|
|
|
cmp::PartialEq, |
|
|
|
|
}; |
|
|
|
|
use serde::{ |
|
|
|
|
Serialize, |
|
|
|
@ -10,80 +10,10 @@ use serde::{ |
|
|
|
|
use uuid::Uuid; |
|
|
|
|
//use tokio::process::Command;
|
|
|
|
|
use super::*; |
|
|
|
|
use crate::UError; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub struct Job<'meta> { |
|
|
|
|
pub result: JobResult, |
|
|
|
|
pub meta: &'meta mut JobMeta, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl<'meta> Job<'meta> { |
|
|
|
|
pub fn new(job_meta: &'meta mut JobMeta) -> Self { |
|
|
|
|
Self { |
|
|
|
|
result: JobResult { |
|
|
|
|
id: job_meta.id.clone(), |
|
|
|
|
state: job_meta.state.clone(), |
|
|
|
|
data: None, |
|
|
|
|
}, |
|
|
|
|
meta: job_meta, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn run(&mut self) { |
|
|
|
|
match self.meta.exec_type { |
|
|
|
|
JobType::Shell => { |
|
|
|
|
match self.meta.state { |
|
|
|
|
JobState::Queued | JobState::Pending => { |
|
|
|
|
self.meta.state = JobState::Running; |
|
|
|
|
}, |
|
|
|
|
JobState::Finished => { |
|
|
|
|
if self.meta.schedule == JobSchedule::Permanent { |
|
|
|
|
self.meta.state = JobState::Running; |
|
|
|
|
} else { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
JobState::Running => return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
match &self.meta.payload { |
|
|
|
|
Some(box_payload) => { |
|
|
|
|
let payload = String::from_utf8_lossy(box_payload).into_owned(); |
|
|
|
|
let mut cmd_parts = payload |
|
|
|
|
.split(" ") |
|
|
|
|
.map(String::from) |
|
|
|
|
.collect::<Vec<String>>() |
|
|
|
|
.into_iter(); |
|
|
|
|
let cmd = cmd_parts.nth(0).unwrap(); |
|
|
|
|
let args = cmd_parts.collect::<Vec<_>>(); |
|
|
|
|
let result = Command::new(cmd) |
|
|
|
|
.args(args) |
|
|
|
|
.output(); |
|
|
|
|
self.result.data = Some(match result { |
|
|
|
|
Ok(output) => { |
|
|
|
|
if output.status.success() { |
|
|
|
|
Ok(output.stdout.to_vec()) |
|
|
|
|
} else { |
|
|
|
|
Err(output.stderr.to_vec()) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Err(e) => Err(e.to_string().into_bytes()) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
None => return |
|
|
|
|
} |
|
|
|
|
self.meta.state = JobState::Finished; |
|
|
|
|
}, |
|
|
|
|
_ => unimplemented!() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn into_result(self) -> JobResult { |
|
|
|
|
self.result |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)] |
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub enum ManageAction { |
|
|
|
|
Ping, |
|
|
|
|
UpdateAvailable, |
|
|
|
@ -91,7 +21,7 @@ pub enum ManageAction { |
|
|
|
|
Terminate |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq)] |
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] |
|
|
|
|
pub enum JobSchedule { |
|
|
|
|
Once, |
|
|
|
|
Permanent, |
|
|
|
@ -99,7 +29,7 @@ pub enum JobSchedule { |
|
|
|
|
//TODO: Scheduled
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)] |
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub enum JobState { |
|
|
|
|
Queued, // server created a job, but client didn't get it yet
|
|
|
|
|
Pending, // client got a job, but not running yet
|
|
|
|
@ -108,7 +38,7 @@ pub enum JobState { |
|
|
|
|
Finished, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)] |
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub enum JobType { |
|
|
|
|
Manage(ManageAction), |
|
|
|
|
Shell, |
|
|
|
@ -116,7 +46,7 @@ pub enum JobType { |
|
|
|
|
Binary |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)] |
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub struct JobMeta { |
|
|
|
|
pub id: Uuid, |
|
|
|
|
pub name: String, |
|
|
|
@ -130,40 +60,192 @@ pub struct JobMeta { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl JobMeta { |
|
|
|
|
pub fn from_shell(shell_cmd: Vec<u8>) -> Self { |
|
|
|
|
pub fn from_shell(shell_cmd: String) -> Self { |
|
|
|
|
let uid = Uuid::new_v4(); |
|
|
|
|
//let str_payload_name: &[u8] = shell_cmd.split(|b| &[*b] == b" ").collect();
|
|
|
|
|
//let job_name = format!("{} {}", uid.to_string()[..6], str_payload_name[0]);
|
|
|
|
|
let job_name = shell_cmd.split(" ").nth(0).unwrap(); |
|
|
|
|
Self { |
|
|
|
|
id: uid.clone(), |
|
|
|
|
name: uid.to_string(), |
|
|
|
|
name: job_name.to_string(), |
|
|
|
|
created: SystemTime::now(), |
|
|
|
|
updated: SystemTime::now(), |
|
|
|
|
state: JobState::Pending, |
|
|
|
|
exec_type: JobType::Shell, |
|
|
|
|
schedule: JobSchedule::Once, |
|
|
|
|
append_result: true, |
|
|
|
|
payload: Some(Box::new(shell_cmd)) |
|
|
|
|
payload: Some(Box::new(shell_cmd.into_bytes())) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn from_shell_str(shell_cmd: String) -> Self { |
|
|
|
|
Self::from_shell(shell_cmd.into_bytes()) |
|
|
|
|
pub fn touch(&mut self) { |
|
|
|
|
self.updated = SystemTime::now(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl ToMsg for JobMeta {} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone)] |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub struct JobOutput { |
|
|
|
|
pub stdout: Vec<u8>, |
|
|
|
|
pub stderr: Vec<u8>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl JobOutput { |
|
|
|
|
const STREAM_BORDER: &'static str = "***"; |
|
|
|
|
const STDOUT: &'static str = "STDOUT"; |
|
|
|
|
const STDERR: &'static str = "STDERR"; |
|
|
|
|
|
|
|
|
|
#[inline] |
|
|
|
|
fn create_delim(header: &'static str) -> String { |
|
|
|
|
format!("{border} {head} {border}\n", |
|
|
|
|
border = JobOutput::STREAM_BORDER, |
|
|
|
|
head = header |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn new() -> Self { |
|
|
|
|
Self { |
|
|
|
|
stdout: Vec::new(), |
|
|
|
|
stderr: Vec::new(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn multiline(&self) -> String { |
|
|
|
|
let mut result = String::new(); |
|
|
|
|
if self.stdout.len() > 0 { |
|
|
|
|
result += &format!("{stdout_head}{stdout}\n", |
|
|
|
|
stdout_head = JobOutput::create_delim(JobOutput::STDOUT), |
|
|
|
|
stdout = String::from_utf8_lossy(&self.stdout)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if self.stderr.len() > 0 { |
|
|
|
|
result += &format!("{stderr_head}{stderr}\n", |
|
|
|
|
stderr_head = JobOutput::create_delim(JobOutput::STDERR), |
|
|
|
|
stderr = String::from_utf8_lossy(&self.stderr)) |
|
|
|
|
} |
|
|
|
|
result |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn from_multiline(raw: &String) -> Option<Self> { |
|
|
|
|
let err_header = JobOutput::create_delim(JobOutput::STDERR); |
|
|
|
|
raw.find(&err_header) |
|
|
|
|
.and(raw.strip_prefix(&JobOutput::create_delim(JobOutput::STDOUT))) |
|
|
|
|
.map(|s: &str| { |
|
|
|
|
let mut parts = s.split(&err_header) |
|
|
|
|
.map(|d| Vec::from(d.trim().as_bytes())) |
|
|
|
|
.collect::<Vec<Vec<u8>>>() |
|
|
|
|
.into_iter(); |
|
|
|
|
let mut instance = JobOutput::new(); |
|
|
|
|
instance.stdout = parts.next().unwrap(); |
|
|
|
|
instance.stderr = parts.next().unwrap(); |
|
|
|
|
instance |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)] |
|
|
|
|
pub struct JobResult { |
|
|
|
|
id: Uuid, |
|
|
|
|
data: Option<Result<Vec<u8>, Vec<u8>>>, |
|
|
|
|
state: JobState |
|
|
|
|
pub id: Uuid, |
|
|
|
|
pub data: Result<JobOutput, UError>, |
|
|
|
|
pub state: JobState, |
|
|
|
|
pub retcode: Option<i32>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl ToMsg for JobResult {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub struct Job<'meta> { |
|
|
|
|
pub result: JobResult, |
|
|
|
|
pub meta: &'meta mut JobMeta, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl<'meta> Job<'meta> { |
|
|
|
|
pub fn new(job_meta: &'meta mut JobMeta) -> Self { |
|
|
|
|
Self { |
|
|
|
|
result: JobResult { |
|
|
|
|
id: job_meta.id.clone(), |
|
|
|
|
state: job_meta.state.clone(), |
|
|
|
|
data: Ok(JobOutput::new()), |
|
|
|
|
retcode: None, |
|
|
|
|
}, |
|
|
|
|
meta: job_meta, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn run(&mut self) { |
|
|
|
|
match self.meta.exec_type { |
|
|
|
|
JobType::Shell => { |
|
|
|
|
match self.meta.state { |
|
|
|
|
JobState::Queued | JobState::Pending => { |
|
|
|
|
self.update_state(Some(JobState::Running)); |
|
|
|
|
}, |
|
|
|
|
JobState::Finished => { |
|
|
|
|
if self.meta.schedule == JobSchedule::Permanent { |
|
|
|
|
self.update_state(Some(JobState::Running)) |
|
|
|
|
} else { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
JobState::Running => return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
match &self.meta.payload { |
|
|
|
|
Some(box_payload) => { |
|
|
|
|
let payload = String::from_utf8_lossy(box_payload).into_owned(); |
|
|
|
|
let mut cmd_parts = payload |
|
|
|
|
.split(" ") |
|
|
|
|
.map(String::from) |
|
|
|
|
.collect::<Vec<String>>() |
|
|
|
|
.into_iter(); |
|
|
|
|
let cmd = cmd_parts.nth(0).unwrap(); |
|
|
|
|
let args = cmd_parts.collect::<Vec<_>>(); |
|
|
|
|
let result = Command::new(cmd) |
|
|
|
|
.args(args) |
|
|
|
|
.output(); |
|
|
|
|
match result { |
|
|
|
|
Ok(output) => { |
|
|
|
|
let job_out: &mut JobOutput = self.result.data.as_mut().unwrap(); |
|
|
|
|
job_out.stdout = output.stdout.to_vec(); |
|
|
|
|
job_out.stderr = output.stderr.to_vec(); |
|
|
|
|
self.result.retcode = output.status.code(); |
|
|
|
|
} |
|
|
|
|
Err(e) => { |
|
|
|
|
self.result.data = Err( |
|
|
|
|
UError::Raw(e.to_string()) |
|
|
|
|
); |
|
|
|
|
self.result.retcode = None; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
None => return |
|
|
|
|
} |
|
|
|
|
self.meta.state = JobState::Finished; |
|
|
|
|
}, |
|
|
|
|
_ => unimplemented!() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// None => state is copied from meta to result field
|
|
|
|
|
/// Some => state is applied to both meta and result fields
|
|
|
|
|
pub fn update_state(&mut self, state: Option<JobState>) { |
|
|
|
|
match state { |
|
|
|
|
Some(state) => { |
|
|
|
|
self.meta.state = state.clone(); |
|
|
|
|
self.result.state = state; |
|
|
|
|
} |
|
|
|
|
None => { |
|
|
|
|
self.result.state = self.meta.state.clone(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn into_result(mut self) -> JobResult { |
|
|
|
|
Self::update_state(&mut self, None); |
|
|
|
|
self.result |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
|
mod tests { |
|
|
|
|
use super::*; |
|
|
|
@ -171,9 +253,23 @@ mod tests { |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
fn test_shell_job() { |
|
|
|
|
let mut job = JobMeta::from_shell_str("whoami".into()); |
|
|
|
|
let mut job = JobMeta::from_shell("whoami".into()); |
|
|
|
|
let mut jobs: Vec<Job> = vec![Job::new(&mut job)]; |
|
|
|
|
execute_jobs(&mut jobs); |
|
|
|
|
assert_eq!(jobs.pop().unwrap().result.data.unwrap().unwrap(), b"plazmoid\n".to_vec()); |
|
|
|
|
let job_result = jobs.pop().unwrap().into_result(); |
|
|
|
|
assert_eq!( |
|
|
|
|
job_result.data.unwrap().stdout, |
|
|
|
|
b"plazmoid\n".to_vec() |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
fn test_failing_shell_job() { |
|
|
|
|
let mut job = JobMeta::from_shell("lol_kek_puk".into()); |
|
|
|
|
let mut jobs: Vec<Job> = vec![Job::new(&mut job)]; |
|
|
|
|
execute_jobs(&mut jobs); |
|
|
|
|
let job_result = jobs.pop().unwrap().into_result(); |
|
|
|
|
assert!(job_result.data.is_err()); |
|
|
|
|
assert_eq!(job_result.retcode, None); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|