1use std::marker::PhantomData;
2use std::time::Duration;
3
4use crate::token_source::auth_token_generator::GoogleAuthTokenGenerator;
5use async_trait::async_trait;
6use once_cell::sync::Lazy;
7use tonic::transport::Channel;
8use tower::ServiceBuilder;
9use tracing::*;
10
11use crate::middleware::{GoogleAuthMiddlewareLayer, GoogleAuthMiddlewareService};
12use crate::token_source::credentials::CredentialsInfo;
13use crate::token_source::*;
14
15#[async_trait]
16pub trait GoogleApiClientBuilder<C>
17where
18 C: Clone + Send,
19{
20 fn create_client(&self, channel: GoogleAuthMiddlewareService<Channel>) -> C;
21}
22
23#[derive(Clone)]
24pub struct GoogleApiClient<B, C>
25where
26 B: GoogleApiClientBuilder<C>,
27 C: Clone + Send,
28{
29 builder: B,
30 service: GoogleAuthMiddlewareService<Channel>,
31 _ph: PhantomData<C>,
32}
33
34impl<B, C> GoogleApiClient<B, C>
35where
36 B: GoogleApiClientBuilder<C>,
37 C: Clone + Send,
38{
39 pub async fn with_token_source<S: AsRef<str>>(
40 builder: B,
41 google_api_url: S,
42 cloud_resource_prefix: Option<String>,
43 token_source_type: TokenSourceType,
44 token_scopes: Vec<String>,
45 ) -> crate::error::Result<Self> {
46 Self::with_token_source_and_headers(
47 builder,
48 google_api_url,
49 cloud_resource_prefix,
50 token_source_type,
51 token_scopes,
52 hyper::HeaderMap::new(),
53 )
54 .await
55 }
56
57 pub async fn with_token_source_and_headers<S: AsRef<str>>(
58 builder: B,
59 google_api_url: S,
60 cloud_resource_prefix: Option<String>,
61 token_source_type: TokenSourceType,
62 token_scopes: Vec<String>,
63 additional_headers: hyper::HeaderMap,
64 ) -> crate::error::Result<Self> {
65 debug!(
66 "Creating a new Google API client for {}. Scopes: {:?}",
67 google_api_url.as_ref(),
68 token_scopes
69 );
70
71 let token_generator =
72 GoogleAuthTokenGenerator::new(token_source_type, token_scopes).await?;
73
74 let mut middleware = GoogleAuthMiddlewareLayer::new(token_generator, cloud_resource_prefix);
75 middleware.set_additional_headers(additional_headers);
76
77 Self::with_token_source_and_middleware(builder, google_api_url, middleware).await
78 }
79
80 pub async fn with_token_source_and_middleware<S: AsRef<str>>(
81 builder: B,
82 google_api_url: S,
83 middleware: GoogleAuthMiddlewareLayer,
84 ) -> crate::error::Result<Self> {
85 let channel = GoogleEnvironment::init_google_services_channel(google_api_url).await?;
86
87 let service: GoogleAuthMiddlewareService<Channel> =
88 ServiceBuilder::new().layer(middleware).service(channel);
89
90 Ok(Self {
91 builder,
92 service,
93 _ph: PhantomData::default(),
94 })
95 }
96
97 pub fn get(&self) -> C {
98 self.builder.create_client(self.service.clone())
99 }
100
101 pub fn amend_user_agent(mut self, user_agent: String) -> Self {
102 self.service.append_user_agent(user_agent);
103 self
104 }
105
106 pub fn amend_x_goog_api_client(mut self, x_goog_api_client: String) -> Self {
107 self.service.append_x_goog_api_client(x_goog_api_client);
108 self
109 }
110}
111
112#[derive(Clone)]
113pub struct GoogleApiClientBuilderFunction<C>
114where
115 C: Clone + Send,
116{
117 f: fn(GoogleAuthMiddlewareService<Channel>) -> C,
118}
119
120impl<C> GoogleApiClientBuilder<C> for GoogleApiClientBuilderFunction<C>
121where
122 C: Clone + Send,
123{
124 fn create_client(&self, channel: GoogleAuthMiddlewareService<Channel>) -> C {
125 (self.f)(channel)
126 }
127}
128
129impl<C> GoogleApiClient<GoogleApiClientBuilderFunction<C>, C>
130where
131 C: Clone + Send,
132{
133 pub async fn from_function<S: AsRef<str>>(
134 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
135 google_api_url: S,
136 cloud_resource_prefix_meta: Option<String>,
137 ) -> crate::error::Result<Self> {
138 Self::from_function_with_scopes(
139 builder_fn,
140 google_api_url,
141 cloud_resource_prefix_meta,
142 GCP_DEFAULT_SCOPES.clone(),
143 )
144 .await
145 }
146
147 pub async fn from_function_with_headers<S: AsRef<str>>(
148 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
149 google_api_url: S,
150 cloud_resource_prefix_meta: Option<String>,
151 headers: hyper::HeaderMap,
152 ) -> crate::error::Result<Self> {
153 Self::from_function_with_scopes_and_headers(
154 builder_fn,
155 google_api_url,
156 cloud_resource_prefix_meta,
157 GCP_DEFAULT_SCOPES.clone(),
158 headers,
159 )
160 .await
161 }
162
163 pub async fn from_function_with_scopes<S: AsRef<str>>(
164 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
165 google_api_url: S,
166 cloud_resource_prefix_meta: Option<String>,
167 token_scopes: Vec<String>,
168 ) -> crate::error::Result<Self> {
169 Self::from_function_with_token_source(
170 builder_fn,
171 google_api_url,
172 cloud_resource_prefix_meta,
173 token_scopes,
174 TokenSourceType::Default,
175 )
176 .await
177 }
178
179 pub async fn from_function_with_scopes_and_headers<S: AsRef<str>>(
180 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
181 google_api_url: S,
182 cloud_resource_prefix_meta: Option<String>,
183 token_scopes: Vec<String>,
184 headers: hyper::HeaderMap,
185 ) -> crate::error::Result<Self> {
186 Self::from_function_with_token_source_and_headers(
187 builder_fn,
188 google_api_url,
189 cloud_resource_prefix_meta,
190 token_scopes,
191 TokenSourceType::Default,
192 headers,
193 )
194 .await
195 }
196
197 pub async fn from_function_with_token_source<S: AsRef<str>>(
198 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
199 google_api_url: S,
200 cloud_resource_prefix_meta: Option<String>,
201 token_scopes: Vec<String>,
202 token_source_type: TokenSourceType,
203 ) -> crate::error::Result<Self> {
204 let builder: GoogleApiClientBuilderFunction<C> =
205 GoogleApiClientBuilderFunction { f: builder_fn };
206
207 Self::with_token_source(
208 builder,
209 google_api_url,
210 cloud_resource_prefix_meta,
211 token_source_type,
212 token_scopes,
213 )
214 .await
215 }
216
217 pub async fn from_function_with_token_source_and_headers<S: AsRef<str>>(
218 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
219 google_api_url: S,
220 cloud_resource_prefix_meta: Option<String>,
221 token_scopes: Vec<String>,
222 token_source_type: TokenSourceType,
223 headers: hyper::HeaderMap,
224 ) -> crate::error::Result<Self> {
225 let builder: GoogleApiClientBuilderFunction<C> =
226 GoogleApiClientBuilderFunction { f: builder_fn };
227
228 Self::with_token_source_and_headers(
229 builder,
230 google_api_url,
231 cloud_resource_prefix_meta,
232 token_source_type,
233 token_scopes,
234 headers,
235 )
236 .await
237 }
238
239 pub async fn from_function_with_middleware<S: AsRef<str>>(
240 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
241 google_api_url: S,
242 middleware: GoogleAuthMiddlewareLayer,
243 ) -> crate::error::Result<Self> {
244 let builder: GoogleApiClientBuilderFunction<C> =
245 GoogleApiClientBuilderFunction { f: builder_fn };
246
247 Self::with_token_source_and_middleware(builder, google_api_url, middleware).await
248 }
249}
250
251pub type GoogleAuthMiddleware = GoogleAuthMiddlewareService<Channel>;
252pub type GoogleApi<C> = GoogleApiClient<GoogleApiClientBuilderFunction<C>, C>;
253
254pub struct GoogleEnvironment;
255
256impl GoogleEnvironment {
257 pub async fn detect_google_project_id() -> Option<String> {
258 let for_env = std::env::var("GCP_PROJECT")
259 .ok()
260 .or_else(|| std::env::var("PROJECT_ID").ok())
261 .or_else(|| std::env::var("GCP_PROJECT_ID").ok());
262 if for_env.is_some() {
263 debug!("Detected GCP Project ID using environment variables");
264 for_env
265 } else {
266 let local_creds = match crate::token_source::from_env_var(&GCP_DEFAULT_SCOPES) {
267 Ok(Some(creds)) => Some(creds),
268 Ok(None) | Err(_) => crate::token_source::from_well_known_file(&GCP_DEFAULT_SCOPES)
269 .ok()
270 .flatten(),
271 };
272
273 let local_quota_project_id =
274 local_creds.and_then(|creds| creds.quota_project_id().map(ToString::to_string));
275
276 if local_quota_project_id.is_some() {
277 debug!("Detected default project id from local defined in quota_project_id for the service account file.");
278 local_quota_project_id
279 } else {
280 let mut metadata_server =
281 crate::token_source::metadata::Metadata::new(GCP_DEFAULT_SCOPES.clone());
282 if metadata_server.init().await {
283 let metadata_result = metadata_server.detect_google_project_id().await;
284 if metadata_result.is_some() {
285 debug!("Detected GCP Project ID using GKE metadata server");
286 metadata_result
287 } else {
288 debug!("No GCP Project ID detected in this environment. Please specify it explicitly using environment variables: `PROJECT_ID`,`GCP_PROJECT_ID`, or `GCP_PROJECT`");
289 metadata_result
290 }
291 } else {
292 debug!("No GCP Project ID detected in this environment. Please specify it explicitly using environment variables: `PROJECT_ID`,`GCP_PROJECT_ID`, or `GCP_PROJECT`");
293 None
294 }
295 }
296 }
297 }
298
299 pub async fn find_default_creds(
300 token_scopes: &[String],
301 ) -> crate::error::Result<Option<CredentialsInfo>> {
302 debug!("Finding default credentials for scopes: {:?}", token_scopes);
303
304 if let Some(src) = from_env_var(token_scopes)? {
305 debug!("Creating credentials based on environment variable: GOOGLE_APPLICATION_CREDENTIALS");
306 return Ok(src.to_credentials_info());
307 }
308 if let Some(src) = from_well_known_file(token_scopes)? {
309 debug!("Creating credentials based on standard config files such as application_default_credentials.json");
310 return Ok(src.to_credentials_info());
311 }
312 let mut metadata_server = crate::token_source::metadata::Metadata::new(token_scopes);
313 if metadata_server.init().await {
314 let metadata_result_email = metadata_server.email().await;
315 if let Some(email) = metadata_result_email {
316 debug!("Detected SA email using GKE metadata server");
317 return Ok(Some(CredentialsInfo {
318 client_email: email,
319 project_id: metadata_server.detect_google_project_id().await,
320 }));
321 }
322 }
323 Ok(None)
324 }
325
326 pub async fn init_google_services_channel<S: AsRef<str>>(
327 api_url: S,
328 ) -> Result<Channel, crate::error::Error> {
329 let api_url_string = api_url.as_ref().to_string();
330 let base_config = Channel::from_shared(api_url_string.clone())?
331 .connect_timeout(Duration::from_secs(30))
332 .tcp_keepalive(Some(Duration::from_secs(60)))
333 .keep_alive_timeout(Duration::from_secs(60))
334 .http2_keep_alive_interval(Duration::from_secs(60))
335 .keep_alive_while_idle(true);
336
337 let config = if !&api_url_string.contains("http://") {
338 let domain_name = api_url_string.replace("https://", "");
339
340 let tls_config = Self::init_tls_config(domain_name);
341 base_config.tls_config(tls_config)?
342 } else {
343 base_config
344 };
345
346 Ok(config.connect().await?)
347 }
348
349 #[cfg(not(any(feature = "tls-roots", feature = "tls-webpki-roots")))]
350 fn init_tls_config(domain_name: String) -> tonic::transport::ClientTlsConfig {
351 tonic::transport::ClientTlsConfig::new()
352 .ca_certificate(tonic::transport::Certificate::from_pem(
353 crate::apis::CERTIFICATES,
354 ))
355 .domain_name(domain_name)
356 }
357
358 #[cfg(feature = "tls-roots")]
359 fn init_tls_config(domain_name: String) -> tonic::transport::ClientTlsConfig {
360 tonic::transport::ClientTlsConfig::new()
361 .with_native_roots()
362 .domain_name(domain_name)
363 }
364
365 #[cfg(all(feature = "tls-webpki-roots", not(feature = "tls-roots")))]
366 fn init_tls_config(domain_name: String) -> tonic::transport::ClientTlsConfig {
367 tonic::transport::ClientTlsConfig::new()
368 .with_webpki_roots()
369 .domain_name(domain_name)
370 }
371}
372
373pub static GCP_DEFAULT_SCOPES: Lazy<Vec<String>> =
374 Lazy::new(|| vec!["https://www.googleapis.com/auth/cloud-platform".into()]);