use async_trait::async_trait;
use crate::{
core::{build::build_options::BuildImageOptions, client::Client, error::Result},
BuildableImage,
};
#[async_trait]
pub trait AsyncBuilder<B: BuildableImage> {
async fn build_image(self) -> Result<B::Built>;
async fn build_image_with(self, options: BuildImageOptions) -> Result<B::Built>;
}
#[async_trait]
impl<T> AsyncBuilder<T> for T
where
T: BuildableImage + Send,
{
async fn build_image(self) -> Result<T::Built> {
self.build_image_with(BuildImageOptions::default()).await
}
async fn build_image_with(self, options: BuildImageOptions) -> Result<T::Built> {
let client = Client::lazy_client().await?;
let build_context = self.build_context();
let descriptor = self.descriptor();
client
.build_image(&descriptor, &build_context, options)
.await?;
Ok(self.into_image())
}
}
#[cfg(test)]
mod tests {
use crate::{
core::{BuildImageOptions, WaitFor},
runners::{AsyncBuilder, AsyncRunner},
GenericBuildableImage,
};
#[tokio::test]
async fn build_image_and_run() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let image = GenericBuildableImage::new("hello-tc", "latest")
.with_dockerfile_string(
r#"FROM alpine:latest
COPY --chmod=0755 hello.sh /sbin/hello
ENTRYPOINT ["/sbin/hello"]
"#,
)
.with_data(
r#"#!/bin/sh
echo "hello from hello-tc""#,
"./hello.sh",
)
.build_image()
.await?;
let _container = image
.with_wait_for(WaitFor::message_on_stdout("hello from hello-tc"))
.start()
.await?;
Ok(())
}
#[tokio::test]
async fn build_image_with_options() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let image = GenericBuildableImage::new("hello-tc-with-options", "test")
.with_dockerfile_string(
r#"FROM alpine:latest
COPY --chmod=0755 hello.sh /sbin/hello
ENTRYPOINT ["/sbin/hello"]
"#,
)
.with_data(
r#"#!/bin/sh
echo "hello from build_image_with_options""#,
"./hello.sh",
)
.build_image_with(BuildImageOptions::new().with_no_cache(true))
.await?;
let _container = image
.with_wait_for(WaitFor::message_on_stdout(
"hello from build_image_with_options",
))
.start()
.await?;
Ok(())
}
#[tokio::test]
async fn build_image_skip_if_exists() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let image1 = GenericBuildableImage::new("hello-tc-skip", "test")
.with_dockerfile_string(
r#"FROM alpine:latest
COPY --chmod=0755 hello.sh /sbin/hello
ENTRYPOINT ["/sbin/hello"]
"#,
)
.with_data(
r#"#!/bin/sh
echo "hello from skip test""#,
"./hello.sh",
)
.build_image_with(BuildImageOptions::new())
.await?;
let _container1 = image1
.with_wait_for(WaitFor::message_on_stdout("hello from skip test"))
.start()
.await?;
let image2 = GenericBuildableImage::new("hello-tc-skip", "test")
.with_dockerfile_string(
r#"FROM alpine:latest
COPY --chmod=0755 hello.sh /sbin/hello
ENTRYPOINT ["/sbin/hello"]
"#,
)
.with_data(
r#"#!/bin/sh
echo "hello from skip test""#,
"./hello.sh",
)
.build_image_with(BuildImageOptions::new().with_skip_if_exists(true))
.await?;
let _container2 = image2
.with_wait_for(WaitFor::message_on_stdout("hello from skip test"))
.start()
.await?;
Ok(())
}
#[tokio::test]
async fn build_image_parallel_with_skip_if_exists() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let build_task = || async {
GenericBuildableImage::new("hello-tc-parallel", "test")
.with_dockerfile_string(
r#"FROM alpine:latest
COPY --chmod=0755 hello.sh /sbin/hello
ENTRYPOINT ["/sbin/hello"]
"#,
)
.with_data(
r#"#!/bin/sh
echo "hello from parallel test""#,
"./hello.sh",
)
.build_image_with(BuildImageOptions::new().with_skip_if_exists(true))
.await
};
let (result1, result2, result3) = tokio::join!(build_task(), build_task(), build_task());
let image1 = result1?;
let image2 = result2?;
let image3 = result3?;
let _container1 = image1
.with_wait_for(WaitFor::message_on_stdout("hello from parallel test"))
.start()
.await?;
let _container2 = image2
.with_wait_for(WaitFor::message_on_stdout("hello from parallel test"))
.start()
.await?;
let _container3 = image3
.with_wait_for(WaitFor::message_on_stdout("hello from parallel test"))
.start()
.await?;
Ok(())
}
#[tokio::test]
async fn build_image_with_build_args() -> anyhow::Result<()> {
let _ = pretty_env_logger::try_init();
let image = GenericBuildableImage::new("hello-tc-buildargs", "test")
.with_dockerfile_string(
r#"FROM alpine:latest
ARG VERSION=unknown
ARG BUILD_DATE=unknown
RUN echo "Building with VERSION=${VERSION} DATE=${BUILD_DATE}" > /build_info.txt
COPY --chmod=0755 hello.sh /sbin/hello
ENTRYPOINT ["/sbin/hello"]
"#,
)
.with_data(
r#"#!/bin/sh
cat /build_info.txt"#,
"./hello.sh",
)
.build_image_with(
BuildImageOptions::new()
.with_build_arg("VERSION", "1.0.0")
.with_build_arg("BUILD_DATE", "2024-10-25"),
)
.await?;
let _container = image
.with_wait_for(WaitFor::message_on_stdout("VERSION=1.0.0"))
.start()
.await?;
Ok(())
}
}