use crate::{errors::*, types::*, util::FutureExt, FirewallBackend};
use failure::{bail, format_err};
use shiplift::{
builder::{ContainerFilter as ContainerFilterShiplift, ContainerListOptions},
rep::{Container, NetworkContainerDetails, NetworkDetails},
Docker,
};
use slog::{debug, o, trace, Logger};
use std::collections::HashMap as Map;
pub trait Process<B: FirewallBackend>
where
DFW<B>: Process<B>,
{
fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>>;
}
impl<B, T> Process<B> for Option<T>
where
B: FirewallBackend,
DFW<B>: Process<B>,
T: Process<B>,
{
fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>> {
match self {
Some(t) => t.process(ctx),
None => Ok(None),
}
}
}
impl<B, T> Process<B> for Vec<T>
where
B: FirewallBackend,
DFW<B>: Process<B>,
T: Process<B>,
{
fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>> {
let mut rules = Vec::new();
for rule in self {
if let Some(mut sub_rules) = rule.process(ctx)? {
rules.append(&mut sub_rules);
}
}
Ok(Some(rules))
}
}
pub struct ProcessContext<'a, B>
where
B: FirewallBackend,
DFW<B>: Process<B>,
{
pub(crate) docker: &'a Docker,
pub(crate) dfw: &'a DFW<B>,
pub(crate) container_map: Map<String, Container>,
pub(crate) network_map: Map<String, NetworkDetails>,
pub(crate) external_network_interfaces: Option<Vec<String>>,
pub(crate) primary_external_network_interface: Option<String>,
pub(crate) logger: Logger,
pub(crate) dry_run: bool,
}
impl<'a, B> ProcessContext<'a, B>
where
B: FirewallBackend,
DFW<B>: Process<B>,
{
pub fn new(
docker: &'a Docker,
dfw: &'a DFW<B>,
processing_options: &'a ProcessingOptions,
logger: &'a Logger,
dry_run: bool,
) -> Result<ProcessContext<'a, B>> {
let logger = logger.new(o!());
let container_list_options = match processing_options.container_filter {
ContainerFilter::All => Default::default(),
ContainerFilter::Running => ContainerListOptions::builder()
.filter(vec![ContainerFilterShiplift::Status("running".to_owned())])
.build(),
};
let containers = docker.containers().list(&container_list_options).sync()?;
debug!(logger, "Got list of containers";
o!("containers" => format!("{:#?}", containers)));
let container_map = get_container_map(&containers)?;
trace!(logger, "Got map of containers";
o!("container_map" => format!("{:#?}", container_map)));
let networks = docker.networks().list(&Default::default()).sync()?;
debug!(logger, "Got list of networks";
o!("networks" => format!("{:#?}", networks)));
let network_map =
get_network_map(&networks)?.ok_or_else(|| format_err!("no networks found"))?;
trace!(logger, "Got map of networks";
o!("container_map" => format!("{:#?}", container_map)));
let external_network_interfaces = dfw
.global_defaults
.external_network_interfaces
.as_ref()
.cloned();
let primary_external_network_interface = external_network_interfaces
.as_ref()
.and_then(|v| v.get(0))
.map(|s| s.to_owned());
Ok(ProcessContext {
docker,
dfw,
container_map,
network_map,
external_network_interfaces,
primary_external_network_interface,
logger,
dry_run,
})
}
pub fn process(&mut self) -> Result<()> {
let rules = Process::<B>::process(self.dfw, self)?;
if let Some(rules) = rules {
B::apply(rules, self)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContainerFilter {
All,
Running,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcessingOptions {
pub container_filter: ContainerFilter,
}
impl Default for ProcessingOptions {
fn default() -> Self {
ProcessingOptions {
container_filter: ContainerFilter::All,
}
}
}
pub(crate) fn get_bridge_name(network_id: &str) -> Result<String> {
if network_id.len() < 12 {
bail!("network has to be longer than 12 characters");
}
Ok(format!("br-{}", &network_id[..12]))
}
pub(crate) fn get_network_for_container(
docker: &Docker,
container_map: &Map<String, Container>,
container_name: &str,
network_id: &str,
) -> Result<Option<NetworkContainerDetails>> {
Ok(match container_map.get(container_name) {
Some(container) => match docker
.networks()
.get(network_id)
.inspect()
.sync()?
.containers
.get(&container.id)
{
Some(network) => Some(network.clone()),
None => None,
},
None => None,
})
}
pub(crate) fn get_container_map(containers: &[Container]) -> Result<Map<String, Container>> {
let mut container_map: Map<String, Container> = Map::new();
for container in containers {
for name in &container.names {
container_map.insert(
name.clone().trim_start_matches('/').to_owned(),
container.clone(),
);
}
}
Ok(container_map)
}
pub(crate) fn get_network_map(
networks: &[NetworkDetails],
) -> Result<Option<Map<String, NetworkDetails>>> {
let mut network_map: Map<String, NetworkDetails> = Map::new();
for network in networks {
network_map.insert(network.name.clone(), network.clone());
}
if network_map.is_empty() {
Ok(None)
} else {
Ok(Some(network_map))
}
}
pub(crate) fn generate_marker(components: &[&str]) -> String {
format!("DFW-MARKER:{}", components.join(";"))
}