use serde_json::Value;
use crate::client::Client;
use crate::error::Error;
pub const REMOTE_DEFAULT: &str = "images:ubuntu/24.04";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalImage {
pub alias: String,
pub description: String,
}
pub fn aliases(images: &Value) -> Vec<LocalImage> {
let mut out: Vec<LocalImage> = images
.as_array()
.into_iter()
.flatten()
.flat_map(|i| {
let description = i["properties"]["description"]
.as_str()
.unwrap_or_default()
.to_string();
i["aliases"]
.as_array()
.into_iter()
.flatten()
.filter_map(|a| a["name"].as_str())
.map(move |a| LocalImage {
alias: a.to_string(),
description: description.clone(),
})
})
.collect();
out.sort_by(|a, b| a.alias.cmp(&b.alias));
out
}
pub fn local(client: &Client) -> crate::error::Result<Vec<LocalImage>> {
Ok(aliases(&client.get("/1.0/images?recursion=1")?))
}
pub(crate) fn missing(alias: &str, have: &[LocalImage]) -> Error {
const SHOWN: usize = 12;
let list = if have.is_empty() {
"this host has no local images".to_string()
} else {
let names: Vec<&str> = have.iter().take(SHOWN).map(|i| i.alias.as_str()).collect();
let more = have.len().saturating_sub(SHOWN);
format!(
"local images: {}{}",
names.join(", "),
if more > 0 {
format!(" and {more} more")
} else {
String::new()
}
)
};
Error::invalid(format!(
"image {alias:?} not found locally ({list}); name a remote image with its server instead, e.g. {REMOTE_DEFAULT}"
))
}
pub(crate) fn missing_on(client: &Client, alias: &str) -> Error {
missing(alias, &local(client).unwrap_or_default())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn lists_aliases_and_says_how_to_pick_a_remote_image() {
let v = json!([
{"aliases": [{"name": "dev-base"}], "properties": {"description": "Ubuntu for dev"}},
{"aliases": [], "properties": {}},
{"aliases": [{"name": "alpine"}, {"name": "a2"}]}
]);
let have = aliases(&v);
let names: Vec<&str> = have.iter().map(|i| i.alias.as_str()).collect();
assert_eq!(names, ["a2", "alpine", "dev-base"]);
assert_eq!(have[2].description, "Ubuntu for dev");
let m = missing("dev-base", &have[..2]).to_string();
assert!(
m.starts_with("image \"dev-base\" not found locally (local images: a2, alpine)"),
"{m}"
);
assert!(m.contains("e.g. images:ubuntu/24.04"), "{m}");
let m = missing("dev-base", &[]).to_string();
assert!(m.contains("(this host has no local images)"), "{m}");
}
}