use crate::config::{
monocore::{Monocore, Service},
PortPair,
};
use super::{EnvPair, PathPair, VolumeMount};
pub struct ServiceBuilder<Name> {
name: Name,
base: Option<String>,
group: Option<String>,
volumes: Vec<PathPair>,
envs: Vec<EnvPair>,
group_volumes: Vec<VolumeMount>,
group_envs: Vec<String>,
depends_on: Vec<String>,
port: Option<PortPair>,
workdir: Option<String>,
command: Option<String>,
args: Vec<String>,
cpus: u8,
ram: u32,
}
impl<Name> ServiceBuilder<Name> {
pub fn base(mut self, base: impl Into<String>) -> Self {
self.base = Some(base.into());
self
}
pub fn group(mut self, group: impl Into<String>) -> Self {
self.group = Some(group.into());
self
}
pub fn volumes(mut self, volumes: impl IntoIterator<Item = PathPair>) -> Self {
self.volumes = volumes.into_iter().collect();
self
}
pub fn envs(mut self, envs: impl IntoIterator<Item = EnvPair>) -> Self {
self.envs = envs.into_iter().collect();
self
}
pub fn group_volumes(mut self, volumes: impl IntoIterator<Item = VolumeMount>) -> Self {
self.group_volumes = volumes.into_iter().collect();
self
}
pub fn group_envs(mut self, envs: impl IntoIterator<Item = String>) -> Self {
self.group_envs = envs.into_iter().collect();
self
}
pub fn depends_on(mut self, depends_on: impl IntoIterator<Item = String>) -> Self {
self.depends_on = depends_on.into_iter().collect();
self
}
pub fn port(mut self, port: PortPair) -> Self {
self.port = Some(port);
self
}
pub fn workdir(mut self, workdir: impl Into<String>) -> Self {
self.workdir = Some(workdir.into());
self
}
pub fn command(mut self, command: impl Into<String>) -> Self {
self.command = Some(command.into());
self
}
pub fn args<'a>(mut self, args: impl IntoIterator<Item = &'a str>) -> Self {
self.args = args.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn cpus(mut self, cpus: u8) -> Self {
self.cpus = cpus;
self
}
pub fn ram(mut self, ram: u32) -> Self {
self.ram = ram;
self
}
pub fn name(self, name: impl Into<String>) -> ServiceBuilder<String> {
ServiceBuilder {
name: name.into(),
base: self.base,
group: self.group,
volumes: self.volumes,
envs: self.envs,
group_volumes: self.group_volumes,
group_envs: self.group_envs,
depends_on: self.depends_on,
port: self.port,
workdir: self.workdir,
command: self.command,
args: self.args,
cpus: self.cpus,
ram: self.ram,
}
}
}
impl ServiceBuilder<String> {
pub fn build(self) -> Service {
Service {
name: self.name,
base: self.base,
group: self.group,
volumes: self.volumes,
envs: self.envs,
group_volumes: self.group_volumes,
group_envs: self.group_envs,
depends_on: self.depends_on,
port: self.port,
workdir: self.workdir,
command: self.command,
args: self.args,
cpus: self.cpus,
ram: self.ram,
}
}
}
impl Default for ServiceBuilder<()> {
fn default() -> Self {
Self {
name: (),
base: None,
group: None,
volumes: vec![],
envs: vec![],
group_volumes: vec![],
group_envs: vec![],
depends_on: vec![],
port: None,
workdir: None,
command: None,
args: vec![],
cpus: Monocore::default_num_vcpus(),
ram: Monocore::default_ram_mib(),
}
}
}
#[cfg(test)]
mod tests {
use crate::config::PathPair;
use super::*;
#[test]
fn test_service_builder_default() -> anyhow::Result<()> {
let service = ServiceBuilder::default()
.name("test-service")
.base("ubuntu:24.04")
.group("app")
.volumes(vec!["/app;".parse()?])
.envs(vec!["ENV=main".parse()?])
.group_volumes(vec![VolumeMount::builder()
.name("main".to_string())
.mount(PathPair::Same("/app".parse()?))
.build()])
.group_envs(vec!["main".to_string()])
.depends_on(vec!["db".to_string()])
.port("8080:80".parse()?)
.workdir("/app")
.command("./app")
.args(vec!["--port", "80"])
.cpus(2)
.ram(1024)
.build();
assert_eq!(service.name, "test-service");
assert_eq!(service.base, Some("ubuntu:24.04".to_string()));
assert_eq!(service.group, Some("app".to_string()));
assert_eq!(service.volumes.len(), 1);
assert_eq!(service.envs, vec!["ENV=main".parse()?]);
assert_eq!(service.group_volumes.len(), 1);
assert_eq!(service.group_envs, vec!["main".to_string()]);
assert_eq!(service.depends_on, vec!["db".to_string()]);
assert_eq!(service.port, Some("8080:80".parse()?));
assert_eq!(service.workdir, Some("/app".to_string()));
assert_eq!(service.command, Some("./app".to_string()));
assert_eq!(service.args, vec!["--port", "80"]);
assert_eq!(service.cpus, 2);
assert_eq!(service.ram, 1024);
Ok(())
}
#[test]
fn test_service_builder_default_minimal() {
let service = ServiceBuilder::default().name("minimal-service").build();
assert_eq!(service.name, "minimal-service");
assert_eq!(service.base, None);
assert_eq!(service.group, None);
assert!(service.volumes.is_empty());
assert!(service.envs.is_empty());
assert!(service.group_volumes.is_empty());
assert!(service.group_envs.is_empty());
assert!(service.depends_on.is_empty());
assert_eq!(service.port, None);
assert_eq!(service.workdir, None);
assert_eq!(service.command, None);
assert!(service.args.is_empty());
assert_eq!(service.cpus, Monocore::default_num_vcpus());
assert_eq!(service.ram, Monocore::default_ram_mib());
}
}