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}