Skip to content

Commit 8c44be5

Browse files
kasobol-msftannelo-msft
authored andcommitted
[Storage] Blobs - ability to traverse blob hierarchy upwards. (Azure#16437)
Resolves Azure#16359
1 parent 606b3fb commit 8c44be5

22 files changed

+1430
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Added CanGenerateSasUri property, GenerateSasUri() to BlobBaseClient, BlobClient, BlockBlobClient, AppendBlobClient, PageBlobClient and BlobContainerClient.
1010
- Added CanAccountGenerateSasUri property, GenerateAccountSasUri() to BlobServiceClient.
1111
- Deprecated property BlobSasBuilder.Version, so when generating SAS will always use the latest Storage Service SAS version.
12+
- Added ability to get parent BlobContainerClient from BlobBaseClient and to get parent BlobServiceClient from BlobContainerClient.
1213

1314
## 12.7.0-preview.1 (2020-09-30)
1415
- Added support for service version 2020-02-10.

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public BlobContainerClient(System.Uri blobContainerUri, Azure.Storage.StorageSha
101101
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; }
102102
protected internal virtual Azure.Storage.Blobs.Specialized.BlockBlobClient GetBlockBlobClientCore(string blobName) { throw null; }
103103
protected internal virtual Azure.Storage.Blobs.Specialized.PageBlobClient GetPageBlobClientCore(string blobName) { throw null; }
104+
protected internal virtual Azure.Storage.Blobs.BlobServiceClient GetParentBlobServiceClientCore() { throw null; }
104105
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; }
105106
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; }
106107
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; }
@@ -1275,6 +1276,7 @@ public BlobBaseClient(System.Uri blobUri, Azure.Storage.StorageSharedKeyCredenti
12751276
public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.BlobSasBuilder builder) { throw null; }
12761277
public virtual System.Uri GenerateSasUri(Azure.Storage.Sas.BlobSasPermissions permissions, System.DateTimeOffset expiresOn) { throw null; }
12771278
protected internal virtual Azure.Storage.Blobs.Specialized.BlobLeaseClient GetBlobLeaseClientCore(string leaseId) { throw null; }
1279+
protected internal virtual Azure.Storage.Blobs.BlobContainerClient GetParentBlobContainerClientCore() { throw null; }
12781280
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; }
12791281
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; }
12801282
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; }
@@ -1435,6 +1437,8 @@ public static partial class SpecializedBlobExtensions
14351437
public static Azure.Storage.Blobs.Specialized.BlobLeaseClient GetBlobLeaseClient(this Azure.Storage.Blobs.Specialized.BlobBaseClient client, string leaseId = null) { throw null; }
14361438
public static Azure.Storage.Blobs.Specialized.BlockBlobClient GetBlockBlobClient(this Azure.Storage.Blobs.BlobContainerClient client, string blobName) { throw null; }
14371439
public static Azure.Storage.Blobs.Specialized.PageBlobClient GetPageBlobClient(this Azure.Storage.Blobs.BlobContainerClient client, string blobName) { throw null; }
1440+
public static Azure.Storage.Blobs.BlobContainerClient GetParentBlobContainerClient(this Azure.Storage.Blobs.Specialized.BlobBaseClient client) { throw null; }
1441+
public static Azure.Storage.Blobs.BlobServiceClient GetParentBlobServiceClient(this Azure.Storage.Blobs.BlobContainerClient client) { throw null; }
14381442
public static Azure.Storage.Blobs.BlobClient WithClientSideEncryptionOptions(this Azure.Storage.Blobs.BlobClient client, Azure.Storage.ClientSideEncryptionOptions clientSideEncryptionOptions) { throw null; }
14391443
}
14401444
}

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4246,6 +4246,43 @@ public virtual Uri GenerateSasUri(BlobSasBuilder builder)
42464246
return sasUri.ToUri();
42474247
}
42484248
#endregion
4249+
4250+
#region GetParentBlobContainerClientCore
4251+
4252+
private BlobContainerClient _parentBlobContainerClient;
4253+
4254+
/// <summary>
4255+
/// Create a new <see cref="BlobContainerClient"/> that pointing to this <see cref="BlobBaseClient"/>'s parent container.
4256+
/// The new <see cref="BlockBlobClient"/>
4257+
/// uses the same request policy pipeline as the
4258+
/// <see cref="BlobBaseClient"/>.
4259+
/// </summary>
4260+
/// <returns>A new <see cref="BlobContainerClient"/> instance.</returns>
4261+
protected internal virtual BlobContainerClient GetParentBlobContainerClientCore()
4262+
{
4263+
if (_parentBlobContainerClient == null)
4264+
{
4265+
BlobUriBuilder blobUriBuilder = new BlobUriBuilder(Uri)
4266+
{
4267+
// erase parameters unrelated to container
4268+
BlobName = null,
4269+
VersionId = null,
4270+
Snapshot = null,
4271+
};
4272+
4273+
_parentBlobContainerClient = new BlobContainerClient(
4274+
blobUriBuilder.ToUri(),
4275+
Pipeline,
4276+
Version,
4277+
ClientDiagnostics,
4278+
CustomerProvidedKey,
4279+
ClientSideEncryption,
4280+
EncryptionScope);
4281+
}
4282+
4283+
return _parentBlobContainerClient;
4284+
}
4285+
#endregion
42494286
}
42504287

