testcontainers/runners/
async_builder.rs

1use async_trait::async_trait;
2
3use crate::{
4    core::{client::Client, error::Result},
5    BuildableImage,
6};
7
8#[async_trait]
9pub trait AsyncBuilder<B: BuildableImage> {
10    async fn build_image(self) -> Result<B::Built>;
11}
12
13#[async_trait]
14/// Helper trait to build Docker images asynchronously from [`BuildableImage`] instances.
15///
16/// Provides an asynchronous interface for building custom Docker images within test environments.
17/// This trait is automatically implemented for any type that implements [`BuildableImage`] + [`Send`].
18///
19/// # Example
20///
21/// ```rust,no_run
22/// use testcontainers::{core::WaitFor, runners::AsyncBuilder, runners::AsyncRunner, GenericBuildableImage};
23///
24/// #[test]
25/// async fn test_custom_image() -> anyhow::Result<()> {
26///     let image = GenericBuildableImage::new("my-test-app", "latest")
27///         .with_dockerfile_string("FROM alpine:latest\nRUN echo 'hello'")
28///         .build_image()?.await;
29///     // Use the built image in containers
30///     let container = image
31///         .with_wait_for(WaitFor::message_on_stdout("Hello from test!"))
32///         .start()?.await;
33///
34///     Ok(())
35/// }
36/// ```
37impl<T> AsyncBuilder<T> for T
38where
39    T: BuildableImage + Send,
40{
41    async fn build_image(self) -> Result<T::Built> {
42        let client = Client::lazy_client().await?;
43
44        // Get build context and image descriptor from the buildable image
45        let build_context = self.build_context();
46        let descriptor = self.descriptor();
47
48        client.build_image(&descriptor, &build_context).await?;
49
50        // consume the BuildableImage into an Image for running
51        Ok(self.into_image())
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use crate::{
58        core::WaitFor,
59        runners::{AsyncBuilder, AsyncRunner},
60        GenericBuildableImage,
61    };
62
63    #[tokio::test]
64    async fn build_image_and_run() -> anyhow::Result<()> {
65        let _ = pretty_env_logger::try_init();
66
67        let image = GenericBuildableImage::new("hello-tc", "latest")
68            .with_dockerfile_string(
69                r#"FROM alpine:latest
70COPY --chmod=0755 hello.sh /sbin/hello
71ENTRYPOINT ["/sbin/hello"]
72"#,
73            )
74            .with_data(
75                r#"#!/bin/sh
76echo "hello from hello-tc""#,
77                "./hello.sh",
78            )
79            .build_image()
80            .await?;
81
82        let _container = image
83            .with_wait_for(WaitFor::message_on_stdout("hello from hello-tc"))
84            .start()
85            .await?;
86
87        Ok(())
88    }
89}