use super::JobType; #[cfg(feature = "server")] use crate::models::schema::*; use crate::utils::Platform; use crate::{UError, UResult}; #[cfg(feature = "server")] use diesel::{Identifiable, Insertable, Queryable}; use serde::{Deserialize, Serialize}; use std::fs; use uuid::Uuid; #[derive(Serialize, Deserialize, Clone, Debug)] #[cfg_attr( feature = "server", derive(Queryable, Identifiable, Insertable, AsChangeset), table_name = "jobs" )] pub struct JobMeta { #[serde(default)] pub alias: Option, /// string like `bash -c {} -a 1 --arg2`, /// where {} is replaced by executable's tmp path #[serde(default)] pub argv: String, #[serde(default = "Uuid::new_v4")] pub id: Uuid, #[serde(default)] pub exec_type: JobType, ///target triple #[serde(default)] pub platform: String, #[serde(default)] pub payload: Option>, /// if payload should be read from external resource #[serde(default)] pub payload_path: Option, ///cron-like string #[serde(default)] pub schedule: Option, } impl JobMeta { pub fn builder() -> JobMetaBuilder { JobMetaBuilder::default() } pub fn into_builder(self) -> JobMetaBuilder { JobMetaBuilder { inner: self } } pub fn from_shell(cmd: impl Into) -> UResult { Self::builder().with_shell(cmd).build() } } impl Default for JobMeta { fn default() -> Self { Self { id: Uuid::new_v4(), alias: None, argv: String::new(), exec_type: JobType::Shell, platform: Platform::current().into_string(), payload: None, schedule: None, payload_path: None, } } } #[derive(Default)] pub struct JobMetaBuilder { inner: JobMeta, } impl JobMetaBuilder { pub fn with_shell(mut self, shell_cmd: impl Into) -> Self { self.inner.argv = shell_cmd.into(); self.inner.exec_type = JobType::Shell; self } pub fn with_payload(mut self, payload: impl Into>) -> Self { self.inner.payload = Some(payload.into()); self } pub fn with_payload_src(mut self, path: impl Into) -> Self { self.inner.payload_path = Some(path.into()); self } pub fn with_alias(mut self, alias: impl Into) -> Self { self.inner.alias = Some(alias.into()); self } pub fn with_type(mut self, e_type: JobType) -> Self { self.inner.exec_type = e_type; self } pub fn build(self) -> UResult { let mut inner = self.inner; match inner.exec_type { JobType::Shell => { if inner.argv.is_empty() { // TODO: fix detecting inner.argv = String::from("/bin/bash -c {}") } let argv_parts = shlex::split(&inner.argv).ok_or(UError::JobArgsError("Shlex failed".into()))?; let empty_err = UError::JobArgsError("Empty argv".into()); if argv_parts.get(0).ok_or(empty_err.clone())?.is_empty() { return Err(empty_err.into()); } if let Some(path) = &inner.payload_path { let data = fs::read(path) .map_err(|e| UError::FSError(path.to_string(), e.to_string()))?; inner.payload = Some(data) } match inner.payload.as_ref() { Some(_) => { if !inner.argv.contains("{}") { return Err(UError::JobArgsError( "Argv contains no executable placeholder".into(), ) .into()); } } None => { if inner.argv.contains("{}") { return Err(UError::JobArgsError( "No payload provided, but argv contains executable placeholder" .into(), ) .into()); } } }; if !Platform::new(&inner.platform).check() { return Err(UError::JobArgsError(format!( "Unknown platform {}", inner.platform ))); } Ok(inner.into()) } _ => Ok(inner.into()), } } }