From 5060ec814087eaa5a42e834561358548feb1a909 Mon Sep 17 00:00:00 2001 From: ryutamago Date: Thu, 12 Sep 2024 13:51:49 +0100 Subject: [PATCH 1/2] feat(jstzd): implement pull image --- Cargo.lock | 2 ++ crates/jstzd/Cargo.toml | 5 +++- crates/jstzd/src/docker/image.rs | 51 ++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b90dd1d09..b1e262e59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2666,7 +2666,9 @@ dependencies = [ "async-dropper", "async-trait", "bollard", + "env_logger", "futures-util", + "log", "tokio", ] diff --git a/crates/jstzd/Cargo.toml b/crates/jstzd/Cargo.toml index b081273b1..667c3d31c 100644 --- a/crates/jstzd/Cargo.toml +++ b/crates/jstzd/Cargo.toml @@ -10,9 +10,12 @@ anyhow.workspace = true async-dropper.workspace = true async-trait.workspace = true bollard.workspace = true +env_logger.workspace = true futures-util.workspace = true tokio.workspace = true +log.workspace = true [[bin]] name = "jstzd" -path = "src/main.rs" \ No newline at end of file +path = "src/main.rs" + diff --git a/crates/jstzd/src/docker/image.rs b/crates/jstzd/src/docker/image.rs index b55179829..1b4489f57 100644 --- a/crates/jstzd/src/docker/image.rs +++ b/crates/jstzd/src/docker/image.rs @@ -1,3 +1,12 @@ +use anyhow::Result; +use bollard::{ + image::{CreateImageOptions, ListImagesOptions}, + Docker, +}; +use futures_util::StreamExt; +use log::info; +use std::{collections::HashMap, sync::Arc}; +#[async_trait::async_trait] pub trait Image: Sized { const LATEST_TAG: &'static str = "latest"; fn image_name(&self) -> &str; @@ -15,6 +24,48 @@ pub trait Image: Sized { fn exposed_ports(&self) -> &[u16] { &[] } + async fn pull_image(&self, client: Arc) -> Result<()> { + if Self::image_exists(self, client.clone()).await { + info!("Image: {:?} already exists ", self.image_name()); + return Ok(()); + } + self.create_image(client.clone()).await + } + async fn image_exists(&self, client: Arc) -> bool { + let filters = [("reference".to_string(), vec![self.image_uri()])] + .into_iter() + .collect::>(); + let images = &client + .list_images(Some(ListImagesOptions:: { + all: true, + filters, + ..Default::default() + })) + .await; + match images { + Ok(images) => !images.is_empty(), + Err(_) => false, + } + } + async fn create_image(&self, client: Arc) -> anyhow::Result<()> { + let options = Some(CreateImageOptions { + from_image: self.image_name(), + tag: self.image_tag(), + ..Default::default() + }); + let mut stream = client.create_image(options, None, None); + while let Some(create_info) = stream.next().await { + match create_info { + Ok(info) => { + if let Some(status) = info.status { + println!("{:?}", status) + } + } + Err(e) => return Err(e.into()), + } + } + Ok(()) + } } pub struct GenericImage { From df8eb4d0db0fe50737597e077ccf0387b2f17b9c Mon Sep 17 00:00:00 2001 From: ryutamago Date: Tue, 17 Sep 2024 12:56:41 +0100 Subject: [PATCH 2/2] test(jstzd): add integration test for pulling image --- Cargo.lock | 1 - crates/jstzd/Cargo.toml | 1 - crates/jstzd/src/docker/image.rs | 2 +- crates/jstzd/tests/pull_image.rs | 41 ++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 crates/jstzd/tests/pull_image.rs diff --git a/Cargo.lock b/Cargo.lock index b1e262e59..021dae973 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2666,7 +2666,6 @@ dependencies = [ "async-dropper", "async-trait", "bollard", - "env_logger", "futures-util", "log", "tokio", diff --git a/crates/jstzd/Cargo.toml b/crates/jstzd/Cargo.toml index 667c3d31c..4270d7d91 100644 --- a/crates/jstzd/Cargo.toml +++ b/crates/jstzd/Cargo.toml @@ -10,7 +10,6 @@ anyhow.workspace = true async-dropper.workspace = true async-trait.workspace = true bollard.workspace = true -env_logger.workspace = true futures-util.workspace = true tokio.workspace = true log.workspace = true diff --git a/crates/jstzd/src/docker/image.rs b/crates/jstzd/src/docker/image.rs index 1b4489f57..0ebd0b6c6 100644 --- a/crates/jstzd/src/docker/image.rs +++ b/crates/jstzd/src/docker/image.rs @@ -26,7 +26,7 @@ pub trait Image: Sized { } async fn pull_image(&self, client: Arc) -> Result<()> { if Self::image_exists(self, client.clone()).await { - info!("Image: {:?} already exists ", self.image_name()); + info!("Image: {:?} already exists ", self.image_uri()); return Ok(()); } self.create_image(client.clone()).await diff --git a/crates/jstzd/tests/pull_image.rs b/crates/jstzd/tests/pull_image.rs new file mode 100644 index 000000000..e7599aecf --- /dev/null +++ b/crates/jstzd/tests/pull_image.rs @@ -0,0 +1,41 @@ +use std::{collections::HashMap, sync::Arc}; + +use anyhow::Result; +use bollard::{image::ListImagesOptions, secret::ImageSummary, Docker}; +use jstzd::docker::{GenericImage, Image}; + +// search image locally +async fn search_local_image( + image_uri: String, + client: Arc, +) -> Result> { + let filters = [("reference".to_string(), vec![image_uri])] + .into_iter() + .collect::>(); + let images = &client + .list_images(Some(ListImagesOptions:: { + all: true, + filters, + ..Default::default() + })) + .await; + match images { + Ok(images) => Ok(images.clone()), + Err(_) => Err(anyhow::anyhow!("Image not found")), + } +} + +#[tokio::test] +async fn test_pull_image() -> Result<()> { + let docker = Docker::connect_with_socket_defaults().unwrap(); + let docker = Arc::new(docker); + let image = GenericImage::new("busybox").with_tag("stable"); + let _ = docker.remove_image(&image.image_uri(), None, None).await; + image.pull_image(docker.clone()).await?; + let expected_image_digest = + "sha256:7db2ddde018a2a56e929855445bc7f30bc83db514a23404bd465a07d2770ac5f"; + let images = search_local_image(image.image_uri(), docker.clone()).await?; + assert!(images.iter().any(|image| image.id == expected_image_digest)); + let _ = docker.remove_image(&image.image_uri(), None, None).await; + Ok(()) +}