From 2289567998fb169a2fc129c6a12b0f3e396d417e Mon Sep 17 00:00:00 2001 From: Sebastian Ziebell Date: Tue, 13 Feb 2024 12:15:01 +0100 Subject: [PATCH] Disconnect all containers before removing network The list of attached containers is determined, all of them are disconnected from the network, then the network is deleted. * set net mask in unbound conf template * expose container id --- packages/dns-test/src/container.rs | 5 ++ packages/dns-test/src/container/network.rs | 69 ++++++++++++++++++- packages/dns-test/src/name_server.rs | 4 ++ packages/dns-test/src/resolver.rs | 9 ++- .../dns-test/src/templates/unbound.conf.jinja | 2 +- 5 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/dns-test/src/container.rs b/packages/dns-test/src/container.rs index 21d621ee..288d7357 100644 --- a/packages/dns-test/src/container.rs +++ b/packages/dns-test/src/container.rs @@ -54,6 +54,7 @@ impl Container { command .args(["run", "--rm", "--detach", "--name", &name]) .arg("-it") + .args(["--network", network.name()]) .arg(image_tag) .args(["sleep", "infinity"]); @@ -154,6 +155,10 @@ impl Container { pub fn ipv4_addr(&self) -> Ipv4Addr { self.inner.ipv4_addr } + + pub fn id(&self) -> &str { + &self.inner.id + } } fn container_count() -> usize { diff --git a/packages/dns-test/src/container/network.rs b/packages/dns-test/src/container/network.rs index 7d7036ab..730e8f79 100644 --- a/packages/dns-test/src/container/network.rs +++ b/packages/dns-test/src/container/network.rs @@ -24,7 +24,7 @@ impl Network { let mut command = Command::new("docker"); command .args(["network", "create"]) - .args(["--internal"]) + .args(["--internal", "--attachable"]) .arg(&network_name); // create network @@ -49,6 +49,11 @@ impl Network { pub fn name(&self) -> &str { self.name.as_str() } + + /// Returns the subnet mask + pub fn netmask(&self) -> &str { + &self.config.subnet + } } /// Collects all important configs. @@ -85,7 +90,20 @@ impl Drop for Network { } } +/// Removes the given network. fn remove_network(network_name: &str) -> Result { + // Disconnects all attached containers + for container_id in get_attached_containers(network_name)? { + let mut command = Command::new("docker"); + let _ = command + .args(["network", "disconnect", "--force"]) + .args([network_name, container_id.as_str()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status()?; + } + + // Remove the network let mut command = Command::new("docker"); command .args(["network", "rm", "--force", network_name]) @@ -94,6 +112,35 @@ fn remove_network(network_name: &str) -> Result { Ok(command.status()?) } +/// Finds the list of connected containers +fn get_attached_containers(network_name: &str) -> Result> { + let mut command = Command::new("docker"); + command.args([ + "network", + "inspect", + network_name, + "-f", + r#"{{ range $k, $v := .Containers }}{{ printf "%s\n" $k }}{{ end }}"#, + ]); + + let output = command.output()?; + let container_ids = match output.status.success() { + true => { + let container_ids = std::str::from_utf8(&output.stdout)? + .trim() + .to_string() + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| line.to_string()) + .collect::>(); + container_ids + } + false => vec![], + }; + + Ok(container_ids) +} + fn network_count() -> usize { static COUNT: AtomicUsize = AtomicUsize::new(1); @@ -102,6 +149,8 @@ fn network_count() -> usize { #[cfg(test)] mod tests { + use crate::{name_server::NameServer, FQDN}; + use super::*; #[test] @@ -117,4 +166,22 @@ mod tests { assert!(config.is_ok()); Ok(()) } + + #[test] + fn remove_network_works() -> Result<()> { + let network = Network::new().expect("Failed to create network"); + let network_name = network.name().to_string(); + let nameserver = NameServer::new(FQDN::ROOT, &network)?; + + let container_ids = get_attached_containers(network.name())?; + assert_eq!(1, container_ids.len()); + assert_eq!(&[nameserver.container_id().to_string()], &container_ids[..]); + + drop(network); + + let container_ids = get_attached_containers(&network_name)?; + assert!(container_ids.is_empty()); + + Ok(()) + } } diff --git a/packages/dns-test/src/name_server.rs b/packages/dns-test/src/name_server.rs index 42d0919c..1189b9cf 100644 --- a/packages/dns-test/src/name_server.rs +++ b/packages/dns-test/src/name_server.rs @@ -155,6 +155,10 @@ impl<'a> NameServer<'a, Stopped> { state: Running { child }, }) } + + pub fn container_id(&self) -> &str { + self.container.id() + } } const ZONES_DIR: &str = "/etc/nsd/zones"; diff --git a/packages/dns-test/src/resolver.rs b/packages/dns-test/src/resolver.rs index 1e15adeb..3a56d11a 100644 --- a/packages/dns-test/src/resolver.rs +++ b/packages/dns-test/src/resolver.rs @@ -44,7 +44,10 @@ impl Resolver { Implementation::Unbound => { container.cp("/etc/unbound/root.hints", &hints)?; - container.cp("/etc/unbound/unbound.conf", &unbound_conf(use_dnssec))?; + container.cp( + "/etc/unbound/unbound.conf", + &unbound_conf(use_dnssec, network.netmask()), + )?; } Implementation::Hickory => { @@ -95,8 +98,8 @@ kill -TERM $(cat {pidfile})" } } -fn unbound_conf(use_dnssec: bool) -> String { - minijinja::render!(include_str!("templates/unbound.conf.jinja"), use_dnssec => use_dnssec) +fn unbound_conf(use_dnssec: bool, netmask: &str) -> String { + minijinja::render!(include_str!("templates/unbound.conf.jinja"), use_dnssec => use_dnssec, netmask => netmask) } fn hickory_conf(use_dnssec: bool) -> String { diff --git a/packages/dns-test/src/templates/unbound.conf.jinja b/packages/dns-test/src/templates/unbound.conf.jinja index fe74a6cf..ca5e54d3 100644 --- a/packages/dns-test/src/templates/unbound.conf.jinja +++ b/packages/dns-test/src/templates/unbound.conf.jinja @@ -2,7 +2,7 @@ server: verbosity: 4 use-syslog: no interface: 0.0.0.0 - access-control: 172.17.0.0/16 allow + access-control: {{ netmask }} allow root-hints: /etc/unbound/root.hints {% if use_dnssec %} trust-anchor-file: /etc/trusted-key.key