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    // not having docs here is currently allowed to address the missing docs problem one place at a time. Helping us by documenting just one of these places helps other devs tremendously
32    #[allow(missing_docs)]
33    pub fn with_local_config(self, config: String) -> Self {
34        let mut env_vars = self.env_vars;
35        env_vars.insert(CONSUL_LOCAL_CONFIG.to_owned(), config);
36        Self { env_vars }
37    }
38}
39
40impl Image for Consul {
41    fn name(&self) -> &str {
42        DEFAULT_IMAGE_NAME
43    }
44
45    fn tag(&self) -> &str {
46        DEFAULT_IMAGE_TAG
47    }
48
49    fn ready_conditions(&self) -> Vec<WaitFor> {
50        vec![WaitFor::message_on_stdout("agent: Consul agent running!")]
51    }
52
53    fn env_vars(
54        &self,
55    ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
56        &self.env_vars
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use serde_json::Value;
63
64    use crate::{consul::Consul, testcontainers::runners::AsyncRunner};
65
66    #[tokio::test]
67    async fn consul_container() -> Result<(), Box<dyn std::error::Error + 'static>> {
68        let consul = Consul::default().with_local_config("{\"datacenter\":\"dc-rust\"}".to_owned());
69        let node = consul.start().await?;
70        let port = node.get_host_port_ipv4(8500).await?;
71
72        let response = reqwest::Client::new()
73            .get(format!("http://localhost:{}/v1/agent/self", port))
74            .send()
75            .await
76            .unwrap()
77            .json::<Value>()
78            .await
79            .unwrap();
80        let config = response.as_object().unwrap().get("Config").unwrap();
81        let dc = config
82            .as_object()
83            .unwrap()
84            .get("Datacenter")
85            .unwrap()
86            .as_str()
87            .unwrap();
88        assert_eq!("dc-rust", dc);
89        Ok(())
90    }
91}