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 debug!(
47 "Creating a new Google API client for {}. Scopes: {:?}",
48 google_api_url.as_ref(),
49 token_scopes
50 );
51
52 let channel = GoogleEnvironment::init_google_services_channel(google_api_url).await?;
53
54 let token_generator =
55 GoogleAuthTokenGenerator::new(token_source_type, token_scopes).await?;
56
57 let middleware = GoogleAuthMiddlewareLayer::new(token_generator, cloud_resource_prefix);
58
59 let service: GoogleAuthMiddlewareService<Channel> =
60 ServiceBuilder::new().layer(middleware).service(channel);
61
62 Ok(Self {
63 builder,
64 service,
65 _ph: PhantomData::default(),
66 })
67 }
68
69 pub fn get(&self) -> C {
70 self.builder.create_client(self.service.clone())
71 }
72}
73
74#[derive(Clone)]
75pub struct GoogleApiClientBuilderFunction<C>
76where
77 C: Clone + Send,
78{
79 f: fn(GoogleAuthMiddlewareService<Channel>) -> C,
80}
81
82impl<C> GoogleApiClientBuilder<C> for GoogleApiClientBuilderFunction<C>
83where
84 C: Clone + Send,
85{
86 fn create_client(&self, channel: GoogleAuthMiddlewareService<Channel>) -> C {
87 (self.f)(channel)
88 }
89}
90
91impl<C> GoogleApiClient<GoogleApiClientBuilderFunction<C>, C>
92where
93 C: Clone + Send,
94{
95 pub async fn from_function<S: AsRef<str>>(
96 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
97 google_api_url: S,
98 cloud_resource_prefix_meta: Option<String>,
99 ) -> crate::error::Result<Self> {
100 Self::from_function_with_scopes(
101 builder_fn,
102 google_api_url,
103 cloud_resource_prefix_meta,
104 GCP_DEFAULT_SCOPES.clone(),
105 )
106 .await
107 }
108
109 pub async fn from_function_with_scopes<S: AsRef<str>>(
110 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
111 google_api_url: S,
112 cloud_resource_prefix_meta: Option<String>,
113 token_scopes: Vec<String>,
114 ) -> crate::error::Result<Self> {
115 Self::from_function_with_token_source(
116 builder_fn,
117 google_api_url,
118 cloud_resource_prefix_meta,
119 token_scopes,
120 TokenSourceType::Default,
121 )
122 .await
123 }
124
125 pub async fn from_function_with_token_source<S: AsRef<str>>(
126 builder_fn: fn(GoogleAuthMiddlewareService<Channel>) -> C,
127 google_api_url: S,
128 cloud_resource_prefix_meta: Option<String>,
129 token_scopes: Vec<String>,
130 token_source_type: TokenSourceType,
131 ) -> crate::error::Result<Self> {
132 let builder: GoogleApiClientBuilderFunction<C> =
133 GoogleApiClientBuilderFunction { f: builder_fn };
134
135 Self::with_token_source(
136 builder,
137 google_api_url,
138 cloud_resource_prefix_meta,
139 token_source_type,
140 token_scopes,
141 )
142 .await
143 }
144}
145
146pub type GoogleAuthMiddleware = GoogleAuthMiddlewareService<Channel>;
147pub type GoogleApi<C> = GoogleApiClient<GoogleApiClientBuilderFunction<C>, C>;
148
149pub struct GoogleEnvironment;
150
151impl GoogleEnvironment {
152 pub async fn detect_google_project_id() -> Option<String> {
153 let for_env = std::env::var("GCP_PROJECT")
154 .ok()
155 .or_else(|| std::env::var("PROJECT_ID").ok())
156 .or_else(|| std::env::var("GCP_PROJECT_ID").ok());
157 if for_env.is_some() {
158 debug!("Detected GCP Project ID using environment variables");
159 for_env
160 } else {
161 let local_creds = match crate::token_source::from_env_var(&GCP_DEFAULT_SCOPES) {
162 Ok(Some(creds)) => Some(creds),
163 Ok(None) | Err(_) => crate::token_source::from_well_known_file(&GCP_DEFAULT_SCOPES)
164 .ok()
165 .flatten(),
166 };
167
168 let local_quota_project_id =
169 local_creds.and_then(|creds| creds.quota_project_id().map(ToString::to_string));
170
171 if local_quota_project_id.is_some() {
172 debug!("Detected default project id from local defined in quota_project_id for the service account file.");
173 local_quota_project_id
174 } else {
175 let mut metadata_server =
176 crate::token_source::metadata::Metadata::new(GCP_DEFAULT_SCOPES.clone());
177 if metadata_server.init().await {
178 let metadata_result = metadata_server.detect_google_project_id().await;
179 if metadata_result.is_some() {
180 debug!("Detected GCP Project ID using GKE metadata server");
181 metadata_result
182 } else {
183 debug!("No GCP Project ID detected in this environment. Please specify it explicitly using environment variables: `PROJECT_ID`,`GCP_PROJECT_ID`, or `GCP_PROJECT`");
184 metadata_result
185 }
186 } else {
187 debug!("No GCP Project ID detected in this environment. Please specify it explicitly using environment variables: `PROJECT_ID`,`GCP_PROJECT_ID`, or `GCP_PROJECT`");
188 None
189 }
190 }
191 }
192 }
193
194 pub async fn find_default_creds(
195 token_scopes: &[String],
196 ) -> crate::error::Result<Option<CredentialsInfo>> {
197 debug!("Finding default credentials for scopes: {:?}", token_scopes);
198
199 if let Some(src) = from_env_var(token_scopes)? {
200 debug!("Creating credentials based on environment variable: GOOGLE_APPLICATION_CREDENTIALS");
201 return Ok(src.to_credentials_info());
202 }
203 if let Some(src) = from_well_known_file(token_scopes)? {
204 debug!("Creating credentials based on standard config files such as application_default_credentials.json");
205 return Ok(src.to_credentials_info());
206 }
207 let mut metadata_server = crate::token_source::metadata::Metadata::new(token_scopes);
208 if metadata_server.init().await {
209 let metadata_result_email = metadata_server.email().await;
210 if let Some(email) = metadata_result_email {
211 debug!("Detected SA email using GKE metadata server");
212 return Ok(Some(CredentialsInfo {
213 client_email: email,
214 project_id: metadata_server.detect_google_project_id().await,
215 }));
216 }
217 }
218 Ok(None)
219 }
220
221 pub async fn init_google_services_channel<S: AsRef<str>>(
222 api_url: S,
223 ) -> Result<Channel, crate::error::Error> {
224 let api_url_string = api_url.as_ref().to_string();
225 let base_config = Channel::from_shared(api_url_string.clone())?
226 .connect_timeout(Duration::from_secs(30))
227 .tcp_keepalive(Some(Duration::from_secs(60)))
228 .keep_alive_timeout(Duration::from_secs(60))
229 .http2_keep_alive_interval(Duration::from_secs(60))
230 .keep_alive_while_idle(true);
231
232 let config = if !&api_url_string.contains("http://") {
233 let domain_name = api_url_string.replace("https://", "");
234
235 let tls_config = Self::init_tls_config(domain_name);
236 base_config.tls_config(tls_config)?
237 } else {
238 base_config
239 };
240
241 Ok(config.connect().await?)
242 }
243
244 #[cfg(not(any(feature = "tls-roots", feature = "tls-webpki-roots")))]
245 fn init_tls_config(domain_name: String) -> tonic::transport::ClientTlsConfig {
246 tonic::transport::ClientTlsConfig::new()
247 .ca_certificate(tonic::transport::Certificate::from_pem(
248 crate::apis::CERTIFICATES,
249 ))
250 .domain_name(domain_name)
251 }
252
253 #[cfg(feature = "tls-roots")]
254 fn init_tls_config(domain_name: String) -> tonic::transport::ClientTlsConfig {
255 tonic::transport::ClientTlsConfig::new()
256 .with_native_roots()
257 .domain_name(domain_name)
258 }
259
260 #[cfg(feature = "tls-webpki-roots")]
261 fn init_tls_config(domain_name: String) -> tonic::transport::ClientTlsConfig {
262 tonic::transport::ClientTlsConfig::new()
263 .with_webpki_roots()
264 .domain_name(domain_name)
265 }
266}
267
268pub static GCP_DEFAULT_SCOPES: Lazy<Vec<String>> =
269 Lazy::new(|| vec!["https://www.googleapis.com/auth/cloud-platform".into()]);