Skip to content

Commit

Permalink
Introduce SecretsManagerPollingWatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
wdolek committed Jan 18, 2024
1 parent e3ced9f commit 3b0071d
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ builder.Configuration.AddSecretsManager(
"my-secret-secrets",
c =>
{
c.ConfigurationWatcher = new PeriodicConfigurationWatcher(TimeSpan.FromMinutes(5)); // implements `IConfigurationWatcher`
c.ConfigurationWatcher = new SecretsManagerPollingWatcher(TimeSpan.FromMinutes(5)); // implements `IConfigurationWatcher`
});
```

Expand Down
8 changes: 8 additions & 0 deletions W4k.Extensions.Configuration.Aws.SecretsManager/Activity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Diagnostics;

namespace W4k.Extensions.Configuration.Aws.SecretsManager;

internal static class Activity
{
public static ActivitySource Source { get; } = new("W4k.Extensions.Configuration.Aws.SecretsManager");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace W4k.Extensions.Configuration.Aws.SecretsManager;

/// <summary>
/// AWS Secrets Manager configuration watcher that polls for changes.
/// </summary>
public sealed class SecretsManagerPollingWatcher : IConfigurationWatcher, IDisposable, IAsyncDisposable
{
private readonly TimeSpan _interval;
private Timer? _timer;

/// <summary>
/// Initializes a new instance of the <see cref="SecretsManagerPollingWatcher"/> class.
/// </summary>
/// <param name="interval">Polling interval.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="interval"/> is less or equal to <see cref="TimeSpan.Zero"/>.</exception>
public SecretsManagerPollingWatcher(TimeSpan interval)
{
#if NET8_0_OR_GREATER
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(interval, TimeSpan.Zero);
#else
if (interval <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(interval));
}
#endif
_interval = interval;
}

/// <inheritdoc />
public void Dispose() => _timer?.Dispose();

/// <inheritdoc />
public async ValueTask DisposeAsync()
{
if (_timer != null)
{
await _timer.DisposeAsync();
}
}

/// <inheritdoc />
/// <exception cref="InvalidOperationException">Thrown when watcher is already started.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="refresher"/> is <see langword="null"/>.</exception>
public void Start(IConfigurationRefresher refresher)
{
ThrowIfStarted(_timer);
ArgumentNullException.ThrowIfNull(refresher);

_timer = new Timer(ExecuteRefresh, refresher, _interval, _interval);
}

[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument", Justification = "Explicitly passing activity name")]
private static void ExecuteRefresh(object? state)
{
using var activity = Activity.Source.StartActivity("Refresh");

var refresher = (IConfigurationRefresher)state!;
try
{
refresher.RefreshAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();

activity?.AddEvent(new ActivityEvent("Refresh completed"));
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch(Exception e)
{
activity?.AddEvent(new ActivityEvent("Refresh failed"));
activity?.SetTag("Error", e.Message);
activity?.SetStatus(ActivityStatusCode.Error);
}
}

private static void ThrowIfStarted(Timer? timer)
{
if (timer is not null)
{
ThrowInvalidOperationNotStarted();
}
}

[DoesNotReturn]
private static void ThrowInvalidOperationNotStarted() =>
throw new InvalidOperationException("Watcher is already started, have you re-used watcher instance?");
}

0 comments on commit 3b0071d

Please sign in to comment.