-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace NetEvolve.HealthChecks.AWS.SNS; | ||
|
||
public enum CreationMode | ||
Check warning on line 3 in src/NetEvolve.HealthChecks.AWS.SNS/CreationMode.cs
|
||
{ | ||
BasicAuthentication = 0, | ||
Check warning on line 5 in src/NetEvolve.HealthChecks.AWS.SNS/CreationMode.cs
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
namespace NetEvolve.HealthChecks.AWS.SNS; | ||
|
||
using System; | ||
using System.Diagnostics.CodeAnalysis; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using NetEvolve.Arguments; | ||
using NetEvolve.HealthChecks.Abstractions; | ||
|
||
/// <summary> | ||
/// Extensions methods for <see cref="IHealthChecksBuilder"/> with custom Health Checks. | ||
/// </summary> | ||
public static class DependencyInjectionExtensions | ||
{ | ||
private static readonly string[] _defaultTags = ["aws", "sns", "message-queue"]; | ||
|
||
/// <summary> | ||
/// Add a health check for AWS SimpleNotificationService (SNS). | ||
/// </summary> | ||
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param> | ||
/// <param name="name">The name of the <see cref="SimpleNotificationServiceHealthCheck"/>.</param> | ||
/// <param name="options">An optional action to configure.</param> | ||
/// <param name="tags">A list of additional tags that can be used to filter sets of health checks. Optional.</param> | ||
/// <exception cref="ArgumentNullException">The <paramref name="builder"/> is <see langword="null" />.</exception> | ||
/// <exception cref="ArgumentNullException">The <paramref name="name"/> is <see langword="null" />.</exception> | ||
/// <exception cref="ArgumentException">The <paramref name="name"/> is <see langword="null" /> or <c>whitespace</c>.</exception> | ||
/// <exception cref="ArgumentException">The <paramref name="name"/> is already in use.</exception> | ||
/// <exception cref="ArgumentNullException">The <paramref name="tags"/> is <see langword="null" />.</exception> | ||
public static IHealthChecksBuilder AddSimpleNotificationService( | ||
[NotNull] this IHealthChecksBuilder builder, | ||
[NotNull] string name, | ||
Action<SimpleNotificationServiceOptions>? options = null, | ||
params string[] tags | ||
) | ||
{ | ||
ArgumentNullException.ThrowIfNull(builder); | ||
Argument.ThrowIfNullOrEmpty(name); | ||
ArgumentNullException.ThrowIfNull(tags); | ||
|
||
if (!builder.IsServiceTypeRegistered<SimpleNotificationServiceCheckMarker>()) | ||
{ | ||
_ = builder | ||
.Services.AddSingleton<SimpleNotificationServiceCheckMarker>() | ||
.AddSingleton<SimpleNotificationServiceHealthCheck>() | ||
.ConfigureOptions<SimpleNotificationServiceConfigure>(); | ||
} | ||
|
||
if (builder.IsNameAlreadyUsed<SimpleNotificationServiceHealthCheck>(name)) | ||
{ | ||
throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null); | ||
} | ||
|
||
if (options is not null) | ||
{ | ||
_ = builder.Services.Configure(name, options); | ||
} | ||
|
||
return builder.AddCheck<SimpleNotificationServiceHealthCheck>( | ||
name, | ||
HealthStatus.Unhealthy, | ||
_defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase) | ||
); | ||
} | ||
|
||
private sealed partial class SimpleNotificationServiceCheckMarker { } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>$(_ProjectTargetFrameworks)</TargetFrameworks> | ||
|
||
<Description>Contains HealthChecks for AWS Simple Notification Service (SNS).</Description> | ||
<PackageTags>$(PackageTags);aws;sns;</PackageTags> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="AWSSDK.SimpleNotificationService" /> | ||
<PackageReference Include="NetEvolve.Extensions.Tasks" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\NetEvolve.HealthChecks.Abstractions\NetEvolve.HealthChecks.Abstractions.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
namespace NetEvolve.HealthChecks.AWS.SNS; | ||
|
||
using Microsoft.Extensions.Options; | ||
|
||
internal sealed class SimpleNotificationServiceConfigure | ||
: IConfigureNamedOptions<SimpleNotificationServiceOptions>, | ||
IValidateOptions<SimpleNotificationServiceOptions> | ||
{ | ||
public void Configure(string? name, SimpleNotificationServiceOptions options) { } | ||
|
||
public void Configure(SimpleNotificationServiceOptions options) { } | ||
|
||
public ValidateOptionsResult Validate(string? name, SimpleNotificationServiceOptions options) => | ||
ValidateOptionsResult.Success; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
namespace NetEvolve.HealthChecks.AWS.SNS; | ||
|
||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Amazon; | ||
using Amazon.SimpleNotificationService; | ||
using Amazon.SimpleNotificationService.Model; | ||
using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
using Microsoft.Extensions.Options; | ||
using NetEvolve.Extensions.Tasks; | ||
using NetEvolve.HealthChecks.Abstractions; | ||
|
||
internal sealed class SimpleNotificationServiceHealthCheck | ||
: ConfigurableHealthCheckBase<SimpleNotificationServiceOptions> | ||
{ | ||
public SimpleNotificationServiceHealthCheck( | ||
IOptionsMonitor<SimpleNotificationServiceOptions> optionsMonitor | ||
) | ||
: base(optionsMonitor) { } | ||
|
||
protected override async ValueTask<HealthCheckResult> ExecuteHealthCheckAsync( | ||
string name, | ||
HealthStatus failureStatus, | ||
SimpleNotificationServiceOptions options, | ||
CancellationToken cancellationToken | ||
) | ||
{ | ||
using var client = CreateClient(options); | ||
|
||
var (isValid, topic) = await client | ||
.GetSubscriptionAttributesAsync( | ||
new GetSubscriptionAttributesRequest { SubscriptionArn = options.TopicName }, | ||
cancellationToken | ||
) | ||
.WithTimeoutAsync(options.Timeout, cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
return HealthCheckState(isValid && topic is not null, name); | ||
} | ||
|
||
private static AmazonSimpleNotificationServiceClient CreateClient( | ||
SimpleNotificationServiceOptions options | ||
) | ||
{ | ||
var hasCredentials = options.GetCredentials() is not null; | ||
var hasEndpoint = options.GetRegionEndpoint() is not null; | ||
|
||
var config = new AmazonSimpleNotificationServiceConfig | ||
{ | ||
ServiceURL = options.ServiceUrl, | ||
RegionEndpoint = RegionEndpoint.USEast1, | ||
}; | ||
|
||
return (hasCredentials, hasEndpoint) switch | ||
{ | ||
(true, true) => new AmazonSimpleNotificationServiceClient( | ||
options.GetCredentials(), | ||
options.GetRegionEndpoint() | ||
), | ||
(true, false) => new AmazonSimpleNotificationServiceClient( | ||
options.GetCredentials(), | ||
config | ||
), | ||
(false, true) => new AmazonSimpleNotificationServiceClient(options.GetRegionEndpoint()), | ||
_ => throw new InvalidOperationException("Invalid ClientCreationMode."), | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
namespace NetEvolve.HealthChecks.AWS.SNS; | ||
|
||
using System; | ||
using Amazon; | ||
using Amazon.Runtime; | ||
|
||
public sealed class SimpleNotificationServiceOptions | ||
Check warning on line 7 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
{ | ||
public string? AccessKey { get; set; } | ||
Check warning on line 9 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
|
||
public CreationMode CreationMode { get; set; } | ||
Check warning on line 11 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
|
||
public string? SecretKey { get; set; } | ||
Check warning on line 13 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
|
||
#pragma warning disable CA1056 // URI-like properties should not be strings | ||
public string? ServiceUrl { get; set; } | ||
Check warning on line 16 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
#pragma warning restore CA1056 // URI-like properties should not be strings | ||
|
||
public string? TopicName { get; set; } | ||
Check warning on line 19 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
|
||
public int Timeout { get; set; } = 100; | ||
Check warning on line 21 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
|
||
internal AWSCredentials? GetCredentials() | ||
{ | ||
return CreationMode switch | ||
Check warning on line 25 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
{ | ||
CreationMode.BasicAuthentication => new BasicAWSCredentials(AccessKey, SecretKey), | ||
_ => null, | ||
}; | ||
} | ||
|
||
internal RegionEndpoint? GetRegionEndpoint() => CreationMode == CreationMode ? null : null; | ||
Check failure on line 32 in src/NetEvolve.HealthChecks.AWS.SNS/SimpleNotificationServiceOptions.cs
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
namespace NetEvolve.HealthChecks.Tests.Integration.AWS; | ||
|
||
using System.Threading.Tasks; | ||
using Testcontainers.LocalStack; | ||
using TestContainer = Testcontainers.LocalStack.LocalStackContainer; | ||
|
||
public sealed class LocalStackInstance : IAsyncLifetime | ||
{ | ||
/// <summary>Access Key</summary> | ||
/// <see href="https://docs.localstack.cloud/references/credentials/#access-key-id" /> | ||
internal const string AccessKey = "LSIAQAAAAAAVNCBMPNSG"; | ||
internal const string SecretKey = "test"; | ||
|
||
private readonly TestContainer _container = new LocalStackBuilder() | ||
.WithEnvironment("AWS_ACCESS_KEY_ID", AccessKey) | ||
.WithEnvironment("AWS_SECRET_ACCESS_KEY", SecretKey) | ||
.Build(); | ||
|
||
internal string ConnectionString => _container.GetConnectionString(); | ||
|
||
public async Task DisposeAsync() => await _container.DisposeAsync().ConfigureAwait(false); | ||
|
||
public async Task InitializeAsync() => await _container.StartAsync().ConfigureAwait(false); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
namespace NetEvolve.HealthChecks.Tests.Integration.AWS.SNS; | ||
|
||
using NetEvolve.HealthChecks.AWS.SNS; | ||
using NodaTime; | ||
|
||
public class SimpleNotificationServiceHealthCheckTests | ||
: HealthCheckTestBase, | ||
IClassFixture<LocalStackInstance> | ||
{ | ||
private readonly LocalStackInstance _instance; | ||
|
||
public SimpleNotificationServiceHealthCheckTests(LocalStackInstance instance) | ||
{ | ||
_instance = instance; | ||
} | ||
|
||
[Fact] | ||
public async Task AddSimpleNotificationService_UseOptionsCreate_ShouldReturnHealthy() => | ||
await RunAndVerify(healthChecks => | ||
{ | ||
_ = healthChecks.AddSimpleNotificationService( | ||
"TestContainerHealthy", | ||
options => | ||
{ | ||
options.AccessKey = LocalStackInstance.AccessKey; | ||
options.SecretKey = LocalStackInstance.SecretKey; | ||
options.ServiceUrl = _instance.ConnectionString; | ||
} | ||
); | ||
}); | ||
} |