Skip to content
Merged
37 changes: 30 additions & 7 deletions sdk/storage/azure_storage_blob/src/clients/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ use crate::{
BlobClientAcquireLeaseResult, BlobClientBreakLeaseResult, BlobClientChangeLeaseResult,
BlobClientDownloadResult, BlobClientGetAccountInfoResult, BlobClientGetPropertiesResult,
BlobClientReleaseLeaseResult, BlobClientRenewLeaseResult,
BlockBlobClientCommitBlockListResult, BlockBlobClientStageBlockResult,
BlockBlobClientUploadResult,
BlockBlobClientCommitBlockListResult, BlockBlobClientPutBlobFromUrlResult,
BlockBlobClientStageBlockResult, BlockBlobClientUploadResult,
},
models::{
AccessTier, BlobClientAcquireLeaseOptions, BlobClientBreakLeaseOptions,
BlobClientChangeLeaseOptions, BlobClientDeleteOptions, BlobClientDownloadOptions,
BlobClientGetAccountInfoOptions, BlobClientGetPropertiesOptions, BlobClientGetTagsOptions,
BlobClientReleaseLeaseOptions, BlobClientRenewLeaseOptions, BlobClientSetMetadataOptions,
BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions,
BlobTags, BlockBlobClientCommitBlockListOptions, BlockBlobClientUploadOptions, BlockList,
BlockListType, BlockLookupList,
BlobTags, BlockBlobClientCommitBlockListOptions, BlockBlobClientPutBlobFromUrlOptions,
BlockBlobClientUploadOptions, BlockList, BlockListType, BlockLookupList,
},
pipeline::StorageHeadersPolicy,
AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient,
Expand Down Expand Up @@ -182,13 +182,36 @@ impl BlobClient {
options.if_none_match = Some(String::from("*"));
}

let block_blob_client = self.client.get_block_blob_client();

block_blob_client
self.client
.get_block_blob_client()
.upload(data, content_length, Some(options))
.await
}

/// Creates a new Block Blob where the content of the blob is read from a given URL. The default behavior is content of an existing blob is overwritten with the new blob.
///
/// # Arguments
///
/// * `content_length` - The blob data to upload.
/// * `copy_source` - A URL of up to 2 KB in length that specifies a file or blob. The value should be URL-encoded as it would appear in a request URI.
/// The source must either be public or must be authenticated via a shared access signature as part of the url or using the source_authorization keyword.
/// If the source is public, no authentication is required. Examples:
/// `https://myaccount.blob.core.windows.net/mycontainer/myblob`
/// `https://myaccount.blob.core.windows.net/mycontainer/myblob?snapshot=<DateTime>`
/// `https://otheraccount.blob.core.windows.net/mycontainer/myblob?sastoken`
/// * `options` - Optional configuration for the request. See [`BlockBlobClientPutBlobFromUrlOptionsExt`](crate::models::BlockBlobClientPutBlobFromUrlOptionsExt) for additional usage helpers.
pub async fn put_blob_from_url(
Comment thread
vincenttran-msft marked this conversation as resolved.
Outdated
Comment thread
vincenttran-msft marked this conversation as resolved.
Outdated
&self,
content_length: u64,
copy_source: String,
options: Option<BlockBlobClientPutBlobFromUrlOptions<'_>>,
) -> Result<Response<BlockBlobClientPutBlobFromUrlResult, NoFormat>> {
self.client
.get_block_blob_client()
.put_blob_from_url(content_length, copy_source, options)
.await
}

/// Sets user-defined metadata for the specified blob as one or more name-value pairs. Each call to this operation
/// replaces all existing metadata attached to the blob. To remove all metadata from the blob, call this operation with
/// no metadata headers.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion sdk/storage/azure_storage_blob/src/models/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Licensed under the MIT License.

