diff --git a/codecov.yml b/codecov.yml index 56496ee7..47749b6e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,6 +3,9 @@ comment: layout: "header, files, footer" hide_project_coverage: false +ignore: + - crates/jstzd/src/main.rs + # Configures coverage checks -- both patch and project-wide checks # can drop by 5% before failing the CI build. # diff --git a/crates/jstzd/src/container.rs b/crates/jstzd/src/container.rs index ad154580..92a34da3 100644 --- a/crates/jstzd/src/container.rs +++ b/crates/jstzd/src/container.rs @@ -11,7 +11,7 @@ struct ContainerInner { pub id: ContainerId, pub name: String, client: Option>, - network: Option, + // TODO: network: Option, _private: (), } @@ -19,20 +19,21 @@ pub struct Container(AsyncDropper); impl ContainerInner { /// Creates a new container with running `id` - pub fn new(client: Arc, id: ContainerId, name: &str) -> Self { + pub fn new( + client: Arc, + id: ContainerId, + name: &str, + _network: Option, + ) -> Self { Self { id, name: name.to_string(), client: Some(client), - network: None, + // network, _private: (), } } - pub fn set_network(&mut self, network: Network) { - self.network = Some(network); - } - /// Starts the container's entrypoint pub async fn start(&self) -> anyhow::Result<()> { let client = self.client()?; @@ -77,8 +78,15 @@ impl AsyncDrop for ContainerInner { } impl Container { - pub fn new(client: Arc, id: ContainerId, name: &str) -> Self { - Self(AsyncDropper::new(ContainerInner::new(client, id, name))) + pub fn new( + client: Arc, + id: ContainerId, + name: &str, + network: Option, + ) -> Self { + Self(AsyncDropper::new(ContainerInner::new( + client, id, name, network, + ))) } pub fn id(&self) -> &ContainerId { @@ -89,10 +97,6 @@ impl Container { &self.0.inner().name } - pub fn set_network(&mut self, network: Network) { - self.0.inner_mut().set_network(network); - } - pub async fn start(&self) -> anyhow::Result<()> { self.0.start().await } diff --git a/crates/jstzd/src/image.rs b/crates/jstzd/src/image.rs index 3fc0d236..b50ed1c8 100644 --- a/crates/jstzd/src/image.rs +++ b/crates/jstzd/src/image.rs @@ -30,14 +30,14 @@ pub trait Image { } } -pub struct DefaultImage<'a> { +pub struct DockerImage<'a> { image_name: &'a str, image_tag: Option<&'a str>, entrypoint: Option<&'a str>, exposed_ports: Option<&'a [u16]>, } -impl Image for DefaultImage<'_> { +impl Image for DockerImage<'_> { fn image_name(&self) -> &str { self.image_name } @@ -55,25 +55,23 @@ impl Image for DefaultImage<'_> { } } -impl<'a> DefaultImage<'a> { - pub fn create(image_name: &'a str) -> ImageBuilder<'a> { - ImageBuilder { +pub struct DockerImageBuilder<'a> { + image_name: &'a str, + image_tag: Option<&'a str>, + entrypoint: Option<&'a str>, + exposed_ports: Option<&'a [u16]>, +} + +impl<'a> DockerImageBuilder<'a> { + pub fn new(image_name: &'a str) -> DockerImageBuilder<'a> { + DockerImageBuilder { image_name, image_tag: None, entrypoint: None, exposed_ports: None, } } -} - -pub struct ImageBuilder<'a> { - image_name: &'a str, - image_tag: Option<&'a str>, - entrypoint: Option<&'a str>, - exposed_ports: Option<&'a [u16]>, -} -impl<'a> ImageBuilder<'a> { pub fn with_tag(mut self, image_tag: &'a str) -> Self { self.image_tag = Some(image_tag); self @@ -89,8 +87,8 @@ impl<'a> ImageBuilder<'a> { self } - pub fn build(self) -> DefaultImage<'a> { - DefaultImage { + pub fn build(self) -> DockerImage<'a> { + DockerImage { image_name: self.image_name, image_tag: self.image_tag, entrypoint: self.entrypoint, @@ -142,3 +140,21 @@ where } Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn builds_docker_image() { + let builder = DockerImageBuilder::new("busybox"); + let image = builder + .with_entrypoint("sh") + .with_tag("stable") + .with_exposed_ports(&[8080]) + .build(); + assert_eq!(image.image_name(), "busybox"); + assert_eq!(image.image_tag(), "stable"); + assert_eq!(image.entrypoint(), Some("sh")); + assert_eq!(image.exposed_ports(), &[8080]); + } +} diff --git a/crates/jstzd/src/main.rs b/crates/jstzd/src/main.rs index 67cd6d82..67added7 100644 --- a/crates/jstzd/src/main.rs +++ b/crates/jstzd/src/main.rs @@ -1,6 +1,6 @@ use bollard::Docker; use jstzd::{ - container::Container, image::DefaultImage, + container::Container, image::DockerImageBuilder, runnable_image::builder::RunnableImageBuilder, }; use std::sync::Arc; @@ -8,7 +8,9 @@ use std::sync::Arc; pub async fn example() -> anyhow::Result<()> { let docker = Docker::connect_with_socket_defaults().unwrap(); let docker = Arc::new(docker); - let image = DefaultImage::create("busybox").with_tag("stable").build(); + let image = DockerImageBuilder::new("busybox") + .with_tag("stable") + .build(); let cmd = vec![ "sh", "-c", @@ -21,8 +23,12 @@ pub async fn example() -> anyhow::Result<()> { .overridden_cmd(cmd) .build(); let container_id = runnable_image.create_container(docker.clone()).await?; - let container = - Container::new(docker.clone(), container_id, &runnable_image.container_name); + let container = Container::new( + docker.clone(), + container_id, + &runnable_image.container_name, + None, + ); println!("Starting container with id: {}", container.id()); container.start().await?; diff --git a/crates/jstzd/src/runnable_image/builder.rs b/crates/jstzd/src/runnable_image/builder.rs index 141c3f3a..44419d59 100644 --- a/crates/jstzd/src/runnable_image/builder.rs +++ b/crates/jstzd/src/runnable_image/builder.rs @@ -74,3 +74,60 @@ impl RunnableImageBuilder { } } } + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + image::DockerImageBuilder, + runnable_image::{AccessMode, MountType}, + }; + use std::net::{IpAddr, Ipv4Addr}; + #[test] + fn builds_runnable_image() { + let image = DockerImageBuilder::new("busybox").build(); + let cmd = vec!["echo", "hello"] + .into_iter() + .map(String::from) + .collect(); + let mount = Mount { + access_mode: AccessMode::ReadWrite, + mount_type: MountType::Bind, + source: Some("/source".to_string()), + target: Some("/target".to_string()), + }; + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let runnable_image = RunnableImageBuilder::new(image, "busybox") + .overridden_cmd(cmd) + .network("some_network") + .env_var("KEY", "VALUE") + .add_host("hostname", Host::Addr(ip)) + .add_mount(mount.clone()) + .add_port(5000, 3000) + .build(); + + assert_eq!(runnable_image.container_name, "busybox"); + assert_eq!( + runnable_image.overridden_cmd, + Some(vec!["echo".to_string(), "hello".to_string()]) + ); + assert_eq!(runnable_image.network, Some("some_network".to_string())); + assert_eq!( + runnable_image.env_vars, + vec![("KEY".to_string(), "VALUE".to_string())] + .into_iter() + .collect() + ); + assert_eq!( + runnable_image.hosts, + vec![("hostname".to_string(), Host::Addr(ip))] + .into_iter() + .collect() + ); + assert_eq!(runnable_image.mounts, vec![mount]); + assert_eq!( + runnable_image.ports, + vec![(5000, 3000)].into_iter().collect() + ); + } +} diff --git a/crates/jstzd/src/runnable_image/mod.rs b/crates/jstzd/src/runnable_image/mod.rs index c4e48f74..35c483a4 100644 --- a/crates/jstzd/src/runnable_image/mod.rs +++ b/crates/jstzd/src/runnable_image/mod.rs @@ -14,15 +14,20 @@ use crate::{container::ContainerId, image::Image}; pub mod builder; +#[derive(Debug, PartialEq, Clone)] pub enum AccessMode { ReadOnly, ReadWrite, } + +#[derive(Debug, PartialEq, Clone)] pub enum MountType { Bind, Volume, Tmpfs, } + +#[derive(Debug, PartialEq, Clone)] pub struct Mount { pub access_mode: AccessMode, pub mount_type: MountType, @@ -30,6 +35,7 @@ pub struct Mount { pub target: Option, } +#[derive(Debug, PartialEq)] pub enum Host { Addr(IpAddr), HostGateway, @@ -113,13 +119,13 @@ fn host_config(runnable_image: &RunnableImage) -> Option( } Ok(()) } + +#[cfg(test)] +mod test { + use builder::RunnableImageBuilder; + + use super::*; + use crate::image::DockerImageBuilder; + #[test] + fn test_env() { + let image = DockerImageBuilder::new("busybox").build(); + let runnable_image = RunnableImageBuilder::new(image, "busybox_test") + .env_var("KEY", "VALUE") + .build(); + let env: Option> = env(&runnable_image); + assert_eq!(env, Some(vec!["KEY=VALUE".to_string()])); + } + + #[test] + fn parses_entrypoint() { + let image = DockerImageBuilder::new("busybox") + .with_entrypoint("echo hello") + .build(); + let runnable_image = RunnableImageBuilder::new(image, "busybox_test").build(); + let entrypoint: Option> = entrypoint(&runnable_image); + assert_eq!( + entrypoint, + Some(vec!["echo".to_string(), "hello".to_string()]) + ); + } + + #[test] + fn parses_host_config() { + let image = DockerImageBuilder::new("busybox").build(); + let runnable_image = RunnableImageBuilder::new(image, "busybox_test") + .add_host("hostname", Host::HostGateway) + .add_port(4000, 3000) + .add_port(5000, 3000) + .build(); + let host_config = host_config(&runnable_image).unwrap(); + assert_eq!( + host_config.extra_hosts, + Some(vec!["hostname:host-gateway".to_string()]) + ); + + let port_bindings = host_config.port_bindings.expect("port bindings not found"); + assert_eq!( + port_bindings.get("3000").unwrap(), + &Some(vec![ + PortBinding { + host_ip: None, + host_port: Some("4000".to_string()) + }, + PortBinding { + host_ip: None, + host_port: Some("5000".to_string()) + } + ]) + ); + } + + #[test] + fn throws_invalid_ports() { + let image = DockerImageBuilder::new("busybox") + .with_exposed_ports(&[3000]) + .build(); + let runnable_image = RunnableImageBuilder::new(image, "busybox_test") + .add_port(3001, 3001) + .build(); + let result = all_container_ports_are_exposed(&runnable_image); + assert!(result.is_err()); + } + + #[test] + fn validates_ports() { + let image = DockerImageBuilder::new("busybox") + .with_exposed_ports(&[3000, 4000]) + .build(); + let runnable_image = RunnableImageBuilder::new(image, "busybox_test") + .add_port(3000, 3000) + .add_port(3000, 4000) + .build(); + let result = all_container_ports_are_exposed(&runnable_image); + assert!(result.is_ok()); + } +}