torrust_tracker/console/ci/e2e/
tracker_container.rs

1use std::time::Duration;
2
3use rand::distributions::Alphanumeric;
4use rand::Rng;
5
6use super::docker::{RunOptions, RunningContainer};
7use super::logs_parser::RunningServices;
8use crate::console::ci::e2e::docker::Docker;
9
10#[derive(Debug)]
11pub struct TrackerContainer {
12    pub image: String,
13    pub name: String,
14    pub running: Option<RunningContainer>,
15}
16
17impl Drop for TrackerContainer {
18    /// Ensures that the temporary container is removed when the
19    /// struct goes out of scope.
20    fn drop(&mut self) {
21        tracing::info!("Dropping tracker container: {}", self.name);
22        if Docker::container_exist(&self.name) {
23            let _unused = Docker::remove(&self.name);
24        }
25    }
26}
27
28impl TrackerContainer {
29    #[must_use]
30    pub fn new(tag: &str, container_name_prefix: &str) -> Self {
31        Self {
32            image: tag.to_owned(),
33            name: Self::generate_random_container_name(container_name_prefix),
34            running: None,
35        }
36    }
37
38    /// # Panics
39    ///
40    /// Will panic if it can't build the docker image.
41    pub fn build_image(&self) {
42        tracing::info!("Building tracker container image with tag: {} ...", self.image);
43        Docker::build("./Containerfile", &self.image).expect("A tracker local docker image should be built");
44    }
45
46    /// # Panics
47    ///
48    /// Will panic if it can't run the container.
49    pub fn run(&mut self, options: &RunOptions) {
50        tracing::info!("Running docker tracker image: {} ...", self.name);
51
52        let container = Docker::run(&self.image, &self.name, options).expect("A tracker local docker image should be running");
53
54        tracing::info!("Waiting for the container {} to be healthy ...", self.name);
55
56        let is_healthy = Docker::wait_until_is_healthy(&self.name, Duration::from_secs(10));
57
58        assert!(is_healthy, "Unhealthy tracker container: {}", &self.name);
59
60        tracing::info!("Container {} is healthy ...", &self.name);
61
62        self.running = Some(container);
63
64        self.assert_there_are_no_panics_in_logs();
65    }
66
67    /// # Panics
68    ///
69    /// Will panic if it can't get the logs from the running container.
70    #[must_use]
71    pub fn running_services(&self) -> RunningServices {
72        let logs = Docker::logs(&self.name).expect("Logs should be captured from running container");
73
74        tracing::info!("Parsing running services from logs. Logs :\n{logs}");
75
76        RunningServices::parse_from_logs(&logs)
77    }
78
79    /// # Panics
80    ///
81    /// Will panic if it can't stop the container.
82    pub fn stop(&mut self) {
83        match &self.running {
84            Some(container) => {
85                tracing::info!("Stopping docker tracker container: {} ...", self.name);
86
87                Docker::stop(container).expect("Container should be stopped");
88
89                self.assert_there_are_no_panics_in_logs();
90            }
91            None => {
92                if Docker::is_container_running(&self.name) {
93                    tracing::error!("Tracker container {} was started manually", self.name);
94                } else {
95                    tracing::info!("Docker tracker container is not running: {} ...", self.name);
96                }
97            }
98        }
99
100        self.running = None;
101    }
102
103    /// # Panics
104    ///
105    /// Will panic if it can't remove the container.
106    pub fn remove(&self) {
107        if let Some(_running_container) = &self.running {
108            tracing::error!("Can't remove running container: {} ...", self.name);
109        } else {
110            tracing::info!("Removing docker tracker container: {} ...", self.name);
111            Docker::remove(&self.name).expect("Container should be removed");
112        }
113    }
114
115    fn generate_random_container_name(prefix: &str) -> String {
116        let rand_string: String = rand::thread_rng()
117            .sample_iter(&Alphanumeric)
118            .take(20)
119            .map(char::from)
120            .collect();
121
122        format!("{prefix}{rand_string}")
123    }
124
125    fn assert_there_are_no_panics_in_logs(&self) {
126        let logs = Docker::logs(&self.name).expect("Logs should be captured from running container");
127
128        assert!(
129            !(logs.contains(" panicked at ") || logs.contains("RUST_BACKTRACE=1")),
130            "{}",
131            format!("Panics found is logs:\n{logs}")
132        );
133    }
134}