use std::path::PathBuf;
use bollard::Docker;
use bollard::models::Mount;
use bollard::models::ServiceSpec;
use bollard::models::ServiceSpecMode;
use bollard::models::ServiceSpecModeReplicated;
use bollard::models::TaskSpec;
use bollard::models::TaskSpecContainerSpec;
use bollard::models::TaskSpecResources;
use bollard::models::TaskSpecRestartPolicy;
use bollard::models::TaskSpecRestartPolicyConditionEnum;
use indexmap::IndexMap;
use tracing::warn;
use super::Service;
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>,
stderr: Option<PathBuf>,
env: IndexMap<String, String>,
work_dir: Option<String>,
mounts: Vec<Mount>,
resources: Option<TaskSpecResources>,
}
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,
env: Default::default(),
work_dir: Default::default(),
mounts: Default::default(),
resources: 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 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 stdout(mut self, path: impl Into<PathBuf>) -> Self {
self.stdout = Some(path.into());
self
}
pub fn stderr(mut self, path: impl Into<PathBuf>) -> Self {
self.stderr = Some(path.into());
self
}
pub fn work_dir(mut self, work_dir: impl Into<String>) -> Self {
self.work_dir = Some(work_dir.into());
self
}
pub fn mount(mut self, mount: impl Into<Mount>) -> Self {
self.mounts.push(mount.into());
self
}
pub fn mounts(mut self, mounts: impl IntoIterator<Item = impl Into<Mount>>) -> Self {
self.mounts.extend(mounts.into_iter().map(Into::into));
self
}
pub fn resources(mut self, resources: TaskSpecResources) -> Self {
self.resources = Some(resources);
self
}
pub async fn try_build(self) -> Result<Service> {
let image = self
.image
.ok_or_else(|| Error::MissingBuilderField("image"))?;
let program = self
.program
.ok_or_else(|| Error::MissingBuilderField("program"))?;
let response = self
.client
.create_service(
ServiceSpec {
name: self.name,
mode: Some(ServiceSpecMode {
replicated: Some(ServiceSpecModeReplicated { replicas: Some(1) }),
..Default::default()
}),
task_template: Some(TaskSpec {
container_spec: Some(TaskSpecContainerSpec {
image: Some(image),
command: Some(vec![program]),
args: Some(self.args),
dir: self.work_dir,
env: Some(self.env.iter().map(|(k, v)| format!("{k}={v}")).collect()),
mounts: Some(self.mounts),
#[cfg(unix)]
groups: Some(vec![nix::unistd::Gid::effective().to_string()]),
..Default::default()
}),
resources: self.resources,
restart_policy: Some(TaskSpecRestartPolicy {
condition: Some(TaskSpecRestartPolicyConditionEnum::NONE),
..Default::default()
}),
..Default::default()
}),
..Default::default()
},
None,
)
.await
.map_err(Error::Docker)?;
for warning in response.warnings.unwrap_or_default() {
warn!("Docker daemon: {warning}");
}
Ok(Service {
client: self.client,
id: response.id.ok_or_else(|| {
Error::Message("Docker daemon response did not contain a service identifier".into())
})?,
stdout: self.stdout,
stderr: self.stderr,
})
}
}