use std::net::Ipv4Addr;
use isoloom_spec::model::Spec;
use crate::addressing::address;
use crate::requirements::Requirement;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PrivateDns {
pub domain: String,
pub records: Vec<Record>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Record {
pub name: String,
pub label: String,
pub machine: String,
pub address: Ipv4Addr,
}
#[must_use]
pub fn plan(spec: &Spec) -> Option<PrivateDns> {
let domain = spec.private_domain()?;
let mut records = Vec::new();
for (name, m) in &spec.machines {
let Some((net, octet)) = m.networks.iter().next() else { continue };
let at = address(spec, net, *octet);
for label in std::iter::once(name).chain(&m.aliases) {
records.push(Record {
name: format!("{label}.{domain}"),
label: label.clone(),
machine: name.clone(),
address: at,
});
}
}
Some(PrivateDns { domain, records })
}
#[must_use]
pub(crate) fn with_domain(spec: &Spec, names: Vec<String>) -> Vec<String> {
match spec.private_domain() {
Some(domain) => {
let full: Vec<String> = names.iter().map(|n| format!("{n}.{domain}")).collect();
names.into_iter().chain(full).collect()
}
None => names,
}
}
#[must_use]
pub fn requirements(spec: &Spec) -> Option<Requirement> {
spec.private_domain().map(|domain| Requirement::PrivateDns { domain })
}
#[cfg(test)]
mod tests {
use super::*;
fn spec(dns: &str) -> Spec {
crate::testing::spec(&format!(
"version: 1\nname: lab\n{dns}\nnetworks: {{ lan: {{ cidr: 10.0.0.0/24 }} }}\nmachines:\n web: {{ networks: {{ lan: 10 }}, aliases: [www], app: {{ image: nginx }} }}\n db: {{ networks: {{ lan: 20 }}, app: {{ image: postgres }} }}\n box: {{ app: {{ image: alpine }} }}\n"
))
}
#[test]
fn every_name_and_alias_is_a_record_at_the_first_address() {
let z = plan(&spec("dns: { private: true, domain: corp.lab }")).unwrap();
assert_eq!(z.domain, "corp.lab");
let records: Vec<(&str, &str, String)> = z.records.iter().map(|r| (r.name.as_str(), r.label.as_str(), r.address.to_string())).collect();
assert_eq!(
records,
[
("web.corp.lab", "web", "10.0.0.10".to_owned()),
("www.corp.lab", "www", "10.0.0.10".to_owned()),
("db.corp.lab", "db", "10.0.0.20".to_owned()),
]
);
assert_eq!(
requirements(&spec("dns: { private: true }")),
Some(Requirement::PrivateDns { domain: "lab.internal".into() })
);
}
#[test]
fn off_there_is_no_zone_and_names_stay_short() {
let s = spec("");
assert_eq!(plan(&s), None);
assert_eq!(requirements(&s), None);
assert_eq!(with_domain(&s, vec!["web".into()]), ["web"]);
let on = spec("dns: { private: true }");
assert_eq!(
with_domain(&on, vec!["web".into(), "www".into()]),
["web", "www", "web.lab.internal", "www.lab.internal"]
);
}
}