use crate::models::{
AppendBlobClientCreateOptions, BlobTag, BlobTags, PageBlobClientCreateOptions,
AppendBlobClientCreateOptions, BlobTag, BlobTags, BlockBlobClientPutBlobFromUrlOptions,
PageBlobClientCreateOptions,
};
use azure_core::error::ErrorKind;
use std::collections::HashMap;
Expand Down Expand Up @@ -43,6 +44,23 @@ impl AppendBlobClientCreateOptionsExt for AppendBlobClientCreateOptions<'_> {
}
}

pub trait BlockBlobClientPutBlobFromUrlOptionsExt {
/// Augments the current options bag to only create if the Block blob does not already exist.
/// # Arguments
///
/// * `self` - The options bag to be modified.
fn with_if_not_exists(self) -> Self;
}

impl BlockBlobClientPutBlobFromUrlOptionsExt for BlockBlobClientPutBlobFromUrlOptions<'_> {
Comment thread
vincenttran-msft marked this conversation as resolved.
Outdated
fn with_if_not_exists(self) -> Self {
Self {
if_none_match: Some("*".into()),
..self
}
}
}

/// Converts a `BlobTags` struct into `HashMap<String, String>`.
impl TryFrom<BlobTags> for HashMap<String, String> {
type Error = azure_core::Error;
Expand Down
38 changes: 20 additions & 18 deletions sdk/storage/azure_storage_blob/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,31 @@ pub use crate::generated::models::{
BlobContainerClientListBlobFlatSegmentOptions, BlobContainerClientReleaseLeaseOptions,
BlobContainerClientReleaseLeaseResult, BlobContainerClientReleaseLeaseResultHeaders,
BlobContainerClientRenewLeaseOptions, BlobContainerClientRenewLeaseResult,
BlobContainerClientSetMetadataOptions, BlobFlatListSegment, BlobImmutabilityPolicyMode,
BlobItemInternal, BlobMetadata, BlobName, BlobPropertiesInternal,
BlobContainerClientSetMetadataOptions, BlobCopySourceTags, BlobDeleteType, BlobFlatListSegment,
BlobImmutabilityPolicyMode, BlobItemInternal, BlobMetadata, BlobName, BlobPropertiesInternal,
BlobServiceClientGetAccountInfoOptions, BlobServiceClientGetAccountInfoResult,
BlobServiceClientGetAccountInfoResultHeaders, BlobServiceClientGetPropertiesOptions,
BlobServiceClientListContainersSegmentOptions, BlobServiceClientSetPropertiesOptions,
BlobServiceProperties, BlobTag, BlobTags, BlobType, Block,
BlockBlobClientCommitBlockListOptions, BlockBlobClientCommitBlockListResult,
BlockBlobClientCommitBlockListResultHeaders, BlockBlobClientGetBlockListOptions,
BlockBlobClientStageBlockOptions, BlockBlobClientStageBlockResult,
BlockBlobClientStageBlockResultHeaders, BlockBlobClientUploadOptions,
BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders, BlockList, BlockListType,
BlockLookupList, ContainerItem, CopyStatus, CorsRule, LeaseDuration, LeaseState, LeaseStatus,
ListBlobsFlatSegmentResponse, ListBlobsIncludeItem, ListContainersIncludeType,
ListContainersSegmentResponse, Logging, Metrics, ObjectReplicationMetadata,
PageBlobClientClearPagesOptions, PageBlobClientClearPagesResult,
PageBlobClientClearPagesResultHeaders, PageBlobClientCreateOptions, PageBlobClientCreateResult,
PageBlobClientCreateResultHeaders, PageBlobClientGetPageRangesOptions,
PageBlobClientResizeOptions, PageBlobClientResizeResult, PageBlobClientResizeResultHeaders,
PageBlobClientSetSequenceNumberOptions, PageBlobClientSetSequenceNumberResult,
PageBlobClientSetSequenceNumberResultHeaders, PageBlobClientUploadPagesFromUrlOptions,
PageBlobClientUploadPagesFromUrlResult, PageBlobClientUploadPagesOptions,
PageBlobClientUploadPagesResult, PageBlobClientUploadPagesResultHeaders, PageList,
PremiumPageBlobAccessTier, PublicAccessType, RehydratePriority, RetentionPolicy,
SequenceNumberActionType, StaticWebsite,
BlockBlobClientPutBlobFromUrlOptions, BlockBlobClientPutBlobFromUrlResult,
BlockBlobClientPutBlobFromUrlResultHeaders, BlockBlobClientStageBlockOptions,
BlockBlobClientStageBlockResult, BlockBlobClientStageBlockResultHeaders,
BlockBlobClientUploadOptions, BlockBlobClientUploadResult, BlockBlobClientUploadResultHeaders,
BlockList, BlockListType, BlockLookupList, ContainerItem, CopyStatus, CorsRule,
DeleteSnapshotsOptionType, EncryptionAlgorithmType, FileShareTokenIntent,
ImmutabilityPolicyMode, LeaseDuration, LeaseState, LeaseStatus, ListBlobsFlatSegmentResponse,
ListBlobsIncludeItem, ListContainersIncludeType, ListContainersSegmentResponse, Logging,
Metrics, ObjectReplicationMetadata, PageBlobClientClearPagesOptions,
PageBlobClientClearPagesResult, PageBlobClientClearPagesResultHeaders,
PageBlobClientCreateOptions, PageBlobClientCreateResult, PageBlobClientCreateResultHeaders,
PageBlobClientGetPageRangesOptions, PageBlobClientResizeOptions, PageBlobClientResizeResult,
PageBlobClientResizeResultHeaders, PageBlobClientSetSequenceNumberOptions,
PageBlobClientSetSequenceNumberResult, PageBlobClientSetSequenceNumberResultHeaders,
PageBlobClientUploadPagesFromUrlOptions, PageBlobClientUploadPagesFromUrlResult,
PageBlobClientUploadPagesOptions, PageBlobClientUploadPagesResult,
PageBlobClientUploadPagesResultHeaders, PageList, PremiumPageBlobAccessTier, PublicAccessType,
RehydratePriority, RetentionPolicy, SequenceNumberActionType, StaticWebsite,
};
pub use extensions::*;
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async fn test_append_block_from_url(ctx: TestContext) -> Result<(), Box<dyn Erro
let container_client = get_container_client(recording, true).await?;
let blob_client = container_client.blob_client(get_blob_name(recording));
let blob_client_2 = container_client.blob_client(get_blob_name(recording));
create_test_blob(&blob_client_2).await?;
create_test_blob(None, &blob_client_2).await?;
let source_url = format!(
"{}{}/{}",
blob_client_2.endpoint(),
Expand Down
76 changes: 67 additions & 9 deletions sdk/storage/azure_storage_blob/tests/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use azure_storage_blob::models::{
BlobClientChangeLeaseResultHeaders, BlobClientDownloadOptions, BlobClientDownloadResultHeaders,
BlobClientGetAccountInfoResultHeaders, BlobClientGetPropertiesOptions,
BlobClientGetPropertiesResultHeaders, BlobClientSetMetadataOptions,
BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientUploadOptions,
LeaseState,
BlobClientSetPropertiesOptions, BlobClientSetTierOptions, BlockBlobClientPutBlobFromUrlOptions,
BlockBlobClientPutBlobFromUrlOptionsExt, BlockBlobClientUploadOptions, LeaseState,
};

use azure_storage_blob_test::{create_test_blob, get_blob_name, get_container_client};
use std::{collections::HashMap, error::Error, time::Duration};
use tokio::time;
Expand All @@ -33,7 +34,7 @@ async fn test_get_blob_properties(ctx: TestContext) -> Result<(), Box<dyn Error>
assert_eq!(StatusCode::NotFound, error.unwrap());

container_client.create_container(None).await?;
create_test_blob(&blob_client).await?;
create_test_blob(None, &blob_client).await?;

// No Option Scenario
let response = blob_client.get_properties(None).await?;
Expand All @@ -59,7 +60,7 @@ async fn test_set_blob_properties(ctx: TestContext) -> Result<(), Box<dyn Error>
let recording = ctx.recording();
let container_client = get_container_client(recording, true).await?;
let blob_client = container_client.blob_client(get_blob_name(recording));
create_test_blob(&blob_client).await?;
create_test_blob(None, &blob_client).await?;

// Set Content Settings
let set_properties_options = BlobClientSetPropertiesOptions {
Expand Down Expand Up @@ -157,7 +158,7 @@ async fn test_delete_blob(ctx: TestContext) -> Result<(), Box<dyn Error>> {
let recording = ctx.recording();
let container_client = get_container_client(recording, true).await?;
let blob_client = container_client.blob_client(get_blob_name(recording));
create_test_blob(&blob_client).await?;
create_test_blob(None, &blob_client).await?;

// Existence Check
blob_client.get_properties(None).await?;
Expand Down Expand Up @@ -263,7 +264,7 @@ async fn test_set_access_tier(ctx: TestContext) -> Result<(), Box<dyn Error>> {
let recording = ctx.recording();
let container_client = get_container_client(recording, true).await?;
let blob_client = container_client.blob_client(get_blob_name(recording));
create_test_blob(&blob_client).await?;
create_test_blob(None, &blob_client).await?;

let original_response = blob_client.get_properties(None).await?;
let og_access_tier = original_response.access_tier()?;
Expand All @@ -289,7 +290,7 @@ async fn test_blob_lease_operations(ctx: TestContext) -> Result<(), Box<dyn Erro
let blob_name = get_blob_name(recording);
let blob_client = container_client.blob_client(blob_name.clone());
let other_blob_client = container_client.blob_client(blob_name);
create_test_blob(&blob_client).await?;
create_test_blob(None, &blob_client).await?;

// Acquire Lease
let acquire_response = blob_client.acquire_lease(15, None).await?;
Expand Down Expand Up @@ -344,7 +345,7 @@ async fn test_leased_blob_operations(ctx: TestContext) -> Result<(), Box<dyn Err
let container_client = get_container_client(recording, true).await?;
let blob_name = get_blob_name(recording);
let blob_client = container_client.blob_client(blob_name.clone());
create_test_blob(&blob_client).await?;
create_test_blob(None, &blob_client).await?;
let acquire_response = blob_client.acquire_lease(-1, None).await?;
let lease_id = acquire_response.lease_id()?.unwrap();

Expand Down Expand Up @@ -432,7 +433,7 @@ async fn test_blob_tags(ctx: TestContext) -> Result<(), Box<dyn Error>> {
recording.set_matcher(Matcher::BodilessMatcher).await?;
let container_client = get_container_client(recording, true).await?;
let blob_client = container_client.blob_client(get_blob_name(recording));
create_test_blob(&blob_client).await?;
create_test_blob(None, &blob_client).await?;

// Set Tags with Tags Specified
let blob_tags = HashMap::from([
Expand Down Expand Up @@ -477,3 +478,60 @@ async fn test_get_account_info(ctx: TestContext) -> Result<(), Box<dyn Error>> {

Ok(())
}

#[recorded::test]
async fn test_put_blob_from_url(ctx: TestContext) -> Result<(), Box<dyn Error>> {
// Recording Setup

let recording = ctx.recording();
let container_client = get_container_client(recording, true).await?;
let source_blob_client = container_client.blob_client(get_blob_name(recording));
create_test_blob(
Some(RequestContent::from(b"initialD ata".to_vec())),
&source_blob_client,
)
.await?;
let source_url = format!(
"{}{}/{}",
source_blob_client.endpoint(),
source_blob_client.container_name(),
source_blob_client.blob_name()
);

let blob_client = container_client.blob_client(get_blob_name(recording));

let overwrite_blob_client = container_client.blob_client(get_blob_name(recording));
create_test_blob(
Some(RequestContent::from(b"overruled!".to_vec())),
&overwrite_blob_client,
)
.await?;
let overwrite_url = format!(
"{}{}/{}",
overwrite_blob_client.endpoint(),
overwrite_blob_client.container_name(),
overwrite_blob_client.blob_name()
);

// Regular Scenario
blob_client
.put_blob_from_url(12, source_url.clone(), None)
.await?;

let create_options = BlockBlobClientPutBlobFromUrlOptions::default().with_if_not_exists();

// No Overwrite Existing Blob Scenario
let response = blob_client
.put_blob_from_url(12, overwrite_url.clone(), Some(create_options))
.await;
// Assert
let error = response.unwrap_err().http_status();
assert_eq!(StatusCode::Conflict, error.unwrap());

// Overwrite Existing Blob Scenario
blob_client
.put_blob_from_url(12, overwrite_url.clone(), None)
.await?;

Ok(())
}
12 changes: 6 additions & 6 deletions sdk/storage/azure_storage_blob/tests/blob_container_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ async fn test_list_blobs(ctx: TestContext) -> Result<(), Box<dyn Error>> {
let blob_names = ["testblob1".to_string(), "testblob2".to_string()];

container_client.create_container(None).await?;
create_test_blob(&container_client.blob_client(blob_names[0].clone())).await?;
create_test_blob(&container_client.blob_client(blob_names[1].clone())).await?;
create_test_blob(None, &container_client.blob_client(blob_names[0].clone())).await?;
create_test_blob(None, &container_client.blob_client(blob_names[1].clone())).await?;

let mut list_blobs_response = container_client.list_blobs(None)?;

Expand Down Expand Up @@ -128,10 +128,10 @@ async fn test_list_blobs_with_continuation(ctx: TestContext) -> Result<(), Box<d
];

container_client.create_container(None).await?;
create_test_blob(&container_client.blob_client(blob_names[0].clone())).await?;
create_test_blob(&container_client.blob_client(blob_names[1].clone())).await?;
create_test_blob(&container_client.blob_client(blob_names[2].clone())).await?;
create_test_blob(&container_client.blob_client(blob_names[3].clone())).await?;
create_test_blob(None, &container_client.blob_client(blob_names[0].clone())).await?;
create_test_blob(None, &container_client.blob_client(blob_names[1].clone())).await?;
create_test_blob(None, &container_client.blob_client(blob_names[2].clone())).await?;
create_test_blob(None, &container_client.blob_client(blob_names[3].clone())).await?;

// Continuation Token with Token Provided
let list_blobs_options = BlobContainerClientListBlobFlatSegmentOptions {
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azure_storage_blob/tsp-location.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
directory: specification/storage/Microsoft.BlobStorage
commit: fcc4cdefee43823ab15e05be6b42228bed96c1b1
commit: 2a6fe409297d74ca477cee4105c2bb9f22bf01db
repo: Azure/azure-rest-api-specs
additionalDirectories:
28 changes: 19 additions & 9 deletions sdk/storage/azure_storage_blob_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use azure_core::{
http::{ClientOptions, NoFormat, RequestContent, Response},
Result,
Bytes, Result,
};
use azure_core_test::Recording;
use azure_storage_blob::{
Expand Down Expand Up @@ -101,14 +101,24 @@ pub async fn get_container_client(
///
/// * `blob_client` - A reference to a BlobClient instance.
pub async fn create_test_blob(
data: Option<RequestContent<Bytes, NoFormat>>,
blob_client: &BlobClient,
) -> Result<Response<BlockBlobClientUploadResult, NoFormat>> {
blob_client
.upload(
RequestContent::from(b"hello rusty world".to_vec()),
true,
17,
None,
)
.await
match data {
Some(content) => {
blob_client
.upload(content.clone(), true, content.body().len() as u64, None)
.await
}
None => {
Comment thread
heaths marked this conversation as resolved.
blob_client
.upload(
RequestContent::from(b"hello rusty world".to_vec()),
true,
17,
None,
)
.await
}
}
}
Loading