use std::collections::HashMap;
use std::path::PathBuf;
use crate::docker::wait_for::WaitFor;
#[derive(Debug, Clone)]
pub struct ContainerConfig {
pub image: String,
pub name: Option<String>,
pub ports: Vec<PortMapping>,
pub env: Vec<(String, String)>,
pub volumes: Vec<VolumeMount>,
pub network: Option<String>,
pub network_aliases: Vec<String>,
pub wait: WaitFor,
pub stop_timeout: u64,
pub memory: Option<String>,
pub cpus: Option<u32>,
pub labels: HashMap<String, String>,
pub extra_hosts: Vec<String>,
pub always_pull: bool,
pub command: Option<Vec<String>>,
pub devices: Vec<DeviceMapping>,
pub cap_add: Vec<String>,
pub required: bool,
}
#[derive(Debug, Clone)]
pub struct PortMapping {
pub container_port: u16,
pub host_port: Option<u16>, pub protocol: PortProtocol,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PortProtocol {
Tcp,
Udp,
}
#[derive(Debug, Clone)]
pub struct VolumeMount {
pub source: VolumeSource,
pub target: PathBuf,
pub read_only: bool,
}
#[derive(Debug, Clone)]
pub enum VolumeSource {
Bind(PathBuf),
Named(String),
}
#[derive(Debug, Clone)]
pub struct DeviceMapping {
pub host_path: PathBuf,
pub container_path: Option<PathBuf>,
}
impl ContainerConfig {
#[must_use]
pub fn new(image: impl Into<String>) -> Self {
Self {
image: image.into(),
name: None,
ports: Vec::new(),
env: Vec::new(),
volumes: Vec::new(),
network: None,
network_aliases: Vec::new(),
wait: WaitFor::None,
stop_timeout: 10,
memory: None,
cpus: None,
labels: HashMap::new(),
extra_hosts: Vec::new(),
always_pull: false,
command: None,
devices: Vec::new(),
cap_add: Vec::new(),
required: false,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn port(mut self, container_port: u16) -> Self {
self.ports.push(PortMapping {
container_port,
host_port: None,
protocol: PortProtocol::Tcp,
});
self
}
#[must_use]
pub fn port_mapped(mut self, container_port: u16, host_port: u16) -> Self {
self.ports.push(PortMapping {
container_port,
host_port: Some(host_port),
protocol: PortProtocol::Tcp,
});
self
}
#[must_use]
pub fn port_udp(mut self, container_port: u16) -> Self {
self.ports.push(PortMapping {
container_port,
host_port: None,
protocol: PortProtocol::Udp,
});
self
}
#[must_use]
pub fn port_udp_mapped(mut self, container_port: u16, host_port: u16) -> Self {
self.ports.push(PortMapping {
container_port,
host_port: Some(host_port),
protocol: PortProtocol::Udp,
});
self
}
#[must_use]
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.push((key.into(), value.into()));
self
}
#[must_use]
pub fn network(mut self, name: impl Into<String>) -> Self {
self.network = Some(name.into());
self
}
#[must_use]
pub fn network_alias(mut self, alias: impl Into<String>) -> Self {
self.network_aliases.push(alias.into());
self
}
#[must_use]
pub fn volume(mut self, host_path: impl Into<PathBuf>, container_path: impl Into<PathBuf>) -> Self {
self.volumes.push(VolumeMount {
source: VolumeSource::Bind(host_path.into()),
target: container_path.into(),
read_only: false,
});
self
}
#[must_use]
#[must_use]
pub fn volume_read_only(
mut self,
host_path: impl Into<PathBuf>,
container_path: impl Into<PathBuf>,
) -> Self {
self.volumes.push(VolumeMount {
source: VolumeSource::Bind(host_path.into()),
target: container_path.into(),
read_only: true,
});
self
}
#[must_use]
pub fn named_volume(mut self, volume_name: impl Into<String>, container_path: impl Into<PathBuf>) -> Self {
self.volumes.push(VolumeMount {
source: VolumeSource::Named(volume_name.into()),
target: container_path.into(),
read_only: false,
});
self
}
#[must_use]
pub fn wait(mut self, strategy: WaitFor) -> Self {
self.wait = strategy;
self
}
#[must_use]
pub fn stop_timeout_secs(mut self, secs: u64) -> Self {
self.stop_timeout = secs;
self
}
#[must_use]
pub fn memory(mut self, mem: impl Into<String>) -> Self {
self.memory = Some(mem.into());
self
}
#[must_use]
pub fn cpus(mut self, count: u32) -> Self {
self.cpus = Some(count);
self
}
#[must_use]
pub fn always_pull(mut self) -> Self {
self.always_pull = true;
self
}
#[must_use]
pub fn command(mut self, cmd: Vec<String>) -> Self {
self.command = Some(cmd);
self
}
#[must_use]
pub fn device(mut self, host_path: impl Into<PathBuf>) -> Self {
self.devices.push(DeviceMapping {
host_path: host_path.into(),
container_path: None,
});
self
}
#[must_use]
pub fn cap_add(mut self, cap: impl Into<String>) -> Self {
self.cap_add.push(cap.into());
self
}
#[must_use]
pub fn required(mut self) -> Self {
self.required = true;
self
}
#[must_use]
pub fn label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.labels.insert(key.into(), value.into());
self
}
}
pub fn parse_memory_bytes(input: &str) -> Result<i64, String> {
let text = input.trim().to_ascii_lowercase();
if text.is_empty() {
return Err("memory limit is empty".to_string());
}
let digits_end = text.find(|c: char| !c.is_ascii_digit()).unwrap_or(text.len());
let (number, suffix) = text.split_at(digits_end);
let value: i64 = number
.parse()
.map_err(|_| format!("invalid memory limit {input:?}: expected a byte count like \"256m\""))?;
let multiplier: i64 = match suffix.trim_end_matches('b') {
"" => 1,
"k" => 1024,
"m" => 1024 * 1024,
"g" => 1024 * 1024 * 1024,
"t" => 1024_i64 * 1024 * 1024 * 1024,
_ => {
return Err(format!(
"invalid memory limit {input:?}: unknown unit {suffix:?} (use b, k, m, g, or t)"
))
}
};
value
.checked_mul(multiplier)
.ok_or_else(|| format!("memory limit {input:?} overflows i64 bytes"))
}