testcontainers_modules/google_cloud_sdk_emulators/
mod.rs

1use std::borrow::Cow;
2
3use testcontainers::{
4    core::{ContainerPort, WaitFor},
5    Image,
6};
7
8const NAME: &str = "google/cloud-sdk";
9const TAG: &str = "362.0.0-emulators";
10
11const HOST: &str = "0.0.0.0";
12/// Port that the [`Bigtable`] emulator container has internally
13/// Can be rebound externally via [`testcontainers::core::ImageExt::with_mapped_port`]
14///
15/// [`Bigtable`]: https://cloud.google.com/bigtable
16pub const BIGTABLE_PORT: u16 = 8086;
17/// Port that the [`Datastore`] emulator container has internally
18/// Can be rebound externally via [`testcontainers::core::ImageExt::with_mapped_port`]
19///
20/// [`Datastore`]: https://cloud.google.com/datastore
21pub const DATASTORE_PORT: u16 = 8081;
22/// Port that the [`Firestore`] emulator container has internally
23/// Can be rebound externally via [`testcontainers::core::ImageExt::with_mapped_port`]
24///
25/// [`Firestore`]: https://cloud.google.com/firestore
26pub const FIRESTORE_PORT: u16 = 8080;
27/// Port that the [`Pub/Sub`] emulator container has internally
28/// Can be rebound externally via [`testcontainers::core::ImageExt::with_mapped_port`]
29///
30/// [`Pub/Sub`]: https://cloud.google.com/pubsub
31pub const PUBSUB_PORT: u16 = 8085;
32/// Port that the [`Spanner`] emulator container has internally
33/// Can be rebound externally via [`testcontainers::core::ImageExt::with_mapped_port`]
34///
35/// [`Spanner`]: https://cloud.google.com/spanner
36pub const SPANNER_PORT: u16 = 9010;
37
38#[allow(missing_docs)]
39// 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
40#[derive(Debug, Clone)]
41pub struct CloudSdkCmd {
42    pub host: String,
43    pub port: u16,
44    pub emulator: Emulator,
45}
46
47#[allow(missing_docs)]
48// 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
49#[derive(Debug, Clone, Eq, PartialEq)]
50pub enum Emulator {
51    Bigtable,
52    Datastore { project: String },
53    Firestore,
54    PubSub,
55    Spanner,
56}
57
58impl IntoIterator for &CloudSdkCmd {
59    type Item = String;
60    type IntoIter = <Vec<String> as IntoIterator>::IntoIter;
61
62    fn into_iter(self) -> Self::IntoIter {
63        let (emulator, project) = match &self.emulator {
64            Emulator::Bigtable => ("bigtable", None),
65            Emulator::Datastore { project } => ("datastore", Some(project)),
66            Emulator::Firestore => ("firestore", None),
67            Emulator::PubSub => ("pubsub", None),
68            Emulator::Spanner => ("spanner", None),
69        };
70        let mut args = vec![
71            "gcloud".to_owned(),
72            "beta".to_owned(),
73            "emulators".to_owned(),
74            emulator.to_owned(),
75            "start".to_owned(),
76        ];
77        if let Some(project) = project {
78            args.push("--project".to_owned());
79            args.push(project.to_owned());
80        }
81        args.push("--host-port".to_owned());
82        args.push(format!("{}:{}", self.host, self.port));
83
84        args.into_iter()
85    }
86}
87
88#[allow(missing_docs)]
89// 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
90#[derive(Debug, Clone)]
91pub struct CloudSdk {
92    exposed_ports: Vec<ContainerPort>,
93    ready_condition: WaitFor,
94    cmd: CloudSdkCmd,
95}
96
97impl Image for CloudSdk {
98    fn name(&self) -> &str {
99        NAME
100    }
101
102    fn tag(&self) -> &str {
103        TAG
104    }
105
106    fn ready_conditions(&self) -> Vec<WaitFor> {
107        vec![self.ready_condition.clone()]
108    }
109
110    fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
111        &self.cmd
112    }
113
114    fn expose_ports(&self) -> &[ContainerPort] {
115        &self.exposed_ports
116    }
117}
118
119impl CloudSdk {
120    fn new(port: u16, emulator: Emulator, ready_condition: WaitFor) -> Self {
121        let cmd = CloudSdkCmd {
122            host: HOST.to_owned(),
123            port,
124            emulator,
125        };
126        Self {
127            exposed_ports: vec![ContainerPort::Tcp(port)],
128            ready_condition,
129            cmd,
130        }
131    }
132
133    // 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
134    #[allow(missing_docs)]
135    pub fn bigtable() -> Self {
136        Self::new(
137            BIGTABLE_PORT,
138            Emulator::Bigtable,
139            WaitFor::message_on_stderr("[bigtable] Cloud Bigtable emulator running on"),
140        )
141    }
142
143    // 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
144    #[allow(missing_docs)]
145    pub fn firestore() -> Self {
146        Self::new(
147            FIRESTORE_PORT,
148            Emulator::Firestore,
149            WaitFor::message_on_stderr("[firestore] Dev App Server is now running"),
150        )
151    }
152
153    // 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
154    #[allow(missing_docs)]
155    pub fn datastore(project: impl Into<String>) -> Self {
156        let project = project.into();
157        Self::new(
158            DATASTORE_PORT,
159            Emulator::Datastore { project },
160            WaitFor::message_on_stderr("[datastore] Dev App Server is now running"),
161        )
162    }
163
164    // 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
165    #[allow(missing_docs)]
166    pub fn pubsub() -> Self {
167        Self::new(
168            PUBSUB_PORT,
169            Emulator::PubSub,
170            WaitFor::message_on_stderr("[pubsub] INFO: Server started, listening on"),
171        )
172    }
173
174    // 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
175    #[allow(missing_docs)]
176    pub fn spanner() -> Self {
177        Self::new(
178            SPANNER_PORT, // gRPC port
179            Emulator::Spanner,
180            WaitFor::message_on_stderr("Cloud Spanner emulator running"),
181        )
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use std::ops::Range;
188
189    use crate::{google_cloud_sdk_emulators, testcontainers::runners::SyncRunner};
190
191    const RANDOM_PORTS: Range<u16> = 32768..65535;
192
193    #[test]
194    fn bigtable_emulator_expose_port() -> Result<(), Box<dyn std::error::Error + 'static>> {
195        let _ = pretty_env_logger::try_init();
196        let node = (google_cloud_sdk_emulators::CloudSdk::bigtable()).start()?;
197        let port = node.get_host_port_ipv4(google_cloud_sdk_emulators::BIGTABLE_PORT)?;
198        assert!(RANDOM_PORTS.contains(&port), "Port {port} not found");
199        Ok(())
200    }
201
202    #[test]
203    fn datastore_emulator_expose_port() -> Result<(), Box<dyn std::error::Error + 'static>> {
204        let _ = pretty_env_logger::try_init();
205        let node = google_cloud_sdk_emulators::CloudSdk::datastore("test").start()?;
206        let port = node.get_host_port_ipv4(google_cloud_sdk_emulators::DATASTORE_PORT)?;
207        assert!(RANDOM_PORTS.contains(&port), "Port {port} not found");
208        Ok(())
209    }
210
211    #[test]
212    fn firestore_emulator_expose_port() -> Result<(), Box<dyn std::error::Error + 'static>> {
213        let _ = pretty_env_logger::try_init();
214        let node = google_cloud_sdk_emulators::CloudSdk::firestore().start()?;
215        let port = node.get_host_port_ipv4(google_cloud_sdk_emulators::FIRESTORE_PORT)?;
216        assert!(RANDOM_PORTS.contains(&port), "Port {port} not found");
217        Ok(())
218    }
219
220    #[test]
221    fn pubsub_emulator_expose_port() -> Result<(), Box<dyn std::error::Error + 'static>> {
222        let _ = pretty_env_logger::try_init();
223        let node = google_cloud_sdk_emulators::CloudSdk::pubsub().start()?;
224        let port = node.get_host_port_ipv4(google_cloud_sdk_emulators::PUBSUB_PORT)?;
225        assert!(RANDOM_PORTS.contains(&port), "Port {port} not found");
226        Ok(())
227    }
228
229    #[test]
230    fn spanner_emulator_expose_port() -> Result<(), Box<dyn std::error::Error + 'static>> {
231        let _ = pretty_env_logger::try_init();
232        let node = google_cloud_sdk_emulators::CloudSdk::spanner().start()?;
233        let port = node.get_host_port_ipv4(google_cloud_sdk_emulators::SPANNER_PORT)?;
234        assert!(RANDOM_PORTS.contains(&port), "Port {port} not found");
235        Ok(())
236    }
237}