testcontainers_modules/cockroach_db/
mod.rs

1use std::borrow::Cow;
2
3use testcontainers::{core::WaitFor, Image};
4
5const DEFAULT_IMAGE_NAME: &str = "cockroachdb/cockroach";
6const DEFAULT_IMAGE_TAG: &str = "v23.2.3";
7
8/// Module to work with [`Cockroach DB`] inside of tests.
9///
10/// This module is based on the official [`Cockroach docker image`].
11///
12/// # Example
13/// ```
14/// use testcontainers_modules::{cockroach_db, testcontainers::runners::SyncRunner};
15///
16/// let cockroach = cockroach_db::CockroachDb::default().start().unwrap();
17/// let http_port = cockroach.get_host_port_ipv4(26257).unwrap();
18///
19/// // do something with the started cockroach instance..
20/// ```
21///
22/// [`Cockroach`]: https://www.cockroachlabs.com/
23/// [`Cockroach docker image`]: https://hub.docker.com/r/cockroachdb/cockroach
24/// [`Cockroach commands`]: https://www.cockroachlabs.com/docs/stable/cockroach-commands
25#[derive(Debug, Default, Clone)]
26pub struct CockroachDb {
27    cmd: CockroachDbCmd,
28}
29
30impl CockroachDb {
31    /// Create a new instance of a CockroachDb image.
32    pub fn new(cmd: CockroachDbCmd) -> Self {
33        CockroachDb { cmd }
34    }
35}
36
37/// Specifies the command how CockroachDb should be started
38#[derive(Debug, Clone, Copy)]
39pub enum CockroachDbCmd {
40    /// Start a single CockroachDB node
41    StartSingleNode {
42        /// `insecure` being set indicates that the container is intended for ***non-production
43        /// testing only***. To run CockroachDB in production, use a secure cluster instead.
44        ///
45        /// Start a node with all security controls disabled.
46        /// There is no encryption, no authentication and internal security checks are also disabled.
47        /// This makes any client able to take over the entire cluster.
48        /// This flag is only intended for non-production testing.
49        ///
50        /// Beware that using this flag on a public network while exposing the port is likely to
51        /// cause the entire host container to become compromised.
52        ///
53        /// To simply accept non-TLS connections for SQL clients while keeping the cluster secure,
54        /// consider using `--accept-sql-without-tls` instead.
55        /// Also see: <https://go.crdb.dev/issue-v/53404/v24.2>
56        insecure: bool,
57    },
58}
59
60impl Default for CockroachDbCmd {
61    fn default() -> Self {
62        Self::StartSingleNode { insecure: true }
63    }
64}
65
66impl Image for CockroachDb {
67    fn name(&self) -> &str {
68        DEFAULT_IMAGE_NAME
69    }
70
71    fn tag(&self) -> &str {
72        DEFAULT_IMAGE_TAG
73    }
74
75    fn ready_conditions(&self) -> Vec<WaitFor> {
76        vec![WaitFor::message_on_stdout("CockroachDB node starting at")]
77    }
78
79    fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
80        self.cmd
81    }
82}
83
84impl IntoIterator for CockroachDbCmd {
85    type Item = String;
86    type IntoIter = <Vec<String> as IntoIterator>::IntoIter;
87
88    fn into_iter(self) -> Self::IntoIter {
89        match self {
90            CockroachDbCmd::StartSingleNode { insecure } => {
91                let mut cmd = vec!["start-single-node".to_string()];
92                if insecure {
93                    cmd.push("--insecure".to_string());
94                }
95                cmd.into_iter()
96            }
97        }
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use testcontainers::core::IntoContainerPort;
104
105    use super::*;
106    use crate::testcontainers::runners::SyncRunner;
107
108    #[test]
109    fn cockroach_db_one_plus_one() -> Result<(), Box<dyn std::error::Error + 'static>> {
110        let cockroach = CockroachDb::default();
111        let node = cockroach.start()?;
112
113        let connection_string = &format!(
114            "postgresql://root@127.0.0.1:{}/defaultdb?sslmode=disable",
115            node.get_host_port_ipv4(26257.tcp())?
116        );
117        let mut conn = postgres::Client::connect(connection_string, postgres::NoTls).unwrap();
118
119        let rows = conn.query("SELECT 1 + 1", &[]).unwrap();
120        assert_eq!(rows.len(), 1);
121
122        let first_row = &rows[0];
123        let first_column: i64 = first_row.get(0);
124        assert_eq!(first_column, 2);
125        Ok(())
126    }
127}