initial Container API

This commit is contained in:
Jorge Aparicio 2024-02-01 17:19:01 +01:00
parent e6691ffc40
commit 3c50ca911a
3 changed files with 349 additions and 5 deletions

150
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<dyn std::error::Error>;
pub type Result<T> = core::result::Result<T, Error>;
pub struct Container {
id: String,
name: String,
}
impl Container {
/// Starts the container in a "parked" state
pub fn run(image: Image) -> Result<Self> {
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<Output> {
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<String> {
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::<Ipv4Addr>().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(())
}
}