move Container into its own module
This commit is contained in:
parent
61bb4bb315
commit
e997c8cff5
194
src/container.rs
Normal file
194
src/container.rs
Normal file
@ -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<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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Output> {
|
||||||
|
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<Child> {
|
||||||
|
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<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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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::<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, 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(())
|
||||||
|
}
|
||||||
|
}
|
191
src/lib.rs
191
src/lib.rs
@ -1,15 +1,8 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::process::{self, Child, Output};
|
use std::process::Child;
|
||||||
use std::sync::atomic;
|
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
path::Path,
|
|
||||||
process::{Command, Stdio},
|
|
||||||
sync::atomic::AtomicUsize,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
use container::Container;
|
||||||
use minijinja::{context, Environment};
|
use minijinja::{context, Environment};
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
pub type Error = Box<dyn std::error::Error>;
|
pub type Error = Box<dyn std::error::Error>;
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
@ -103,145 +96,7 @@ impl Drop for NsdContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Container {
|
mod 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 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<Output> {
|
|
||||||
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<Child> {
|
|
||||||
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<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 {
|
pub enum Image {
|
||||||
Nsd, // for ROOT, TLD, DOMAIN
|
Nsd, // for ROOT, TLD, DOMAIN
|
||||||
@ -262,48 +117,8 @@ impl fmt::Display for Image {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::net::Ipv4Addr;
|
|
||||||
|
|
||||||
use super::*;
|
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::<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, 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]
|
#[test]
|
||||||
fn tld_setup() -> Result<()> {
|
fn tld_setup() -> Result<()> {
|
||||||
let tld_ns = NsdContainer::start(Domain::Tld { domain: "com." })?;
|
let tld_ns = NsdContainer::start(Domain::Tld { domain: "com." })?;
|
||||||
|
Loading…
Reference in New Issue
Block a user