use indexmap::IndexMap;
use isoloom_spec::groups;
use isoloom_spec::model::Spec;
#[must_use]
pub fn groups(spec: &Spec) -> IndexMap<String, Vec<String>> {
let mut out: IndexMap<String, Vec<String>> = IndexMap::new();
let declared = spec.provision.iter().flat_map(|s| s.groups.iter().map(|(g, m)| (g.clone(), m.clone())));
let resolved = spec.groups.keys().map(|g| (g.clone(), groups::members(spec, g)));
for (group, members) in declared.chain(resolved) {
let entry = out.entry(group).or_default();
for m in members {
if !entry.contains(&m) {
entry.push(m);
}
}
}
out
}
#[must_use]
pub fn host_vars(spec: &Spec, machine: &str) -> IndexMap<String, serde_json::Value> {
spec.provision
.iter()
.filter_map(|s| s.host_vars.get(machine))
.flatten()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
const SPEC: &str = r"
version: 1
name: g
networks:
lab: { cidr: 10.9.0.0/24 }
groups:
web: { members: [w*] }
all: { members: [web, db] }
machines:
w1: { networks: { lab: 10 }, os: debian@12 }
w2: { networks: { lab: 11 }, os: debian@12 }
db: { networks: { lab: 20 }, os: debian@12 }
provision:
- ansible: a.yml
groups: { web: [w2], dbs: [db] }
host_vars: { db: { port: 5432, name: one } }
- ansible: b.yml
groups: { dbs: [db, w1] }
host_vars: { db: { name: two } }
";
fn spec() -> Spec {
crate::testing::spec(SPEC)
}
#[test]
fn provision_groups_come_first_then_the_specs_own_merged() {
let g = groups(&spec());
assert_eq!(g.keys().collect::<Vec<_>>(), ["web", "dbs", "all"]);
assert_eq!(g["web"], ["w2", "w1"]);
assert_eq!(g["dbs"], ["db", "w1"]);
assert_eq!(g["all"], ["w1", "w2", "db"]);
}
#[test]
fn host_vars_merge_across_steps_with_the_later_value() {
let v = host_vars(&spec(), "db");
assert_eq!(v.keys().collect::<Vec<_>>(), ["port", "name"]);
assert_eq!(v["name"], serde_json::json!("two"));
assert!(host_vars(&spec(), "w1").is_empty());
}
}