159 lines
4.6 KiB

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>,
/// 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<Vec<u8>>,
/// if payload should be read from external resource
#[serde(default)]
pub payload_path: Option<String>,
///cron-like string
#[serde(default)]
pub schedule: Option<String>,
}
impl JobMeta {
pub fn builder() -> JobMetaBuilder {
JobMetaBuilder::default()
}
pub fn into_builder(self) -> JobMetaBuilder {
JobMetaBuilder { inner: self }
}
pub fn from_shell(cmd: impl Into<String>) -> UResult<JobMeta> {
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<String>) -> Self {
self.inner.argv = shell_cmd.into();
self.inner.exec_type = JobType::Shell;
self
}
pub fn with_payload(mut self, payload: impl Into<Vec<u8>>) -> Self {
self.inner.payload = Some(payload.into());
self
}
pub fn with_payload_src(mut self, path: impl Into<String>) -> Self {
self.inner.payload_path = Some(path.into());
self
}
pub fn with_alias(mut self, alias: impl Into<String>) -> 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<JobMeta> {
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()),
}
}
}