use super::ensure::{PrepareDir, ensure_service_names_in};
use super::*;
pub fn of_project(p: &Value) -> Option<OrgId> {
let org = OrgId::from_incus_project(p["name"].as_str()?)?;
(p["config"][KEY_ORG].as_str() == Some(org.as_str())).then_some(org)
}
pub fn names(base: &Client) -> Result<Vec<OrgId>> {
let v = host(base).get("/1.0/projects?recursion=1")?;
let mut out: Vec<OrgId> = v
.as_array()
.into_iter()
.flatten()
.filter_map(of_project)
.collect();
out.sort();
Ok(out)
}
pub fn ensure_default(base: &Client, report: &mut dyn FnMut(&str)) -> Result<bool> {
ensure_default_in(base, &crate::discovery::prepare_org, report)
}
fn ensure_default_in(
base: &Client,
prepare: PrepareDir,
report: &mut dyn FnMut(&str),
) -> Result<bool> {
let org = OrgId::default_org();
match host(base).get_opt(&format!("/1.0/projects/{DEFAULT_ORG_PROJECT}"))? {
Some(p) if p["config"][KEY_ORG].as_str() == Some(DEFAULT_ORG) => {
ensure_service_names_in(base, &org, prepare, report)?;
Ok(false)
}
Some(_) => Err(Error::AlreadyExists(format!(
"incus project {DEFAULT_ORG_PROJECT} exists but is not isb's default org"
))),
None => ensure(base, &org, &OrgOptions::default(), report).map(|_| true),
}
}
pub fn ensure_all_service_names(base: &Client, report: &mut dyn FnMut(&str)) -> Result<usize> {
let v = host(base).get("/1.0/projects?recursion=1")?;
let mut off = 0;
for p in v.as_array().into_iter().flatten() {
let Some(org) = of_project(p) else {
continue;
};
match ensure_service_names(base, &org, report) {
Ok(Names::Unavailable) => off += 1,
Ok(_) => {}
Err(e) => {
report(&format!("{org}: service names: {e}"));
off += 1;
}
}
}
if let Ok(orgs) = names(base) {
for o in crate::discovery::prune_orgs(&orgs, crate::discovery::STALE_ORG_DIR) {
report(&format!(
"{o}: removed the hosts directory of an org that no longer exists"
));
}
}
Ok(off)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::client::fake::{Route, serve};
#[test]
fn ensure_default_turns_service_names_on_for_an_existing_default_org() {
let bridge = bridge_name(&OrgId::default_org());
let routes = |patch: bool| {
let mut r = vec![
Route {
prefix: "GET /1.0/projects/isb-default",
status: 200,
body: json!({"config": {KEY_ORG: "default"}}),
},
Route {
prefix: "GET /1.0/networks/",
status: 200,
body: json!({"config": {"ipv4.address": "10.1.2.1/24"}}),
},
];
if patch {
r.push(Route {
prefix: "PATCH /1.0/networks/",
status: 200,
body: json!({}),
});
}
r
};
let dir = |_: &OrgId| Ok(Some(PathBuf::from("/var/lib/isb/dns/default")));
let (_d, c) = serve(routes(true));
let mut lines = Vec::new();
assert!(!ensure_default_in(&c, &dir, &mut |l| lines.push(l.to_string())).unwrap());
assert!(
lines
.iter()
.any(|l| l.contains("turning on service names") && l.contains(&bridge)),
"{lines:?}"
);
let none = |_: &OrgId| Ok(None);
let (_d, c) = serve(routes(false));
let mut lines = Vec::new();
assert!(!ensure_default_in(&c, &none, &mut |l| lines.push(l.to_string())).unwrap());
assert!(
lines.iter().any(|l| l.contains("service names are off")),
"{lines:?}"
);
}
#[test]
fn ensure_default_leaves_an_existing_default_org_alone() {
let (_d, c) = serve(vec![Route {
prefix: "GET /1.0/projects/isb-default",
status: 200,
body: json!({"config": {KEY_ORG: "default"}}),
}]);
let mut lines = Vec::new();
let none = |_: &OrgId| Ok(None);
assert!(!ensure_default_in(&c, &none, &mut |l| lines.push(l.to_string())).unwrap());
assert!(lines.is_empty());
let (_d, c) = serve(vec![Route {
prefix: "GET /1.0/projects/isb-default",
status: 200,
body: json!({"config": {}}),
}]);
let e = ensure_default_in(&c, &none, &mut |_| {}).unwrap_err();
assert!(e.to_string().contains("not isb's default org"), "{e}");
}
#[test]
fn every_org_is_visited_and_a_missing_directory_is_counted() {
let (_d, c) = serve(vec![Route {
prefix: "GET /1.0/projects?recursion=1",
status: 200,
body: json!([
{"name": "default", "config": {}},
{"name": "isb-default", "config": {KEY_ORG: "default"}},
{"name": "isb-acme", "config": {KEY_ORG: "acme"}},
{"name": "isb-other", "config": {}},
]),
}]);
let mut lines = Vec::new();
let off = ensure_all_service_names(&c, &mut |l| lines.push(l.to_string())).unwrap();
assert_eq!(off, 0, "{lines:?}");
}
#[test]
fn only_marked_isb_projects_are_orgs() {
let (_d, c) = serve(vec![Route {
prefix: "GET /1.0/projects?recursion=1",
status: 200,
body: json!([
{"name": "isb-ocai", "config": {KEY_ORG: "ocai"}},
{"name": "isb-default", "config": {KEY_ORG: "default"}},
{"name": "isb-test-own-1", "config": {}},
{"name": "isb-lab", "config": {KEY_ORG: "other"}},
{"name": "default", "config": {}},
]),
}]);
let n: Vec<String> = names(&c).unwrap().iter().map(|o| o.to_string()).collect();
assert_eq!(n, ["default", "ocai"]);
}
}