use crate::image::Image;
use crate::waitfor::{NoWait, WaitFor};
use std::collections::HashMap;
use tracing::{event, Level};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StartPolicy {
Relaxed,
Strict,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StaticManagementPolicy {
External,
Internal,
Dynamic,
}
#[derive(Clone, Debug)]
pub enum LogAction {
Forward,
ForwardToFile {
path: String,
},
ForwardToStdOut,
ForwardToStdErr,
}
#[derive(Clone, Debug)]
pub enum LogSource {
StdErr,
StdOut,
Both,
}
#[derive(Clone, Debug)]
pub enum LogPolicy {
Always,
OnError,
OnStartupError,
}
#[derive(Clone, Debug)]
pub struct LogOptions {
pub action: LogAction,
pub policy: LogPolicy,
pub source: LogSource,
}
impl Default for LogOptions {
fn default() -> LogOptions {
LogOptions {
action: LogAction::Forward,
policy: LogPolicy::OnError,
source: LogSource::StdErr,
}
}
}
#[derive(Clone, Debug)]
pub struct Composition {
user_provided_container_name: Option<String>,
pub(crate) network_aliases: Option<Vec<String>>,
pub(crate) container_name: String,
pub(crate) wait: Box<dyn WaitFor>,
pub(crate) env: HashMap<String, String>,
pub(crate) cmd: Vec<String>,
pub(crate) start_policy: StartPolicy,
image: Image,
pub(crate) named_volumes: Vec<(String, String)>,
pub(crate) final_named_volume_names: Vec<String>,
pub(crate) bind_mounts: Vec<String>,
pub(crate) inject_container_name_env: Vec<(String, String)>,
pub(crate) port: Vec<(String, String)>,
pub(crate) publish_all_ports: bool,
management: Option<StaticManagementPolicy>,
pub(crate) log_options: Option<LogOptions>,
pub(crate) tmpfs: Vec<String>,
pub(crate) privileged: bool,
}
impl Composition {
pub fn with_repository<T: ToString>(repository: T) -> Composition {
let copy = repository.to_string();
Composition {
user_provided_container_name: None,
network_aliases: None,
image: Image::with_repository(©),
container_name: copy.replace('/', "-"),
wait: Box::new(NoWait {}),
env: HashMap::new(),
cmd: Vec::new(),
start_policy: StartPolicy::Relaxed,
bind_mounts: Vec::new(),
named_volumes: Vec::new(),
inject_container_name_env: Vec::new(),
final_named_volume_names: Vec::new(),
port: Vec::new(),
publish_all_ports: false,
management: None,
log_options: Some(LogOptions::default()),
privileged: false,
tmpfs: Vec::new(),
}
}
pub fn with_image(image: Image) -> Composition {
Composition {
user_provided_container_name: None,
network_aliases: None,
container_name: image.repository().to_string().replace('/', "-"),
image,
wait: Box::new(NoWait {}),
env: HashMap::new(),
cmd: Vec::new(),
start_policy: StartPolicy::Relaxed,
bind_mounts: Vec::new(),
named_volumes: Vec::new(),
inject_container_name_env: Vec::new(),
final_named_volume_names: Vec::new(),
port: Vec::new(),
publish_all_ports: false,
management: None,
log_options: Some(LogOptions::default()),
privileged: false,
tmpfs: Vec::new(),
}
}
#[cfg(target_os = "linux")]
pub fn with_tmpfs(self, paths: Vec<String>) -> Composition {
Composition {
tmpfs: paths,
..self
}
}
pub fn with_start_policy(self, start_policy: StartPolicy) -> Composition {
Composition {
start_policy,
..self
}
}
pub fn with_env(self, env: HashMap<String, String>) -> Composition {
Composition { env, ..self }
}
pub fn with_cmd(self, cmd: Vec<String>) -> Composition {
Composition { cmd, ..self }
}
pub fn port_map(&mut self, exported: u32, host: u32) -> &mut Composition {
self.port
.push((format!("{}/tcp", exported), format!("{}", host)));
self
}
pub fn publish_all_ports(&mut self, publish: bool) -> &mut Composition {
self.publish_all_ports = publish;
self
}
pub fn with_container_name<T: ToString>(self, container_name: T) -> Composition {
Composition {
user_provided_container_name: Some(container_name.to_string()),
..self
}
}
pub fn with_alias(self, aliases: Vec<String>) -> Composition {
Composition {
network_aliases: Some(aliases),
..self
}
}
pub fn alias(&mut self, alias: String) -> &mut Composition {
match self.network_aliases {
Some(ref mut network_aliases) => network_aliases.push(alias),
None => self.network_aliases = Some(vec![alias]),
};
self
}
pub fn with_wait_for(self, wait: Box<dyn WaitFor>) -> Composition {
Composition { wait, ..self }
}
pub fn with_log_options(self, log_options: Option<LogOptions>) -> Composition {
Composition {
log_options,
..self
}
}
pub fn env<T: ToString, S: ToString>(&mut self, name: T, value: S) -> &mut Composition {
self.env.insert(name.to_string(), value.to_string());
self
}
pub fn cmd<T: ToString>(&mut self, cmd: T) -> &mut Composition {
self.cmd.push(cmd.to_string());
self
}
#[cfg(target_os = "linux")]
pub fn tmpfs<T: ToString>(&mut self, path: T) -> &mut Composition {
self.tmpfs.push(path.to_string());
self
}
pub fn named_volume<T: ToString, S: ToString>(
&mut self,
volume_name: T,
path_in_container: S,
) -> &mut Composition {
self.named_volumes
.push((volume_name.to_string(), path_in_container.to_string()));
self
}
pub fn bind_mount<T: ToString, S: ToString>(
&mut self,
host_path: T,
path_in_container: S,
) -> &mut Composition {
self.bind_mounts.push(format!(
"{}:{}:Z",
host_path.to_string(),
path_in_container.to_string()
));
self
}
pub fn inject_container_name<T: ToString, E: ToString>(
&mut self,
handle: T,
env: E,
) -> &mut Composition {
self.inject_container_name_env
.push((handle.to_string(), env.to_string()));
self
}
pub fn static_container(&mut self, management: StaticManagementPolicy) -> &mut Composition {
let management = match management {
StaticManagementPolicy::External | StaticManagementPolicy::Internal => management,
StaticManagementPolicy::Dynamic => match std::env::var("DOCKERTEST_DYNAMIC") {
Ok(val) => match val.as_str() {
"EXTERNAL" => StaticManagementPolicy::External,
"INTERNAL" => StaticManagementPolicy::Internal,
"DYNAMIC" => StaticManagementPolicy::Dynamic,
_ => {
event!(Level::WARN, "DOCKERTEST_DYNAMIC environment variable set to unknown value, defaulting to Dynamic policy");
StaticManagementPolicy::Dynamic
}
},
Err(_) => management,
},
};
self.management = Some(management);
self
}
pub fn privileged(&mut self) -> &mut Composition {
self.privileged = true;
self
}
pub(crate) fn static_management_policy(&self) -> &Option<StaticManagementPolicy> {
&self.management
}
pub(crate) fn is_static(&self) -> bool {
self.management.is_some()
}
pub(crate) fn configure_container_name(&mut self, namespace: &str, suffix: &str) {
let name = match &self.user_provided_container_name {
None => self.image.repository(),
Some(n) => n,
};
if !self.is_static() {
let stripped_name = name.replace('/', "_");
self.container_name = format!("{}-{}-{}", namespace, stripped_name, suffix);
} else {
self.container_name = name.to_string();
}
}
pub(crate) fn image(&self) -> &Image {
&self.image
}
pub fn handle(&self) -> String {
match &self.user_provided_container_name {
None => self.image.repository().to_string(),
Some(n) => n.clone(),
}
}
}