diff --git a/src/docker.rs b/src/docker.rs index e7e8db7..ab0887f 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -42,7 +42,26 @@ pub async fn get_images_from_docker_daemon( references: &Option>, ) -> Vec { let client: Docker = create_docker_client(ctx.config.socket.as_deref()); - match references { + let mut swarm_images = match client.list_services::(None).await { + Ok(services) => services + .iter() + .filter_map(|service| match &service.spec { + Some(service_spec) => match &service_spec.task_template { + Some(task_spec) => match &task_spec.container_spec { + Some(container_spec) => match &container_spec.image { + Some(image) => Image::from_inspect_data(image), + None => None, + }, + None => None, + }, + None => None, + }, + None => None, + }) + .collect(), + Err(_) => Vec::new(), + }; + let mut local_images = match references { Some(refs) => { let mut inspect_handles = Vec::with_capacity(refs.len()); for reference in refs { @@ -69,7 +88,9 @@ pub async fn get_images_from_docker_daemon( images .iter() .filter_map(|image| Image::from_inspect_data(image.clone())) - .collect() + .collect::>() } - } + }; + local_images.append(&mut swarm_images); + local_images } diff --git a/src/structs/image.rs b/src/structs/image.rs index fe31222..5617b26 100644 --- a/src/structs/image.rs +++ b/src/structs/image.rs @@ -49,6 +49,9 @@ impl Image { let digests = image.digests().unwrap(); if !tags.is_empty() && !digests.is_empty() { let reference = tags[0].clone(); + if reference.contains('@') { + return None; // As far as I know, references that contain @ are either manually pulled by the user or automatically created because of swarm. In the first case AFAICT we can't know what tag was originally pulled, so we'd have to make assumptions and I've decided to remove this. The other case is already handled seperately, so this also ensures images aren't displayed twice, once with and once without a digest. + }; let (registry, repository, tag) = split(&reference); let version_tag = Version::from_tag(&tag); let local_digests = digests diff --git a/src/structs/inspectdata.rs b/src/structs/inspectdata.rs index 52b1299..c4640e1 100644 --- a/src/structs/inspectdata.rs +++ b/src/structs/inspectdata.rs @@ -1,18 +1,18 @@ use bollard::secret::{ImageInspect, ImageSummary}; pub trait InspectData { - fn tags(&self) -> Option<&Vec>; - fn digests(&self) -> Option<&Vec>; + fn tags(&self) -> Option>; + fn digests(&self) -> Option>; fn url(&self) -> Option; } impl InspectData for ImageInspect { - fn tags(&self) -> Option<&Vec> { - self.repo_tags.as_ref() + fn tags(&self) -> Option> { + self.repo_tags.clone() } - fn digests(&self) -> Option<&Vec> { - self.repo_digests.as_ref() + fn digests(&self) -> Option> { + self.repo_digests.clone() } fn url(&self) -> Option { @@ -27,15 +27,36 @@ impl InspectData for ImageInspect { } impl InspectData for ImageSummary { - fn tags(&self) -> Option<&Vec> { - Some(&self.repo_tags) + fn tags(&self) -> Option> { + Some(self.repo_tags.clone()) } - fn digests(&self) -> Option<&Vec> { - Some(&self.repo_digests) + fn digests(&self) -> Option> { + Some(self.repo_digests.clone()) } fn url(&self) -> Option { self.labels.get("org.opencontainers.image.url").cloned() } } + +impl InspectData for &String { + fn tags(&self) -> Option> { + self.split('@').next().map(|tag| vec![tag.to_string()]) + } + + fn digests(&self) -> Option> { + match self.split_once('@') { + Some((reference, digest)) => Some(vec![format!( + "{}@{}", + reference.split(':').next().unwrap(), + digest + )]), + None => Some(vec![]), + } + } + + fn url(&self) -> Option { + None + } +}