Skip to main content

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