Skip to content

Commit

Permalink
Use operation helpers
Browse files Browse the repository at this point in the history
Partially fixes Azure#22912
  • Loading branch information
heaths committed Dec 1, 2021
1 parent db1a076 commit 197426a
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>This is the Microsoft Azure Key Vault Secrets client library</Description>
Expand Down Expand Up @@ -37,6 +37,8 @@
<Compile Include="$(AzureCoreSharedSources)PageResponseEnumerator.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)DiagnosticScopeFactory.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationHelpers.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationInternal.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)OperationInternalBase.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)TaskExtensions.cs" LinkBase="Shared" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,26 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;

namespace Azure.Security.KeyVault.Secrets
{
/// <summary>
/// A long-running operation for <see cref="SecretClient.StartDeleteSecret(string, CancellationToken)"/> or <see cref="SecretClient.StartDeleteSecretAsync(string, CancellationToken)"/>.
/// </summary>
public class DeleteSecretOperation : Operation<DeletedSecret>
public class DeleteSecretOperation : Operation<DeletedSecret>, IOperation
{
private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2);

private readonly KeyVaultPipeline _pipeline;
private readonly OperationInternal _operationInternal;
private readonly DeletedSecret _value;
private Response _response;
private bool _completed;
private readonly bool _completed;

internal DeleteSecretOperation(KeyVaultPipeline pipeline, Response<DeletedSecret> response)
{
_pipeline = pipeline;
_value = response.Value ?? throw new InvalidOperationException("The response does not contain a value.");
_response = response.GetRawResponse();
_operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(DeleteSecretOperation));

// The recoveryId is only returned if soft-delete is enabled.
if (_value.RecoveryId is null)
Expand All @@ -50,33 +49,20 @@ protected DeleteSecretOperation() {}
public override DeletedSecret Value => _value;

/// <inheritdoc/>
public override bool HasCompleted => _completed;
public override bool HasCompleted => _completed || _operationInternal.HasCompleted;

/// <inheritdoc/>
public override bool HasValue => true;

/// <inheritdoc/>
public override Response GetRawResponse() => _response;
public override Response GetRawResponse() => _operationInternal.RawResponse;

/// <inheritdoc/>
public override Response UpdateStatus(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteSecretOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name);
_completed = CheckCompleted(_response);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return _operationInternal.UpdateStatus(cancellationToken);
}

return GetRawResponse();
Expand All @@ -85,22 +71,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa
/// <inheritdoc/>
public override async ValueTask<Response> UpdateStatusAsync(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(DeleteSecretOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name).ConfigureAwait(false);
_completed = await CheckCompletedAsync(_response).ConfigureAwait(false);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false);
}

return GetRawResponse();
Expand All @@ -114,34 +87,27 @@ public override ValueTask<Response<DeletedSecret>> WaitForCompletionAsync(Cancel
public override ValueTask<Response<DeletedSecret>> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) =>
this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken);

private async ValueTask<bool> CheckCompletedAsync(Response response)
async ValueTask<OperationState> IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the secret was deleted.
return true;

case 404:
return false;
Response response = async
? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name).ConfigureAwait(false)
: _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.DeletedSecretsPath, _value.Name);

default:
throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false);
}
}
private bool CheckCompleted(Response response)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the secret was deleted.
return true;
return OperationState.Success(response);

case 404:
return false;
return OperationState.Pending(response);

default:
throw _pipeline.Diagnostics.CreateRequestFailedException(response);
RequestFailedException ex = async
? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false)
: _pipeline.Diagnostics.CreateRequestFailedException(response);

return OperationState.Failure(response, ex);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,25 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;

