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}