42514288
/// <summary>
@@ -4254,6 +4291,19 @@ public virtual Uri GenerateSasUri(BlobSasBuilder builder)
42544291
/// </summary>
42554292
public static partial class SpecializedBlobExtensions
42564293
{
4294+
/// <summary>
4295+
/// Create a new <see cref="BlobContainerClient"/> that pointing to this <see cref="BlobBaseClient"/>'s parent container.
4296+
/// The new <see cref="BlockBlobClient"/>
4297+
/// uses the same request policy pipeline as the
4298+
/// <see cref="BlobBaseClient"/>.
4299+
/// </summary>
4300+
/// <param name="client">The <see cref="BlobBaseClient"/>.</param>
4301+
/// <returns>A new <see cref="BlobContainerClient"/> instance.</returns>
4302+
public static BlobContainerClient GetParentBlobContainerClient(this BlobBaseClient client)
4303+
{
4304+
return client.GetParentBlobContainerClientCore();
4305+
}
4306+
42574307
/// <summary>
42584308
/// Create a new <see cref="BlobBaseClient"/> object by concatenating
42594309
/// <paramref name="blobName"/> to the end of the

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
using Azure.Storage.Sas;
1616
using Metadata = System.Collections.Generic.IDictionary<string, string>;
1717

18+
#pragma warning disable SA1402 // File may only contain a single type
19+
1820
namespace Azure.Storage.Blobs
1921
{
2022
/// <summary>
@@ -2967,5 +2969,66 @@ public virtual Uri GenerateSasUri(BlobSasBuilder builder)
29672969
return sasUri.ToUri();
29682970
}
29692971
#endregion
2972+
2973+
#region GetParentBlobServiceClientCore
2974+
2975+
private BlobServiceClient _parentBlobServiceClient;
2976+
2977+
/// <summary>
2978+
/// Create a new <see cref="BlobServiceClient"/> that pointing to this <see cref="BlobContainerClient"/>'s blob service.
2979+
/// The new <see cref="BlobServiceClient"/>
2980+
/// uses the same request policy pipeline as the
2981+
/// <see cref="BlobContainerClient"/>.
2982+
/// </summary>
2983+
/// <returns>A new <see cref="BlobServiceClient"/> instance.</returns>
2984+
protected internal virtual BlobServiceClient GetParentBlobServiceClientCore()
2985+
{
2986+
if (_parentBlobServiceClient == null)
2987+
{
2988+
BlobUriBuilder blobUriBuilder = new BlobUriBuilder(Uri)
2989+
{
2990+
// erase parameters unrelated to service
2991+
BlobContainerName = null,
2992+
BlobName = null,
2993+
VersionId = null,
2994+
Snapshot = null,
2995+
};
2996+
2997+
_parentBlobServiceClient = new BlobServiceClient(
2998+
blobUriBuilder.ToUri(),
2999+
null,
3000+
Version,
3001+
ClientDiagnostics,
3002+
CustomerProvidedKey,
3003+
ClientSideEncryption,
3004+
EncryptionScope,
3005+
Pipeline);
3006+
}
3007+
3008+
return _parentBlobServiceClient;
3009+
}
3010+
#endregion
3011+
}
3012+
3013+
namespace Specialized
3014+
{
3015+
/// <summary>
3016+
/// Add easy to discover methods to <see cref="BlobContainerClient"/> for
3017+
/// creating <see cref="BlobServiceClient"/> instances.
3018+
/// </summary>
3019+
public static partial class SpecializedBlobExtensions
3020+
{
3021+
/// <summary>
3022+
/// Create a new <see cref="BlobServiceClient"/> that pointing to this <see cref="BlobContainerClient"/>'s blob service.
3023+
/// The new <see cref="BlobServiceClient"/>
3024+
/// uses the same request policy pipeline as the
3025+
/// <see cref="BlobContainerClient"/>.
3026+
/// </summary>
3027+
/// <returns>A new <see cref="BlobServiceClient"/> instance.</returns>
3028+
public static BlobServiceClient GetParentBlobServiceClient(this BlobContainerClient client)
3029+
{
3030+
return client.GetParentBlobServiceClientCore();
3031+
}
3032+
}
29703033
}
29713034
}

sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6124,6 +6124,102 @@ public void CanMockBlobLeaseClientRetrieval()
61246124
Assert.AreSame(blobLeaseClientMock.Object, blobLeaseClient);
61256125
}
61266126

6127+
[Test]
6128+
public async Task CanGetParentContainerClient()
6129+
{
6130+
// Arrange
6131+
await using DisposingContainer test = await GetTestContainerAsync();
6132+
BlobClient blobClient = InstrumentClient(test.Container.GetBlobClient(GetNewBlobName()));
6133+
6134+
// Act
6135+
var containerClient = blobClient.GetParentBlobContainerClient();
6136+
// make sure that client is functional
6137+
BlobContainerProperties containerProperties = await containerClient.GetPropertiesAsync();
6138+
6139+
// Assert
6140+
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
6141+
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
6142+
Assert.IsNotNull(containerProperties);
6143+
}
6144+
6145+
[Test]
6146+
public async Task CanGetParentContainerClient_FromBlobClientThatHasExtraQueryParameters()
6147+
{
6148+
// Arrange
6149+
await using DisposingContainer test = await GetTestContainerAsync();
6150+
BlobClient blobClient = InstrumentClient(test.Container.GetBlobClient(GetNewBlobName())).WithVersion(Recording.Random.NewGuid().ToString());
6151+
6152+
// Act
6153+
var containerClient = blobClient.GetParentBlobContainerClient();
6154+
// make sure that client is functional
6155+
BlobContainerProperties containerProperties = await containerClient.GetPropertiesAsync();
6156+
6157+
// Assert
6158+
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
6159+
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
6160+
Assert.IsNotNull(containerProperties);
6161+
}
6162+
6163+
[Test]
6164+
public async Task CanGetParentContainerClient_WithAccountSAS()
6165+
{
6166+
// Arrange
6167+
await using DisposingContainer test = await GetTestContainerAsync();
6168+
var blobName = GetNewBlobName();
6169+
BlobBaseClient blobClient = InstrumentClient(
6170+
GetServiceClient_AccountSas()
6171+
.GetBlobContainerClient(test.Container.Name)
6172+
.GetBlobClient(blobName));
6173+
6174+
// Act
6175+
var containerClient = blobClient.GetParentBlobContainerClient();
6176+
// make sure that client is functional
6177+
BlobContainerProperties containerProperties = await containerClient.GetPropertiesAsync();
6178+
6179+
// Assert
6180+
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
6181+
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
6182+
Assert.IsNotNull(containerProperties);
6183+
}
6184+
6185+
[Test]
6186+
public async Task CanGetParentContainerClient_WithContainerSAS()
6187+
{
6188+
// Arrange
6189+
await using DisposingContainer test = await GetTestContainerAsync();
6190+
var blobName = GetNewBlobName();
6191+
BlobBaseClient blobClient = InstrumentClient(
6192+
GetServiceClient_BlobServiceSas_Container(test.Container.Name)
6193+
.GetBlobContainerClient(test.Container.Name)
6194+
.GetBlobClient(blobName));
6195+
6196+
// Act
6197+
var containerClient = blobClient.GetParentBlobContainerClient();
6198+
// make sure that client is functional
6199+
var blobItems = await containerClient.GetBlobsAsync().ToListAsync();
6200+
6201+
// Assert
6202+
Assert.AreEqual(blobClient.BlobContainerName, containerClient.Name);
6203+
Assert.AreEqual(blobClient.AccountName, containerClient.AccountName);
6204+
Assert.IsNotNull(blobItems);
6205+
}
6206+
6207+
[Test]
6208+
public void CanMockParentContainerClientRetrieval()
6209+
{
6210+
// Arrange
6211+
Mock<BlobBaseClient> blobBaseClientMock = new Mock<BlobBaseClient>();
6212+
Mock<BlobContainerClient> blobContainerClientMock = new Mock<BlobContainerClient>();
6213+
blobBaseClientMock.Protected().Setup<BlobContainerClient>("GetParentBlobContainerClientCore").Returns(blobContainerClientMock.Object);
6214+
6215+
// Act
6216+
var blobContainerClient = blobBaseClientMock.Object.GetParentBlobContainerClient();
6217+
6218+
// Assert
6219+
Assert.IsNotNull(blobContainerClient);
6220+
Assert.AreSame(blobContainerClientMock.Object, blobContainerClient);
6221+
}
6222+
61276223
public IEnumerable<AccessConditionParameters> AccessConditions_Data
61286224
=> new[]
61296225
{

sdk/storage/Azure.Storage.Blobs/tests/ContainerClientTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,60 @@ public void CanMockBlobClientsRetrieval()
27732773
Assert.AreSame(blobLeaseClientMock.Object, blobLeaseClient);
27742774
}
27752775

