use std::collections::HashMap;
use anyhow::Result;
use testcontainers::bollard::Docker;
use testcontainers::bollard::query_parameters::ListNetworksOptions;
use testcontainers::core::ContainerRequest;
use testcontainers::core::logs::consumer::{LogConsumer, logging_consumer::LoggingConsumer};
use testcontainers::{Image, ImageExt};
pub trait ContainerLogConsumer: LogConsumer + Send + Sync + 'static {}
impl<L> ContainerLogConsumer for L where L: LogConsumer + Send + Sync + 'static {}
#[allow(clippy::missing_docs_in_private_items)]
#[derive(Debug, Clone)]
pub struct ImageExtConfig<L>
where
L: ContainerLogConsumer,
{
container_name: Option<String>,
network: Option<String>,
log_consumer: Option<L>,
}
impl<L> ImageExtConfig<L>
where
L: ContainerLogConsumer,
{
#[must_use]
pub fn with_container_name(mut self, container_name: String) -> Self {
self.container_name = Some(container_name);
self
}
#[must_use]
pub fn with_network(mut self, name: String) -> Self {
self.network = Some(name);
self
}
#[must_use]
pub fn with_log_consumer(mut self, log_consumer: L) -> Self {
self.log_consumer = Some(log_consumer);
self
}
pub fn configure_container<RI, I>(self, image: RI) -> ContainerRequest<I>
where
RI: Into<ContainerRequest<I>>,
I: Image,
{
let mut container_request = image.into();
if let Some(container_name) = self.container_name {
container_request = container_request.with_container_name(container_name);
}
if let Some(network) = self.network {
container_request = container_request.with_network(network);
}
if let Some(log_consumer) = self.log_consumer {
container_request = container_request.with_log_consumer(log_consumer);
}
container_request
}
}
impl Default for ImageExtConfig<LoggingConsumer> {
fn default() -> Self {
Self {
container_name: None,
network: None,
log_consumer: None,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct ImageConfig {
pub should_expose_ports: bool,
}
impl Default for ImageConfig {
fn default() -> Self {
Self {
should_expose_ports: true,
}
}
}
impl ImageConfig {
#[must_use]
pub fn with_expose_ports(mut self, should_expose_ports: bool) -> Self {
self.should_expose_ports = should_expose_ports;
self
}
}
#[derive(Debug)]
pub struct ContainerConfig<L>
where
L: ContainerLogConsumer,
{
pub image_config: ImageConfig,
pub image_ext_config: ImageExtConfig<L>,
}
impl<L> ContainerConfig<L>
where
L: ContainerLogConsumer,
{
pub fn new(image_config: ImageConfig, image_ext_config: ImageExtConfig<L>) -> Self {
Self {
image_config,
image_ext_config,
}
}
pub fn configure_container<RI, I>(self, image: RI) -> ContainerRequest<I>
where
RI: Into<ContainerRequest<I>>,
I: Image,
{
self.image_ext_config.configure_container(image)
}
}
impl Default for ContainerConfig<LoggingConsumer> {
fn default() -> Self {
Self {
image_config: ImageConfig::default(),
image_ext_config: ImageExtConfig::default(),
}
}
}
pub async fn get_bridge_gateway_ip() -> Result<String> {
let docker = Docker::connect_with_socket_defaults()?;
let mut filters = HashMap::new();
let network_name = "bridge".to_string();
filters.insert("name".to_string(), vec![network_name.clone()]);
let list_options = ListNetworksOptions {
filters: Some(filters),
};
let networks = docker.list_networks(Some(list_options)).await?;
let bridge = networks
.first()
.ok_or_else(|| anyhow::anyhow!("can't find network '{network_name}'"))?;
let gateway_ip = bridge
.ipam
.as_ref()
.and_then(|ipam| ipam.config.as_ref())
.and_then(|configs| configs.first())
.and_then(|config| config.gateway.as_ref())
.ok_or_else(|| {
anyhow::anyhow!(
"can't get the IPAM gateway IP for network. \
full network object:\n{bridge:#?}"
)
})?;
Ok(gateway_ip.clone())
}