use crate::{
core::{env::Command, logs::LogStream, ports::Ports, ExecCommand, WaitFor},
Image, RunnableImage,
};
use bollard_stubs::models::ContainerInspectResponse;
use std::{fmt, marker::PhantomData, net::IpAddr, str::FromStr};
pub struct Container<'d, I: Image> {
id: String,
docker_client: Box<dyn Docker>,
image: RunnableImage<I>,
command: Command,
ports: Ports,
client_lifetime: PhantomData<&'d ()>,
}
impl<'d, I> fmt::Debug for Container<'d, I>
where
I: fmt::Debug + Image,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Container")
.field("id", &self.id)
.field("image", &self.image)
.field("command", &self.command)
.finish()
}
}
impl<'d, I> Container<'d, I>
where
I: Image,
{
pub(crate) fn new(
id: String,
docker_client: impl Docker + 'static,
image: RunnableImage<I>,
command: Command,
) -> Self {
let ports = docker_client.ports(&id);
Self {
id,
docker_client: Box::new(docker_client),
image,
command,
ports,
client_lifetime: PhantomData,
}
}
pub fn image(&self) -> &I {
self.image.inner()
}
pub fn image_args(&self) -> &I::Args {
self.image.args()
}
pub fn ports(&self) -> Ports {
self.ports.clone()
}
}
impl<'d, I> Container<'d, I>
where
I: Image,
{
pub fn id(&self) -> &str {
&self.id
}
pub fn get_host_port(&self, internal_port: u16) -> u16 {
self.ports
.map_to_host_port(internal_port)
.unwrap_or_else(|| {
panic!(
"container {} does not expose port {}",
self.id, internal_port
)
})
}
pub fn get_bridge_ip_address(&self) -> IpAddr {
IpAddr::from_str(
&self
.docker_client
.inspect(&self.id)
.network_settings
.unwrap_or_default()
.ip_address
.unwrap_or_default(),
)
.unwrap_or_else(|_| panic!("container {} has missing or invalid bridge IP", self.id))
}
pub fn exec(&self, cmd: ExecCommand) {
let ExecCommand {
cmd,
ready_conditions,
} = cmd;
log::debug!("Executing command {:?}", cmd);
self.docker_client.exec(self.id(), cmd);
self.docker_client
.block_until_ready(self.id(), ready_conditions);
}
pub fn stop(&self) {
log::debug!("Stopping docker container {}", self.id);
self.docker_client.stop(&self.id)
}
pub fn start(&self) {
self.docker_client.start(&self.id);
}
pub fn rm(&self) {
log::debug!("Deleting docker container {}", self.id);
self.docker_client.rm(&self.id)
}
}
impl<'d, I> Drop for Container<'d, I>
where
I: Image,
{
fn drop(&mut self) {
match self.command {
Command::Keep => {}
Command::Remove => self.rm(),
}
}
}
pub(crate) trait Docker {
fn stdout_logs(&self, id: &str) -> LogStream;
fn stderr_logs(&self, id: &str) -> LogStream;
fn ports(&self, id: &str) -> Ports;
fn inspect(&self, id: &str) -> ContainerInspectResponse;
fn rm(&self, id: &str);
fn stop(&self, id: &str);
fn start(&self, id: &str);
fn exec(&self, id: &str, cmd: String);
fn block_until_ready(&self, id: &str, ready_conditions: Vec<WaitFor>);
}