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}