You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/03/18 10:55:28 UTC

(opendal-reqsign) branch main updated: feat(google): migrate default credential provider API (#714)

This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git


The following commit(s) were added to refs/heads/main by this push:
     new a9fbc1c  feat(google): migrate default credential provider API (#714)
a9fbc1c is described below

commit a9fbc1c30f98d343dd4da38d439b7916277935af
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Wed Mar 18 18:55:23 2026 +0800

    feat(google): migrate default credential provider API (#714)
    
    This updates the Google default credential provider to match the
    repository-wide API defined in
    `docs/default-credential-provider-api.md`. The builder now uses explicit
    `slot(...)` and `no_slot()` methods for the env, well-known ADC, and VM
    metadata providers, and removes the old patch-style and boolean-toggle
    APIs.
    
    The change also exports the Google-specific env and well-known ADC
    providers so callers can still customize those slots directly, and adds
    tests that verify removed slots are truly absent from the default chain.
---
 services/google/src/lib.rs                        |   5 +-
 services/google/src/provide_credential/default.rs | 280 +++++++++++++---------
 services/google/src/provide_credential/mod.rs     |   5 +-
 3 files changed, 171 insertions(+), 119 deletions(-)

diff --git a/services/google/src/lib.rs b/services/google/src/lib.rs
index b9d1cae..d72c98e 100644
--- a/services/google/src/lib.rs
+++ b/services/google/src/lib.rs
@@ -27,6 +27,7 @@ pub use sign_request::RequestSigner;
 
 mod provide_credential;
 pub use provide_credential::{
-    DefaultCredentialProvider, DefaultCredentialProviderBuilder, FileCredentialProvider,
-    StaticCredentialProvider, TokenCredentialProvider, VmMetadataCredentialProvider,
+    DefaultCredentialProvider, DefaultCredentialProviderBuilder, EnvCredentialProvider,
+    FileCredentialProvider, StaticCredentialProvider, TokenCredentialProvider,
+    VmMetadataCredentialProvider, WellKnownCredentialProvider,
 };
diff --git a/services/google/src/provide_credential/default.rs b/services/google/src/provide_credential/default.rs
index 576eca4..85d40e6 100644
--- a/services/google/src/provide_credential/default.rs
+++ b/services/google/src/provide_credential/default.rs
@@ -66,46 +66,6 @@ impl DefaultCredentialProvider {
         self.chain = self.chain.push_front(provider);
         self
     }
-
-    /// Set the OAuth2 scope for ADC providers (deprecated).
-    ///
-    /// This helper configures the scope used by the environment and
-    /// well-known ADC providers, as well as the VM metadata provider, by
-    /// constructing a new chain with the provided scope. Prefer configuring
-    /// scope via specific providers (e.g., `VmMetadataCredentialProvider::with_scope`)
-    /// or using the `GOOGLE_SCOPE` environment variable.
-    #[deprecated(
-        since = "1.0.0",
-        note = "Configure scope via specific providers or GOOGLE_SCOPE env var"
-    )]
-    pub fn with_scope(self, scope: impl Into<String>) -> Self {
-        let s = scope.into();
-        let chain = ProvideCredentialChain::new()
-            .push(EnvAdcCredentialProvider::new().with_scope(s.clone()))
-            .push(WellKnownAdcCredentialProvider::new().with_scope(s.clone()))
-            .push(VmMetadataCredentialProvider::new().with_scope(s));
-        Self { chain }
-    }
-
-    #[deprecated(
-        since = "1.0.0",
-        note = "Use DefaultCredentialProvider::builder().disable_env(skip).build() instead"
-    )]
-    pub fn skip_env_credentials(self, skip: bool) -> Self {
-        DefaultCredentialProvider::builder()
-            .disable_env(skip)
-            .build()
-    }
-
-    #[deprecated(
-        since = "1.0.0",
-        note = "Use DefaultCredentialProvider::builder().disable_well_known(skip).build() instead"
-    )]
-    pub fn skip_well_known_location(self, skip: bool) -> Self {
-        DefaultCredentialProvider::builder()
-            .disable_well_known(skip)
-            .build()
-    }
 }
 impl ProvideCredential for DefaultCredentialProvider {
     type Credential = Credential;
@@ -116,30 +76,26 @@ impl ProvideCredential for DefaultCredentialProvider {
 }
 
 #[derive(Default, Clone, Debug)]
-struct EnvAdcCredentialProvider {
-    disabled: Option<bool>,
+pub struct EnvCredentialProvider {
     scope: Option<String>,
 }
 
-impl EnvAdcCredentialProvider {
-    fn new() -> Self {
+impl EnvCredentialProvider {
+    /// Create a new env ADC credential provider.
+    pub fn new() -> Self {
         Self::default()
     }
 
     /// Set the OAuth2 scope to request when exchanging ADC credentials.
-    fn with_scope(mut self, scope: impl Into<String>) -> Self {
+    pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
         self.scope = Some(scope.into());
         self
     }
 }
-impl ProvideCredential for EnvAdcCredentialProvider {
+impl ProvideCredential for EnvCredentialProvider {
     type Credential = Credential;
 
     async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
-        if self.disabled.unwrap_or(false) {
-            return Ok(None);
-        }
-
         let path = match ctx.env_var(GOOGLE_APPLICATION_CREDENTIALS) {
             Some(path) if !path.is_empty() => path,
             _ => return Ok(None),
@@ -153,30 +109,26 @@ impl ProvideCredential for EnvAdcCredentialProvider {
 }
 
 #[derive(Default, Clone, Debug)]
-struct WellKnownAdcCredentialProvider {
-    disabled: Option<bool>,
+pub struct WellKnownCredentialProvider {
     scope: Option<String>,
 }
 
-impl WellKnownAdcCredentialProvider {
-    fn new() -> Self {
+impl WellKnownCredentialProvider {
+    /// Create a new well-known ADC credential provider.
+    pub fn new() -> Self {
         Self::default()
     }
 
     /// Set the OAuth2 scope to request when exchanging ADC credentials.
-    fn with_scope(mut self, scope: impl Into<String>) -> Self {
+    pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
         self.scope = Some(scope.into());
         self
     }
 }
-impl ProvideCredential for WellKnownAdcCredentialProvider {
+impl ProvideCredential for WellKnownCredentialProvider {
     type Credential = Credential;
 
     async fn provide_credential(&self, ctx: &Context) -> Result<Option<Self::Credential>> {
-        if self.disabled.unwrap_or(false) {
-            return Ok(None);
-        }
-
         let config_dir = if let Some(v) = ctx.env_var("APPDATA") {
             v
         } else if let Some(v) = ctx.env_var("XDG_CONFIG_HOME") {
@@ -203,66 +155,61 @@ impl ProvideCredential for WellKnownAdcCredentialProvider {
 }
 
 /// Builder for `DefaultCredentialProvider`.
-///
-/// Use `configure_vm_metadata` to customize VM metadata behavior and
-/// `disable_env` / `disable_well_known` / `disable_vm_metadata` to control
-/// participation. Call `build()` to construct the provider.
-#[derive(Default)]
 pub struct DefaultCredentialProviderBuilder {
-    env_adc: Option<EnvAdcCredentialProvider>,
-    well_known_adc: Option<WellKnownAdcCredentialProvider>,
+    env: Option<EnvCredentialProvider>,
+    well_known: Option<WellKnownCredentialProvider>,
     vm_metadata: Option<VmMetadataCredentialProvider>,
 }
 
+impl Default for DefaultCredentialProviderBuilder {
+    fn default() -> Self {
+        Self {
+            env: Some(EnvCredentialProvider::new()),
+            well_known: Some(WellKnownCredentialProvider::new()),
+            vm_metadata: Some(VmMetadataCredentialProvider::new()),
+        }
+    }
+}
+
 impl DefaultCredentialProviderBuilder {
     /// Create a new builder with default state.
     pub fn new() -> Self {
         Self::default()
     }
 
-    // No global scope configurator; configure scope on specific providers if needed.
+    /// Set the env ADC provider slot.
+    pub fn env(mut self, provider: EnvCredentialProvider) -> Self {
+        self.env = Some(provider);
+        self
+    }
 
-    /// Configure the VM metadata provider.
-    ///
-    /// This allows setting a custom endpoint, service account, or other options
-    /// for retrieving tokens when running on Google Compute Engine or
-    /// compatible environments.
-    pub fn configure_vm_metadata<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(VmMetadataCredentialProvider) -> VmMetadataCredentialProvider,
-    {
-        let p = self.vm_metadata.take().unwrap_or_default();
-        self.vm_metadata = Some(f(p));
+    /// Remove the env ADC provider slot.
+    pub fn no_env(mut self) -> Self {
+        self.env = None;
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the env-based ADC provider.
-    pub fn disable_env(mut self, disable: bool) -> Self {
-        if disable {
-            self.env_adc = None;
-        } else if self.env_adc.is_none() {
-            self.env_adc = Some(EnvAdcCredentialProvider::new());
-        }
+    /// Set the well-known ADC provider slot.
+    pub fn well_known(mut self, provider: WellKnownCredentialProvider) -> Self {
+        self.well_known = Some(provider);
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the well-known ADC provider.
-    pub fn disable_well_known(mut self, disable: bool) -> Self {
-        if disable {
-            self.well_known_adc = None;
-        } else if self.well_known_adc.is_none() {
-            self.well_known_adc = Some(WellKnownAdcCredentialProvider::new());
-        }
+    /// Remove the well-known ADC provider slot.
+    pub fn no_well_known(mut self) -> Self {
+        self.well_known = None;
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the VM metadata provider.
-    pub fn disable_vm_metadata(mut self, disable: bool) -> Self {
-        if disable {
-            self.vm_metadata = None;
-        } else if self.vm_metadata.is_none() {
-            self.vm_metadata = Some(VmMetadataCredentialProvider::new());
-        }
+    /// Set the VM metadata provider slot.
+    pub fn vm_metadata(mut self, provider: VmMetadataCredentialProvider) -> Self {
+        self.vm_metadata = Some(provider);
+        self
+    }
+
+    /// Remove the VM metadata provider slot.
+    pub fn no_vm_metadata(mut self) -> Self {
+        self.vm_metadata = None;
         self
     }
 
@@ -270,22 +217,16 @@ impl DefaultCredentialProviderBuilder {
     pub fn build(self) -> DefaultCredentialProvider {
         let mut chain = ProvideCredentialChain::new();
 
-        if let Some(p) = self.env_adc {
+        if let Some(p) = self.env {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(EnvAdcCredentialProvider::new());
         }
 
-        if let Some(p) = self.well_known_adc {
+        if let Some(p) = self.well_known {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(WellKnownAdcCredentialProvider::new());
         }
 
         if let Some(p) = self.vm_metadata {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(VmMetadataCredentialProvider::new());
         }
 
         DefaultCredentialProvider::with_chain(chain)
@@ -296,8 +237,7 @@ impl DefaultCredentialProviderBuilder {
 mod tests {
     use super::*;
     use bytes::Bytes;
-    use reqsign_core::HttpSend;
-    use reqsign_core::{Context, StaticEnv};
+    use reqsign_core::{Context, FileRead, HttpSend, StaticEnv};
     use std::collections::HashMap;
     use std::env;
     use std::sync::{Arc, Mutex};
@@ -322,6 +262,30 @@ mod tests {
         }
     }
 
+    #[derive(Clone, Debug, Default)]
+    struct MockFileRead {
+        files: Arc<HashMap<String, Vec<u8>>>,
+        paths: Arc<Mutex<Vec<String>>>,
+    }
+
+    impl MockFileRead {
+        fn new(files: HashMap<String, Vec<u8>>) -> Self {
+            Self {
+                files: Arc::new(files),
+                paths: Arc::new(Mutex::new(Vec::new())),
+            }
+        }
+    }
+
+    impl FileRead for MockFileRead {
+        async fn file_read(&self, path: &str) -> Result<Vec<u8>> {
+            self.paths.lock().unwrap().push(path.to_string());
+            self.files.get(path).cloned().ok_or_else(|| {
+                reqsign_core::Error::config_invalid(format!("file not found: {path}"))
+            })
+        }
+    }
+
     #[tokio::test]
     async fn test_default_provider_env() {
         let envs = HashMap::from([(
@@ -356,7 +320,7 @@ mod tests {
     }
 
     #[tokio::test]
-    async fn test_default_provider_with_scope() {
+    async fn test_default_provider_builder_default_chain() {
         let provider = DefaultCredentialProvider::builder().build();
 
         // Even without valid credentials, this should not panic
@@ -367,15 +331,99 @@ mod tests {
     }
 
     #[tokio::test]
-    async fn test_default_provider_configures_vm_metadata_service_account() -> Result<()> {
+    async fn test_default_provider_no_env_removes_provider() -> Result<()> {
+        let env_path = "/tmp/google-env-adc.json";
+        let file_read = MockFileRead::new(HashMap::from([(
+            env_path.to_string(),
+            include_bytes!(concat!(
+                env!("CARGO_MANIFEST_DIR"),
+                "/testdata/test_credential.json"
+            ))
+            .to_vec(),
+        )]));
+        let ctx = Context::new()
+            .with_file_read(file_read.clone())
+            .with_env(StaticEnv {
+                home_dir: None,
+                envs: HashMap::from([(
+                    GOOGLE_APPLICATION_CREDENTIALS.to_string(),
+                    env_path.to_string(),
+                )]),
+            });
+
+        let provider = DefaultCredentialProvider::builder()
+            .no_env()
+            .no_well_known()
+            .no_vm_metadata()
+            .build();
+
+        assert!(provider.provide_credential(&ctx).await?.is_none());
+        assert!(file_read.paths.lock().unwrap().is_empty());
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_default_provider_no_well_known_removes_provider() -> Result<()> {
+        let home = "/tmp/google-home";
+        let well_known_path = format!("{home}/.config/gcloud/application_default_credentials.json");
+        let file_read = MockFileRead::new(HashMap::from([(
+            well_known_path.clone(),
+            include_bytes!(concat!(
+                env!("CARGO_MANIFEST_DIR"),
+                "/testdata/test_credential.json"
+            ))
+            .to_vec(),
+        )]));
+        let ctx = Context::new()
+            .with_file_read(file_read.clone())
+            .with_env(StaticEnv {
+                home_dir: None,
+                envs: HashMap::from([("HOME".to_string(), home.to_string())]),
+            });
+
+        let provider = DefaultCredentialProvider::builder()
+            .no_env()
+            .no_well_known()
+            .no_vm_metadata()
+            .build();
+
+        assert!(provider.provide_credential(&ctx).await?.is_none());
+        assert!(file_read.paths.lock().unwrap().is_empty());
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_default_provider_no_vm_metadata_removes_provider() -> Result<()> {
         let http = MockHttpSend::default();
         let ctx = Context::new().with_http_send(http.clone());
 
         let provider = DefaultCredentialProvider::builder()
-            .configure_vm_metadata(|p| {
-                p.with_endpoint("127.0.0.1:8080")
-                    .with_service_account("custom@test-project.iam.gserviceaccount.com")
-            })
+            .no_env()
+            .no_well_known()
+            .no_vm_metadata()
+            .build();
+
+        assert!(provider.provide_credential(&ctx).await?.is_none());
+        assert!(http.uris.lock().unwrap().is_empty());
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_default_provider_custom_vm_metadata_service_account() -> Result<()> {
+        let http = MockHttpSend::default();
+        let ctx = Context::new().with_http_send(http.clone());
+
+        let provider = DefaultCredentialProvider::builder()
+            .no_env()
+            .no_well_known()
+            .vm_metadata(
+                VmMetadataCredentialProvider::new()
+                    .with_endpoint("127.0.0.1:8080")
+                    .with_service_account("custom@test-project.iam.gserviceaccount.com"),
+            )
             .build();
 
         let cred = provider
diff --git a/services/google/src/provide_credential/mod.rs b/services/google/src/provide_credential/mod.rs
index f61c487..a3b53b4 100644
--- a/services/google/src/provide_credential/mod.rs
+++ b/services/google/src/provide_credential/mod.rs
@@ -17,7 +17,10 @@
 
 mod default;
 #[allow(unused_imports)]
-pub use default::{DefaultCredentialProvider, DefaultCredentialProviderBuilder};
+pub use default::{
+    DefaultCredentialProvider, DefaultCredentialProviderBuilder, EnvCredentialProvider,
+    WellKnownCredentialProvider,
+};
 
 mod file;
 pub use file::FileCredentialProvider;