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

[Storage] Blobs - ability to traverse blob hierarchy upwards. #16437

Merged
11 commits merged into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
1 change: 1 addition & 0 deletions sdk/storage/Azure.Storage.Blobs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Added additional info to exception messages.
- Fixed bug where Blobs SDK coudn't handle SASs with start and expiry time in format other than yyyy-MM-ddTHH:mm:ssZ.
- Added ability to set Position on streams created with BlobBaseClient.OpenRead().
- Added ability to get parent BlobContainerClient from BlobBaseClient and to get parent BlobServiceClient from BlobContainerClient.

## 12.7.0-preview.1 (2020-09-30)
- Added support for service version 2020-02-10.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public BlobContainerClient(System.Uri blobContainerUri, Azure.Storage.StorageSha
public virtual Azure.AsyncPageable<Azure.Storage.Blobs.Models.BlobItem> GetBlobsAsync(Azure.Storage.Blobs.Models.BlobTraits traits = Azure.Storage.Blobs.Models.BlobTraits.None, Azure.Storage.Blobs.Models.BlobStates states = Azure.Storage.Blobs.Models.BlobStates.None, string prefix = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Pageable<Azure.Storage.Blobs.Models.BlobHierarchyItem> GetBlobsByHierarchy(Azure.Storage.Blobs.Models.BlobTraits traits = Azure.Storage.Blobs.Models.BlobTraits.None, Azure.Storage.Blobs.Models.BlobStates states = Azure.Storage.Blobs.Models.BlobStates.None, string delimiter = null, string prefix = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.AsyncPageable<Azure.Storage.Blobs.Models.BlobHierarchyItem> GetBlobsByHierarchyAsync(Azure.Storage.Blobs.Models.BlobTraits traits = Azure.Storage.Blobs.Models.BlobTraits.None, Azure.Storage.Blobs.Models.BlobStates states = Azure.Storage.Blobs.Models.BlobStates.None, string delimiter = null, string prefix = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
protected internal virtual Azure.Storage.Blobs.BlobServiceClient GetBlobServiceClientCore() { throw null; }
protected internal virtual Azure.Storage.Blobs.Specialized.BlockBlobClient GetBlockBlobClientCore(string blobName) { throw null; }
protected internal virtual Azure.Storage.Blobs.Specialized.PageBlobClient GetPageBlobClientCore(string blobName) { throw null; }
public virtual Azure.Response<Azure.Storage.Blobs.Models.BlobContainerProperties> GetProperties(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down Expand Up @@ -1265,6 +1266,7 @@ public BlobBaseClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredenti
public virtual System.Threading.Tasks.Task<Azure.Response> DownloadToAsync(string path, System.Threading.CancellationToken cancellationToken) { throw null; }
public virtual Azure.Response<bool> Exists(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<bool>> ExistsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
protected internal virtual Azure.Storage.Blobs.BlobContainerClient GetBlobContainerClientCore() { throw null; }
protected internal virtual Azure.Storage.Blobs.Specialized.BlobLeaseClient GetBlobLeaseClientCore(string leaseId) { throw null; }
public virtual Azure.Response<Azure.Storage.Blobs.Models.BlobProperties> GetProperties(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Storage.Blobs.Models.BlobProperties>> GetPropertiesAsync(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down Expand Up @@ -1422,8 +1424,10 @@ public static partial class SpecializedBlobExtensions
{
public static Azure.Storage.Blobs.Specialized.AppendBlobClient GetAppendBlobClient(this Azure.Storage.Blobs.BlobContainerClient client, string blobName) { throw null; }
public static Azure.Storage.Blobs.Specialized.BlobBaseClient GetBlobBaseClient(this Azure.Storage.Blobs.BlobContainerClient client, string blobName) { throw null; }
public static Azure.Storage.Blobs.BlobContainerClient GetBlobContainerClient(this Azure.Storage.Blobs.Specialized.BlobBaseClient client) { throw null; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost wonder if we want the name Parent in there like GetParentBlobContainerClient or something to indicate you're moving up? I could go either way since this is an advanced API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was itching to do this, but wanted to wait and see if somebody speaks up. I'll make that change.

public static Azure.Storage.Blobs.Specialized.BlobLeaseClient GetBlobLeaseClient(this Azure.Storage.Blobs.BlobContainerClient client, string leaseId = null) { throw null; }
public static Azure.Storage.Blobs.Specialized.BlobLeaseClient GetBlobLeaseClient(this Azure.Storage.Blobs.Specialized.BlobBaseClient client, string leaseId = null) { throw null; }
public static Azure.Storage.Blobs.BlobServiceClient GetBlobServiceClient(this Azure.Storage.Blobs.BlobContainerClient client) { throw null; }
public static Azure.Storage.Blobs.Specialized.BlockBlobClient GetBlockBlobClient(this Azure.Storage.Blobs.BlobContainerClient client, string blobName) { throw null; }
public static Azure.Storage.Blobs.Specialized.PageBlobClient GetPageBlobClient(this Azure.Storage.Blobs.BlobContainerClient client, string blobName) { throw null; }
public static Azure.Storage.Blobs.BlobClient WithClientSideEncryptionOptions(this Azure.Storage.Blobs.BlobClient client, Azure.Storage.ClientSideEncryptionOptions clientSideEncryptionOptions) { throw null; }
Expand Down
42 changes: 42 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4103,6 +4103,35 @@ private async Task<Response> SetTagsInternal(
}
}
#endregion

#region GetBlobContainerClientCore
/// <summary>
/// Create a new <see cref="BlobContainerClient"/> that pointing to this <see cref="BlobBaseClient"/>'s parent container.
/// The new <see cref="BlockBlobClient"/>
/// uses the same request policy pipeline as the
/// <see cref="BlobBaseClient"/>.
/// </summary>
/// <returns>A new <see cref="BlobContainerClient"/> instance.</returns>
protected internal virtual BlobContainerClient GetBlobContainerClientCore()
{
BlobUriBuilder blobUriBuilder = new BlobUriBuilder(Uri)
{
// erase parameters unrelated to container
BlobName = null,
VersionId = null,
Snapshot = null,
};

return new BlobContainerClient(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as other comment.

blobUriBuilder.ToUri(),
Pipeline,
Version,
ClientDiagnostics,
CustomerProvidedKey,
ClientSideEncryption,
EncryptionScope);
}
#endregion
}

/// <summary>
Expand All @@ -4111,6 +4140,19 @@ private async Task<Response> SetTagsInternal(
/// </summary>
public static partial class SpecializedBlobExtensions
{
/// <summary>
/// Create a new <see cref="BlobContainerClient"/> that pointing to this <see cref="BlobBaseClient"/>'s parent container.
/// The new <see cref="BlockBlobClient"/>
/// uses the same request policy pipeline as the
/// <see cref="BlobBaseClient"/>.
/// </summary>
/// <param name="client">The <see cref="BlobBaseClient"/>.</param>
/// <returns>A new <see cref="BlobContainerClient"/> instance.</returns>
public static BlobContainerClient GetBlobContainerClient(this BlobBaseClient client)
{
return client.GetBlobContainerClientCore();
}

/// <summary>
/// Create a new <see cref="BlobBaseClient"/> object by concatenating
/// <paramref name="blobName"/> to the end of the
Expand Down
55 changes: 55 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/BlobContainerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using Azure.Storage.Sas;
using Metadata = System.Collections.Generic.IDictionary<string, string>;

#pragma warning disable SA1402 // File may only contain a single type

namespace Azure.Storage.Blobs
{
/// <summary>
Expand Down Expand Up @@ -2875,5 +2877,58 @@ await GetBlobClient(blobName).DeleteIfExistsAsync(
.ConfigureAwait(false);

#endregion DeleteBlob

#region GetBlobServiceClientCore
/// <summary>
/// Create a new <see cref="BlobServiceClient"/> that pointing to this <see cref="BlobContainerClient"/>'s blob service.
/// The new <see cref="BlobServiceClient"/>
/// uses the same request policy pipeline as the
/// <see cref="BlobContainerClient"/>.
/// </summary>
/// <returns>A new <see cref="BlobServiceClient"/> instance.</returns>
protected internal virtual BlobServiceClient GetBlobServiceClientCore()
{
BlobUriBuilder blobUriBuilder = new BlobUriBuilder(Uri)
{
// erase parameters unrelated to service
BlobContainerName = null,
BlobName = null,
VersionId = null,
Snapshot = null,
};

return new BlobServiceClient(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to initialize a new client each time? Or would we have a persistent Client as a private property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can cache it since properties it's made don't change.

blobUriBuilder.ToUri(),
null,
Version,
ClientDiagnostics,
CustomerProvidedKey,
ClientSideEncryption,
EncryptionScope,
Pipeline);
}
#endregion
}

namespace Specialized
{
/// <summary>
/// Add easy to discover methods to <see cref="BlobContainerClient"/> for
/// creating <see cref="BlobServiceClient"/> instances.
/// </summary>
public static partial class SpecializedBlobExtensions
{
/// <summary>
/// Create a new <see cref="BlobServiceClient"/> that pointing to this <see cref="BlobContainerClient"/>'s blob service.
/// The new <see cref="BlobServiceClient"/>
/// uses the same request policy pipeline as the
/// <see cref="BlobContainerClient"/>.
/// </summary>
/// <returns>A new <see cref="BlobServiceClient"/> instance.</returns>
public static BlobServiceClient GetBlobServiceClient(this BlobContainerClient client)
{
return client.GetBlobServiceClientCore();
}
}
}
}
96 changes: 96 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5837,6 +5837,102 @@ public void CanMockBlobLeaseClientRetrieval()
Assert.AreSame(blobLeaseClientMock.Object, blobLeaseClient);
}

[Test]
public async Task CanGetParentContainerClient()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();
BlobClient blobClient = InstrumentClient(test.Container.GetBlobClient(GetNewBlobName()));

// Act
var containerClient = blobClient.GetBlobContainerClient();
// make sure that client is functional
BlobContainerProperties containerProperties = await containerClient.GetPropertiesAsync();

// Assert
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
Assert.IsNotNull(containerProperties);
}

[Test]
public async Task CanGetParentContainerClient_FromBlobClientThatHasExtraQueryParameters()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();
BlobClient blobClient = InstrumentClient(test.Container.GetBlobClient(GetNewBlobName())).WithVersion(Recording.Random.NewGuid().ToString());

// Act
var containerClient = blobClient.GetBlobContainerClient();
// make sure that client is functional
BlobContainerProperties containerProperties = await containerClient.GetPropertiesAsync();

// Assert
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
Assert.IsNotNull(containerProperties);
}

[Test]
public async Task CanGetParentContainerClient_WithAccountSAS()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();
var blobName = GetNewBlobName();
BlobBaseClient blobClient = InstrumentClient(
GetServiceClient_AccountSas()
.GetBlobContainerClient(test.Container.Name)
.GetBlobClient(blobName));

// Act
var containerClient = blobClient.GetBlobContainerClient();
// make sure that client is functional
BlobContainerProperties containerProperties = await containerClient.GetPropertiesAsync();

// Assert
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
Assert.IsNotNull(containerProperties);
}

[Test]
public async Task CanGetParentContainerClient_WithContainerSAS()
{
// Arrange
await using DisposingContainer test = await GetTestContainerAsync();
var blobName = GetNewBlobName();
BlobBaseClient blobClient = InstrumentClient(
GetServiceClient_BlobServiceSas_Container(test.Container.Name)
.GetBlobContainerClient(test.Container.Name)
.GetBlobClient(blobName));

// Act
var containerClient = blobClient.GetBlobContainerClient();
// make sure that client is functional
var blobItems = await containerClient.GetBlobsAsync().ToListAsync();

// Assert
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
Assert.IsNotNull(blobItems);
}

[Test]
public void CanMockParentContainerClientRetrieval()
{
// Arrange
Mock<BlobBaseClient> blobBaseClientMock = new Mock<BlobBaseClient>();
Mock<BlobContainerClient> blobContainerClientMock = new Mock<BlobContainerClient>();
blobBaseClientMock.Protected().Setup<BlobContainerClient>("GetBlobContainerClientCore").Returns(blobContainerClientMock.Object);

// Act
var blobContainerClient = blobBaseClientMock.Object.GetBlobContainerClient();

// Assert
Assert.IsNotNull(blobContainerClient);
Assert.AreSame(blobContainerClientMock.Object, blobContainerClient);
}

private async Task<BlobBaseClient> GetNewBlobClient(BlobContainerClient container, string blobName = default)
{
blobName ??= GetNewBlobName();
Expand Down
54 changes: 54 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/tests/ContainerClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,60 @@ public void CanMockBlobClientsRetrieval()
Assert.AreSame(blobLeaseClientMock.Object, blobLeaseClient);
}

[Test]
public void CanMockBlobServiceClientRetrieval()
{
// Arrange
Mock<BlobContainerClient> containerClientMock = new Mock<BlobContainerClient>();
Mock<BlobServiceClient> blobServiceClientMock = new Mock<BlobServiceClient>();
containerClientMock.Protected().Setup<BlobServiceClient>("GetBlobServiceClientCore").Returns(blobServiceClientMock.Object);

// Act
var blobServiceClient = containerClientMock.Object.GetBlobServiceClient();

// Assert
Assert.IsNotNull(blobServiceClient);
Assert.AreSame(blobServiceClientMock.Object, blobServiceClient);
}

[Test]
public async Task CanGetParentBlobServiceClient()
{
// Arrange
BlobContainerClient container = InstrumentClient(GetServiceClient_SharedKey().GetBlobContainerClient(GetNewContainerName()));

// Act
BlobServiceClient service = container.GetBlobServiceClient();
//make sure it's functional
var containers = await service.GetBlobContainersAsync().ToListAsync();

// Assert
Assert.AreEqual(container.AccountName, service.AccountName);
Assert.IsNotNull(container);

// Cleanup
await container.DeleteIfExistsAsync();
}

[Test]
public async Task CanGetParentBlobServiceClient_WithAccountSAS()
{
// Arrange
BlobContainerClient container = InstrumentClient(GetServiceClient_AccountSas().GetBlobContainerClient(GetNewContainerName()));

// Act
BlobServiceClient service = container.GetBlobServiceClient();
//make sure it's functional
var containers = await service.GetBlobContainersAsync().ToListAsync();

// Assert
Assert.AreEqual(container.AccountName, service.AccountName);
Assert.IsNotNull(container);

// Cleanup
await container.DeleteIfExistsAsync();
}

#region Secondary Storage
[Test]
public async Task ListContainersSegmentAsync_SecondaryStorageFirstRetrySuccessful()
Expand Down
Loading