testcontainers_modules/consul/
mod.rs

1use std::{borrow::Cow, collections::BTreeMap};
2
3use testcontainers::{core::WaitFor, Image};
4
5const DEFAULT_IMAGE_NAME: &str = "hashicorp/consul";
6const DEFAULT_IMAGE_TAG: &str = "1.16.1";
7const CONSUL_LOCAL_CONFIG: &str = "CONSUL_LOCAL_CONFIG";
8
9/// Module to work with [`Consul`] inside of tests.
10///
11/// This module is based on the official [`Consul docker image`].
12///
13/// # Example
14/// ```
15/// use testcontainers_modules::{consul, testcontainers::runners::SyncRunner};
16///
17/// let consul = consul::Consul::default().start().unwrap();
18/// let http_port = consul.get_host_port_ipv4(8500).unwrap();
19///
20/// // do something with the started consul instance..
21/// ```
22///
23/// [`Consul`]: https://www.consul.io/
24/// [`Consul docker image`]: https://hub.docker.com/r/hashicorp/consul
25#[derive(Debug, Default, Clone)]
26pub struct Consul {
27    env_vars: BTreeMap<String, String>,
28}
29
30impl Consul {
31    /// Passes a JSON string of configuration options to the Consul agent
32    ///
33    /// For details on which options are avalible, please see [the consul docs](https://developer.hashicorp.com/consul/docs/reference/agent/configuration-file).
34    ///
35    /// # Example
36    /// ```
37    /// use testcontainers_modules::{consul, testcontainers::runners::SyncRunner};
38    ///
39    /// let consul = consul::Consul::default()
40    ///     .with_local_config(
41    ///         r#"{
42    ///         "datacenter": "us_west",
43    ///         "server": true,
44    ///         "enable_debug": true
45    ///     }"#,
46    ///     )
47    ///     .start()
48    ///     .unwrap();
49    /// ```
50    pub fn with_local_config(self, config: impl ToString) -> Self {
51        let mut env_vars = self.env_vars;
52        env_vars.insert(CONSUL_LOCAL_CONFIG.to_owned(), config.to_string());
53        Self { env_vars }
54    }
55}
56
57impl Image for Consul {
58    fn name(&self) -> &str {
59        DEFAULT_IMAGE_NAME
60    }
61
62    fn tag(&self) -> &str {
63        DEFAULT_IMAGE_TAG
64    }
65
66    fn ready_conditions(&self) -> Vec<WaitFor> {
67        vec![WaitFor::message_on_stdout("agent: Consul agent running!")]
68    }
69
70    fn env_vars(
71        &self,
72    ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
73        &self.env_vars
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use serde_json::Value;
80
81    use crate::{consul::Consul, testcontainers::runners::AsyncRunner};
82
83    #[tokio::test]
84    async fn consul_container() -> Result<(), Box<dyn std::error::Error + 'static>> {
85        let consul = Consul::default().with_local_config("{\"datacenter\":\"dc-rust\"}".to_owned());
86        let node = consul.start().await?;
87        let port = node.get_host_port_ipv4(8500).await?;
88
89        let response = reqwest::Client::new()
90            .get(format!("http://localhost:{}/v1/agent/self", port))
91            .send()
92            .await
93            .unwrap()
94            .json::<Value>()
95            .await
96            .unwrap();
97        let config = response.as_object().unwrap().get("Config").unwrap();
98        let dc = config
99            .as_object()
100            .unwrap()
101            .get("Datacenter")
102            .unwrap()
103            .as_str()
104            .unwrap();
105        assert_eq!("dc-rust", dc);
106        Ok(())
107    }
108}