diff --git a/Cargo.lock b/Cargo.lock index 426b416d..a2e24963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,156 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "dnssec-tests" version = "0.1.0" +dependencies = [ + "tempfile", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index 2b8c6086..b1f7ab22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ license = "MIT or Apache 2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +tempfile = "3.9.0" diff --git a/src/lib.rs b/src/lib.rs index 7d12d9af..2422ee27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,207 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +use core::fmt; +use std::process::Output; +use std::sync::atomic; +use std::{ + fs, + path::Path, + process::{Command, ExitStatus, Stdio}, + sync::atomic::AtomicUsize, +}; + +use tempfile::NamedTempFile; + +pub type Error = Box; +pub type Result = core::result::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()); + } + + // run container based on image + // `docker run --rm -it $IMAGE sleep infinity` + + let mut command = Command::new("docker"); + let container_name = format!("{image}-{}", 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); + + Ok(Self { + id, + name: container_name, + }) + } + + pub fn cp(&self, path_in_container: &str, file_contents: &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()); + } + + 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 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(); + } +} + +pub enum Image { + Nsd, // for ROOT, TLD, DOMAIN + Unbound, + Client, +} + +impl fmt::Display for Image { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + Image::Nsd => "nsd", + Image::Unbound => "unbound", + Image::Client => "client", + }; + f.write_str(name) + } } #[cfg(test)] mod tests { + use std::net::Ipv4Addr; + use super::*; #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + 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)?; + + let output = container.exec(&["cat", path])?; + dbg!(&output); + + assert!(output.status.success()); + + assert_eq!(contents, core::str::from_utf8(&output.stdout)?); + + Ok(()) + } + + #[ignore = "TODO"] + #[test] + fn tld_setup() -> Result<()> { + let container = Container::run(Image::Nsd)?; + + container.cp("/etc/nsd/zones/main.zone", "TODO")?; + + container.exec(&["nsd", "-d"])?; + + Ok(()) } }