testcontainers/images/
generic.rs

1use crate::{
2    core::{ports::ContainerPort, WaitFor},
3    Image,
4};
5
6/// A configurable image from which a [`Container`] or [`ContainerAsync`] can be started.
7///
8/// The various methods on this struct allow for configuring the resulting container using the
9/// builder pattern. Further configuration is available through the [`ImageExt`] extension trait.
10/// Make sure to invoke the configuration methods on [`GenericImage`] first, before those from
11/// [`ImageExt`].
12///
13/// For example:
14///
15/// ```
16/// use testcontainers::{
17///     core::{IntoContainerPort, WaitFor}, runners::AsyncRunner, GenericImage, ImageExt
18/// };
19///
20/// # /*
21/// #[tokio::test]
22/// # */
23/// async fn test_redis() {
24///     let container = GenericImage::new("redis", "7.2.4")
25///         .with_exposed_port(6379.tcp())
26///         .with_wait_for(WaitFor::message_on_stdout("Ready to accept connections"))
27///         .with_network("bridge")
28///         .with_env_var("DEBUG", "1")
29///         .start()
30///         .await
31///         .expect("Redis started");
32/// #   container.stop().await.unwrap();
33/// }
34/// # let rt = tokio::runtime::Runtime::new().unwrap();
35/// # rt.block_on(test_redis());
36/// ```
37///
38/// The extension traits [`SyncRunner`] and [`AsyncRunner`] each provide the method `start()` to
39/// start the container once it is configured.
40///
41/// [`Container`]: crate::Container
42/// [`ContainerAsync`]: crate::ContainerAsync
43/// [`ImageExt`]: crate::core::ImageExt
44/// [`SyncRunner`]: crate::runners::SyncRunner
45/// [`AsyncRunner`]: crate::runners::AsyncRunner
46#[must_use]
47#[derive(Debug, Clone)]
48pub struct GenericImage {
49    name: String,
50    tag: String,
51    wait_for: Vec<WaitFor>,
52    entrypoint: Option<String>,
53    exposed_ports: Vec<ContainerPort>,
54}
55
56impl GenericImage {
57    pub fn new<S: Into<String>>(name: S, tag: S) -> GenericImage {
58        Self {
59            name: name.into(),
60            tag: tag.into(),
61            wait_for: Vec::new(),
62            entrypoint: None,
63            exposed_ports: Vec::new(),
64        }
65    }
66
67    pub fn with_wait_for(mut self, wait_for: WaitFor) -> Self {
68        self.wait_for.push(wait_for);
69        self
70    }
71
72    pub fn with_entrypoint(mut self, entrypoint: &str) -> Self {
73        self.entrypoint = Some(entrypoint.to_string());
74        self
75    }
76
77    pub fn with_exposed_port(mut self, port: ContainerPort) -> Self {
78        self.exposed_ports.push(port);
79        self
80    }
81}
82
83impl Image for GenericImage {
84    fn name(&self) -> &str {
85        &self.name
86    }
87
88    fn tag(&self) -> &str {
89        &self.tag
90    }
91
92    fn ready_conditions(&self) -> Vec<WaitFor> {
93        self.wait_for.clone()
94    }
95
96    fn entrypoint(&self) -> Option<&str> {
97        self.entrypoint.as_deref()
98    }
99
100    fn expose_ports(&self) -> &[ContainerPort] {
101        &self.exposed_ports
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::ImageExt;
109
110    #[test]
111    fn should_return_env_vars() {
112        let image = GenericImage::new("hello-world", "latest")
113            .with_env_var("one-key", "one-value")
114            .with_env_var("two-key", "two-value");
115
116        let mut env_vars = image.env_vars();
117        let (first_key, first_value) = env_vars.next().unwrap();
118        let (second_key, second_value) = env_vars.next().unwrap();
119
120        assert_eq!(first_key, "one-key");
121        assert_eq!(first_value, "one-value");
122        assert_eq!(second_key, "two-key");
123        assert_eq!(second_value, "two-value");
124    }
125}