From 0f99cc65f7c8a7c2d386722687e099b511e21439 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Wed, 16 Jul 2025 12:19:22 -0700 Subject: [PATCH 1/3] Some sort of SAS support --- .../src/clients/blob_client.rs | 26 ++++++++++++ .../src/generated/clients/blob_client.rs | 42 +++++++++++++++++-- .../azure_storage_blob/tests/blob_client.rs | 31 +++++++++++--- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 1c2564d279..afd4f340ee 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -83,6 +83,32 @@ impl BlobClient { }) } + pub fn new_no_credential( + endpoint: &str, + container_name: String, + blob_name: String, + options: Option, + ) -> Result { + let mut options = options.unwrap_or_default(); + + let storage_headers_policy = Arc::new(StorageHeadersPolicy); + options + .client_options + .per_call_policies + .push(storage_headers_policy); + + let client = GeneratedBlobClient::new_no_credential( + endpoint, + container_name.clone(), + blob_name.clone(), + Some(options), + )?; + Ok(Self { + endpoint: endpoint.parse()?, + client, + }) + } + /// Returns a new instance of AppendBlobClient. /// /// # Arguments diff --git a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs index 9dac2880bf..68cdbe5da7 100644 --- a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs @@ -102,6 +102,35 @@ impl BlobClient { }) } + pub fn new_no_credential( + endpoint: &str, + container_name: String, + blob_name: String, + options: Option, + ) -> Result { + let options = options.unwrap_or_default(); + let endpoint = Url::parse(endpoint)?; + if !endpoint.scheme().starts_with("http") { + return Err(azure_core::Error::message( + azure_core::error::ErrorKind::Other, + format!("{endpoint} must use http(s)"), + )); + } + Ok(Self { + blob_name, + container_name, + endpoint, + version: options.version, + pipeline: Pipeline::new( + option_env!("CARGO_PKG_NAME"), + option_env!("CARGO_PKG_VERSION"), + options.client_options, + Vec::default(), + Vec::default(), + ), + }) + } + /// Returns the Url associated with this client. pub fn endpoint(&self) -> &Url { &self.endpoint @@ -770,10 +799,15 @@ impl BlobClient { let options = options.unwrap_or_default(); let ctx = Context::with_context(&options.method_options.context); let mut url = self.endpoint.clone(); - let mut path = String::from("{containerName}/{blobName}"); - path = path.replace("{blobName}", &self.blob_name); - path = path.replace("{containerName}", &self.container_name); - url = url.join(&path)?; + // let mut path = String::from("{containerName}/{blobName}"); + // path = path.replace("{blobName}", &self.blob_name); + // path = path.replace("{containerName}", &self.container_name); + // url = url.join(&path)?; + { + let mut path_segments = url.path_segments_mut().expect("Cannot be base"); + path_segments.push(&self.container_name); + path_segments.push(&self.blob_name); + } if let Some(snapshot) = options.snapshot { url.query_pairs_mut().append_pair("snapshot", &snapshot); } diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 5d5818b2f9..225608ef39 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -6,12 +6,15 @@ use azure_core::{ Bytes, }; use azure_core_test::{recorded, TestContext}; -use azure_storage_blob::models::{ - AccessTier, BlobClientAcquireLeaseResultHeaders, BlobClientChangeLeaseResultHeaders, - BlobClientDownloadOptions, BlobClientDownloadResultHeaders, BlobClientGetPropertiesOptions, - BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions, - BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientUploadOptions, - LeaseState, +use azure_storage_blob::{ + models::{ + AccessTier, BlobClientAcquireLeaseResultHeaders, BlobClientChangeLeaseResultHeaders, + BlobClientDownloadOptions, BlobClientDownloadResultHeaders, BlobClientGetPropertiesOptions, + BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions, + BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientUploadOptions, + LeaseState, + }, + BlobClient, }; use azure_storage_blob_test::{create_test_blob, get_blob_name, get_container_client}; use std::{collections::HashMap, error::Error, time::Duration}; @@ -423,3 +426,19 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { + // SAS + let endpoint = "endpoint_with_sas"; + let container_name = "acontainer108f32e8"; + let blob_name = "goodbye.txt"; + let sas_blob_client = + BlobClient::new_no_credential(endpoint, container_name.into(), blob_name.into(), None)?; + + let blob_properties = sas_blob_client.get_properties(None).await?; + let content_length = blob_properties.content_length()?; + assert_eq!(11, content_length.unwrap()); + + Ok(()) +} From cdd65fbd021cf35826a4bb89e4ff046d40d6e986 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 21 Jul 2025 15:06:56 -0700 Subject: [PATCH 2/3] Refactor based on PR feedback --- .../src/clients/blob_client.rs | 36 +++++++++++++------ .../src/generated/clients/blob_client.rs | 11 ++++-- .../azure_storage_blob/tests/blob_client.rs | 8 ++--- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index afd4f340ee..7e12904ad7 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -83,12 +83,7 @@ impl BlobClient { }) } - pub fn new_no_credential( - endpoint: &str, - container_name: String, - blob_name: String, - options: Option, - ) -> Result { + pub fn from_blob_url(blob_url: &str, options: Option) -> Result { let mut options = options.unwrap_or_default(); let storage_headers_policy = Arc::new(StorageHeadersPolicy); @@ -97,14 +92,35 @@ impl BlobClient { .per_call_policies .push(storage_headers_policy); + // TODO: We would abstract this out to a helper that is fancier and can handle edge-cases, which Url crate may not have been designed to handle + // Parse out container name and blob name, then remove them from the path + let mut container_name = ""; + let mut blob_name = ""; + let mut url = Url::parse(blob_url)?; + // URL BEFORE: https://vincenttranstock.blob.core.windows.net/acontainer108f32e8/goodbye.txt?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig + // println!("URL BEFORE: {}", url.clone()); + + let url_for_path_segments = url.clone(); + if let Some(mut segments) = url_for_path_segments.path_segments() { + container_name = segments.next().unwrap(); + blob_name = segments.next().unwrap(); + } + url.set_path(""); + // URL AFTER NULLED PATH: https://vincenttranstock.blob.core.windows.net/?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig + // println!("URL AFTER NULLED PATH: {}", url.clone()); + + // Therefore, given the current design, the endpoint passed to the generated client will have query parameters on it. let client = GeneratedBlobClient::new_no_credential( - endpoint, - container_name.clone(), - blob_name.clone(), + url.as_str(), + container_name.to_string(), + blob_name.to_string(), Some(options), )?; + + // Consequently that means currently endpoint() will have all the query parameters in it. + // We could strip it out here before saving on this client, but that would mean a mismatch of endpoint values between our client and generated. Ok(Self { - endpoint: endpoint.parse()?, + endpoint: url, client, }) } diff --git a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs index 68cdbe5da7..b0de2925b1 100644 --- a/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/generated/clients/blob_client.rs @@ -803,11 +803,16 @@ impl BlobClient { // path = path.replace("{blobName}", &self.blob_name); // path = path.replace("{containerName}", &self.container_name); // url = url.join(&path)?; + + // request uri before: https://vincenttranstock.blob.core.windows.net/?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig= + // println!("request uri before: {}", url); { - let mut path_segments = url.path_segments_mut().expect("Cannot be base"); - path_segments.push(&self.container_name); - path_segments.push(&self.blob_name); + url.path_segments_mut() + .expect("Cannot be base") + .extend([&self.container_name, &self.blob_name]); } + // request uri after: https://vincenttranstock.blob.core.windows.net/acontainer108f32e8/goodbye.txt?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig= + // println!("request uri after: {}", url); if let Some(snapshot) = options.snapshot { url.query_pairs_mut().append_pair("snapshot", &snapshot); } diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 225608ef39..595022af60 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -430,11 +430,9 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { // SAS - let endpoint = "endpoint_with_sas"; - let container_name = "acontainer108f32e8"; - let blob_name = "goodbye.txt"; - let sas_blob_client = - BlobClient::new_no_credential(endpoint, container_name.into(), blob_name.into(), None)?; + let blob_url = "https://vincenttranstock.blob.core.windows.net/acontainer108f32e8/goodbye.txt?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig=e7QwWlj42eY%2FqDMSdSppRqSSqlkinW00%2Bymu03LAuek%3D"; + + let sas_blob_client = BlobClient::from_blob_url(blob_url, None)?; let blob_properties = sas_blob_client.get_properties(None).await?; let content_length = blob_properties.content_length()?; From 19338b0fdab0425bf74a0db2f8dd7cc4f6af75e3 Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 21 Jul 2025 15:14:38 -0700 Subject: [PATCH 3/3] nit --- sdk/storage/azure_storage_blob/tests/blob_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index 595022af60..cdff30225c 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -430,7 +430,7 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box Result<(), Box> { // SAS - let blob_url = "https://vincenttranstock.blob.core.windows.net/acontainer108f32e8/goodbye.txt?sp=racwd&st=2025-07-21T21:22:28Z&se=2025-07-22T05:37:28Z&spr=https&sv=2024-11-04&sr=b&sig=e7QwWlj42eY%2FqDMSdSppRqSSqlkinW00%2Bymu03LAuek%3D"; + let blob_url = ""; let sas_blob_client = BlobClient::from_blob_url(blob_url, None)?;