diff --git a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.net8.0.cs b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.net8.0.cs index 5c3e6aab3b5d..9ddfa7513f43 100644 --- a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.net8.0.cs +++ b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.net8.0.cs @@ -1024,6 +1024,8 @@ protected BlobQueryTextOptions() { } public partial class BlobRequestConditions : Azure.Storage.Blobs.Models.BlobLeaseRequestConditions { public BlobRequestConditions() { } + public System.DateTimeOffset? AccessTierIfModifiedSince { get { throw null; } set { } } + public System.DateTimeOffset? AccessTierIfUnmodifiedSince { get { throw null; } set { } } public string LeaseId { get { throw null; } set { } } public override string ToString() { throw null; } } diff --git a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs index fd669ff7bbc4..43858ae373bc 100644 --- a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs +++ b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.0.cs @@ -1024,6 +1024,8 @@ protected BlobQueryTextOptions() { } public partial class BlobRequestConditions : Azure.Storage.Blobs.Models.BlobLeaseRequestConditions { public BlobRequestConditions() { } + public System.DateTimeOffset? AccessTierIfModifiedSince { get { throw null; } set { } } + public System.DateTimeOffset? AccessTierIfUnmodifiedSince { get { throw null; } set { } } public string LeaseId { get { throw null; } set { } } public override string ToString() { throw null; } } diff --git a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.1.cs b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.1.cs index fd669ff7bbc4..43858ae373bc 100644 --- a/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.1.cs +++ b/sdk/storage/Azure.Storage.Blobs/api/Azure.Storage.Blobs.netstandard2.1.cs @@ -1024,6 +1024,8 @@ protected BlobQueryTextOptions() { } public partial class BlobRequestConditions : Azure.Storage.Blobs.Models.BlobLeaseRequestConditions { public BlobRequestConditions() { } + public System.DateTimeOffset? AccessTierIfModifiedSince { get { throw null; } set { } } + public System.DateTimeOffset? AccessTierIfUnmodifiedSince { get { throw null; } set { } } public string LeaseId { get { throw null; } set { } } public override string ToString() { throw null; } } diff --git a/sdk/storage/Azure.Storage.Blobs/assets.json b/sdk/storage/Azure.Storage.Blobs/assets.json index 48511d06cc0d..9a5e69ccd490 100644 --- a/sdk/storage/Azure.Storage.Blobs/assets.json +++ b/sdk/storage/Azure.Storage.Blobs/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "net", "TagPrefix": "net/storage/Azure.Storage.Blobs", - "Tag": "net/storage/Azure.Storage.Blobs_6b5ddc8447" + "Tag": "net/storage/Azure.Storage.Blobs_81d74b8ce9" } diff --git a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs index c096f0c57e02..efff33c3abb2 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/BlobBaseClient.cs @@ -4676,6 +4676,8 @@ private async Task DeleteInternal( ifMatch: conditions?.IfMatch?.ToString(), ifNoneMatch: conditions?.IfNoneMatch?.ToString(), ifTags: conditions?.TagConditions, + accessTierIfModifiedSince: conditions?.AccessTierIfModifiedSince, + accessTierIfUnmodifiedSince: conditions?.AccessTierIfUnmodifiedSince, cancellationToken: cancellationToken) .ConfigureAwait(false); } @@ -4689,6 +4691,8 @@ private async Task DeleteInternal( ifMatch: conditions?.IfMatch?.ToString(), ifNoneMatch: conditions?.IfNoneMatch?.ToString(), ifTags: conditions?.TagConditions, + accessTierIfModifiedSince: conditions?.AccessTierIfModifiedSince, + accessTierIfUnmodifiedSince: conditions?.AccessTierIfUnmodifiedSince, cancellationToken: cancellationToken); } diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs index 67a713dab5ae..a49c66442eab 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/AppendBlobRestClient.cs @@ -29,7 +29,7 @@ internal partial class AppendBlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". + /// Specifies the version of the operation to use for this request. The default value is "2026-04-06". /// , , or is null. public AppendBlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs index 720caef66b4a..2646b7652280 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs @@ -30,7 +30,7 @@ internal partial class BlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". + /// Specifies the version of the operation to use for this request. The default value is "2026-04-06". /// , , or is null. public BlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { @@ -436,7 +436,7 @@ public virtual Response GetProperties(string snapshot = null, string versionId = } } - internal HttpMessage CreateDeleteRequest(string snapshot, string versionId, int? timeout, string leaseId, DeleteSnapshotsOption? deleteSnapshots, DateTimeOffset? ifModifiedSince, DateTimeOffset? ifUnmodifiedSince, string ifMatch, string ifNoneMatch, string ifTags, BlobDeleteType? blobDeleteType) + internal HttpMessage CreateDeleteRequest(string snapshot, string versionId, int? timeout, string leaseId, DeleteSnapshotsOption? deleteSnapshots, DateTimeOffset? ifModifiedSince, DateTimeOffset? ifUnmodifiedSince, string ifMatch, string ifNoneMatch, string ifTags, BlobDeleteType? blobDeleteType, DateTimeOffset? accessTierIfModifiedSince, DateTimeOffset? accessTierIfUnmodifiedSince) { var message = _pipeline.CreateMessage(); var request = message.Request; @@ -489,6 +489,14 @@ internal HttpMessage CreateDeleteRequest(string snapshot, string versionId, int? request.Headers.Add("x-ms-if-tags", ifTags); } request.Headers.Add("x-ms-version", _version); + if (accessTierIfModifiedSince != null) + { + request.Headers.Add("x-ms-access-tier-if-modified-since", accessTierIfModifiedSince.Value, "R"); + } + if (accessTierIfUnmodifiedSince != null) + { + request.Headers.Add("x-ms-access-tier-if-unmodified-since", accessTierIfUnmodifiedSince.Value, "R"); + } request.Headers.Add("Accept", "application/xml"); return message; } @@ -505,10 +513,12 @@ internal HttpMessage CreateDeleteRequest(string snapshot, string versionId, int? /// Specify an ETag value to operate only on blobs without a matching value. /// Specify a SQL where clause on blob tags to operate only on blobs with a matching value. /// Optional. Only possible value is 'permanent', which specifies to permanently delete a blob if blob soft delete is enabled. + /// Specify this header value to operate only on a blob if the access-tier has been modified since the specified date/time. + /// Specify this header value to operate only on a blob if the access-tier has not been modified since the specified date/time. /// The cancellation token to use. - public async Task> DeleteAsync(string snapshot = null, string versionId = null, int? timeout = null, string leaseId = null, DeleteSnapshotsOption? deleteSnapshots = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, string ifTags = null, BlobDeleteType? blobDeleteType = null, CancellationToken cancellationToken = default) + public async Task> DeleteAsync(string snapshot = null, string versionId = null, int? timeout = null, string leaseId = null, DeleteSnapshotsOption? deleteSnapshots = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, string ifTags = null, BlobDeleteType? blobDeleteType = null, DateTimeOffset? accessTierIfModifiedSince = null, DateTimeOffset? accessTierIfUnmodifiedSince = null, CancellationToken cancellationToken = default) { - using var message = CreateDeleteRequest(snapshot, versionId, timeout, leaseId, deleteSnapshots, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, ifTags, blobDeleteType); + using var message = CreateDeleteRequest(snapshot, versionId, timeout, leaseId, deleteSnapshots, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, ifTags, blobDeleteType, accessTierIfModifiedSince, accessTierIfUnmodifiedSince); await _pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false); var headers = new BlobDeleteHeaders(message.Response); switch (message.Response.Status) @@ -532,10 +542,12 @@ public async Task> DeleteAsync(string sna /// Specify an ETag value to operate only on blobs without a matching value. /// Specify a SQL where clause on blob tags to operate only on blobs with a matching value. /// Optional. Only possible value is 'permanent', which specifies to permanently delete a blob if blob soft delete is enabled. + /// Specify this header value to operate only on a blob if the access-tier has been modified since the specified date/time. + /// Specify this header value to operate only on a blob if the access-tier has not been modified since the specified date/time. /// The cancellation token to use. - public ResponseWithHeaders Delete(string snapshot = null, string versionId = null, int? timeout = null, string leaseId = null, DeleteSnapshotsOption? deleteSnapshots = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, string ifTags = null, BlobDeleteType? blobDeleteType = null, CancellationToken cancellationToken = default) + public ResponseWithHeaders Delete(string snapshot = null, string versionId = null, int? timeout = null, string leaseId = null, DeleteSnapshotsOption? deleteSnapshots = null, DateTimeOffset? ifModifiedSince = null, DateTimeOffset? ifUnmodifiedSince = null, string ifMatch = null, string ifNoneMatch = null, string ifTags = null, BlobDeleteType? blobDeleteType = null, DateTimeOffset? accessTierIfModifiedSince = null, DateTimeOffset? accessTierIfUnmodifiedSince = null, CancellationToken cancellationToken = default) { - using var message = CreateDeleteRequest(snapshot, versionId, timeout, leaseId, deleteSnapshots, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, ifTags, blobDeleteType); + using var message = CreateDeleteRequest(snapshot, versionId, timeout, leaseId, deleteSnapshots, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch, ifTags, blobDeleteType, accessTierIfModifiedSince, accessTierIfUnmodifiedSince); _pipeline.Send(message, cancellationToken); var headers = new BlobDeleteHeaders(message.Response); switch (message.Response.Status) diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs index af938b490fe5..0f2471800bf9 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlockBlobRestClient.cs @@ -30,7 +30,7 @@ internal partial class BlockBlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". + /// Specifies the version of the operation to use for this request. The default value is "2026-04-06". /// , , or is null. public BlockBlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs index c70fa61447fc..d1a508864bc2 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/ContainerRestClient.cs @@ -31,7 +31,7 @@ internal partial class ContainerRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". + /// Specifies the version of the operation to use for this request. The default value is "2026-04-06". /// , , or is null. public ContainerRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs index 26024e2ad053..36844cb1e8c2 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/PageBlobRestClient.cs @@ -30,7 +30,7 @@ internal partial class PageBlobRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". + /// Specifies the version of the operation to use for this request. The default value is "2026-04-06". /// , , or is null. public PageBlobRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs index 8936d67e02ca..e21bada16a70 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/ServiceRestClient.cs @@ -31,7 +31,7 @@ internal partial class ServiceRestClient /// The handler for diagnostic messaging in the client. /// The HTTP pipeline for sending and receiving REST requests and responses. /// The URL of the service account, container, or blob that is the target of the desired operation. - /// Specifies the version of the operation to use for this request. The default value is "2026-02-06". + /// Specifies the version of the operation to use for this request. The default value is "2026-04-06". /// , , or is null. public ServiceRestClient(ClientDiagnostics clientDiagnostics, HttpPipeline pipeline, string url, string version) { diff --git a/sdk/storage/Azure.Storage.Blobs/src/Models/BlobRequestConditions.cs b/sdk/storage/Azure.Storage.Blobs/src/Models/BlobRequestConditions.cs index da2dd476b456..6ae8c048cc96 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/Models/BlobRequestConditions.cs +++ b/sdk/storage/Azure.Storage.Blobs/src/Models/BlobRequestConditions.cs @@ -20,6 +20,18 @@ public class BlobRequestConditions : BlobLeaseRequestConditions /// public string LeaseId { get; set; } + /// + /// Specify this header value to operate only on a blob if the access-tier has been modified since the specified date/time. + /// Note: If this is specified, AccessTierIfUnmodifiedSince cannot be specified. + /// + public DateTimeOffset? AccessTierIfModifiedSince { get; set; } + + /// + /// Specify this header value to operate only on a blob if the access-tier has not been modified since the specified date/time. + /// Note: If this is specified, AccessTierIfModifiedSince cannot be specified. + /// + public DateTimeOffset? AccessTierIfUnmodifiedSince { get; set; } + /// /// Default constructor. /// @@ -31,6 +43,8 @@ private BlobRequestConditions(BlobRequestConditions deepCopySource) : base(deepC { Argument.AssertNotNull(deepCopySource, nameof(deepCopySource)); LeaseId = deepCopySource.LeaseId; + AccessTierIfModifiedSince = deepCopySource.AccessTierIfModifiedSince; + AccessTierIfUnmodifiedSince = deepCopySource.AccessTierIfUnmodifiedSince; } /// @@ -112,6 +126,16 @@ internal virtual void AddConditions(StringBuilder conditions) { conditions.Append(nameof(TagConditions)).Append('=').Append(TagConditions).Append(';'); } + + if (AccessTierIfModifiedSince != null) + { + conditions.Append(nameof(AccessTierIfModifiedSince)).Append('=').Append(AccessTierIfModifiedSince).Append(';'); + } + + if (AccessTierIfUnmodifiedSince != null) + { + conditions.Append(nameof(AccessTierIfUnmodifiedSince)).Append('=').Append(AccessTierIfUnmodifiedSince).Append(';'); + } } } } diff --git a/sdk/storage/Azure.Storage.Blobs/src/autorest.md b/sdk/storage/Azure.Storage.Blobs/src/autorest.md index 68802c82f330..90b8df43a36b 100644 --- a/sdk/storage/Azure.Storage.Blobs/src/autorest.md +++ b/sdk/storage/Azure.Storage.Blobs/src/autorest.md @@ -4,7 +4,7 @@ Run `dotnet build /t:GenerateCode` to generate code. ``` yaml input-file: - - https://raw.githubusercontent.com/Azure/azure-rest-api-specs/b6472ffd34d5d4a155101b41b4eb1f356abff600/specification/storage/data-plane/Microsoft.BlobStorage/stable/2026-02-06/blob.json + - https://raw.githubusercontent.com/Azure/azure-rest-api-specs/933db48a0939e55518e7f3f442dd78ea66e3fef0/specification/storage/data-plane/Microsoft.BlobStorage/stable/2026-04-06/blob.json generation1-convenience-client: true # https://github.com/Azure/autorest/issues/4075 skip-semantics-validation: true diff --git a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs index 399320cd4a72..d816a73f9a03 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs @@ -3922,6 +3922,92 @@ public async Task DeleteAsync_InvalidSAS() Assert.IsTrue(await blob.ExistsAsync()); } + [RecordedTest] + [TestCase(true)] + [TestCase(false)] + public async Task DeleteAsync_BlobAccessTierRequestConditions(bool isAccessTierModifiedSince) + { + await using DisposingContainer test = await GetTestContainerAsync(); + + // Arrange + BlobBaseClient blob = await GetNewBlobClient(test.Container); + + // modify the access tier + await blob.SetAccessTierAsync(AccessTier.Cool); + DateTimeOffset changeTime = Recording.UtcNow; + + BlobRequestConditions accessConditions; + if (isAccessTierModifiedSince) + { + accessConditions = new BlobRequestConditions + { + // requires modification since yesterday (which there should be modification in this time window) + AccessTierIfModifiedSince = changeTime.AddDays(-1) + }; + } + else + { + accessConditions = new BlobRequestConditions + { + // requires no modification after 5 minutes from now (which there should be no modification then) + AccessTierIfUnmodifiedSince = changeTime.AddMinutes(5) + }; + } + + // Act + Response response = await blob.DeleteAsync(conditions: accessConditions); + + // Assert + Assert.IsNotNull(response.Headers.RequestId); + Assert.IsFalse(await blob.ExistsAsync()); + } + + [RecordedTest] + [TestCase(true)] + [TestCase(false)] + public async Task DeleteAsync_BlobAccessTierRequestConditions_Fail(bool isAccessTierModifiedSince) + { + await using DisposingContainer test = await GetTestContainerAsync(); + + // Arrange + BlobBaseClient blob = await GetNewBlobClient(test.Container); + + // modify the access tier + await blob.SetAccessTierAsync(AccessTier.Cool); + DateTimeOffset changeTime = Recording.UtcNow; + + BlobRequestConditions accessConditions; + if (isAccessTierModifiedSince) + { + accessConditions = new BlobRequestConditions + { + // requires modification after 5 minutes from now (which there should be no modification then) + AccessTierIfModifiedSince = changeTime.AddMinutes(5) + }; + } + else + { + accessConditions = new BlobRequestConditions + { + // requires no modification since yesterday (which there should be modification in this time window) + AccessTierIfUnmodifiedSince = changeTime.AddDays(-1) + }; + } + + // Act + await TestHelper.AssertExpectedExceptionAsync( + blob.DeleteAsync(conditions: accessConditions), + e => + { + Assert.AreEqual(412, e.Status); + Assert.AreEqual("AccessTierChangeTimeConditionNotMet", e.ErrorCode); + StringAssert.Contains("The condition specified using access tier change time conditional header(s) is not met.", e.Message); + }); + + // Assert + Assert.IsTrue(await blob.ExistsAsync()); + } + [RecordedTest] public async Task DeleteIfExistsAsync() {