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