use std::{borrow::Cow, collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize};
use tracing::warn;
use crate::{
hash::Hashable,
resources::{LocalResource, RemoteResource},
};
pub type ImageName = String;
#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum Image {
Docker(ImageName),
RemoteDocker((RemoteResource, ImageName)),
LocalDocker((LocalResource, ImageName)),
}
impl Image {
pub fn name(&self) -> &str {
match self {
Self::Docker(name) => name,
Self::RemoteDocker((_, name)) => name,
Self::LocalDocker((_, name)) => {
warn!("Local docker is for local development only!");
name
}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum Source {
File(RemoteResource),
Files(Vec<(PathBuf, RemoteResource)>),
UnZipDirectory(RemoteResource),
}
#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct InMount {
pub source: Source,
pub target: PathBuf,
pub temporary: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum ResultExtractor {
File(PathBuf),
NegativeExitCode(i64),
RegexStdout(String),
}
impl ResultExtractor {
pub fn mount_point(&self) -> Option<PathBuf> {
match self {
Self::File(path) => path.parent().map(PathBuf::from),
Self::RegexStdout(_) => None,
Self::NegativeExitCode(_) => None,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Hash, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum Injector {
File(PathBuf),
Directory(PathBuf),
}
impl Injector {
pub fn mount_point(&self) -> Option<PathBuf> {
match self {
Self::File(path) => Some(path.clone()),
Self::Directory(path) => Some(path.clone()),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Hash)]
#[serde(rename_all = "camelCase")]
pub enum ExtractedResult {
Bytes(Vec<u8>),
ZipDirectory(Vec<u8>),
Success,
NegativeResult,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Executable {
pub image: Image,
pub platform: Option<String>,
pub in_mounts: Vec<InMount>,
pub result_extractor: Option<ResultExtractor>,
pub injector: Option<Injector>,
pub entrypoint: Vec<String>,
pub cmd: Vec<String>,
pub env_vars: Option<HashMap<String, String>>,
pub network_enabled: bool,
pub privileged: bool,
pub docker_access: bool,
}
impl Hashable for Executable {
fn collect(&self) -> Cow<[u8]> {
let mut buf = vec![];
buf.extend_from_slice(&serde_json::to_vec(&self.image).unwrap());
buf.extend_from_slice(&serde_json::to_vec(&self.in_mounts).unwrap());
buf.extend_from_slice(&serde_json::to_vec(&self.result_extractor).unwrap());
buf.extend_from_slice(&serde_json::to_vec(&self.injector).unwrap());
buf.extend_from_slice(&serde_json::to_vec(&self.entrypoint).unwrap());
if let Some(ev) = &self.env_vars {
let mut ev = ev.iter().collect::<Vec<(&String, &String)>>();
ev.sort();
ev.iter().for_each(|(k, v)| {
buf.extend_from_slice(k.as_bytes());
buf.extend_from_slice(v.as_bytes());
})
} else {
buf.extend_from_slice("ev".as_bytes());
};
let flags = (self.docker_access as u8) << 2
| (self.privileged as u8) << 1
| (self.network_enabled as u8);
buf.extend_from_slice(&[flags]);
Cow::Owned(buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialization() {
let rrs = vec![
Executable {
image: Image::Docker("dummy_prover:latest".to_string()),
platform: None,
in_mounts: vec![],
result_extractor: None,
injector: None,
entrypoint: vec![],
cmd: vec![],
env_vars: None,
network_enabled: false,
privileged: false,
docker_access: false,
},
];
let s = serde_json::to_string_pretty(&rrs).unwrap();
println!("{}", s);
let rs: Vec<Executable> = serde_json::from_str(&s).unwrap();
assert_eq!(rrs, rs);
println!("{:?}", rs);
let x = bincode::serialize(&rs).unwrap();
let x = bincode::deserialize::<Vec<Executable>>(&x).unwrap();
assert_eq!(x, rs)
}
}