Skip to content

Commit

Permalink
test: docker
Browse files Browse the repository at this point in the history
  • Loading branch information
ryutamago committed Sep 9, 2024
1 parent 3aba3ab commit 7eda38d
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 40 deletions.
3 changes: 3 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand Down
30 changes: 17 additions & 13 deletions crates/jstzd/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,29 @@ struct ContainerInner {
pub id: ContainerId,
pub name: String,
client: Option<Arc<Docker>>,
network: Option<Network>,
// TODO: network: Option<Network>,
_private: (),
}

pub struct Container(AsyncDropper<ContainerInner>);

impl ContainerInner {
/// Creates a new container with running `id`
pub fn new(client: Arc<Docker>, id: ContainerId, name: &str) -> Self {
pub fn new(
client: Arc<Docker>,
id: ContainerId,
name: &str,
_network: Option<Network>,
) -> 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()?;
Expand Down Expand Up @@ -77,8 +78,15 @@ impl AsyncDrop for ContainerInner {
}

impl Container {
pub fn new(client: Arc<Docker>, id: ContainerId, name: &str) -> Self {
Self(AsyncDropper::new(ContainerInner::new(client, id, name)))
pub fn new(
client: Arc<Docker>,
id: ContainerId,
name: &str,
network: Option<Network>,
) -> Self {
Self(AsyncDropper::new(ContainerInner::new(
client, id, name, network,
)))
}

pub fn id(&self) -> &ContainerId {
Expand All @@ -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
}
Expand Down
48 changes: 32 additions & 16 deletions crates/jstzd/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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]);
}
}
14 changes: 10 additions & 4 deletions crates/jstzd/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use bollard::Docker;
use jstzd::{
container::Container, image::DefaultImage,
container::Container, image::DockerImageBuilder,
runnable_image::builder::RunnableImageBuilder,
};
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",
Expand All @@ -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?;

Expand Down
57 changes: 57 additions & 0 deletions crates/jstzd/src/runnable_image/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,60 @@ impl<I: Image> RunnableImageBuilder<I> {
}
}
}

#[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()
);
}
}
105 changes: 98 additions & 7 deletions crates/jstzd/src/runnable_image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,28 @@ 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,
pub source: Option<String>,
pub target: Option<String>,
}

#[derive(Debug, PartialEq)]
pub enum Host {
Addr(IpAddr),
HostGateway,
Expand Down Expand Up @@ -113,13 +119,13 @@ fn host_config<I: Image>(runnable_image: &RunnableImage<I>) -> Option<HostConfig
host_ip: None,
host_port: Some(host_port.to_string()),
};
acc.entry(container_port).and_modify(|bindings| {
if let Some(vec) = bindings {
vec.push(binding);
} else {
*bindings = Some(vec![binding]);
}
});
acc.entry(container_port)
.and_modify(|bindings| {
if let Some(vec) = bindings {
vec.push(binding.clone());
}
})
.or_insert(Some(vec![binding]));
acc
},
);
Expand Down Expand Up @@ -156,3 +162,88 @@ fn all_container_ports_are_exposed<I: Image>(
}
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<Vec<String>> = 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<Vec<String>> = 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());
}
}

0 comments on commit 7eda38d

Please sign in to comment.