2776+
[Test]
2777+
public void CanMockBlobServiceClientRetrieval()
2778+
{
2779+
// Arrange
2780+
Mock<BlobContainerClient> containerClientMock = new Mock<BlobContainerClient>();
2781+
Mock<BlobServiceClient> blobServiceClientMock = new Mock<BlobServiceClient>();
2782+
containerClientMock.Protected().Setup<BlobServiceClient>("GetParentBlobServiceClientCore").Returns(blobServiceClientMock.Object);
2783+
2784+
// Act
2785+
var blobServiceClient = containerClientMock.Object.GetParentBlobServiceClient();
2786+
2787+
// Assert
2788+
Assert.IsNotNull(blobServiceClient);
2789+
Assert.AreSame(blobServiceClientMock.Object, blobServiceClient);
2790+
}
2791+
2792+
[Test]
2793+
public async Task CanGetParentBlobServiceClient()
2794+
{
2795+
// Arrange
2796+
BlobContainerClient container = InstrumentClient(GetServiceClient_SharedKey().GetBlobContainerClient(GetNewContainerName()));
2797+
2798+
// Act
2799+
BlobServiceClient service = container.GetParentBlobServiceClient();
2800+
//make sure it's functional
2801+
var containers = await service.GetBlobContainersAsync().ToListAsync();
2802+
2803+
// Assert
2804+
Assert.AreEqual(container.AccountName, service.AccountName);
2805+
Assert.IsNotNull(container);
2806+
2807+
// Cleanup
2808+
await container.DeleteIfExistsAsync();
2809+
}
2810+
2811+
[Test]
2812+
public async Task CanGetParentBlobServiceClient_WithAccountSAS()
2813+
{
2814+
// Arrange
2815+
BlobContainerClient container = InstrumentClient(GetServiceClient_AccountSas().GetBlobContainerClient(GetNewContainerName()));
2816+
2817+
// Act
2818+
BlobServiceClient service = container.GetParentBlobServiceClient();
2819+
//make sure it's functional
2820+
var containers = await service.GetBlobContainersAsync().ToListAsync();
2821+
2822+
// Assert
2823+
Assert.AreEqual(container.AccountName, service.AccountName);
2824+
Assert.IsNotNull(container);
2825+
2826+
// Cleanup
2827+
await container.DeleteIfExistsAsync();
2828+
}
2829+
27762830
#region Secondary Storage
27772831
[Test]
27782832
public async Task ListContainersSegmentAsync_SecondaryStorageFirstRetrySuccessful()

0 commit comments

Comments
 (0)