consortium_cli/
inventory.rs1use std::collections::HashMap;
31use std::path::Path;
32
33use anyhow::{Context, Result};
34use serde::Deserialize;
35
36use consortium_nix::cascade::{CascadeNode, NodeId, NodeIdAlloc};
37
38#[derive(Debug, Deserialize)]
39struct InventoryFile {
40 seed: String,
42 nodes: Vec<String>,
44}
45
46pub fn load_inventory<P: AsRef<Path>>(
52 path: P,
53) -> Result<(Vec<CascadeNode>, NodeId, HashMap<NodeId, String>)> {
54 let path = path.as_ref();
55 let raw = std::fs::read_to_string(path)
56 .with_context(|| format!("failed to read inventory file: {}", path.display()))?;
57 let inv: InventoryFile = toml::from_str(&raw)
58 .with_context(|| format!("failed to parse inventory file as TOML: {}", path.display()))?;
59
60 if inv.seed.trim().is_empty() {
61 anyhow::bail!("inventory `seed` is empty");
62 }
63
64 let mut alloc = NodeIdAlloc::new();
65 let mut nodes = Vec::with_capacity(1 + inv.nodes.len());
66 let mut addrs = HashMap::with_capacity(1 + inv.nodes.len());
67
68 let seed_id = alloc.alloc();
69 nodes.push(CascadeNode::new(seed_id, inv.seed.clone()));
70 addrs.insert(seed_id, inv.seed);
71
72 for addr in inv.nodes {
73 if addr.trim().is_empty() {
74 continue;
75 }
76 let id = alloc.alloc();
77 nodes.push(CascadeNode::new(id, addr.clone()));
78 addrs.insert(id, addr);
79 }
80
81 Ok((nodes, seed_id, addrs))
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use std::io::Write;
88
89 fn write_temp(content: &str) -> tempfile::NamedTempFile {
90 let mut f = tempfile::NamedTempFile::new().unwrap();
91 f.write_all(content.as_bytes()).unwrap();
92 f.flush().unwrap();
93 f
94 }
95
96 #[test]
97 fn loads_basic_inventory() {
98 let f = write_temp(
99 r#"
100seed = "olive@seir"
101nodes = ["root@hp01", "root@hp02", "olive@mm01"]
102"#,
103 );
104 let (nodes, seed, addrs) = load_inventory(f.path()).unwrap();
105 assert_eq!(seed, NodeId(0));
106 assert_eq!(nodes.len(), 4);
107 assert_eq!(nodes[0].addr, "olive@seir");
108 assert_eq!(nodes[1].addr, "root@hp01");
109 assert_eq!(
110 addrs.get(&NodeId(0)).map(String::as_str),
111 Some("olive@seir")
112 );
113 assert_eq!(addrs.get(&NodeId(2)).map(String::as_str), Some("root@hp02"));
114 }
115
116 #[test]
117 fn rejects_empty_seed() {
118 let f = write_temp(r#"seed = "" \nnodes = []"#);
119 assert!(load_inventory(f.path()).is_err());
120 }
121
122 #[test]
123 fn skips_empty_node_entries() {
124 let f = write_temp(
125 r#"
126seed = "olive@seir"
127nodes = ["root@hp01", "", "root@hp02"]
128"#,
129 );
130 let (nodes, _, _) = load_inventory(f.path()).unwrap();
131 assert_eq!(nodes.len(), 3); }
133}