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:49:56 UTC

(opendal-reqsign) 01/01: feat(azure-storage): migrate default credential provider api

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

xuanwo pushed a commit to branch xuanwo/default-provider-api-azure-storage
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git

commit 7f8edb3db8fb6c449492e692a38e546d596699d6
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Wed Mar 18 18:49:47 2026 +0800

    feat(azure-storage): migrate default credential provider api
---
 services/azure-storage/examples/blob_storage.rs    |   6 +-
 services/azure-storage/src/lib.rs                  |  14 ++
 .../src/provide_credential/default.rs              | 213 +++++++--------------
 .../tests/credential_providers/default.rs          |  37 +++-
 4 files changed, 127 insertions(+), 143 deletions(-)

diff --git a/services/azure-storage/examples/blob_storage.rs b/services/azure-storage/examples/blob_storage.rs
index 5ca242d..51cd197 100644
--- a/services/azure-storage/examples/blob_storage.rs
+++ b/services/azure-storage/examples/blob_storage.rs
@@ -16,7 +16,7 @@
 // under the License.
 
 use anyhow::Result;
-use reqsign_azure_storage::{DefaultCredentialProvider, RequestSigner};
+use reqsign_azure_storage::{DefaultCredentialProvider, EnvCredentialProvider, RequestSigner};
 use reqsign_core::{Context, OsEnv, Signer};
 use reqsign_file_read_tokio::TokioFileRead;
 use reqsign_http_send_reqwest::ReqwestHttpSend;
@@ -56,7 +56,9 @@ async fn main() -> Result<()> {
     }
 
     // Create credential loader
-    let loader = DefaultCredentialProvider::new();
+    let loader = DefaultCredentialProvider::builder()
+        .env(EnvCredentialProvider::new())
+        .build();
 
     // Create request builder
     let builder = RequestSigner::new();
diff --git a/services/azure-storage/src/lib.rs b/services/azure-storage/src/lib.rs
index 180c756..21c4581 100644
--- a/services/azure-storage/src/lib.rs
+++ b/services/azure-storage/src/lib.rs
@@ -97,6 +97,20 @@
 //! let loader = DefaultCredentialProvider::new();
 //! ```
 //!
+//! Customize the default chain with slot-based builder APIs:
+//!
+//! ```no_run
+//! use reqsign_azure_storage::{
+//!     ClientSecretCredentialProvider, DefaultCredentialProvider,
+//! };
+//!
+//! let loader = DefaultCredentialProvider::builder()
+//!     .no_env()
+//!     .client_secret(ClientSecretCredentialProvider::new().with_tenant_id("tenant-id"))
+//!     .no_imds()
+//!     .build();
+//! ```
+//!
 //! ## Storage Services
 //!
 //! ### Blob Storage
