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