use std::{collections::BTreeMap, env::var, fmt::Debug, time::Duration};
use super::ports::Ports;
pub trait Image
where
Self: Sized,
Self::Args: ImageArgs + Clone + Debug,
{
type Args;
fn name(&self) -> String;
fn tag(&self) -> String;
fn ready_conditions(&self) -> Vec<WaitFor>;
fn env_vars(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
Box::new(std::iter::empty())
}
fn volumes(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
Box::new(std::iter::empty())
}
fn entrypoint(&self) -> Option<String> {
None
}
fn expose_ports(&self) -> Vec<u16> {
Default::default()
}
#[allow(unused_variables)]
fn exec_after_start(&self, cs: ContainerState) -> Vec<ExecCommand> {
Default::default()
}
}
#[derive(Default, Debug)]
pub struct ExecCommand {
pub cmd: String,
pub ready_conditions: Vec<WaitFor>,
}
#[derive(Debug)]
pub struct ContainerState {
ports: Ports,
}
impl ContainerState {
pub fn new(ports: Ports) -> Self {
Self { ports }
}
pub fn host_port(&self, internal_port: u16) -> u16 {
self.ports
.map_to_host_port(internal_port)
.unwrap_or_else(|| {
panic!(
"Container does not have a mapped port for {}",
internal_port
)
})
}
}
pub trait ImageArgs {
fn into_iterator(self) -> Box<dyn Iterator<Item = String>>;
}
impl ImageArgs for () {
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
Box::new(vec![].into_iter())
}
}
#[must_use]
#[derive(Debug)]
pub struct RunnableImage<I: Image> {
image: I,
image_args: I::Args,
image_tag: Option<String>,
container_name: Option<String>,
network: Option<String>,
env_vars: BTreeMap<String, String>,
volumes: BTreeMap<String, String>,
ports: Option<Vec<Port>>,
}
impl<I: Image> RunnableImage<I> {
pub fn inner(&self) -> &I {
&self.image
}
pub fn args(&self) -> &I::Args {
&self.image_args
}
pub fn network(&self) -> &Option<String> {
&self.network
}
pub fn container_name(&self) -> &Option<String> {
&self.container_name
}
pub fn env_vars(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
Box::new(self.image.env_vars().chain(self.env_vars.iter()))
}
pub fn volumes(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
Box::new(self.image.volumes().chain(self.volumes.iter()))
}
pub fn ports(&self) -> &Option<Vec<Port>> {
&self.ports
}
pub fn entrypoint(&self) -> Option<String> {
self.image.entrypoint()
}
pub fn descriptor(&self) -> String {
if let Some(tag) = &self.image_tag {
format!("{}:{}", self.image.name(), tag)
} else {
format!("{}:{}", self.image.name(), self.image.tag())
}
}
pub fn ready_conditions(&self) -> Vec<WaitFor> {
self.image.ready_conditions()
}
pub fn expose_ports(&self) -> Vec<u16> {
self.image.expose_ports()
}
pub fn exec_after_start(&self, cs: ContainerState) -> Vec<ExecCommand> {
self.image.exec_after_start(cs)
}
}
impl<I: Image> RunnableImage<I> {
pub fn with_tag(self, tag: impl Into<String>) -> Self {
Self {
image_tag: Some(tag.into()),
..self
}
}
pub fn with_container_name(self, name: impl Into<String>) -> Self {
Self {
container_name: Some(name.into()),
..self
}
}
pub fn with_network(self, network: impl Into<String>) -> Self {
Self {
network: Some(network.into()),
..self
}
}
pub fn with_env_var(self, (key, value): (impl Into<String>, impl Into<String>)) -> Self {
let mut env_vars = self.env_vars;
env_vars.insert(key.into(), value.into());
Self { env_vars, ..self }
}
pub fn with_volume(self, (orig, dest): (impl Into<String>, impl Into<String>)) -> Self {
let mut volumes = self.volumes;
volumes.insert(orig.into(), dest.into());
Self { volumes, ..self }
}
pub fn with_mapped_port<P: Into<Port>>(self, port: P) -> Self {
let mut ports = self.ports.unwrap_or_default();
ports.push(port.into());
Self {
ports: Some(ports),
..self
}
}
}
impl<I> From<I> for RunnableImage<I>
where
I: Image,
I::Args: Default,
{
fn from(image: I) -> Self {
Self::from((image, I::Args::default()))
}
}
impl<I: Image> From<(I, I::Args)> for RunnableImage<I> {
fn from((image, image_args): (I, I::Args)) -> Self {
Self {
image,
image_args,
image_tag: None,
container_name: None,
network: None,
env_vars: BTreeMap::default(),
volumes: BTreeMap::default(),
ports: None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Port {
pub local: u16,
pub internal: u16,
}
#[derive(Debug, PartialEq, Clone)]
pub enum WaitFor {
Nothing,
StdOutMessage { message: String },
StdErrMessage { message: String },
Duration { length: Duration },
Healthcheck,
}
impl WaitFor {
pub fn message_on_stdout<S: Into<String>>(message: S) -> WaitFor {
WaitFor::StdOutMessage {
message: message.into(),
}
}
pub fn message_on_stderr<S: Into<String>>(message: S) -> WaitFor {
WaitFor::StdErrMessage {
message: message.into(),
}
}
pub fn seconds(length: u64) -> WaitFor {
WaitFor::Duration {
length: Duration::from_secs(length),
}
}
pub fn millis(length: u64) -> WaitFor {
WaitFor::Duration {
length: Duration::from_millis(length),
}
}
pub fn millis_in_env_var(name: &'static str) -> WaitFor {
let additional_sleep_period = var(name).map(|value| value.parse());
(|| {
let length = additional_sleep_period.ok()?.ok()?;
Some(WaitFor::Duration {
length: Duration::from_millis(length),
})
})()
.unwrap_or(WaitFor::Nothing)
}
}
impl From<(u16, u16)> for Port {
fn from((local, internal): (u16, u16)) -> Self {
Port { local, internal }
}
}