use std::collections::HashMap;
use bollard::models::{PortBinding, PortMap};
use bollard::service::{Mount, MountTypeEnum};
use crate::error::TestError;
use crate::host::Mode;
use crate::image::Image;
use crate::port::{Port, PortAccess};
use crate::probe::Probe;
pub(crate) type Sockets = HashMap<Port, String>;
#[derive(Debug)]
pub struct ContainerConfig {
image: Image,
hostname: String,
ports: Vec<PortAccess>,
mounts: Vec<(String, String, String)>,
env: Vec<(String, String)>,
readiness: Option<Box<dyn Probe>>,
cmd: Vec<String>,
}
impl ContainerConfig {
pub fn new(hostname: String, image: Image) -> Self {
Self {
image,
hostname,
ports: vec![],
mounts: vec![],
env: vec![],
readiness: None,
cmd: vec![],
}
}
pub fn hostname(&self) -> &str {
&self.hostname
}
pub fn image(&self) -> &Image {
&self.image
}
pub fn ports(&self) -> &[PortAccess] {
&self.ports
}
pub fn mounts(&self) -> &[(String, String, String)] {
&self.mounts
}
pub fn env(&self) -> &[(String, String)] {
&self.env
}
pub fn readiness(&self) -> Option<&dyn Probe> {
self.readiness.as_deref()
}
pub fn cmd(&self) -> &[String] {
&self.cmd
}
pub fn builder<H>(hostname: H, image: Image) -> ContainerConfigBuilder
where
H: Into<String>,
{
ContainerConfigBuilder::new(hostname, image)
}
pub(super) fn formatted_env(&self) -> Vec<String> {
self.env
.iter()
.map(|(var, value)| format!("{var}={value}"))
.collect()
}
fn standalone_pas(&self) -> Result<(PortMap, Sockets), TestError> {
let bindings: Vec<_> = self
.ports
.iter()
.filter(|p| p.visibility().is_published())
.map(|p| {
p.public_port()
.map(|e| (p.port(), e))
.ok_or(TestError::UnavailablePorts)
})
.collect::<Result<_, _>>()?;
let sockets = bindings
.iter()
.map(|(internal, external)| (*internal, format!("localhost:{external}")))
.collect();
let port_bindings = bindings
.iter()
.map(|(internal, external)| {
(
format!("{internal}/tcp"),
Some(vec![PortBinding {
host_ip: None,
host_port: Some(external.to_string()),
}]),
)
})
.collect();
Ok((port_bindings, sockets))
}
fn container_pas(&self) -> Result<(PortMap, Sockets), TestError> {
let hostname = &self.hostname;
let sockets = self
.ports
.iter()
.filter(|p| p.visibility().is_published())
.map(PortAccess::port)
.map(|port| (port, format!("{hostname}:{port}")))
.collect();
Ok((PortMap::new(), sockets))
}
pub(super) fn ports_and_sockets(&self, mode: Mode) -> Result<(PortMap, Sockets), TestError> {
match mode {
Mode::Standalone => self.standalone_pas(),
Mode::Containerized => self.container_pas(),
}
}
pub fn docker_mounts(&self) -> Vec<Mount> {
self.mounts()
.iter()
.map(|(src, dst_base, dst)| bollard::models::Mount {
typ: Some(MountTypeEnum::BIND),
source: Some(src.to_string()),
target: Some(format!("/{dst_base}/{dst}")),
..Default::default()
})
.collect()
}
}
#[derive(Debug)]
pub struct ContainerConfigBuilder {
config: ContainerConfig,
}
impl ContainerConfigBuilder {
fn new<H>(hostname: H, image: Image) -> Self
where
H: Into<String>,
{
Self {
config: ContainerConfig::new(hostname.into(), image),
}
}
pub fn ports<T>(self, ports: T) -> Self
where
T: IntoIterator<Item = PortAccess>,
{
Self {
config: ContainerConfig {
ports: ports.into_iter().collect(),
..self.config
},
}
}
pub fn mounts<T, S, B, D>(self, mounts: T) -> Self
where
T: IntoIterator<Item = (S, B, D)>,
S: Into<String>,
B: Into<String>,
D: Into<String>,
{
Self {
config: ContainerConfig {
mounts: mounts
.into_iter()
.map(|(s, b, d)| (s.into(), b.into(), d.into()))
.collect(),
..self.config
},
}
}
pub fn env<T, N, V>(self, env: T) -> Self
where
T: IntoIterator<Item = (N, V)>,
N: Into<String>,
V: ToString,
{
Self {
config: ContainerConfig {
env: env
.into_iter()
.map(|(n, v)| (n.into(), v.to_string()))
.collect(),
..self.config
},
}
}
pub fn readiness<T: Probe + 'static>(self, readiness: T) -> Self {
Self {
config: ContainerConfig {
readiness: Some(Box::new(readiness)),
..self.config
},
}
}
pub fn cmd<T, C>(self, cmd: T) -> Self
where
T: IntoIterator<Item = C>,
C: Into<String>,
{
Self {
config: ContainerConfig {
cmd: cmd.into_iter().map(|c| c.into()).collect(),
..self.config
},
}
}
pub fn build(self) -> ContainerConfig {
self.config
}
}
pub trait Config {
fn hostname(&self) -> &str;
fn port(&self) -> Port;
fn schema(&self) -> &str;
fn to_container_config(&self) -> Result<ContainerConfig, TestError>;
}