Skip to main content

gcloud_sdk/
api_client.rs

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()]);