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
This commit is contained in:
Sebastian Ziebell 2024-02-13 12:15:01 +01:00
parent 820f1c3447
commit 2289567998
No known key found for this signature in database
GPG Key ID: 7CFCF2A8E3AE3A09
5 changed files with 84 additions and 5 deletions

View File

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

View File

@ -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<ExitStatus> {
// 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<ExitStatus> {
Ok(command.status()?)
}
/// Finds the list of connected containers
fn get_attached_containers(network_name: &str) -> Result<Vec<String>> {
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::<Vec<_>>();
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(())
}
}

View File

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

View File

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

View File

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