isb_server/servers/
store.rs1use std::collections::BTreeMap;
6use std::path::{Path, PathBuf};
7
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Serialize};
10
11use crate::error::{Error, Result};
12use crate::org::OrgId;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct ServerRecord {
17 pub name: String,
18 pub address: String,
20 pub port: u16,
22 pub ssh: String,
24 pub ssh_port: u16,
25 pub added_at: u64,
27 pub fingerprint: String,
29 #[serde(default)]
31 pub cert_not_after: Option<u64>,
32 pub isb_version: String,
34 #[serde(default)]
36 pub allow_from: Vec<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub vm: Option<VmRecord>,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct VmRecord {
46 pub org: OrgId,
48 pub project: String,
50 pub instance: String,
51 pub cpus: u32,
52 pub memory: String,
53 pub disk: String,
54}
55
56fn read<T: DeserializeOwned + Default>(p: &Path) -> Result<T> {
57 match std::fs::read(p) {
58 Ok(b) => {
59 serde_json::from_slice(&b).map_err(|e| Error::invalid(format!("{}: {e}", p.display())))
60 }
61 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(T::default()),
62 Err(e) => Err(e.into()),
63 }
64}
65
66fn write<T: Serialize>(p: &Path, v: &T) -> Result<()> {
67 super::pki::write_private(p, &serde_json::to_string_pretty(v)?)
68}
69
70#[derive(Debug)]
72pub struct Store {
73 dir: PathBuf,
74 pub servers: BTreeMap<String, ServerRecord>,
75 pub placement: BTreeMap<OrgId, String>,
76}
77
78impl Store {
79 pub fn open(dir: &Path) -> Result<Store> {
80 std::fs::create_dir_all(dir)?;
81 Ok(Store {
82 dir: dir.to_path_buf(),
83 servers: read(&dir.join("servers.json"))?,
84 placement: read(&dir.join("placement.json"))?,
85 })
86 }
87
88 pub fn save(&self) -> Result<()> {
89 write(&self.dir.join("servers.json"), &self.servers)?;
90 write(&self.dir.join("placement.json"), &self.placement)
91 }
92
93 pub fn orgs_on(&self, server: &str) -> Vec<OrgId> {
95 self.placement
96 .iter()
97 .filter(|(_, s)| s.as_str() == server)
98 .map(|(o, _)| o.clone())
99 .collect()
100 }
101}
102
103#[derive(Debug)]
106pub struct AgentOrgs {
107 path: PathBuf,
108 orgs: std::sync::Mutex<Vec<OrgId>>,
109}
110
111impl AgentOrgs {
112 pub fn open(state_dir: &Path) -> Result<AgentOrgs> {
113 let dir = state_dir.join("agent");
114 std::fs::create_dir_all(&dir)?;
115 let path = dir.join("orgs.json");
116 let orgs: Vec<OrgId> = read(&path)?;
117 Ok(AgentOrgs {
118 path,
119 orgs: std::sync::Mutex::new(orgs),
120 })
121 }
122
123 pub fn contains(&self, o: &OrgId) -> bool {
124 self.orgs.lock().unwrap().contains(o)
125 }
126
127 pub fn list(&self) -> Vec<OrgId> {
128 self.orgs.lock().unwrap().clone()
129 }
130
131 pub fn set(&self, o: &OrgId, placed: bool) -> Result<()> {
132 let mut v = self.orgs.lock().unwrap();
133 v.retain(|x| x != o);
134 if placed {
135 v.push(o.clone());
136 v.sort();
137 }
138 write(&self.path, &*v)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn store_and_agent_orgs_persist() {
148 let d = tempfile::tempdir().unwrap();
149 let mut s = Store::open(d.path()).unwrap();
150 s.placement
151 .insert(OrgId::new("acme").unwrap(), "box".into());
152 s.save().unwrap();
153 let s = Store::open(d.path()).unwrap();
154 assert_eq!(s.orgs_on("box"), vec![OrgId::new("acme").unwrap()]);
155 assert!(s.orgs_on("other").is_empty());
156
157 let a = AgentOrgs::open(d.path()).unwrap();
158 let acme = OrgId::new("acme").unwrap();
159 a.set(&acme, true).unwrap();
160 assert!(AgentOrgs::open(d.path()).unwrap().contains(&acme));
161 a.set(&acme, false).unwrap();
162 assert!(!AgentOrgs::open(d.path()).unwrap().contains(&acme));
163 }
164}