Skip to content

Commit

Permalink
[Storage] Blobs - ability to traverse blob hierarchy upwards. (#16437)
Browse files Browse the repository at this point in the history
Resolves #16359
  • Loading branch information
kasobol-msft authored Nov 3, 2020
1 parent c450950 commit 4fdce1e
Show file tree
Hide file tree
Showing 22 changed files with 1,430 additions and 0 deletions.
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

0 comments on commit 4fdce1e

Please sign in to comment.