isb_core/sandbox/
images.rs1use serde_json::Value;
5
6use crate::client::Client;
7use crate::error::Error;
8
9pub const REMOTE_DEFAULT: &str = "images:ubuntu/24.04";
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct LocalImage {
16 pub alias: String,
17 pub description: String,
18}
19
20pub fn aliases(images: &Value) -> Vec<LocalImage> {
23 let mut out: Vec<LocalImage> = images
24 .as_array()
25 .into_iter()
26 .flatten()
27 .flat_map(|i| {
28 let description = i["properties"]["description"]
29 .as_str()
30 .unwrap_or_default()
31 .to_string();
32 i["aliases"]
33 .as_array()
34 .into_iter()
35 .flatten()
36 .filter_map(|a| a["name"].as_str())
37 .map(move |a| LocalImage {
38 alias: a.to_string(),
39 description: description.clone(),
40 })
41 })
42 .collect();
43 out.sort_by(|a, b| a.alias.cmp(&b.alias));
44 out
45}
46
47pub fn local(client: &Client) -> crate::error::Result<Vec<LocalImage>> {
49 Ok(aliases(&client.get("/1.0/images?recursion=1")?))
50}
51
52pub(crate) fn missing(alias: &str, have: &[LocalImage]) -> Error {
55 const SHOWN: usize = 12;
56 let list = if have.is_empty() {
57 "this host has no local images".to_string()
58 } else {
59 let names: Vec<&str> = have.iter().take(SHOWN).map(|i| i.alias.as_str()).collect();
60 let more = have.len().saturating_sub(SHOWN);
61 format!(
62 "local images: {}{}",
63 names.join(", "),
64 if more > 0 {
65 format!(" and {more} more")
66 } else {
67 String::new()
68 }
69 )
70 };
71 Error::invalid(format!(
72 "image {alias:?} not found locally ({list}); name a remote image with its server instead, e.g. {REMOTE_DEFAULT}"
73 ))
74}
75
76pub(crate) fn missing_on(client: &Client, alias: &str) -> Error {
79 missing(alias, &local(client).unwrap_or_default())
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use serde_json::json;
86
87 #[test]
88 fn lists_aliases_and_says_how_to_pick_a_remote_image() {
89 let v = json!([
90 {"aliases": [{"name": "dev-base"}], "properties": {"description": "Ubuntu for dev"}},
91 {"aliases": [], "properties": {}},
92 {"aliases": [{"name": "alpine"}, {"name": "a2"}]}
93 ]);
94 let have = aliases(&v);
95 let names: Vec<&str> = have.iter().map(|i| i.alias.as_str()).collect();
96 assert_eq!(names, ["a2", "alpine", "dev-base"]);
97 assert_eq!(have[2].description, "Ubuntu for dev");
98
99 let m = missing("dev-base", &have[..2]).to_string();
100 assert!(
101 m.starts_with("image \"dev-base\" not found locally (local images: a2, alpine)"),
102 "{m}"
103 );
104 assert!(m.contains("e.g. images:ubuntu/24.04"), "{m}");
105 let m = missing("dev-base", &[]).to_string();
106 assert!(m.contains("(this host has no local images)"), "{m}");
107 }
108}