use std::{
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use crate::{error::Error, utils, DefaultExecutor, Io, LogFormat, Runc, Spawner};
pub const JSON: &str = "json";
pub const TEXT: &str = "text";
const DEBUG: &str = "--debug";
const LOG: &str = "--log";
const LOG_FORMAT: &str = "--log-format";
const ROOT: &str = "--root";
const ROOTLESS: &str = "--rootless";
const SYSTEMD_CGROUP: &str = "--systemd-cgroup";
const CONSOLE_SOCKET: &str = "--console-socket";
const DETACH: &str = "--detach";
const NO_NEW_KEYRING: &str = "--no-new-keyring";
const NO_PIVOT: &str = "--no-pivot";
const PID_FILE: &str = "--pid-file";
const ALL: &str = "--all";
const FORCE: &str = "--force";
pub const DEFAULT_COMMAND: &str = "runc";
pub trait Args {
type Output;
fn args(&self) -> Self::Output;
}
#[derive(Debug, Default)]
pub struct GlobalOpts {
command: Option<PathBuf>,
debug: bool,
log: Option<PathBuf>,
log_format: LogFormat,
root: Option<PathBuf>,
rootless: Option<bool>,
set_pgid: bool,
systemd_cgroup: bool,
timeout: Duration,
executor: Option<Arc<dyn Spawner + Send + Sync>>,
}
impl GlobalOpts {
pub fn new() -> Self {
Default::default()
}
pub fn command(mut self, command: impl AsRef<Path>) -> Self {
self.command = Some(command.as_ref().to_path_buf());
self
}
pub fn root(mut self, root: impl AsRef<Path>) -> Self {
self.root = Some(root.as_ref().to_path_buf());
self
}
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn log(mut self, log: impl AsRef<Path>) -> Self {
self.log = Some(log.as_ref().to_path_buf());
self
}
pub fn log_format(mut self, log_format: LogFormat) -> Self {
self.log_format = log_format;
self
}
pub fn log_json(self) -> Self {
self.log_format(LogFormat::Json)
}
pub fn log_text(self) -> Self {
self.log_format(LogFormat::Text)
}
pub fn systemd_cgroup(mut self, systemd_cgroup: bool) -> Self {
self.systemd_cgroup = systemd_cgroup;
self
}
pub fn rootless(mut self, rootless: bool) -> Self {
self.rootless = Some(rootless);
self
}
pub fn rootless_auto(mut self) -> Self {
self.rootless = None;
self
}
pub fn set_pgid(mut self, set_pgid: bool) -> Self {
self.set_pgid = set_pgid;
self
}
pub fn timeout(&mut self, millis: u64) -> &mut Self {
self.timeout = Duration::from_millis(millis);
self
}
pub fn custom_spawner(&mut self, executor: Arc<dyn Spawner + Send + Sync>) -> &mut Self {
self.executor = Some(executor);
self
}
pub fn build(self) -> Result<Runc, Error> {
self.args()
}
fn output(&self) -> Result<(PathBuf, Vec<String>), Error> {
let path = self
.command
.clone()
.unwrap_or_else(|| PathBuf::from("runc"));
let command = utils::binary_path(path).ok_or(Error::NotFound)?;
let mut args = Vec::new();
if let Some(root) = &self.root {
args.push(ROOT.into());
args.push(utils::abs_string(root)?);
}
if self.debug {
args.push(DEBUG.into());
}
if let Some(log_path) = &self.log {
args.push(LOG.into());
args.push(utils::abs_string(log_path)?);
}
args.push(LOG_FORMAT.into());
args.push(self.log_format.to_string());
if self.systemd_cgroup {
args.push(SYSTEMD_CGROUP.into());
}
if let Some(mode) = self.rootless {
let arg = format!("{}={}", ROOTLESS, mode);
args.push(arg);
}
Ok((command, args))
}
}
impl Args for GlobalOpts {
type Output = Result<Runc, Error>;
fn args(&self) -> Self::Output {
let (command, args) = self.output()?;
let executor = if let Some(exec) = self.executor.clone() {
exec
} else {
Arc::new(DefaultExecutor {})
};
Ok(Runc {
command,
args,
spawner: executor,
})
}
}
#[derive(Clone, Default)]
pub struct CreateOpts {
pub io: Option<Arc<dyn Io>>,
pub pid_file: Option<PathBuf>,
pub console_socket: Option<PathBuf>,
pub detach: bool,
pub no_pivot: bool,
pub no_new_keyring: bool,
}
impl Args for CreateOpts {
type Output = Result<Vec<String>, Error>;
fn args(&self) -> Self::Output {
let mut args: Vec<String> = vec![];
if let Some(pid_file) = &self.pid_file {
args.push(PID_FILE.to_string());
args.push(utils::abs_string(pid_file)?);
}
if let Some(console_socket) = &self.console_socket {
args.push(CONSOLE_SOCKET.to_string());
args.push(utils::abs_string(console_socket)?);
}
if self.no_pivot {
args.push(NO_PIVOT.to_string());
}
if self.no_new_keyring {
args.push(NO_NEW_KEYRING.to_string());
}
if self.detach {
args.push(DETACH.to_string());
}
Ok(args)
}
}
impl CreateOpts {
pub fn new() -> Self {
Self::default()
}
pub fn io(mut self, io: Arc<dyn Io>) -> Self {
self.io = Some(io);
self
}
pub fn pid_file<P>(mut self, pid_file: P) -> Self
where
P: AsRef<Path>,
{
self.pid_file = Some(pid_file.as_ref().to_path_buf());
self
}
pub fn console_socket<P>(mut self, console_socket: P) -> Self
where
P: AsRef<Path>,
{
self.console_socket = Some(console_socket.as_ref().to_path_buf());
self
}
pub fn detach(mut self, detach: bool) -> Self {
self.detach = detach;
self
}
pub fn no_pivot(mut self, no_pivot: bool) -> Self {
self.no_pivot = no_pivot;
self
}
pub fn no_new_keyring(mut self, no_new_keyring: bool) -> Self {
self.no_new_keyring = no_new_keyring;
self
}
}
#[derive(Clone, Default)]
pub struct ExecOpts {
pub io: Option<Arc<dyn Io>>,
pub pid_file: Option<PathBuf>,
pub console_socket: Option<PathBuf>,
pub detach: bool,
}
impl Args for ExecOpts {
type Output = Result<Vec<String>, Error>;
fn args(&self) -> Self::Output {
let mut args: Vec<String> = vec![];
if let Some(pid_file) = &self.pid_file {
args.push(PID_FILE.to_string());
args.push(utils::abs_string(pid_file)?);
}
if let Some(console_socket) = &self.console_socket {
args.push(CONSOLE_SOCKET.to_string());
args.push(utils::abs_string(console_socket)?);
}
if self.detach {
args.push(DETACH.to_string());
}
Ok(args)
}
}
impl ExecOpts {
pub fn new() -> Self {
Self::default()
}
pub fn io(mut self, io: Arc<dyn Io>) -> Self {
self.io = Some(io);
self
}
pub fn pid_file<P>(mut self, pid_file: P) -> Self
where
P: AsRef<Path>,
{
self.pid_file = Some(pid_file.as_ref().to_path_buf());
self
}
pub fn console_socket<P>(mut self, console_socket: P) -> Self
where
P: AsRef<Path>,
{
self.console_socket = Some(console_socket.as_ref().to_path_buf());
self
}
pub fn detach(mut self, detach: bool) -> Self {
self.detach = detach;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct DeleteOpts {
pub force: bool,
}
impl Args for DeleteOpts {
type Output = Vec<String>;
fn args(&self) -> Self::Output {
let mut args: Vec<String> = vec![];
if self.force {
args.push(FORCE.to_string());
}
args
}
}
impl DeleteOpts {
pub fn new() -> Self {
Self::default()
}
pub fn force(mut self, force: bool) -> Self {
self.force = force;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct KillOpts {
pub all: bool,
}
impl Args for KillOpts {
type Output = Vec<String>;
fn args(&self) -> Self::Output {
let mut args: Vec<String> = vec![];
if self.all {
args.push(ALL.to_string());
}
args
}
}
impl KillOpts {
pub fn new() -> Self {
Self::default()
}
pub fn all(mut self, all: bool) -> Self {
self.all = all;
self
}
}
#[cfg(test)]
mod tests {
use std::env;
use super::*;
const ARGS_FAIL_MSG: &str = "Args.args() failed.";
#[test]
fn create_opts_test() {
assert_eq!(
CreateOpts::new().args().expect(ARGS_FAIL_MSG),
Vec::<String>::new()
);
assert_eq!(
CreateOpts::new().pid_file(".").args().expect(ARGS_FAIL_MSG),
vec![
"--pid-file".to_string(),
env::current_dir()
.unwrap()
.to_string_lossy()
.parse::<String>()
.unwrap()
]
);
assert_eq!(
CreateOpts::new()
.console_socket("..")
.args()
.expect(ARGS_FAIL_MSG),
vec![
"--console-socket".to_string(),
env::current_dir()
.unwrap()
.parent()
.unwrap()
.to_string_lossy()
.parse::<String>()
.unwrap()
]
);
assert_eq!(
CreateOpts::new()
.detach(true)
.no_pivot(true)
.no_new_keyring(true)
.args()
.expect(ARGS_FAIL_MSG),
vec![
"--no-pivot".to_string(),
"--no-new-keyring".to_string(),
"--detach".to_string(),
]
);
}
#[test]
fn exec_opts_test() {
assert_eq!(
ExecOpts::new().args().expect(ARGS_FAIL_MSG),
Vec::<String>::new()
);
assert_eq!(
ExecOpts::new().pid_file(".").args().expect(ARGS_FAIL_MSG),
vec![
"--pid-file".to_string(),
env::current_dir()
.unwrap()
.to_string_lossy()
.parse::<String>()
.unwrap()
]
);
assert_eq!(
ExecOpts::new()
.console_socket("..")
.args()
.expect(ARGS_FAIL_MSG),
vec![
"--console-socket".to_string(),
env::current_dir()
.unwrap()
.parent()
.unwrap()
.to_string_lossy()
.parse::<String>()
.unwrap()
]
);
assert_eq!(
ExecOpts::new().detach(true).args().expect(ARGS_FAIL_MSG),
vec!["--detach".to_string(),]
);
}
#[test]
fn delete_opts_test() {
assert_eq!(DeleteOpts::new().force(false).args(), Vec::<String>::new());
assert_eq!(
DeleteOpts::new().force(true).args(),
vec!["--force".to_string()],
);
}
#[test]
fn kill_opts_test() {
assert_eq!(KillOpts::new().all(false).args(), Vec::<String>::new());
assert_eq!(KillOpts::new().all(true).args(), vec!["--all".to_string()],);
}
#[cfg(target_os = "linux")]
#[test]
fn global_opts_test() {
let cfg = GlobalOpts::default().command("true");
let runc = cfg.build().unwrap();
let args = &runc.args;
assert_eq!(args.len(), 2);
assert!(args.contains(&LOG_FORMAT.to_string()));
assert!(args.contains(&TEXT.to_string()));
let cfg = GlobalOpts::default().command("/bin/true");
let runc = cfg.build().unwrap();
assert_eq!(runc.args.len(), 2);
let cfg = GlobalOpts::default()
.command("true")
.root("/tmp")
.debug(true)
.log("/tmp/runc.log")
.log_json()
.systemd_cgroup(true)
.rootless(true);
let runc = cfg.build().unwrap();
let args = &runc.args;
assert!(args.contains(&ROOT.to_string()));
assert!(args.contains(&DEBUG.to_string()));
assert!(args.contains(&"/tmp".to_string()));
assert!(args.contains(&LOG.to_string()));
assert!(args.contains(&"/tmp/runc.log".to_string()));
assert!(args.contains(&LOG_FORMAT.to_string()));
assert!(args.contains(&JSON.to_string()));
assert!(args.contains(&"--rootless=true".to_string()));
assert!(args.contains(&SYSTEMD_CGROUP.to_string()));
assert_eq!(args.len(), 9);
}
}