Skip to content

Commit d607009

Browse files
authored
move StorageCredentials out of CloudLocation (#1423)
This is a needed step towards allowing the storage credentials to be updated without recreating all of the clients, such as what is needed to address #1415.
1 parent edd2434 commit d607009

File tree

8 files changed

+149
-97
lines changed

8 files changed

+149
-97
lines changed

sdk/data_tables/src/clients/table_service_client.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,61 @@ use super::TableClient;
1212
pub struct TableServiceClientBuilder {
1313
cloud_location: CloudLocation,
1414
options: ClientOptions,
15+
credentials: StorageCredentials,
1516
}
1617

1718
impl TableServiceClientBuilder {
18-
/// Create a new instance of `BlobServiceClientBuilder`.
19+
/// Create a new instance of `TableServiceClientBuilder`.
1920
#[must_use]
20-
pub fn new(account: impl Into<String>, credentials: impl Into<StorageCredentials>) -> Self {
21-
Self::with_location(CloudLocation::Public {
22-
account: account.into(),
23-
credentials: credentials.into(),
24-
})
21+
pub fn new<A, C>(account: A, credentials: C) -> Self
22+
where
23+
A: Into<String>,
24+
C: Into<StorageCredentials>,
25+
{
26+
Self::with_location(
27+
CloudLocation::Public {
28+
account: account.into(),
29+
},
30+
credentials,
31+
)
2532
}
2633

2734
/// Create a new instance of `BlobServiceClientBuilder` with a cloud location.
2835
#[must_use]
29-
pub fn with_location(cloud_location: CloudLocation) -> Self {
36+
pub fn with_location<C>(cloud_location: CloudLocation, credentials: C) -> Self
37+
where
38+
C: Into<StorageCredentials>,
39+
{
3040
Self {
3141
options: ClientOptions::default(),
3242
cloud_location,
43+
credentials: credentials.into(),
3344
}
3445
}
3546

3647
/// Use the emulator with default settings
3748
#[must_use]
3849
pub fn emulator() -> Self {
39-
Self::with_location(CloudLocation::Emulator {
40-
address: "127.0.0.1".to_owned(),
41-
port: 10002,
42-
})
50+
Self::with_location(
51+
CloudLocation::Emulator {
52+
address: "127.0.0.1".to_owned(),
53+
port: 10002,
54+
},
55+
StorageCredentials::emulator(),
56+
)
4357
}
4458

4559
/// Convert the builder into a `TableServiceClient` instance.
4660
#[must_use]
4761
pub fn build(self) -> TableServiceClient {
48-
let credentials = self.cloud_location.credentials();
62+
let Self {
63+
cloud_location,
64+
options,
65+
credentials,
66+
} = self;
4967
TableServiceClient {
50-
pipeline: azure_storage::clients::new_pipeline_from_options(
51-
self.options,
52-
credentials.clone(),
53-
),
54-
cloud_location: self.cloud_location,
68+
pipeline: azure_storage::clients::new_pipeline_from_options(options, credentials),
69+
cloud_location,
5570
}
5671
}
5772

sdk/storage/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ url = "2.2"
2525
uuid = { version = "1.0", features = ["v4"] }
2626
bytes = "1.0"
2727
RustyXML = "0.3"
28-
once_cell = "1.7"
2928
hmac = "0.12"
3029
sha2 = "0.10"
3130

sdk/storage/src/authorization/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use azure_core::{
55
error::{ErrorKind, ResultExt},
66
};
77
use std::sync::Arc;
8+
use url::Url;
89

910
pub(crate) use authorization_policy::AuthorizationPolicy;
1011

12+
use crate::clients::{EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY};
13+
1114
/// Credentials for accessing a storage account.
1215
///
1316
/// # Example
@@ -112,6 +115,11 @@ impl StorageCredentials {
112115
pub fn anonymous() -> Self {
113116
Self::Anonymous
114117
}
118+
119+
/// Create an Access Key credential for use with the Azure Storage emulator
120+
pub fn emulator() -> Self {
121+
Self::access_key(EMULATOR_ACCOUNT, EMULATOR_ACCOUNT_KEY)
122+
}
115123
}
116124

117125
impl std::fmt::Debug for StorageCredentials {
@@ -147,6 +155,16 @@ impl From<Arc<dyn TokenCredential>> for StorageCredentials {
147155
}
148156
}
149157

158+
impl TryFrom<&Url> for StorageCredentials {
159+
type Error = azure_core::Error;
160+
fn try_from(value: &Url) -> Result<Self, Self::Error> {
161+
match value.query() {
162+
Some(query) => Self::sas_token(query),
163+
None => Ok(Self::Anonymous),
164+
}
165+
}
166+
}
167+
150168
fn get_sas_token_parms(sas_token: &str) -> azure_core::Result<Vec<(String, String)>> {
151169
// Any base url will do: we just need to parse the SAS token
152170
// to get its query pairs.

sdk/storage/src/cloud_location.rs

Lines changed: 6 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use crate::{clients::ServiceType, StorageCredentials};
2-
use once_cell::sync::Lazy;
1+
use crate::clients::ServiceType;
32
use std::convert::TryFrom;
43
use url::Url;
54

@@ -8,22 +7,13 @@ use url::Url;
87
#[derive(Debug, Clone)]
98
pub enum CloudLocation {
109
/// Azure public cloud
11-
Public {
12-
account: String,
13-
credentials: StorageCredentials,
14-
},
10+
Public { account: String },
1511
/// Azure China cloud
16-
China {
17-
account: String,
18-
credentials: StorageCredentials,
19-
},
12+
China { account: String },
2013
/// Use the well-known emulator
2114
Emulator { address: String, port: u16 },
2215
/// A custom base URL
23-
Custom {
24-
uri: String,
25-
credentials: StorageCredentials,
26-
},
16+
Custom { uri: String },
2717
}
2818

2919
impl CloudLocation {
@@ -51,15 +41,6 @@ impl CloudLocation {
5141
};
5242
Ok(url::Url::parse(&url)?)
5343
}
54-
55-
pub fn credentials(&self) -> &StorageCredentials {
56-
match self {
57-
CloudLocation::Public { credentials, .. }
58-
| CloudLocation::China { credentials, .. }
59-
| CloudLocation::Custom { credentials, .. } => credentials,
60-
CloudLocation::Emulator { .. } => &EMULATOR_CREDENTIALS,
61-
}
62-
}
6344
}
6445

6546
impl TryFrom<&Url> for CloudLocation {
@@ -68,11 +49,6 @@ impl TryFrom<&Url> for CloudLocation {
6849
// TODO: This only works for Public and China clouds.
6950
// ref: https://github.com/Azure/azure-sdk-for-rust/issues/502
7051
fn try_from(url: &Url) -> azure_core::Result<Self> {
71-
let credentials = match url.query() {
72-
Some(token) => StorageCredentials::sas_token(token)?,
73-
None => StorageCredentials::Anonymous,
74-
};
75-
7652
let host = url.host_str().ok_or_else(|| {
7753
azure_core::Error::with_message(azure_core::error::ErrorKind::DataConversion, || {
7854
"unable to find the target host in the URL"
@@ -92,14 +68,8 @@ impl TryFrom<&Url> for CloudLocation {
9268
let rest = domain.join(".");
9369

9470
match rest.as_str() {
95-
"core.windows.net" => Ok(CloudLocation::Public {
96-
account,
97-
credentials,
98-
}),
99-
"core.chinacloudapi.cn" => Ok(CloudLocation::China {
100-
account,
101-
credentials,
102-
}),
71+
"core.windows.net" => Ok(CloudLocation::Public { account }),
72+
"core.chinacloudapi.cn" => Ok(CloudLocation::China { account }),
10373
_ => Err(azure_core::Error::with_message(
10474
azure_core::error::ErrorKind::DataConversion,
10575
|| format!("URL refers to a domain that is not a Public or China domain: {host}"),
@@ -108,10 +78,6 @@ impl TryFrom<&Url> for CloudLocation {
10878
}
10979
}
11080

111-
pub static EMULATOR_CREDENTIALS: Lazy<StorageCredentials> = Lazy::new(|| {
112-
StorageCredentials::Key(EMULATOR_ACCOUNT.to_owned(), EMULATOR_ACCOUNT_KEY.to_owned())
113-
});
114-
11581
/// The well-known account used by Azurite and the legacy Azure Storage Emulator.
11682
/// <https://docs.microsoft.com/azure/storage/common/storage-use-azurite#well-known-storage-account-and-key>
11783
pub const EMULATOR_ACCOUNT: &str = "devstoreaccount1";
@@ -133,9 +99,6 @@ mod tests {
13399
let cloud_location: CloudLocation = (&public_with_token).try_into()?;
134100
assert_eq!(public_without_token, cloud_location.url(ServiceType::Blob)?);
135101

136-
let creds = cloud_location.credentials();
137-
assert!(matches!(creds, &StorageCredentials::SASToken(_)));
138-
139102
let file_url = Url::parse("file://tmp/test.txt")?;
140103
let result: azure_core::Result<CloudLocation> = (&file_url).try_into();
141104
assert!(result.is_err());

sdk/storage_blobs/src/clients/blob_service_client.rs

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,63 @@ use super::{BlobClient, BlobLeaseClient, ContainerClient, ContainerLeaseClient};
1818
pub struct ClientBuilder {
1919
cloud_location: CloudLocation,
2020
options: ClientOptions,
21+
credentials: StorageCredentials,
2122
}
2223

2324
impl ClientBuilder {
2425
/// Create a new instance of `ClientBuilder`.
2526
#[must_use]
26-
pub fn new(account: impl Into<String>, credentials: impl Into<StorageCredentials>) -> Self {
27-
Self::with_location(CloudLocation::Public {
28-
account: account.into(),
29-
credentials: credentials.into(),
30-
})
27+
pub fn new<A, C>(account: A, credentials: C) -> Self
28+
where
29+
A: Into<String>,
30+
C: Into<StorageCredentials>,
31+
{
32+
Self::with_location(
33+
CloudLocation::Public {
34+
account: account.into(),
35+
},
36+
credentials,
37+
)
3138
}
3239

3340
/// Create a new instance of `ClientBuilder` with a cloud location.
3441
#[must_use]
35-
pub fn with_location(cloud_location: CloudLocation) -> Self {
42+
pub fn with_location<C>(cloud_location: CloudLocation, credentials: C) -> Self
43+
where
44+
C: Into<StorageCredentials>,
45+
{
3646
Self {
3747
options: ClientOptions::default(),
3848
cloud_location,
49+
credentials: credentials.into(),
3950
}
4051
}
4152

4253
/// Use the emulator with default settings
4354
#[must_use]
4455
pub fn emulator() -> Self {
45-
Self::with_location(CloudLocation::Emulator {
46-
address: "127.0.0.1".to_owned(),
47-
port: 10000,
48-
})
56+
Self::with_location(
57+
CloudLocation::Emulator {
58+
address: "127.0.0.1".to_owned(),
59+
port: 10000,
60+
},
61+
StorageCredentials::emulator(),
62+
)
4963
}
5064

5165
/// Convert the builder into a `BlobServiceClient` instance.
5266
#[must_use]
5367
pub fn blob_service_client(self) -> BlobServiceClient {
54-
let credentials = self.cloud_location.credentials();
68+
let Self {
69+
cloud_location,
70+
options,
71+
credentials,
72+
} = self;
73+
5574
BlobServiceClient {
56-
pipeline: new_pipeline_from_options(self.options, credentials.clone()),
57-
cloud_location: self.cloud_location,
75+
pipeline: new_pipeline_from_options(options, credentials.clone()),
76+
cloud_location,
77+
credentials,
5878
}
5979
}
6080

@@ -138,6 +158,7 @@ impl ClientBuilder {
138158
pub struct BlobServiceClient {
139159
pipeline: Pipeline,
140160
cloud_location: CloudLocation,
161+
credentials: StorageCredentials,
141162
}
142163

143164
impl BlobServiceClient {
@@ -203,7 +224,7 @@ impl BlobServiceClient {
203224
}
204225

205226
pub(crate) fn credentials(&self) -> &StorageCredentials {
206-
self.cloud_location.credentials()
227+
&self.credentials
207228
}
208229

209230
pub(crate) fn finalize_request(

sdk/storage_blobs/src/clients/container_client.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ impl ContainerClient {
3232

3333
pub fn from_sas_url(url: &Url) -> azure_core::Result<Self> {
3434
let cloud_location: CloudLocation = url.try_into()?;
35+
let credentials: StorageCredentials = url.try_into()?;
3536

3637
let container = url.path().split_terminator('/').nth(1).ok_or_else(|| {
3738
azure_core::Error::with_message(azure_core::error::ErrorKind::DataConversion, || {
3839
"unable to find storage container from url"
3940
})
4041
})?;
4142

42-
let client = ClientBuilder::with_location(cloud_location).container_client(container);
43+
let client =
44+
ClientBuilder::with_location(cloud_location, credentials).container_client(container);
4345
Ok(client)
4446
}
4547

sdk/storage_datalake/src/clients/data_lake_client.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,49 @@ use azure_storage::CloudLocation;
1010
pub struct DataLakeClientBuilder {
1111
cloud_location: CloudLocation,
1212
options: ClientOptions,
13+
credentials: StorageCredentials,
1314
}
1415

1516
impl DataLakeClientBuilder {
1617
/// Create a new instance of `DataLakeClientBuilder`.
1718
#[must_use]
18-
pub fn new(account: impl Into<String>, credentials: StorageCredentials) -> Self {
19-
Self::with_location(CloudLocation::Public {
20-
account: account.into(),
19+
pub fn new<A, C>(account: A, credentials: C) -> Self
20+
where
21+
A: Into<String>,
22+
C: Into<StorageCredentials>,
23+
{
24+
Self::with_location(
25+
CloudLocation::Public {
26+
account: account.into(),
27+
},
2128
credentials,
22-
})
29+
)
2330
}
2431

2532
/// Create a new instance of `DataLakeClientBuilder` with a cloud location.
2633
#[must_use]
27-
pub fn with_location(cloud_location: CloudLocation) -> Self {
34+
pub fn with_location<C>(cloud_location: CloudLocation, credentials: C) -> Self
35+
where
36+
C: Into<StorageCredentials>,
37+
{
2838
Self {
2939
options: ClientOptions::default(),
3040
cloud_location,
41+
credentials: credentials.into(),
3142
}
3243
}
3344

3445
/// Convert the builder into a `DataLakeClient` instance.
3546
#[must_use]
3647
pub fn build(self) -> DataLakeClient {
37-
let credentials = self.cloud_location.credentials();
48+
let Self {
49+
credentials,
50+
cloud_location,
51+
options,
52+
} = self;
3853
DataLakeClient {
39-
pipeline: new_pipeline_from_options(self.options, credentials.clone()),
40-
cloud_location: self.cloud_location,
54+
pipeline: new_pipeline_from_options(options, credentials),
55+
cloud_location,
4156
}
4257
}
4358

0 commit comments

Comments
 (0)