diff --git a/services/azure-storage/src/provide_credential/default.rs b/services/azure-storage/src/provide_credential/default.rs
index 5a00a7f..cd2dc49 100644
--- a/services/azure-storage/src/provide_credential/default.rs
+++ b/services/azure-storage/src/provide_credential/default.rs
@@ -85,10 +85,21 @@ impl DefaultCredentialProvider {
 
 /// Builder for `DefaultCredentialProvider`.
 ///
-/// Use `configure_*` to adjust provider behavior and `disable_*(bool)` to
-/// control participation in the chain. Then call `build()` to construct the
-/// final provider that resolves credentials in the documented order.
-#[derive(Default)]
+/// Each slot is represented by `Option<T>`:
+/// - `Some(provider)` keeps the provider in the default chain.
+/// - `None` removes the provider from the chain.
+///
+/// ```no_run
+/// use reqsign_azure_storage::{
+///     ClientSecretCredentialProvider, DefaultCredentialProvider,
+/// };
+///
+/// let provider = DefaultCredentialProvider::builder()
+///     .no_env()
+///     .client_secret(ClientSecretCredentialProvider::new().with_tenant_id("tenant-id"))
+///     .no_imds()
+///     .build();
+/// ```
 pub struct DefaultCredentialProviderBuilder {
     env: Option<EnvCredentialProvider>,
     #[cfg(not(target_arch = "wasm32"))]
@@ -101,176 +112,112 @@ pub struct DefaultCredentialProviderBuilder {
     imds: Option<ImdsCredentialProvider>,
 }
 
+impl Default for DefaultCredentialProviderBuilder {
+    fn default() -> Self {
+        Self {
+            env: Some(EnvCredentialProvider::new()),
+            #[cfg(not(target_arch = "wasm32"))]
+            azure_cli: Some(AzureCliCredentialProvider::new()),
+            #[cfg(not(target_arch = "wasm32"))]
+            client_certificate: Some(ClientCertificateCredentialProvider::new()),
+            client_secret: Some(ClientSecretCredentialProvider::new()),
+            azure_pipelines: Some(AzurePipelinesCredentialProvider::new()),
+            workload_identity: Some(WorkloadIdentityCredentialProvider::new()),
+            imds: Some(ImdsCredentialProvider::new()),
+        }
+    }
+}
+
 impl DefaultCredentialProviderBuilder {
     pub fn new() -> Self {
         Self::default()
     }
 
-    pub fn configure_env<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(EnvCredentialProvider) -> EnvCredentialProvider,
-    {
-        let p = self.env.take().unwrap_or_default();
-        self.env = Some(f(p));
+    /// Set the environment credential provider slot.
+    pub fn env(mut self, provider: EnvCredentialProvider) -> Self {
+        self.env = Some(provider);
         self
     }
 
-    pub fn disable_env(mut self, disable: bool) -> Self {
-        if disable {
-            self.env = None;
-        } else if self.env.is_none() {
-            self.env = Some(EnvCredentialProvider::new());
-        }
+    /// Remove the environment credential provider slot.
+    pub fn no_env(mut self) -> Self {
+        self.env = None;
         self
     }
 
-    /// Configure the Azure CLI credential provider.
-    ///
-    /// This is typically used for local development where Azure CLI is
-    /// available and authenticated. You can tweak behavior (like alternative
-    /// token acquisition strategies) via the provided closure. Only available
-    /// on non-wasm32 targets.
+    /// Set the Azure CLI credential provider slot.
     #[cfg(not(target_arch = "wasm32"))]
-    pub fn configure_azure_cli<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(AzureCliCredentialProvider) -> AzureCliCredentialProvider,
-    {
-        let p = self.azure_cli.take().unwrap_or_default();
-        self.azure_cli = Some(f(p));
+    pub fn azure_cli(mut self, provider: AzureCliCredentialProvider) -> Self {
+        self.azure_cli = Some(provider);
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the Azure CLI provider.
-    ///
-    /// Useful in CI or constrained environments where Azure CLI is not
-    /// present, or to explicitly opt out. Only available on non-wasm32 targets.
+    /// Remove the Azure CLI credential provider slot.
     #[cfg(not(target_arch = "wasm32"))]
-    pub fn disable_azure_cli(mut self, disable: bool) -> Self {
-        if disable {
-            self.azure_cli = None;
-        } else if self.azure_cli.is_none() {
-            self.azure_cli = Some(AzureCliCredentialProvider::new());
-        }
+    pub fn no_azure_cli(mut self) -> Self {
+        self.azure_cli = None;
         self
     }
 
-    /// Configure the client certificate credential provider.
-    ///
-    /// Customize certificate-based SPN authentication (tenant/client/cert
-    /// parameters). Only available on non-wasm32 targets.
+    /// Set the client certificate credential provider slot.
     #[cfg(not(target_arch = "wasm32"))]
-    pub fn configure_client_certificate<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(ClientCertificateCredentialProvider) -> ClientCertificateCredentialProvider,
-    {
-        let p = self.client_certificate.take().unwrap_or_default();
-        self.client_certificate = Some(f(p));
+    pub fn client_certificate(mut self, provider: ClientCertificateCredentialProvider) -> Self {
+        self.client_certificate = Some(provider);
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the client certificate provider.
-    ///
-    /// Use to explicitly remove certificate-based auth from the chain or to
-    /// ensure participation. Only available on non-wasm32 targets.
+    /// Remove the client certificate credential provider slot.
     #[cfg(not(target_arch = "wasm32"))]
-    pub fn disable_client_certificate(mut self, disable: bool) -> Self {
-        if disable {
-            self.client_certificate = None;
-        } else if self.client_certificate.is_none() {
-            self.client_certificate = Some(ClientCertificateCredentialProvider::new());
-        }
+    pub fn no_client_certificate(mut self) -> Self {
+        self.client_certificate = None;
         self
     }
 
-    /// Configure the client secret credential provider.
-    ///
-    /// Customize tenant/client parameters used to exchange a client secret
-    /// for an access token suitable for Azure Storage.
-    pub fn configure_client_secret<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(ClientSecretCredentialProvider) -> ClientSecretCredentialProvider,
-    {
-        let p = self.client_secret.take().unwrap_or_default();
-        self.client_secret = Some(f(p));
+    /// Set the client secret credential provider slot.
+    pub fn client_secret(mut self, provider: ClientSecretCredentialProvider) -> Self {
+        self.client_secret = Some(provider);
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the client secret provider.
-    pub fn disable_client_secret(mut self, disable: bool) -> Self {
-        if disable {
-            self.client_secret = None;
-        } else if self.client_secret.is_none() {
-            self.client_secret = Some(ClientSecretCredentialProvider::new());
-        }
+    /// Remove the client secret credential provider slot.
+    pub fn no_client_secret(mut self) -> Self {
+        self.client_secret = None;
         self
     }
 
-    /// Configure the Azure Pipelines workload identity provider.
-    ///
-    /// Allows customizing how OIDC tokens from Azure Pipelines are exchanged
-    /// for Azure AD access tokens.
-    pub fn configure_azure_pipelines<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(AzurePipelinesCredentialProvider) -> AzurePipelinesCredentialProvider,
-    {
-        let p = self.azure_pipelines.take().unwrap_or_default();
-        self.azure_pipelines = Some(f(p));
+    /// Set the Azure Pipelines credential provider slot.
+    pub fn azure_pipelines(mut self, provider: AzurePipelinesCredentialProvider) -> Self {
+        self.azure_pipelines = Some(provider);
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the Azure Pipelines provider.
-    pub fn disable_azure_pipelines(mut self, disable: bool) -> Self {
-        if disable {
-            self.azure_pipelines = None;
-        } else if self.azure_pipelines.is_none() {
-            self.azure_pipelines = Some(AzurePipelinesCredentialProvider::new());
-        }
+    /// Remove the Azure Pipelines credential provider slot.
+    pub fn no_azure_pipelines(mut self) -> Self {
+        self.azure_pipelines = None;
         self
     }
 
-    /// Configure the Kubernetes workload identity provider.
-    ///
-    /// Allows customizing tenant or other parameters used to exchange
-    /// federated tokens for Azure AD access tokens.
-    pub fn configure_workload_identity<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(WorkloadIdentityCredentialProvider) -> WorkloadIdentityCredentialProvider,
-    {
-        let p = self.workload_identity.take().unwrap_or_default();
-        self.workload_identity = Some(f(p));
+    /// Set the workload identity credential provider slot.
+    pub fn workload_identity(mut self, provider: WorkloadIdentityCredentialProvider) -> Self {
+        self.workload_identity = Some(provider);
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the workload identity provider.
-    pub fn disable_workload_identity(mut self, disable: bool) -> Self {
-        if disable {
-            self.workload_identity = None;
-        } else if self.workload_identity.is_none() {
-            self.workload_identity = Some(WorkloadIdentityCredentialProvider::new());
-        }
+    /// Remove the workload identity credential provider slot.
+    pub fn no_workload_identity(mut self) -> Self {
+        self.workload_identity = None;
         self
     }
 
-    /// Configure the Azure IMDS provider.
-    ///
-    /// Allows setting an alternate metadata endpoint or other parameters used
-    /// to obtain managed identity tokens on Azure VMs.
-    pub fn configure_imds<F>(mut self, f: F) -> Self
-    where
-        F: FnOnce(ImdsCredentialProvider) -> ImdsCredentialProvider,
-    {
-        let p = self.imds.take().unwrap_or_default();
-        self.imds = Some(f(p));
+    /// Set the Azure IMDS credential provider slot.
+    pub fn imds(mut self, provider: ImdsCredentialProvider) -> Self {
+        self.imds = Some(provider);
         self
     }
 
-    /// Disable (true) or ensure enabled (false) the IMDS provider.
-    pub fn disable_imds(mut self, disable: bool) -> Self {
-        if disable {
-            self.imds = None;
-        } else if self.imds.is_none() {
-            self.imds = Some(ImdsCredentialProvider::new());
-        }
+    /// Remove the Azure IMDS credential provider slot.
+    pub fn no_imds(mut self) -> Self {
+        self.imds = None;
         self
     }
 
@@ -280,47 +227,33 @@ impl DefaultCredentialProviderBuilder {
 
         if let Some(p) = self.env {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(EnvCredentialProvider::new());
         }
 
         #[cfg(not(target_arch = "wasm32"))]
         {
             if let Some(p) = self.azure_cli {
                 chain = chain.push(p);
-            } else {
-                chain = chain.push(AzureCliCredentialProvider::new());
             }
 
             if let Some(p) = self.client_certificate {
                 chain = chain.push(p);
-            } else {
-                chain = chain.push(ClientCertificateCredentialProvider::new());
             }
         }
 
         if let Some(p) = self.client_secret {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(ClientSecretCredentialProvider::new());
         }
 
         if let Some(p) = self.azure_pipelines {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(AzurePipelinesCredentialProvider::new());
         }
 
         if let Some(p) = self.workload_identity {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(WorkloadIdentityCredentialProvider::new());
         }
 
         if let Some(p) = self.imds {
             chain = chain.push(p);
-        } else {
-            chain = chain.push(ImdsCredentialProvider::new());
         }
 
         DefaultCredentialProvider::with_chain(chain)
diff --git a/services/azure-storage/tests/credential_providers/default.rs b/services/azure-storage/tests/credential_providers/default.rs
index e76b5eb..51c9845 100644
--- a/services/azure-storage/tests/credential_providers/default.rs
+++ b/services/azure-storage/tests/credential_providers/default.rs
@@ -16,9 +16,10 @@
 // under the License.
 
 use reqsign_azure_storage::{Credential, DefaultCredentialProvider};
-use reqsign_core::{Context, OsEnv, ProvideCredential, ProvideCredentialChain, Result};
+use reqsign_core::{Context, OsEnv, ProvideCredential, ProvideCredentialChain, Result, StaticEnv};
 use reqsign_file_read_tokio::TokioFileRead;
 use reqsign_http_send_reqwest::ReqwestHttpSend;
+use std::collections::HashMap;
 use std::sync::Arc;
 
 fn is_test_enabled() -> bool {
@@ -146,3 +147,37 @@ async fn test_chain_returns_none_when_all_fail() {
     assert_eq!(*count1.lock().unwrap(), 1);
     assert_eq!(*count2.lock().unwrap(), 1);
 }
+
+#[tokio::test]
+async fn test_builder_no_env_removes_provider() {
+    let ctx = Context::new()
+        .with_file_read(TokioFileRead)
+        .with_http_send(ReqwestHttpSend::default())
+        .with_env(StaticEnv {
+            home_dir: None,
+            envs: HashMap::from([
+                (
+                    "AZURE_STORAGE_ACCOUNT_NAME".to_string(),
+                    "testaccount".to_string(),
+                ),
+                (
+                    "AZURE_STORAGE_ACCOUNT_KEY".to_string(),
+                    "dGVzdGtleQ==".to_string(),
+                ),
+            ]),
+        });
+
+    let builder = DefaultCredentialProvider::builder()
+        .no_env()
+        .no_client_secret()
+        .no_azure_pipelines()
+        .no_workload_identity()
+        .no_imds();
+    #[cfg(not(target_arch = "wasm32"))]
+    let builder = builder.no_azure_cli().no_client_certificate();
+
+    let provider = builder.build();
+    let result = provider.provide_credential(&ctx).await.unwrap();
+
+    assert!(result.is_none());
+}