diff --git a/src/container.rs b/src/container.rs new file mode 100644 index 00000000..9c16cfe2 --- /dev/null +++ b/src/container.rs @@ -0,0 +1,194 @@ +use std::fs; +use std::path::Path; +use std::process::{self, Child, Output}; +use std::process::{Command, Stdio}; +use std::sync::atomic; +use std::sync::atomic::AtomicUsize; + +use tempfile::NamedTempFile; + +use crate::{Image, Result}; + +pub struct Container { + id: String, + name: String, +} + +impl Container { + /// Starts the container in a "parked" state + pub fn run(image: Image) -> Result { + static COUNT: AtomicUsize = AtomicUsize::new(0); + + let image_tag = format!("dnssec-tests-{image}"); + + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let dockerfile_path = manifest_dir + .join("docker") + .join(format!("{image}.Dockerfile")); + let docker_dir_path = manifest_dir.join("docker"); + dbg!(&image_tag); + + let mut command = Command::new("docker"); + command + .args(&["build", "-t"]) + .arg(&image_tag) + .arg("-f") + .arg(dockerfile_path) + .arg(docker_dir_path); + let status = command.status()?; + + if !status.success() { + return Err(format!("`{command:?}` failed").into()); + } + + let mut command = Command::new("docker"); + let pid = process::id(); + let container_name = format!( + "{image}-{pid}-{}", + COUNT.fetch_add(1, atomic::Ordering::Relaxed) + ); + command.args(&["run", "--rm", "--detach", "--name", &container_name]); + let output = command + .arg("-it") + .arg(image_tag) + .args(["sleep", "infinity"]) + .output()?; + + if !output.status.success() { + return Err(format!("`{command:?}` failed").into()); + } + + let id = core::str::from_utf8(&output.stdout)?.trim().to_string(); + dbg!(&id); + let container = Self { + id, + name: container_name, + }; + dbg!(container.ip_addr()?); + + Ok(container) + } + + pub fn cp(&self, path_in_container: &str, file_contents: &str, chmod: &str) -> Result<()> { + let mut temp_file = NamedTempFile::new()?; + fs::write(&mut temp_file, file_contents)?; + + let src_path = temp_file.path().display().to_string(); + let dest_path = format!("{}:{path_in_container}", self.id); + + let mut command = Command::new("docker"); + command.args(["cp", &src_path, &dest_path]); + + let status = command.status()?; + if !status.success() { + return Err(format!("`{command:?}` failed").into()); + } + + let command = &["chmod", chmod, path_in_container]; + let output = self.exec(command)?; + if !output.status.success() { + return Err(format!("`{command:?}` failed").into()); + } + + Ok(()) + } + + pub fn exec(&self, cmd: &[&str]) -> Result { + let mut command = Command::new("docker"); + command.args(&["exec", "-t", &self.id]).args(cmd); + + let output = command.output()?; + + Ok(output) + } + + pub fn spawn(&self, cmd: &[&str]) -> Result { + let mut command = Command::new("docker"); + command.args(&["exec", "-t", &self.id]).args(cmd); + + let child = command.spawn()?; + + Ok(child) + } + + pub fn ip_addr(&self) -> Result { + let mut command = Command::new("docker"); + command + .args(&[ + "inspect", + "-f", + "{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}", + ]) + .arg(&self.id); + + let output = command.output()?; + if !output.status.success() { + return Err(format!("`{command:?}` failed").into()); + } + + let ip_addr = core::str::from_utf8(&output.stdout)?.trim().to_string(); + dbg!(&ip_addr); + + Ok(ip_addr) + } +} + +// ensure the container gets deleted +impl Drop for Container { + fn drop(&mut self) { + // running this to completion would block the current thread for several seconds so just + // fire and forget + let _ = Command::new("docker") + .args(["rm", "-f", &self.id]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + } +} + +#[cfg(test)] +mod tests { + use std::net::Ipv4Addr; + + use crate::CHMOD_RW_EVERYONE; + + use super::*; + + #[test] + fn run_works() -> Result<()> { + let container = Container::run(Image::Client)?; + + let output = container.exec(&["true"])?; + assert!(output.status.success()); + + Ok(()) + } + + #[test] + fn ip_addr_works() -> Result<()> { + let container = Container::run(Image::Client)?; + + let ip_addr = container.ip_addr()?; + assert!(ip_addr.parse::().is_ok()); + + Ok(()) + } + + #[test] + fn cp_works() -> Result<()> { + let container = Container::run(Image::Client)?; + + let path = "/tmp/somefile"; + let contents = "hello"; + container.cp(path, contents, CHMOD_RW_EVERYONE)?; + + let output = container.exec(&["cat", path])?; + dbg!(&output); + + assert!(output.status.success()); + + assert_eq!(contents, core::str::from_utf8(&output.stdout)?); + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 80bc2a5a..40113604 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,8 @@ use core::fmt; -use std::process::{self, Child, Output}; -use std::sync::atomic; -use std::{ - fs, - path::Path, - process::{Command, Stdio}, - sync::atomic::AtomicUsize, -}; +use std::process::Child; +use container::Container; use minijinja::{context, Environment}; -use tempfile::NamedTempFile; pub type Error = Box; pub type Result = core::result::Result; @@ -103,145 +96,7 @@ impl Drop for NsdContainer { } } -pub struct Container { - id: String, - name: String, -} - -impl Container { - /// Starts the container in a "parked" state - pub fn run(image: Image) -> Result { - static COUNT: AtomicUsize = AtomicUsize::new(0); - - let image_tag = format!("dnssec-tests-{image}"); - - let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); - let dockerfile_path = manifest_dir - .join("docker") - .join(format!("{image}.Dockerfile")); - let docker_dir_path = manifest_dir.join("docker"); - dbg!(&image_tag); - - let mut command = Command::new("docker"); - command - .args(&["build", "-t"]) - .arg(&image_tag) - .arg("-f") - .arg(dockerfile_path) - .arg(docker_dir_path); - let status = command.status()?; - - if !status.success() { - return Err(format!("`{command:?}` failed").into()); - } - - // run container based on image - // `docker run --rm -it $IMAGE sleep infinity` - - let mut command = Command::new("docker"); - let pid = process::id(); - let container_name = format!( - "{image}-{pid}-{}", - COUNT.fetch_add(1, atomic::Ordering::Relaxed) - ); - command.args(&["run", "--rm", "--detach", "--name", &container_name]); - let output = command - .arg("-it") - .arg(image_tag) - .args(["sleep", "infinity"]) - .output()?; - - if !output.status.success() { - return Err(format!("`{command:?}` failed").into()); - } - - let id = core::str::from_utf8(&output.stdout)?.trim().to_string(); - dbg!(&id); - let container = Self { - id, - name: container_name, - }; - dbg!(container.ip_addr()?); - - Ok(container) - } - - pub fn cp(&self, path_in_container: &str, file_contents: &str, chmod: &str) -> Result<()> { - let mut temp_file = NamedTempFile::new()?; - fs::write(&mut temp_file, file_contents)?; - - let src_path = temp_file.path().display().to_string(); - let dest_path = format!("{}:{path_in_container}", self.id); - - let mut command = Command::new("docker"); - command.args(["cp", &src_path, &dest_path]); - - let status = command.status()?; - if !status.success() { - return Err(format!("`{command:?}` failed").into()); - } - - let command = &["chmod", chmod, path_in_container]; - let output = self.exec(command)?; - if !output.status.success() { - return Err(format!("`{command:?}` failed").into()); - } - - Ok(()) - } - - pub fn exec(&self, cmd: &[&str]) -> Result { - let mut command = Command::new("docker"); - command.args(&["exec", "-t", &self.id]).args(cmd); - - let output = command.output()?; - - Ok(output) - } - - pub fn spawn(&self, cmd: &[&str]) -> Result { - let mut command = Command::new("docker"); - command.args(&["exec", "-t", &self.id]).args(cmd); - - let child = command.spawn()?; - - Ok(child) - } - - pub fn ip_addr(&self) -> Result { - let mut command = Command::new("docker"); - command - .args(&[ - "inspect", - "-f", - "{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}", - ]) - .arg(&self.id); - - let output = command.output()?; - if !output.status.success() { - return Err(format!("`{command:?}` failed").into()); - } - - let ip_addr = core::str::from_utf8(&output.stdout)?.trim().to_string(); - dbg!(&ip_addr); - - Ok(ip_addr) - } -} - -// ensure the container gets deleted -impl Drop for Container { - fn drop(&mut self) { - // running this to completion would block the current thread for several seconds so just - // fire and forget - let _ = Command::new("docker") - .args(["rm", "-f", &self.id]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - } -} +mod container; pub enum Image { Nsd, // for ROOT, TLD, DOMAIN @@ -262,48 +117,8 @@ impl fmt::Display for Image { #[cfg(test)] mod tests { - use std::net::Ipv4Addr; - use super::*; - #[test] - fn run_works() -> Result<()> { - let container = Container::run(Image::Client)?; - - let output = container.exec(&["true"])?; - assert!(output.status.success()); - - Ok(()) - } - - #[test] - fn ip_addr_works() -> Result<()> { - let container = Container::run(Image::Client)?; - - let ip_addr = container.ip_addr()?; - assert!(ip_addr.parse::().is_ok()); - - Ok(()) - } - - #[test] - fn cp_works() -> Result<()> { - let container = Container::run(Image::Client)?; - - let path = "/tmp/somefile"; - let contents = "hello"; - container.cp(path, contents, CHMOD_RW_EVERYONE)?; - - let output = container.exec(&["cat", path])?; - dbg!(&output); - - assert!(output.status.success()); - - assert_eq!(contents, core::str::from_utf8(&output.stdout)?); - - Ok(()) - } - #[test] fn tld_setup() -> Result<()> { let tld_ns = NsdContainer::start(Domain::Tld { domain: "com." })?;