testcontainers_modules/google_cloud_sdk_emulators/
mod.rs1use 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";
12pub const BIGTABLE_PORT: u16 = 8086;
17pub const DATASTORE_PORT: u16 = 8081;
22pub const FIRESTORE_PORT: u16 = 8080;
27pub const PUBSUB_PORT: u16 = 8085;
32pub const SPANNER_PORT: u16 = 9010;
37
38#[allow(missing_docs)]
39#[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#[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#[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 #[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 #[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 #[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 #[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 #[allow(missing_docs)]
176 pub fn spanner() -> Self {
177 Self::new(
178 SPANNER_PORT, 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}