summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <matthias.beyer@atos.net>2021-07-27 16:36:37 +0200
committerMatthias Beyer <matthias.beyer@atos.net>2021-07-27 16:36:37 +0200
commit69d2334a5121edbbaa7356428e2d6e8fbd5d3443 (patch)
treee99dbdf04ce67123f2339e12e9a67e5345f9c4fc
parent6c018b14d1ddd2d1d89e62b2e1488ccbcebbaf49 (diff)
parent707898fe04354001130f02a68e1443288d85af3f (diff)
Merge branch 'endpoint-images'
-rw-r--r--src/cli.rs26
-rw-r--r--src/commands/endpoint.rs88
-rw-r--r--src/endpoint/configured.rs39
3 files changed, 153 insertions, 0 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 512e35a..0da6745 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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 {