2022-03-01 11:29:13 +00:00
|
|
|
use log::debug;
|
|
|
|
|
2022-04-08 10:53:31 +01:00
|
|
|
use crate::{Error, Result};
|
2022-02-13 23:52:41 +00:00
|
|
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
2022-04-08 12:58:36 +01:00
|
|
|
use std::net::SocketAddr;
|
2022-02-13 23:52:41 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
use ipnetwork::{Ipv4Network, Ipv6Network};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct Specification {
|
|
|
|
pub entrypoints: HashMap<String, Entrypoint>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct Entrypoint {
|
2022-02-28 20:42:44 +00:00
|
|
|
#[serde(default)]
|
2022-02-13 23:52:41 +00:00
|
|
|
pub trigger: Trigger,
|
2022-02-28 20:42:44 +00:00
|
|
|
|
|
|
|
#[serde(default = "Arg::default_vec")]
|
2022-02-13 23:52:41 +00:00
|
|
|
pub args: Vec<Arg>,
|
2022-04-13 21:55:53 +01:00
|
|
|
|
|
|
|
#[serde(default)]
|
|
|
|
pub environment: HashSet<Environment>,
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub enum Trigger {
|
2022-04-07 15:24:32 +01:00
|
|
|
/// Start this entrypoint at application startup
|
2022-02-13 23:52:41 +00:00
|
|
|
Startup,
|
2022-04-07 15:24:32 +01:00
|
|
|
|
|
|
|
/// Trigger this entrypoint when a named pipe receives data
|
2022-02-13 23:52:41 +00:00
|
|
|
Pipe(String),
|
2022-04-07 15:24:32 +01:00
|
|
|
|
|
|
|
/// Trigger this entrypoint when a named file socket receives data
|
|
|
|
FileSocket(String),
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
|
2022-02-28 20:42:44 +00:00
|
|
|
impl Default for Trigger {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Startup
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-01 11:29:13 +00:00
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
2022-02-13 23:52:41 +00:00
|
|
|
pub enum Arg {
|
|
|
|
/// The binary name, or argv[0], of the original program start
|
|
|
|
BinaryName,
|
|
|
|
|
|
|
|
/// The name of this entrypoint
|
|
|
|
Entrypoint,
|
|
|
|
|
2022-04-07 15:24:32 +01:00
|
|
|
/// A file descriptor for a file on the filesystem in the launching namespace
|
|
|
|
File(PathBuf),
|
|
|
|
|
2022-02-13 23:52:41 +00:00
|
|
|
/// A chosen end of a named pipe
|
|
|
|
Pipe(Pipe),
|
|
|
|
|
2022-04-07 15:24:32 +01:00
|
|
|
/// File socket
|
|
|
|
FileSocket(FileSocket),
|
|
|
|
|
2022-04-08 20:45:10 +01:00
|
|
|
/// A value specified by the trigger
|
2022-04-07 15:24:32 +01:00
|
|
|
/// NOTE: Only valid if the trigger is of type Pipe(...) or FileSocket(...)
|
2022-04-08 20:45:10 +01:00
|
|
|
Trigger,
|
2022-03-01 11:29:13 +00:00
|
|
|
|
|
|
|
/// A TCP Listener
|
2022-04-08 12:58:36 +01:00
|
|
|
TcpListener { addr: SocketAddr },
|
2022-03-01 11:29:13 +00:00
|
|
|
|
2022-05-03 14:34:26 +01:00
|
|
|
/// An RPC socket that accepts specified commands
|
|
|
|
Rpc(Vec<RpcSpecification>),
|
|
|
|
|
2022-02-13 23:52:41 +00:00
|
|
|
/// The rest of argv[1..], 0 or more arguments
|
|
|
|
Trailing,
|
|
|
|
}
|
|
|
|
|
2022-02-28 20:42:44 +00:00
|
|
|
impl Arg {
|
|
|
|
fn default_vec() -> Vec<Arg> {
|
|
|
|
vec![Arg::BinaryName]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-03 14:34:26 +01:00
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
|
|
pub enum RpcSpecification {
|
|
|
|
/// Open a TCP socket
|
|
|
|
///
|
|
|
|
/// None for each value means that any value is allowed in the call.
|
|
|
|
/// A specified value restricts to exactly that.
|
|
|
|
OpenTcpSocket {
|
|
|
|
family: Option<AddressFamily>,
|
|
|
|
port: Option<u16>,
|
|
|
|
host: Option<String>,
|
|
|
|
},
|
|
|
|
|
|
|
|
/// Open a UDP socket
|
|
|
|
///
|
|
|
|
/// None for each value means that any value is allowed in the call.
|
|
|
|
/// A specified value restricts to exactly that.
|
|
|
|
OpenUdpSocket {
|
|
|
|
family: Option<AddressFamily>,
|
|
|
|
port: Option<u16>,
|
|
|
|
host: Option<String>,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
|
|
pub enum AddressFamily {
|
|
|
|
/// IPv4 address
|
|
|
|
Inet,
|
|
|
|
|
|
|
|
/// IPv6 address
|
|
|
|
Inet6,
|
|
|
|
}
|
|
|
|
|
2022-04-26 17:33:57 +01:00
|
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
2022-02-13 23:52:41 +00:00
|
|
|
pub enum Pipe {
|
|
|
|
Rx(String),
|
|
|
|
Tx(String),
|
|
|
|
}
|
|
|
|
|
2022-04-08 20:45:10 +01:00
|
|
|
impl Pipe {
|
|
|
|
pub fn get_name(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
Pipe::Rx(n) => n,
|
|
|
|
Pipe::Tx(n) => n,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 17:33:57 +01:00
|
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
2022-04-07 15:24:32 +01:00
|
|
|
pub enum FileSocket {
|
|
|
|
Rx(String),
|
|
|
|
Tx(String),
|
|
|
|
}
|
|
|
|
|
2022-05-19 18:01:58 +01:00
|
|
|
impl FileSocket {
|
|
|
|
pub fn get_name(&self) -> &str {
|
|
|
|
match self {
|
|
|
|
FileSocket::Rx(n) => n,
|
|
|
|
FileSocket::Tx(n) => n,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 23:52:41 +00:00
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
2022-04-13 21:55:53 +01:00
|
|
|
pub enum Environment {
|
2022-02-13 23:52:41 +00:00
|
|
|
Filesystem {
|
|
|
|
host_path: PathBuf,
|
2022-04-13 21:55:53 +01:00
|
|
|
environment_path: PathBuf,
|
2022-02-13 23:52:41 +00:00
|
|
|
},
|
2022-05-10 17:37:38 +01:00
|
|
|
|
|
|
|
Hostname(String),
|
|
|
|
DomainName(String),
|
2022-05-17 13:24:13 +01:00
|
|
|
|
|
|
|
Procfs,
|
2022-05-20 17:15:14 +01:00
|
|
|
|
|
|
|
Stdin,
|
|
|
|
Stdout,
|
|
|
|
Stderr,
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
|
|
|
|
pub enum Network {
|
|
|
|
InternetV4,
|
|
|
|
InternetV6,
|
|
|
|
PrivateV4(Ipv4Network),
|
|
|
|
PrivateV6(Ipv6Network),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Specification {
|
|
|
|
pub fn pipes(&self) -> (Vec<&str>, Vec<&str>) {
|
|
|
|
let mut read = Vec::new();
|
|
|
|
let mut write = Vec::new();
|
|
|
|
|
2022-02-28 20:42:44 +00:00
|
|
|
for entry in self.entrypoints.values() {
|
2022-04-07 15:24:32 +01:00
|
|
|
if let Trigger::Pipe(s) = &entry.trigger {
|
|
|
|
read.push(s.as_str());
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for arg in &entry.args {
|
2022-03-01 11:29:13 +00:00
|
|
|
if let Arg::Pipe(p) = arg {
|
|
|
|
match p {
|
2022-02-13 23:52:41 +00:00
|
|
|
Pipe::Rx(s) => read.push(s.as_str()),
|
|
|
|
Pipe::Tx(s) => write.push(s.as_str()),
|
2022-03-01 11:29:13 +00:00
|
|
|
}
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-01 11:29:13 +00:00
|
|
|
debug!("read pipes: {:?}", &read);
|
|
|
|
debug!("write pipes: {:?}", &write);
|
2022-02-13 23:52:41 +00:00
|
|
|
(read, write)
|
|
|
|
}
|
|
|
|
|
2022-04-07 15:24:32 +01:00
|
|
|
pub fn sockets(&self) -> (Vec<&str>, Vec<&str>) {
|
|
|
|
let mut read = Vec::new();
|
|
|
|
let mut write = Vec::new();
|
|
|
|
|
|
|
|
for entry in self.entrypoints.values() {
|
|
|
|
if let Trigger::FileSocket(s) = &entry.trigger {
|
|
|
|
read.push(s.as_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
for arg in &entry.args {
|
|
|
|
if let Arg::FileSocket(p) = arg {
|
|
|
|
match p {
|
|
|
|
FileSocket::Rx(s) => read.push(s.as_str()),
|
|
|
|
FileSocket::Tx(s) => write.push(s.as_str()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
debug!("read sockets: {:?}", &read);
|
|
|
|
debug!("write sockets: {:?}", &write);
|
|
|
|
(read, write)
|
|
|
|
}
|
|
|
|
|
2022-04-08 10:53:31 +01:00
|
|
|
pub fn validate(&self) -> Result<()> {
|
2022-02-13 23:52:41 +00:00
|
|
|
// validate pipes match
|
|
|
|
let (read, write) = self.pipes();
|
|
|
|
let mut read_set = HashSet::with_capacity(read.len());
|
|
|
|
|
|
|
|
for pipe in read {
|
2022-03-01 11:29:13 +00:00
|
|
|
if !read_set.insert(pipe) {
|
2022-04-08 20:54:31 +01:00
|
|
|
return Err(Error::BadPipe(pipe.to_string()));
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut write_set = HashSet::with_capacity(write.len());
|
|
|
|
for pipe in write {
|
2022-03-01 11:29:13 +00:00
|
|
|
if !write_set.insert(pipe) {
|
2022-04-08 20:54:31 +01:00
|
|
|
return Err(Error::BadPipe(pipe.to_string()));
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for pipe in read_set {
|
|
|
|
if !write_set.remove(pipe) {
|
2022-04-08 20:54:31 +01:00
|
|
|
return Err(Error::BadPipe(pipe.to_string()));
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-28 20:42:44 +00:00
|
|
|
if let Some(pipe) = write_set.into_iter().next() {
|
2022-04-08 20:54:31 +01:00
|
|
|
return Err(Error::BadPipe(pipe.to_string()));
|
2022-02-13 23:52:41 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 23:34:27 +01:00
|
|
|
// validate sockets match
|
|
|
|
let (read, write) = self.sockets();
|
|
|
|
let mut read_set = HashSet::with_capacity(read.len());
|
|
|
|
|
|
|
|
for socket in read {
|
|
|
|
if !read_set.insert(socket) {
|
|
|
|
return Err(Error::BadFileSocket(socket.to_string()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut write_set = HashSet::with_capacity(write.len());
|
|
|
|
for socket in write {
|
|
|
|
write_set.insert(socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
for socket in &read_set {
|
|
|
|
if !write_set.contains(socket) {
|
|
|
|
return Err(Error::BadFileSocket(socket.to_string()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(socket) = (&write_set - &read_set).into_iter().next() {
|
|
|
|
return Err(Error::BadFileSocket(socket.to_string()));
|
|
|
|
}
|
|
|
|
|
2022-04-07 15:24:32 +01:00
|
|
|
// validate trigger arguments make sense
|
2022-03-01 11:29:13 +00:00
|
|
|
for entrypoint in self.entrypoints.values() {
|
2022-04-08 20:45:10 +01:00
|
|
|
if entrypoint.args.contains(&Arg::Trigger) {
|
2022-03-01 11:29:13 +00:00
|
|
|
match entrypoint.trigger {
|
|
|
|
Trigger::Pipe(_) => {}
|
2022-04-07 15:24:32 +01:00
|
|
|
Trigger::FileSocket(_) => {}
|
2022-04-08 20:54:31 +01:00
|
|
|
_ => return Err(Error::BadTriggerArgument),
|
2022-03-01 11:29:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 23:52:41 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|