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 all 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 @@ -9,6 +9,7 @@
- Added CanGenerateSasUri property, GenerateSasUri() to BlobBaseClient, BlobClient, BlockBlobClient, AppendBlobClient, PageBlobClient and BlobContainerClient.
- Added CanAccountGenerateSasUri property, GenerateAccountSasUri() to BlobServiceClient.
- Deprecated property BlobSasBuilder.Version, so when generating SAS will always use the latest Storage Service SAS version.
- 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 @@ -101,6 +101,7 @@ public BlobContainerClient(System.Uri blobContainerUri, Azure.Storage.StorageSha
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.Specialized.BlockBlobClient GetBlockBlobClientCore(string blobName) { throw null; }
protected internal virtual Azure.Storage.Blobs.Specialized.PageBlobClient GetPageBlobClientCore(string blobName) { throw null; }
protected internal virtual Azure.Storage.Blobs.BlobServiceClient GetParentBlobServiceClientCore() { 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; }
public virtual System.Threading.Tasks.Task<Azure.Response<Azure.Storage.Blobs.Models.BlobContainerProperties>> GetPropertiesAsync(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual Azure.Response<Azure.Storage.Blobs.Models.BlobContainerInfo> SetAccessPolicy(Azure.Storage.Blobs.Models.PublicAccessType accessType = Azure.Storage.Blobs.Models.PublicAccessType.None, System.Collections.Generic.IEnumerable<Azure.Storage.Blobs.Models.BlobSignedIdentifier> permissions = null, Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down Expand Up @@ -1275,6 +1276,7 @@ public BlobBaseClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredenti
public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.BlobSasBuilder builder) { throw null; }
public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.BlobSasPermissions permissions, System.DateTimeOffset expiresOn) { throw null; }
protected internal virtual Azure.Storage.Blobs.Specialized.BlobLeaseClient GetBlobLeaseClientCore(string leaseId) { throw null; }
protected internal virtual Azure.Storage.Blobs.BlobContainerClient GetParentBlobContainerClientCore() { 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; }
public virtual Azure.Response<Azure.Storage.Blobs.Models.GetBlobTagResult> GetTags(Azure.Storage.Blobs.Models.BlobRequestConditions conditions = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
Expand Down Expand Up @@ -1435,6 +1437,8 @@ public static partial class SpecializedBlobExtensions
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.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.BlobContainerClient GetParentBlobContainerClient(this Azure.Storage.Blobs.Specialized.BlobBaseClient client) { throw null; }
public static Azure.Storage.Blobs.BlobServiceClient GetParentBlobServiceClient(this Azure.Storage.Blobs.BlobContainerClient client) { throw null; }
public static Azure.Storage.Blobs.BlobClient WithClientSideEncryptionOptions(this Azure.Storage.Blobs.BlobClient client, Azure.Storage.ClientSideEncryptionOptions clientSideEncryptionOptions) { throw null; }
}
}
Expand Down
50 changes: 50 additions & 0 deletions sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4246,6 +4246,43 @@ public virtual Uri GenerateSasUri(BlobSasBuilder builder)
return sasUri.ToUri();
}
#endregion

#region GetParentBlobContainerClientCore

private BlobContainerClient _parentBlobContainerClient;

/// <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 GetParentBlobContainerClientCore()
{
if (_parentBlobContainerClient == null)
{
BlobUriBuilder blobUriBuilder = new BlobUriBuilder(Uri)
{
// erase parameters unrelated to container
BlobName = null,
VersionId = null,
Snapshot = null,
};

_parentBlobContainerClient = new BlobContainerClient(
blobUriBuilder.ToUri(),
Pipeline,
Version,
ClientDiagnostics,
CustomerProvidedKey,
ClientSideEncryption,
EncryptionScope);
}

return _parentBlobContainerClient;
}
#endregion
}

/// <summary>
Expand All @@ -4254,6 +4291,19 @@ public virtual Uri GenerateSasUri(BlobSasBuilder builder)
/// </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 GetParentBlobContainerClient(this BlobBaseClient client)
{
return client.GetParentBlobContainerClientCore();
}

/// <summary>
/// Create a new <see cref="BlobBaseClient"/> object by concatenating
/// <paramref name="blobName"/> to the end of the
Expand Down
63 changes: 63 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 @@ -2967,5 +2969,66 @@ public virtual Uri GenerateSasUri(BlobSasBuilder builder)
return sasUri.ToUri();
}
#endregion

#region GetParentBlobServiceClientCore

private BlobServiceClient _parentBlobServiceClient;

/// <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 GetParentBlobServiceClientCore()
{
if (_parentBlobServiceClient == null)
{
BlobUriBuilder blobUriBuilder = new BlobUriBuilder(Uri)
{
// erase parameters unrelated to service
BlobContainerName = null,
BlobName = null,
VersionId = null,
Snapshot = null,
};

_parentBlobServiceClient = new BlobServiceClient(
blobUriBuilder.ToUri(),
null,
Version,
ClientDiagnostics,
CustomerProvidedKey,
ClientSideEncryption,
EncryptionScope,
Pipeline);
}

return _parentBlobServiceClient;
}
#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 GetParentBlobServiceClient(this BlobContainerClient client)
{
return client.GetParentBlobServiceClientCore();
}
}
}
}
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 @@ -6124,6 +6124,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.GetParentBlobContainerClient();
// 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.GetParentBlobContainerClient();
// 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.GetParentBlobContainerClient();
// 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.GetParentBlobContainerClient();
// 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>("GetParentBlobContainerClientCore").Returns(blobContainerClientMock.Object);

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

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

public IEnumerable<AccessConditionParameters> AccessConditions_Data
=> new[]
{
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 @@ -2773,6 +2773,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>("GetParentBlobServiceClientCore").Returns(blobServiceClientMock.Object);

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

// 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.GetParentBlobServiceClient();
//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.GetParentBlobServiceClient();
//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