namespace Azure.Security.KeyVault.Secrets
{
/// <summary>
/// A long-running operation for <see cref="SecretClient.StartRecoverDeletedSecret(string, CancellationToken)"/> or <see cref="SecretClient.StartRecoverDeletedSecretAsync(string, CancellationToken)"/>.
/// </summary>
public class RecoverDeletedSecretOperation : Operation<SecretProperties>
public class RecoverDeletedSecretOperation : Operation<SecretProperties>, IOperation
{
private static readonly TimeSpan s_defaultPollingInterval = TimeSpan.FromSeconds(2);

private readonly KeyVaultPipeline _pipeline;
private readonly OperationInternal _operationInternal;
private readonly SecretProperties _value;
private Response _response;
private bool _completed;

internal RecoverDeletedSecretOperation(KeyVaultPipeline pipeline, Response<SecretProperties> response)
{
_pipeline = pipeline;
_value = response.Value ?? throw new InvalidOperationException("The response does not contain a value.");
_response = response.GetRawResponse();
_operationInternal = new(_pipeline.Diagnostics, this, response.GetRawResponse(), nameof(RecoverDeletedSecretOperation));
}

/// <summary> Initializes a new instance of <see cref="RecoverDeletedSecretOperation" /> for mocking. </summary>
Expand All @@ -44,33 +42,20 @@ protected RecoverDeletedSecretOperation() {}
public override SecretProperties Value => _value;

/// <inheritdoc/>
public override bool HasCompleted => _completed;
public override bool HasCompleted => _operationInternal.HasCompleted;

/// <inheritdoc/>
public override bool HasValue => true;

/// <inheritdoc/>
public override Response GetRawResponse() => _response;
public override Response GetRawResponse() => _operationInternal.RawResponse;

/// <inheritdoc/>
public override Response UpdateStatus(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedSecretOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version);
_completed = CheckCompleted(_response);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return _operationInternal.UpdateStatus(cancellationToken);
}

return GetRawResponse();
Expand All @@ -79,22 +64,9 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa
/// <inheritdoc/>
public override async ValueTask<Response> UpdateStatusAsync(CancellationToken cancellationToken = default)
{
if (!_completed)
if (!HasCompleted)
{
using DiagnosticScope scope = _pipeline.CreateScope($"{nameof(RecoverDeletedSecretOperation)}.{nameof(UpdateStatus)}");
scope.AddAttribute("secret", _value.Name);
scope.Start();

try
{
_response = await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version).ConfigureAwait(false);
_completed = await CheckCompletedAsync(_response).ConfigureAwait(false);
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
return await _operationInternal.UpdateStatusAsync(cancellationToken).ConfigureAwait(false);
}

return GetRawResponse();
Expand All @@ -108,34 +80,27 @@ public override ValueTask<Response<SecretProperties>> WaitForCompletionAsync(Can
public override ValueTask<Response<SecretProperties>> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellationToken) =>
this.DefaultWaitForCompletionAsync(pollingInterval, cancellationToken);

private async ValueTask<bool> CheckCompletedAsync(Response response)
async ValueTask<OperationState> IOperation.UpdateStateAsync(bool async, CancellationToken cancellationToken)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the secret was recovered.
return true;

case 404:
return false;
Response response = async
? await _pipeline.GetResponseAsync(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version).ConfigureAwait(false)
: _pipeline.GetResponse(RequestMethod.Get, cancellationToken, SecretClient.SecretsPath, _value.Name, "/", _value.Version);

default:
throw await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false);
}
}
private bool CheckCompleted(Response response)
{
switch (response.Status)
{
case 200:
case 403: // Access denied but proof the secret was recovered.
return true;
case 403: // Access denied but proof the secret was deleted.
return OperationState.Success(response);

case 404:
return false;
return OperationState.Pending(response);

default:
throw _pipeline.Diagnostics.CreateRequestFailedException(response);
RequestFailedException ex = async
? await _pipeline.Diagnostics.CreateRequestFailedExceptionAsync(response).ConfigureAwait(false)
: _pipeline.Diagnostics.CreateRequestFailedException(response);

return OperationState.Failure(response, ex);
}
}
}
Expand Down

0 comments on commit 197426a

Please sign in to comment.