Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Rust] Rust Storage SDK Wrapper Proof of Concepts #1

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bd65517
Initial wrapping work, BlobClient with working e2e download, no auth
vincenttran-msft Oct 30, 2024
bc60739
Moved gen code into our dir, rough draft StorageHeadersPolicy, having…
vincenttran-msft Oct 31, 2024
da34a57
Modifiable options POC, next step is bringing in auth and verifying S…
vincenttran-msft Oct 31, 2024
3d8fad6
Added StorageHeadersPolicy to per_call_policies, but weird side effec…
vincenttran-msft Nov 1, 2024
11afbc6
Working authenticated download
vincenttran-msft Nov 2, 2024
c04ebff
Fleshed out more of ContainerClient, BlobServiceClient, added get_blo…
vincenttran-msft Nov 5, 2024
d30397c
Found root-cause of API failures: %-encoding
vincenttran-msft Nov 5, 2024
ba209be
All written tests working except commented, double-ended issues with …
vincenttran-msft Nov 6, 2024
2818038
Finally working tags header value, working options bag
vincenttran-msft Nov 7, 2024
61e55eb
Refactor get-subclient, bring back type-state for blob types(append,p…
vincenttran-msft Nov 12, 2024
0282be7
Attempt to make it one crate
vincenttran-msft Nov 12, 2024
c71e264
Create container working, list blobs and containers working
vincenttran-msft Nov 13, 2024
0d081a6
stage_block and commit_block_list fleshed out, but too many issues to…
vincenttran-msft Nov 14, 2024
39fa6b3
Generated code changes necessary
vincenttran-msft Nov 14, 2024
bf66185
Merge branch 'main' into vincenttran/wrapping_proof_of_concept
vincenttran-msft Nov 14, 2024
906db1c
Working overwrite options bag, example for pub(crate) options in opti…
vincenttran-msft Nov 15, 2024
9b081bc
Working setting tags on upload, sample helper
vincenttran-msft Nov 15, 2024
96d5ca8
Slight cleanup, ranged downloads
vincenttran-msft Nov 19, 2024
e1e96f3
blob_client.rs all passing w/ asserts
vincenttran-msft Nov 19, 2024
a597ebf
Code cleanup
vincenttran-msft Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion sdk/storage/azure_storage_blob/src/clients/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use azure_core::{
Result, Url,
};
use azure_identity::DefaultAzureCredentialBuilder;
use blob_storage::blob_blob::BlobBlobDownloadOptions;
use blob_storage::blob_blob::{BlobBlobDownloadOptions, BlobBlobGetPropertiesOptions};
use blob_storage::blob_client::BlobClientOptions;
use blob_storage::BlobClient as GeneratedBlobClient;
use std::sync::Arc;
Expand Down Expand Up @@ -86,6 +86,26 @@ impl BlobClient {
.await
}

pub async fn get_blob_properties(
&self,
options: Option<BlobBlobGetPropertiesOptions<'_>>,
) -> Result<Response<()>> {
// This hard-coded value still works, even though this is technically a bug for version_id
let version = String::from("80bc3c5e-3bb7-95f6-6c57-8ceb2c9155");
self.client
.get_blob_blob_client()
.get_properties(
self.container_name.clone(),
self.blob_name.clone(),
version, //blob version
String::from(Self::VERSION_ID), //svc version
Some(BlobBlobGetPropertiesOptions::default()),
)
.await
}

// pub fn get_container_client(&self) ->

// pub async fn get_blob_properties(&self) -> Result<Response> {
// // Build the get properties request itself
// let mut request = Request::new(self.url.to_owned(), Method::Head); // This is technically cloning
Expand Down Expand Up @@ -125,6 +145,27 @@ mod tests {
);
}

#[tokio::test]
async fn test_get_blob_properties() {
let blob_client = BlobClient::new(
String::from("https://vincenttranpublicac.blob.core.windows.net/"),
String::from("public"),
String::from("hello.txt"),
None,
Some(BlobClientOptions::default()),
)
.unwrap();
let response = blob_client
.get_blob_properties(Some(BlobBlobGetPropertiesOptions::default()))
.await
.unwrap();
print!("{:?}", response);
print!(
"\n{:?}",
response.into_body().collect_string().await.unwrap()
);
}

