diff options
author | Matthias Beyer <matthias.beyer@atos.net> | 2021-07-27 16:36:37 +0200 |
---|---|---|
committer | Matthias Beyer <matthias.beyer@atos.net> | 2021-07-27 16:36:37 +0200 |
commit | 69d2334a5121edbbaa7356428e2d6e8fbd5d3443 (patch) | |
tree | e99dbdf04ce67123f2339e12e9a67e5345f9c4fc | |
parent | 6c018b14d1ddd2d1d89e62b2e1488ccbcebbaf49 (diff) | |
parent | 707898fe04354001130f02a68e1443288d85af3f (diff) |
Merge branch 'endpoint-images'
-rw-r--r-- | src/cli.rs | 26 | ||||
-rw-r--r-- | src/commands/endpoint.rs | 88 | ||||
-rw-r--r-- | src/endpoint/configured.rs | 39 |
3 files changed, 153 insertions, 0 deletions
@@ -1214,6 +1214,32 @@ pub fn cli<'a>() -> App<'a> { .long_about("Display details about the container. Do not assume the output format to be stable.") ) ) + .subcommand(App::new("images") + .version(crate_version!()) + .about("Query images on endpoint(s)") + .subcommand(App::new("list") + .version(crate_version!()) + .about("List images on endpoint(s)") + .arg(Arg::new("csv") + .required(false) + .multiple(false) + .long("csv") + .takes_value(false) + .about("List top output as CSV") + ) + ) + .subcommand(App::new("verify-present") + .version(crate_version!()) + .about("Verify that all configured images are present on endpoint(s)") + .arg(Arg::new("csv") + .required(false) + .multiple(false) + .long("csv") + .takes_value(false) + .about("List top output as CSV") + ) + ) + ) ) } diff --git a/src/commands/endpoint.rs b/src/commands/endpoint.rs index 6fd7d69..cd65887 100644 --- a/src/commands/endpoint.rs +++ b/src/commands/endpoint.rs @@ -11,6 +11,7 @@ //! Implementation of the 'endpoint' subcommand use std::collections::HashMap; +use std::io::Write; use std::ops::Deref; use std::str::FromStr; use std::sync::Arc; @@ -49,6 +50,7 @@ pub async fn endpoint(matches: &ArgMatches, config: &Configuration, progress_gen Some(("stats", matches)) => stats(endpoint_names, matches, config, progress_generator).await, Some(("container", matches)) => crate::commands::endpoint_container::container(endpoint_names, matches, config).await, Some(("containers", matches)) => containers(endpoint_names, matches, config).await, + Some(("images", matches)) => images(endpoint_names, matches, config).await, Some((other, _)) => Err(anyhow!("Unknown subcommand: {}", other)), None => Err(anyhow!("No subcommand")), } @@ -422,6 +424,92 @@ async fn containers_stop(endpoint_names: Vec<EndpointName>, } +async fn images(endpoint_names: Vec<EndpointName>, + matches: &ArgMatches, + config: &Configuration, +) -> Result<()> { + match matches.subcommand() { + Some(("list", matches)) => images_list(endpoint_names, matches, config).await, + Some(("verify-present", matches)) => images_present(endpoint_names, matches, config).await, + Some((other, _)) => Err(anyhow!("Unknown subcommand: {}", other)), + None => Err(anyhow!("No subcommand")), + } +} + +async fn images_list(endpoint_names: Vec<EndpointName>, + _matches: &ArgMatches, + config: &Configuration, +) -> Result<()> { + let mut iter = connect_to_endpoints(config, &endpoint_names) + .await? + .into_iter() + .map(move |ep| async move { ep.images(None).await }) + .collect::<futures::stream::FuturesUnordered<_>>() + .collect::<Result<Vec<_>>>() + .await? + .into_iter() + .flatten(); + + let out = std::io::stdout(); + let mut lock = out.lock(); + + iter.try_for_each(|img| { + writeln!(lock, "{created} {id}", created = img.created(), id = { + if let Some(tags)= img.tags() { + tags.join(", ") + } else { + img.id().clone() + } + }).map_err(Error::from) + }) +} + +async fn images_present(endpoint_names: Vec<EndpointName>, + _matches: &ArgMatches, + config: &Configuration, +) -> Result<()> { + use crate::util::docker::ImageName; + + let eps = connect_to_endpoints(config, &endpoint_names).await?; + + let ep_names_to_images = eps.iter() + .map(|ep| async move { + ep.images(None).await.map(|imgs| { + let img_tags = imgs.filter_map(|img| img.tags().clone().map(Vec::into_iter)) + .flatten() + .map(ImageName::from) + .collect(); + + (ep.name().clone(), img_tags) + }) + }) + .collect::<futures::stream::FuturesUnordered<_>>() + .collect::<Result<Vec<(EndpointName, Vec<ImageName>)>>>() + .await? + .into_iter() + .collect::<HashMap<EndpointName, Vec<ImageName>>>(); + + let out = std::io::stdout(); + let mut lock = out.lock(); + + ep_names_to_images + .iter() + .map(|(ep_name, ep_imgs)| { + config.docker() + .images() + .iter() + .map(|config_img| (ep_imgs.contains(config_img), config_img)) + .try_for_each(|(found, img_name)| { + if found { + writeln!(lock, "found {img} in {ep}", img = img_name, ep = ep_name).map_err(Error::from) + } else { + writeln!(lock, "{img} not found", img = img_name).map_err(Error::from) + } + }) + }) + .collect::<Result<()>>() +} + /// Helper function to connect to all endpoints from the configuration, that appear (by name) in /// the `endpoint_names` list pub(super) async fn connect_to_endpoints(config: &Configuration, endpoint_names: &[EndpointName]) -> Result<Vec<Arc<Endpoint>>> { diff --git a/src/endpoint/configured.rs b/src/endpoint/configured.rs index 6a1e3be..e38258c 100644 --- a/src/endpoint/configured.rs +++ b/src/endpoint/configured.rs @@ -297,6 +297,23 @@ impl Endpoint { Ok(None) } } + + pub async fn images(&self, name_filter: Option<&str>) -> Result<impl Iterator<Item = Image>> { + let mut listopts = shiplift::builder::ImageListOptions::builder(); + + if let Some(name) = name_filter { + listopts.filter_name(name); + } else { + listopts.all(); + } + + self.docker + .images() + .list(&listopts.build()) + .await + .map_err(Error::from) + .map(|v| v.into_iter().map(Image::from)) + } } /// Helper type to store endpoint statistics @@ -358,6 +375,28 @@ impl From<shiplift::rep::Container> for ContainerStat { } } +#[derive(Getters)] +pub struct Image { + #[getset(get = "pub")] + created: chrono::DateTime<chrono::Utc>, + + #[getset(get = "pub")] + id: String, + + #[getset(get = "pub")] + tags: Option<Vec<String>>, +} + +impl From<shiplift::rep::Image> for Image { + fn from(img: shiplift::rep::Image) -> Self { + Image { + created: img.created, + id: img.id, + tags: img.repo_tags, + } + } +} + pub struct EndpointHandle(Arc<Endpoint>); impl EndpointHandle { |