use std::path::PathBuf;
use bollard::Docker;
use bollard::models::ContainerCreateBody;
use bollard::models::HostConfig;
use bollard::query_parameters::CreateContainerOptions;
use indexmap::IndexMap;
use tracing::warn;
use crate::Container;
use crate::Error;
use crate::Result;
pub struct Builder {
client: Docker,
name: Option<String>,
image: Option<String>,
program: Option<String>,
args: Vec<String>,
stdout: Option<PathBuf>,
attach_stdout: bool,
stderr: Option<PathBuf>,
attach_stderr: bool,
env: IndexMap<String, String>,
work_dir: Option<String>,
host_config: Option<HostConfig>,
}
impl Builder {
pub fn new(client: Docker) -> Self {
Self {
client,
name: None,
image: Default::default(),
program: Default::default(),
args: Default::default(),
stdout: None,
stderr: None,
attach_stdout: false,
attach_stderr: false,
env: Default::default(),
work_dir: Default::default(),
host_config: Default::default(),
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn image(mut self, image: impl Into<String>) -> Self {
self.image = Some(image.into());
self
}
pub fn program(mut self, program: impl Into<String>) -> Self {
self.program = Some(program.into());
self
}
pub fn arg(mut self, arg: impl Into<String>) -> Self {
self.args.push(arg.into());
self
}
pub fn args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.args.extend(args.into_iter().map(Into::into));
self
}
pub fn stdout(mut self, path: impl Into<PathBuf>) -> Self {
self.stdout = Some(path.into());
self
}
pub fn attach_stdout(mut self, attach: bool) -> Self {
self.attach_stdout = attach;
self
}
pub fn stderr(mut self, path: impl Into<PathBuf>) -> Self {
self.stderr = Some(path.into());
self
}
pub fn attach_stderr(mut self, attach: bool) -> Self {
self.attach_stderr = attach;
self
}
pub fn env(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.env.insert(name.into(), value.into());
self
}
pub fn envs(
mut self,
variables: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
self.env
.extend(variables.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
pub fn work_dir(mut self, work_dir: impl Into<String>) -> Self {
self.work_dir = Some(work_dir.into());
self
}
pub fn host_config(mut self, host_config: HostConfig) -> Self {
self.host_config = Some(host_config);
self
}
pub async fn try_build(self) -> Result<Container> {
let image = self
.image
.ok_or_else(|| Error::MissingBuilderField("image"))?;
let program = self
.program
.ok_or_else(|| Error::MissingBuilderField("program"))?;
let mut cmd = Vec::with_capacity(1 + self.args.len());
cmd.push(program);
cmd.extend(self.args);
let response = self
.client
.create_container(
Some(CreateContainerOptions {
name: self.name,
..Default::default()
}),
ContainerCreateBody {
cmd: Some(cmd),
image: Some(image),
entrypoint: Some(vec![String::new()]),
attach_stdout: Some(self.stdout.is_some() || self.attach_stdout),
attach_stderr: Some(self.stderr.is_some() || self.attach_stderr),
working_dir: self.work_dir,
host_config: self.host_config,
env: Some(self.env.iter().map(|(k, v)| format!("{k}={v}")).collect()),
..Default::default()
},
)
.await
.map_err(Error::Docker)?;
for warning in &response.warnings {
warn!("{warning}");
}
Ok(Container::new(
self.client,
response.id,
self.stdout,
self.stderr,
))
}
}