#[tokio::test]
// Don't forget to az-login
async fn test_download_blob_authenticated() {
Expand Down
181 changes: 181 additions & 0 deletions sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,183 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

use crate::policies::storage_headers_policy::StorageHeadersPolicy;
use azure_core::credentials::TokenCredential;
use azure_core::headers::HeaderName;
use azure_core::{
AsClientOptions, BearerTokenCredentialPolicy, Context, Method, Policy, Request, Response,
Result, Url,
};
use azure_identity::DefaultAzureCredentialBuilder;
use blob_storage::blob_client::BlobClientOptions;
use blob_storage::blob_container::{
BlobContainer, BlobContainerCreateOptions, BlobContainerGetAccountInfoOptions,
BlobContainerGetPropertiesOptions,
};
use blob_storage::BlobClient as GeneratedBlobClient;
use std::sync::Arc;
use uuid::Uuid;
pub struct ContainerClient {
endpoint: String,
container_name: String,
credential: Option<Arc<dyn TokenCredential>>,
client: GeneratedBlobClient,
}

impl ContainerClient {
const VERSION_ID: &'static str = ("2024-08-04");

pub fn new(
endpoint: String,
container_name: String,
credential: Option<Arc<dyn TokenCredential>>,
options: Option<BlobClientOptions>,
) -> Result<Self> {
let mut options = BlobClientOptions::default();

// Fold in StorageHeadersPolicy policy via ClientOptions
let mut client_options = options.client_options.clone();
let mut per_call_policies = client_options.per_call_policies().clone();
let storage_headers_policy = Arc::new(StorageHeadersPolicy::new());
per_call_policies.push(storage_headers_policy);
client_options.set_per_call_policies(per_call_policies);

// Conditionally add authentication if provided
if credential.is_some() {
let oauth_token_policy = BearerTokenCredentialPolicy::new(
credential.clone().unwrap(),
["https://storage.azure.com/.default"],
);
let mut per_try_policies = client_options.per_call_policies().clone();
per_try_policies.push(Arc::new(oauth_token_policy) as Arc<dyn Policy>);
client_options.set_per_try_policies(per_try_policies);
}

// Set it after modifying everything
options.client_options = client_options.clone();

let client =
GeneratedBlobClient::with_no_credential(endpoint.clone(), Some(options.clone()))?;

Ok(Self {
endpoint: endpoint.clone(),
container_name: container_name.clone(),
credential,
client: client,
})
}

pub async fn create_container(
&self,
options: Option<BlobContainerCreateOptions<'_>>,
) -> Result<Response<()>> {
self.client
.get_blob_container_client()
.create(
self.container_name.clone(),
String::from(Self::VERSION_ID), //svc version
Some(BlobContainerCreateOptions::default()),
)
.await
}

pub async fn get_container_properties(
&self,
options: Option<BlobContainerGetPropertiesOptions<'_>>,
) -> Result<Response<()>> {
self.client
.get_blob_container_client()
.get_properties(
self.container_name.clone(),
String::from(Self::VERSION_ID), //svc version
Some(BlobContainerGetPropertiesOptions::default()),
)
.await
}

pub async fn get_account_info(
&self,
options: Option<BlobContainerGetAccountInfoOptions<'_>>,
) -> Result<Response<()>> {
self.client
.get_blob_container_client()
.get_account_info(
self.container_name.clone(),
String::from(Self::VERSION_ID), //svc version
Some(BlobContainerGetAccountInfoOptions::default()),
)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
// Don't forget to az-login
// This fails for ContainerNotFound
async fn test_get_container_properties_auth() {
let credential = DefaultAzureCredentialBuilder::default().build().unwrap();
let container_client = ContainerClient::new(
String::from("https://vincenttranstock.blob.core.windows.net/"),
String::from("acontainer108f32e8"),
Some(credential),
Some(BlobClientOptions::default()),
)
.unwrap();
let response = container_client
.get_container_properties(Some(BlobContainerGetPropertiesOptions::default()))
.await
.unwrap();
print!("{:?}", response);
print!(
"\n{:?}",
response.into_body().collect_string().await.unwrap()
);
}

#[tokio::test]
// Don't forget to az-login
// This fails for kind: HttpResponse { status: LengthRequired, error_code: None
async fn test_create_container() {
let credential = DefaultAzureCredentialBuilder::default().build().unwrap();
let container_client = ContainerClient::new(
String::from("https://vincenttranstock.blob.core.windows.net/"),
String::from("mynewcontainer"),
Some(credential),
Some(BlobClientOptions::default()),
)
.unwrap();
let response = container_client
.create_container(Some(BlobContainerCreateOptions::default()))
.await
.unwrap();
print!("{:?}", response);
print!(
"\n{:?}",
response.into_body().collect_string().await.unwrap()
);
}

#[tokio::test]
// Don't forget to az-login
// This also fails for ContainerNotFound
async fn test_get_account_info_auth() {
let credential = DefaultAzureCredentialBuilder::default().build().unwrap();
let container_client = ContainerClient::new(
String::from("https://vincenttranstock.blob.core.windows.net/"),
String::from("acontainer108f32e8"),
Some(credential),
Some(BlobClientOptions::default()),
)
.unwrap();
let response = container_client
.get_account_info(Some(BlobContainerGetAccountInfoOptions::default()))
.await
.unwrap();
print!("{:?}", response);
print!(
"\n{:?}",
response.into_body().collect_string().await.unwrap()
);
}
}
Loading