testcontainers_avk/core/container.rs
1use crate::{
2 core::{env::Command, logs::LogStream, ports::Ports, ExecCommand, WaitFor},
3 Image, RunnableImage,
4};
5use bollard_stubs::models::ContainerInspectResponse;
6use std::{fmt, marker::PhantomData, net::IpAddr, str::FromStr};
7
8/// Represents a running docker container.
9///
10/// Containers have a [`custom destructor`][drop_impl] that removes them as soon as they go out of scope:
11///
12/// ```rust
13/// use testcontainers::*;
14/// #[test]
15/// fn a_test() {
16/// let docker = clients::Cli::default();
17///
18/// {
19/// let container = docker.run(MyImage::default());
20///
21/// // Docker container is stopped/removed at the end of this scope.
22/// }
23/// }
24///
25/// ```
26///
27/// [drop_impl]: struct.Container.html#impl-Drop
28pub struct Container<'d, I: Image> {
29 id: String,
30 docker_client: Box<dyn Docker>,
31 image: RunnableImage<I>,
32 command: Command,
33 ports: Ports,
34 /// Tracks the lifetime of the client to make sure the container is dropped before the client.
35 client_lifetime: PhantomData<&'d ()>,
36}
37
38impl<'d, I> fmt::Debug for Container<'d, I>
39where
40 I: fmt::Debug + Image,
41{
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 f.debug_struct("Container")
44 .field("id", &self.id)
45 .field("image", &self.image)
46 .field("command", &self.command)
47 .finish()
48 }
49}
50
51impl<'d, I> Container<'d, I>
52where
53 I: Image,
54{
55 /// Constructs a new container given an id, a docker client and the image.
56 ///
57 /// This function will block the current thread (if [`wait_until_ready`] is implemented correctly) until the container is actually ready to be used.
58 ///
59 /// [`wait_until_ready`]: trait.Image.html#tymethod.wait_until_ready
60 pub(crate) fn new(
61 id: String,
62 docker_client: impl Docker + 'static,
63 image: RunnableImage<I>,
64 command: Command,
65 ) -> Self {
66 let ports = docker_client.ports(&id);
67 Self {
68 id,
69 docker_client: Box::new(docker_client),
70 image,
71 command,
72 ports,
73 client_lifetime: PhantomData,
74 }
75 }
76
77 /// Returns a reference to the [`Image`] of this container.
78 ///
79 /// [`Image`]: trait.Image.html
80 pub fn image(&self) -> &I {
81 self.image.inner()
82 }
83
84 /// Returns a reference to the [`arguments`] of the [`Image`] of this container.
85 ///
86 /// Access to this is useful to retrieve relevant information which had been passed as [`arguments`]
87 ///
88 /// [`Image`]: trait.Image.html
89 /// [`arguments`]: trait.Image.html#associatedtype.Args
90 pub fn image_args(&self) -> &I::Args {
91 self.image.args()
92 }
93
94 pub fn ports(&self) -> Ports {
95 self.ports.clone()
96 }
97}
98
99impl<'d, I> Container<'d, I>
100where
101 I: Image,
102{
103 /// Returns the id of this container.
104 pub fn id(&self) -> &str {
105 &self.id
106 }
107
108 /// Returns the mapped host port for an internal port of this docker container.
109 ///
110 /// This method does **not** magically expose the given port, it simply performs a mapping on
111 /// the already exposed ports. If a docker container does not expose a port, this method will panic.
112 ///
113 /// # Panics
114 ///
115 /// This method panics if the given port is not mapped.
116 /// Testcontainers is designed to be used in tests only. If a certain port is not mapped, the container
117 /// is unlikely to be useful.
118 pub fn get_host_port(&self, internal_port: u16) -> u16 {
119 self.ports
120 .map_to_host_port(internal_port)
121 .unwrap_or_else(|| {
122 panic!(
123 "container {} does not expose port {}",
124 self.id, internal_port
125 )
126 })
127 }
128
129 /// Returns the bridge ip address of docker container as specified in NetworkSettings.IPAddress
130 pub fn get_bridge_ip_address(&self) -> IpAddr {
131 IpAddr::from_str(
132 &self
133 .docker_client
134 .inspect(&self.id)
135 .network_settings
136 .unwrap_or_default()
137 .ip_address
138 .unwrap_or_default(),
139 )
140 .unwrap_or_else(|_| panic!("container {} has missing or invalid bridge IP", self.id))
141 }
142
143 pub fn exec(&self, cmd: ExecCommand) {
144 let ExecCommand {
145 cmd,
146 ready_conditions,
147 } = cmd;
148
149 log::debug!("Executing command {:?}", cmd);
150
151 self.docker_client.exec(self.id(), cmd);
152
153 self.docker_client
154 .block_until_ready(self.id(), ready_conditions);
155 }
156
157 pub fn stop(&self) {
158 log::debug!("Stopping docker container {}", self.id);
159
160 self.docker_client.stop(&self.id)
161 }
162
163 pub fn start(&self) {
164 self.docker_client.start(&self.id);
165 }
166
167 pub fn rm(&self) {
168 log::debug!("Deleting docker container {}", self.id);
169
170 self.docker_client.rm(&self.id)
171 }
172}
173
174/// The destructor implementation for a Container.
175///
176/// As soon as the container goes out of scope, the destructor will either only stop or delete the docker container, depending on the [`Command`] value.
177///
178/// Setting it to `keep` will stop container.
179/// Setting it to `remove` will remove it.
180impl<'d, I> Drop for Container<'d, I>
181where
182 I: Image,
183{
184 fn drop(&mut self) {
185 match self.command {
186 Command::Keep => {}
187 Command::Remove => self.rm(),
188 }
189 }
190}
191
192/// Defines operations that we need to perform on docker containers and other entities.
193///
194/// This trait is pub(crate) because it should not be used directly by users but only represents an internal abstraction that allows containers to be generic over the client they have been started with.
195/// All functionality of this trait is available on [`Container`]s directly.
196pub(crate) trait Docker {
197 fn stdout_logs(&self, id: &str) -> LogStream;
198 fn stderr_logs(&self, id: &str) -> LogStream;
199 fn ports(&self, id: &str) -> Ports;
200 fn inspect(&self, id: &str) -> ContainerInspectResponse;
201 fn rm(&self, id: &str);
202 fn stop(&self, id: &str);
203 fn start(&self, id: &str);
204 fn exec(&self, id: &str, cmd: String);
205 fn block_until_ready(&self, id: &str, ready_conditions: Vec<WaitFor>);
206}