Skip to content

Commit 67090e5

Browse files
BlobClient.OpenWriteAsync() (Azure#25623)
* initial OpenWrite add * initial test port Moved open write tests from BlockBlobClientTests to OpenWriteTestBase and BlobBaseClientOpenWriteTests. Hooked up client functionality through BlobBaseClientOpenWriteTests and BlockBlobClientOpenWriteTests. Moved recordings into correct folder to be picked up by framework. Most tests ran in playback out of box. Some are failing due to recording mismatches. Next step to determine if tests need correcting or if rerecording is fine. * rerecorded some tests * BlobClientTests Fixed bug with scope names. Extended open write tests for BlobClient. Recorded tests. * tests * integrated clientside encryption openwrite now works with clientside encryption updated tests * export api * fixed merge bug restored changes lost in merge. * fixed operation name bug & rerecord * recording service version update * changelog Co-authored-by: jschrepp-MSFT <41338290+jschrepp-MSFT@users.noreply.github.com>
1 parent b0a25bc commit 67090e5

File tree

169 files changed

+29868
-15301
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

169 files changed

+29868
-15301
lines changed

sdk/storage/Azure.Storage.Blobs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 12.11.0-beta.3 (Unreleased)
44
- Added support for service version 2021-04-10.
55
- Added support for BlobContainerClient.FilterBlobsByTag().
6+
- Added support for BlobContainerClient.OpenWriteAsync().
67
- Fixed bug where BlobSasBuilder.SetPermissions(string rawPermissions) was not properly handling the Permanent Delete ('y') and set Immutability Policy ('i') permissions.
78

89
### Other Changes

sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public BlobClient(System.Uri blobUri, Azure.AzureSasCredential credential, Azure
99
public BlobClient(System.Uri blobUri, Azure.Core.TokenCredential credential, Azure.Storage.Blobs.BlobClientOptions options = null) { }
1010
public BlobClient(System.Uri blobUri, Azure.Storage.Blobs.BlobClientOptions options = null) { }
1111
public BlobClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredential credential, Azure.Storage.Blobs.BlobClientOptions options = null) { }
12+
public virtual System.IO.Stream OpenWrite(bool overwrite, Azure.Storage.Blobs.Models.BlobOpenWriteOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
13+
public virtual System.Threading.Tasks.Task<System.IO.Stream> OpenWriteAsync(bool overwrite, Azure.Storage.Blobs.Models.BlobOpenWriteOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
1214
public virtual Azure.Response<Azure.Storage.Blobs.Models.BlobContentInfo> Upload(System.BinaryData content) { throw null; }
1315
public virtual Azure.Response<Azure.Storage.Blobs.Models.BlobContentInfo> Upload(System.BinaryData content, Azure.Storage.Blobs.Models.BlobUploadOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
1416
public virtual Azure.Response<Azure.Storage.Blobs.Models.BlobContentInfo> Upload(System.BinaryData content, bool overwrite = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
@@ -806,6 +808,17 @@ public BlobOpenReadOptions(bool allowModifications) { }
806808
public long Position { get { throw null; } set { } }
807809
public Azure.Storage.DownloadTransactionalHashingOptions TransactionalHashingOptions { get { throw null; } set { } }
808810
}
811+
public partial class BlobOpenWriteOptions
812+
{
813+
public BlobOpenWriteOptions() { }
814+
public long? BufferSize { get { throw null; } set { } }
815+
public Azure.Storage.Blobs.Models.BlobHttpHeaders HttpHeaders { get { throw null; } set { } }
816+
public System.Collections.Generic.IDictionary<string, string> Metadata { get { throw null; } set { } }
817+
public Azure.Storage.Blobs.Models.BlobRequestConditions OpenConditions { get { throw null; } set { } }
818+
public System.IProgress<long> ProgressHandler { get { throw null; } set { } }
819+
public System.Collections.Generic.IDictionary<string, string> Tags { get { throw null; } set { } }
820+
public Azure.Storage.UploadTransactionalHashingOptions TransactionalHashingOptions { get { throw null; } set { } }
821+
}
809822
public partial class BlobProperties
810823
{
811824
public BlobProperties() { }

sdk/storage/Azure.Storage.Blobs/src/BlobClient.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,102 @@ internal async Task<Response<BlobContentInfo>> StagedUploadInternal(
17211721
}
17221722
#endregion Upload
17231723

1724+
#region OpenWrite
1725+
/// <summary>
1726+
/// Opens a stream for writing to the blob.
1727+
/// </summary>
1728+
/// <param name="overwrite">
1729+
/// Whether an existing blob should be deleted and recreated.
1730+
/// </param>
1731+
/// <param name="options">
1732+
/// Optional parameters.
1733+
/// </param>
1734+
/// <param name="cancellationToken">
1735+
/// Optional <see cref="CancellationToken"/> to propagate
1736+
/// notifications that the operation should be cancelled.
1737+
/// </param>
1738+
/// <returns>
1739+
/// A stream to write to the Append Blob.
1740+
/// </returns>
1741+
/// <remarks>
1742+
/// A <see cref="RequestFailedException"/> will be thrown if
1743+
/// a failure occurs.
1744+
/// </remarks>
1745+
#pragma warning disable AZC0015 // Unexpected client method return type.
1746+
public virtual Stream OpenWrite(
1747+
#pragma warning restore AZC0015 // Unexpected client method return type.
1748+
bool overwrite,
1749+
BlobOpenWriteOptions options = default,
1750+
CancellationToken cancellationToken = default)
1751+
=> OpenWriteInternal(
1752+
overwrite,
1753+
options,
1754+
async: false,
1755+
cancellationToken).EnsureCompleted();
1756+
1757+
/// <summary>
1758+
/// Opens a stream for writing to the blob. If the blob exists,
1759+
/// it will be overwritten.
1760+
/// </summary>
1761+
/// <param name="overwrite">
1762+
/// Whether an existing blob should be deleted and recreated.
1763+
/// </param>
1764+
/// <param name="options">
1765+
/// Optional parameters.
1766+
/// </param>
1767+
/// <param name="cancellationToken">
1768+
/// Optional <see cref="CancellationToken"/> to propagate
1769+
/// notifications that the operation should be cancelled.
1770+
/// </param>
1771+
/// <returns>
1772+
/// A stream to write to the Append Blob.
1773+
/// </returns>
1774+
/// <remarks>
1775+
/// A <see cref="RequestFailedException"/> will be thrown if
1776+
/// a failure occurs.
1777+
/// </remarks>
1778+
#pragma warning disable AZC0015 // Unexpected client method return type.
1779+
public virtual async Task<Stream> OpenWriteAsync(
1780+
#pragma warning restore AZC0015 // Unexpected client method return type.
1781+
bool overwrite,
1782+
BlobOpenWriteOptions options = default,
1783+
CancellationToken cancellationToken = default)
1784+
=> await OpenWriteInternal(
1785+
overwrite,
1786+
options,
1787+
async: true,
1788+
cancellationToken).ConfigureAwait(false);
1789+
1790+
internal async Task<Stream> OpenWriteInternal(
1791+
bool overwrite,
1792+
BlobOpenWriteOptions options,
1793+
bool async,
1794+
CancellationToken cancellationToken)
1795+
{
1796+
if (UsingClientSideEncryption)
1797+
{
1798+
if (UsingClientSideEncryption && options.TransactionalHashingOptions != default)
1799+
{
1800+
throw Errors.TransactionalHashingNotSupportedWithClientSideEncryption();
1801+
}
1802+
1803+
return await new BlobClientSideEncryptor(new ClientSideEncryptor(ClientSideEncryption))
1804+
.ClientSideEncryptionOpenWriteInternal(
1805+
BlockBlobClient,
1806+
overwrite,
1807+
options.ToBlockBlobOpenWriteOptions(),
1808+
async,
1809+
cancellationToken).ConfigureAwait(false);
1810+
}
1811+
1812+
return await BlockBlobClient.OpenWriteInternal(
1813+
overwrite,
1814+
options.ToBlockBlobOpenWriteOptions(),
1815+
async,
1816+
cancellationToken).ConfigureAwait(false);
1817+
}
1818+
#endregion
1819+
17241820
private BlockBlobClient _blockBlobClient;
17251821

17261822
private BlockBlobClient BlockBlobClient

sdk/storage/Azure.Storage.Blobs/src/BlobClientSideEncryptor.cs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.IO;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Azure.Storage.Blobs.Models;
10+
using Azure.Storage.Blobs.Specialized;
911
using Azure.Storage.Cryptography;
1012
using Azure.Storage.Cryptography.Models;
1113
using Metadata = System.Collections.Generic.IDictionary<string, string>;
@@ -48,12 +50,70 @@ public BlobClientSideEncryptor(ClientSideEncryptor encryptor)
4850
async,
4951
cancellationToken).ConfigureAwait(false);
5052

53+
Metadata modifiedMetadata = TransformMetadata(metadata, EncryptionData);
54+
55+
return (NonSeekableCiphertext, modifiedMetadata);
56+
}
57+
58+
/// <summary>
59+
/// Creates a crypto transform stream to write blob contents to.
60+
/// </summary>
61+
/// <param name="blobClient">
62+
/// BlobClient to open write with.
63+
/// </param>
64+
/// <param name="overwrite">
65+
/// Overwrite parameter to open write.
66+
/// </param>
67+
/// <param name="options">
68+
/// Options parameter to open write.
69+
/// </param>
70+
/// <param name="async">
71+
/// Whether to perform this operation asynchronously.
72+
/// </param>
73+
/// <param name="cancellationToken">
74+
/// Cancellation token.
75+
/// </param>
76+
/// <returns>
77+
/// Content transform write stream and metadata.
78+
/// </returns>
79+
public async Task<Stream> ClientSideEncryptionOpenWriteInternal(
80+
BlockBlobClient blobClient,
81+
bool overwrite,
82+
BlockBlobOpenWriteOptions options,
83+
bool async,
84+
CancellationToken cancellationToken)
85+
{
86+
Stream encryptionWriteStream = await _encryptor.EncryptedOpenWriteInternal(
87+
async (encryptiondata, funcAsync, funcCancellationToken) =>
88+
{
89+
options.Metadata = TransformMetadata(options.Metadata, encryptiondata);
90+
91+
return await blobClient.OpenWriteInternal(
92+
overwrite,
93+
options,
94+
funcAsync,
95+
funcCancellationToken).ConfigureAwait(false);
96+
},
97+
async,
98+
cancellationToken).ConfigureAwait(false);
99+
100+
return encryptionWriteStream;
101+
}
102+
103+
/// <summary>
104+
/// Adds encryption data to provided blob metadata, overwriting previous entry if any.
105+
/// Safely creates new metadata object if none is provided.
106+
/// </summary>
107+
/// <param name="metadata">Optionally existing metadata.</param>
108+
/// <param name="encryptionData">Encryption data to add.</param>
109+
private static Metadata TransformMetadata(Metadata metadata, EncryptionData encryptionData)
110+
{
51111
Metadata modifiedMetadata = metadata == default
52112
? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
53113
: new Dictionary<string, string>(metadata, StringComparer.OrdinalIgnoreCase);
54-
modifiedMetadata[Constants.ClientSideEncryption.EncryptionDataKey] = EncryptionDataSerializer.Serialize(EncryptionData);
114+
modifiedMetadata[Constants.ClientSideEncryption.EncryptionDataKey] = EncryptionDataSerializer.Serialize(encryptionData);
55115

56-
return (NonSeekableCiphertext, modifiedMetadata);
116+
return modifiedMetadata;
57117
}
58118
}
59119
}

sdk/storage/Azure.Storage.Blobs/src/BlockBlobClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2742,13 +2742,13 @@ public virtual async Task<Stream> OpenWriteAsync(
27422742
/// A <see cref="RequestFailedException"/> will be thrown if
27432743
/// a failure occurs.
27442744
/// </remarks>
2745-
private async Task<Stream> OpenWriteInternal(
2745+
internal async Task<Stream> OpenWriteInternal(
27462746
bool overwrite,
27472747
BlockBlobOpenWriteOptions options,
27482748
bool async,
27492749
CancellationToken cancellationToken)
27502750
{
2751-
string operationName = $"{nameof(BlockBlobClient)}.{nameof(OpenWrite)}";
2751+
string operationName = options?.OperationName ?? $"{nameof(BlockBlobClient)}.{nameof(OpenWrite)}";
27522752
DiagnosticScope scope = ClientConfiguration.ClientDiagnostics.CreateScope(operationName);
27532753

27542754
try
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using Azure.Storage.Blobs.Specialized;
6+
using Metadata = System.Collections.Generic.IDictionary<string, string>;
7+
using Tags = System.Collections.Generic.IDictionary<string, string>;
8+
9+
#pragma warning disable SA1402 // File may only contain a single type
10+
11+
namespace Azure.Storage.Blobs.Models
12+
{
13+
/// <summary>
14+
/// Optional parameters for Page Blob Open Write.
15+
/// </summary>
16+
public class BlobOpenWriteOptions
17+
{
18+
/// <summary>
19+
/// The size of the buffer to use. Default is 4 MB,
20+
/// max is 4000 MB. See <see cref="BlockBlobClient.BlockBlobMaxStageBlockLongBytes"/>.
21+
/// </summary>
22+
public long? BufferSize { get; set; }
23+
24+
/// <summary>
25+
/// Access conditions used to open the write stream.
26+
/// </summary>
27+
public BlobRequestConditions OpenConditions { get; set; }
28+
29+
/// <summary>
30+
/// Optional <see cref="IProgress{Long}"/> to provide
31+
/// progress updates about data transfers.
32+
/// </summary>
33+
public IProgress<long> ProgressHandler { get; set; }
34+
35+
/// <summary>
36+
/// Optional standard HTTP header properties that can be set for this block blob.
37+
/// </summary>
38+
public BlobHttpHeaders HttpHeaders { get; set; }
39+
40+
/// <summary>
41+
/// Optional custom metadata to set for this block blob.
42+
/// </summary>
43+
#pragma warning disable CA2227 // Collection properties should be readonly
44+
public Metadata Metadata { get; set; }
45+
#pragma warning restore CA2227 // Collection properties should be readonly
46+
47+
/// <summary>
48+
/// Options tags to set for this block blob.
49+
/// </summary>
50+
#pragma warning disable CA2227 // Collection properties should be readonly
51+
public Tags Tags { get; set; }
52+
#pragma warning restore CA2227 // Collection properties should be readonly
53+
54+
/// <summary>
55+
/// Optional <see cref="UploadTransactionalHashingOptions"/> for using transactional
56+
/// hashing on uploads.
57+
/// </summary>
58+
public UploadTransactionalHashingOptions TransactionalHashingOptions { get; set; }
59+
}
60+
61+
internal static class BlobOpenWriteOptionsExtensions
62+
{
63+
public static BlockBlobOpenWriteOptions ToBlockBlobOpenWriteOptions(this BlobOpenWriteOptions options)
64+
=> new BlockBlobOpenWriteOptions
65+
{
66+
BufferSize = options.BufferSize,
67+
OpenConditions = options.OpenConditions,
68+
ProgressHandler = options.ProgressHandler,
69+
HttpHeaders = options.HttpHeaders,
70+
Metadata = options.Metadata,
71+
Tags = options.Tags,
72+
TransactionalHashingOptions = options.TransactionalHashingOptions,
73+
OperationName = $"{nameof(BlobClient)}.{nameof(BlockBlobClient.OpenWrite)}"
74+
};
75+
}
76+
}

sdk/storage/Azure.Storage.Blobs/src/Models/BlockBlobOpenWriteOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,7 @@ public class BlockBlobOpenWriteOptions
5454
/// hashing on uploads.
5555
/// </summary>
5656
public UploadTransactionalHashingOptions TransactionalHashingOptions { get; set; }
57+
58+
internal string OperationName { get; set; }
5759
}
5860
}

0 commit comments

Comments
 (0)