Skip to content

Commit 1ef7589

Browse files
committed
feat: Added AWS SNS package (#573)
1 parent 6bd40b8 commit 1ef7589

File tree

30 files changed

+924
-2
lines changed

30 files changed

+924
-2
lines changed

Directory.Packages.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
/>
2020
</ItemGroup>
2121
<ItemGroup>
22+
<PackageVersion Include="Apache.NMS.ActiveMQ" Version="2.1.0" />
23+
<PackageVersion Include="AWSSDK.SimpleNotificationService" Version="3.7.400.65" />
2224
<PackageVersion Include="Azure.Data.Tables" Version="12.10.0" />
2325
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
2426
<PackageVersion Include="Azure.Storage.Blobs" Version="12.24.0" />
@@ -45,9 +47,11 @@
4547
<PackageVersion Include="StackExchange.Redis" Version="2.8.31" />
4648
<PackageVersion Include="System.Data.SqlClient" Version="4.9.0" />
4749
<PackageVersion Include="System.Text.Json" Version="9.0.4" />
50+
<PackageVersion Include="Testcontainers.ActiveMq" Version="4.1.0" />
4851
<PackageVersion Include="Testcontainers.Azurite" Version="4.4.0" />
4952
<PackageVersion Include="Testcontainers.ClickHouse" Version="4.4.0" />
5053
<PackageVersion Include="Testcontainers.Kafka" Version="4.4.0" />
54+
<PackageVersion Include="Testcontainers.LocalStack" Version="4.4.0" />
5155
<PackageVersion Include="Testcontainers.MsSql" Version="4.4.0" />
5256
<PackageVersion Include="Testcontainers.MySql" Version="4.4.0" />
5357
<PackageVersion Include="Testcontainers.Oracle" Version="4.4.0" />

HealthChecks.sln

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Test
6565
EndProject
6666
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Tests.Architecture", "tests\NetEvolve.HealthChecks.Tests.Architecture\NetEvolve.HealthChecks.Tests.Architecture.csproj", "{17BCA132-1FBB-46C1-B6A1-60F64969383D}"
6767
EndProject
68+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.Apache.ActiveMq", "src\NetEvolve.HealthChecks.Apache.ActiveMq\NetEvolve.HealthChecks.Apache.ActiveMq.csproj", "{1F26E911-E73F-409A-87FC-1214EF36FF61}"
69+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.AWS.SNS", "src\NetEvolve.HealthChecks.AWS.SNS\NetEvolve.HealthChecks.AWS.SNS.csproj", "{546D5904-1811-457F-8C48-3F78D7F0C803}"
70+
EndProject
6871
Global
6972
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7073
Debug|Any CPU = Debug|Any CPU
@@ -315,6 +318,30 @@ Global
315318
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x64.Build.0 = Release|Any CPU
316319
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x86.ActiveCfg = Release|Any CPU
317320
{17BCA132-1FBB-46C1-B6A1-60F64969383D}.Release|x86.Build.0 = Release|Any CPU
321+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
322+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|Any CPU.Build.0 = Debug|Any CPU
323+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x64.ActiveCfg = Debug|Any CPU
324+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x64.Build.0 = Debug|Any CPU
325+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x86.ActiveCfg = Debug|Any CPU
326+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Debug|x86.Build.0 = Debug|Any CPU
327+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|Any CPU.ActiveCfg = Release|Any CPU
328+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|Any CPU.Build.0 = Release|Any CPU
329+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x64.ActiveCfg = Release|Any CPU
330+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x64.Build.0 = Release|Any CPU
331+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x86.ActiveCfg = Release|Any CPU
332+
{1F26E911-E73F-409A-87FC-1214EF36FF61}.Release|x86.Build.0 = Release|Any CPU
333+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
334+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|Any CPU.Build.0 = Debug|Any CPU
335+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x64.ActiveCfg = Debug|Any CPU
336+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x64.Build.0 = Debug|Any CPU
337+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x86.ActiveCfg = Debug|Any CPU
338+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Debug|x86.Build.0 = Debug|Any CPU
339+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|Any CPU.ActiveCfg = Release|Any CPU
340+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|Any CPU.Build.0 = Release|Any CPU
341+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x64.ActiveCfg = Release|Any CPU
342+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x64.Build.0 = Release|Any CPU
343+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x86.ActiveCfg = Release|Any CPU
344+
{546D5904-1811-457F-8C48-3F78D7F0C803}.Release|x86.Build.0 = Release|Any CPU
318345
EndGlobalSection
319346
GlobalSection(SolutionProperties) = preSolution
320347
HideSolutionNode = FALSE
@@ -340,6 +367,8 @@ Global
340367
{66406BE8-0281-4C95-B90B-20CAE4516A16} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
341368
{2B089420-E791-44E7-B471-F6F527B33E1C} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
342369
{17BCA132-1FBB-46C1-B6A1-60F64969383D} = {E412EC77-2022-4A1D-AAC1-FDF1A4A45827}
370+
{1F26E911-E73F-409A-87FC-1214EF36FF61} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
371+
{546D5904-1811-457F-8C48-3F78D7F0C803} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
343372
EndGlobalSection
344373
GlobalSection(ExtensibilityGlobals) = postSolution
345374
SolutionGuid = {28B4CC2B-39E8-49C0-9687-78121BD83A53}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace NetEvolve.HealthChecks.AWS.SNS;
2+
3+
public enum CreationMode
4+
{
5+
BasicAuthentication = 0,
6+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
namespace NetEvolve.HealthChecks.AWS.SNS;
2+
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Diagnostics.HealthChecks;
7+
using NetEvolve.Arguments;
8+
using NetEvolve.HealthChecks.Abstractions;
9+
10+
/// <summary>
11+
/// Extensions methods for <see cref="IHealthChecksBuilder"/> with custom Health Checks.
12+
/// </summary>
13+
public static class DependencyInjectionExtensions
14+
{
15+
private static readonly string[] _defaultTags = ["aws", "sns", "message-queue"];
16+
17+
/// <summary>
18+
/// Add a health check for AWS SimpleNotificationService (SNS).
19+
/// </summary>
20+
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
21+
/// <param name="name">The name of the <see cref="SimpleNotificationServiceHealthCheck"/>.</param>
22+
/// <param name="options">An optional action to configure.</param>
23+
/// <param name="tags">A list of additional tags that can be used to filter sets of health checks. Optional.</param>
24+
/// <exception cref="ArgumentNullException">The <paramref name="builder"/> is <see langword="null" />.</exception>
25+
/// <exception cref="ArgumentNullException">The <paramref name="name"/> is <see langword="null" />.</exception>
26+
/// <exception cref="ArgumentException">The <paramref name="name"/> is <see langword="null" /> or <c>whitespace</c>.</exception>
27+
/// <exception cref="ArgumentException">The <paramref name="name"/> is already in use.</exception>
28+
/// <exception cref="ArgumentNullException">The <paramref name="tags"/> is <see langword="null" />.</exception>
29+
public static IHealthChecksBuilder AddSimpleNotificationService(
30+
[NotNull] this IHealthChecksBuilder builder,
31+
[NotNull] string name,
32+
Action<SimpleNotificationServiceOptions>? options = null,
33+
params string[] tags
34+
)
35+
{
36+
ArgumentNullException.ThrowIfNull(builder);
37+
Argument.ThrowIfNullOrEmpty(name);
38+
ArgumentNullException.ThrowIfNull(tags);
39+
40+
if (!builder.IsServiceTypeRegistered<SimpleNotificationServiceCheckMarker>())
41+
{
42+
_ = builder
43+
.Services.AddSingleton<SimpleNotificationServiceCheckMarker>()
44+
.AddSingleton<SimpleNotificationServiceHealthCheck>()
45+
.ConfigureOptions<SimpleNotificationServiceConfigure>();
46+
}
47+
48+
if (builder.IsNameAlreadyUsed<SimpleNotificationServiceHealthCheck>(name))
49+
{
50+
throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null);
51+
}
52+
53+
if (options is not null)
54+
{
55+
_ = builder.Services.Configure(name, options);
56+
}
57+
58+
return builder.AddCheck<SimpleNotificationServiceHealthCheck>(
59+
name,
60+
HealthStatus.Unhealthy,
61+
_defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase)
62+
);
63+
}
64+
65+
private sealed partial class SimpleNotificationServiceCheckMarker { }
66+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>$(_ProjectTargetFrameworks)</TargetFrameworks>
5+
6+
<Description>Contains HealthChecks for AWS Simple Notification Service (SNS).</Description>
7+
<PackageTags>$(PackageTags);aws;sns;</PackageTags>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="AWSSDK.SimpleNotificationService" />
12+
<PackageReference Include="NetEvolve.Extensions.Tasks" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\NetEvolve.HealthChecks.Abstractions\NetEvolve.HealthChecks.Abstractions.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace NetEvolve.HealthChecks.AWS.SNS;
2+
3+
using Microsoft.Extensions.Options;
4+
5+
internal sealed class SimpleNotificationServiceConfigure
6+
: IConfigureNamedOptions<SimpleNotificationServiceOptions>,
7+
IValidateOptions<SimpleNotificationServiceOptions>
8+
{
9+
public void Configure(string? name, SimpleNotificationServiceOptions options) { }
10+
11+
public void Configure(SimpleNotificationServiceOptions options) { }
12+
13+
public ValidateOptionsResult Validate(string? name, SimpleNotificationServiceOptions options) =>
14+
ValidateOptionsResult.Success;
15+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
namespace NetEvolve.HealthChecks.AWS.SNS;
2+
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Amazon;
7+
using Amazon.SimpleNotificationService;
8+
using Amazon.SimpleNotificationService.Model;
9+
using Microsoft.Extensions.Diagnostics.HealthChecks;
10+
using Microsoft.Extensions.Options;
11+
using NetEvolve.Extensions.Tasks;
12+
using NetEvolve.HealthChecks.Abstractions;
13+
14+
internal sealed class SimpleNotificationServiceHealthCheck
15+
: ConfigurableHealthCheckBase<SimpleNotificationServiceOptions>
16+
{
17+
public SimpleNotificationServiceHealthCheck(
18+
IOptionsMonitor<SimpleNotificationServiceOptions> optionsMonitor
19+
)
20+
: base(optionsMonitor) { }
21+
22+
protected override async ValueTask<HealthCheckResult> ExecuteHealthCheckAsync(
23+
string name,
24+
HealthStatus failureStatus,
25+
SimpleNotificationServiceOptions options,
26+
CancellationToken cancellationToken
27+
)
28+
{
29+
using var client = CreateClient(options);
30+
31+
var (isValid, topic) = await client
32+
.GetSubscriptionAttributesAsync(
33+
new GetSubscriptionAttributesRequest { SubscriptionArn = options.TopicName },
34+
cancellationToken
35+
)
36+
.WithTimeoutAsync(options.Timeout, cancellationToken)
37+
.ConfigureAwait(false);
38+
39+
return HealthCheckState(isValid && topic is not null, name);
40+
}
41+
42+
private static AmazonSimpleNotificationServiceClient CreateClient(
43+
SimpleNotificationServiceOptions options
44+
)
45+
{
46+
var hasCredentials = options.GetCredentials() is not null;
47+
var hasEndpoint = options.GetRegionEndpoint() is not null;
48+
49+
var config = new AmazonSimpleNotificationServiceConfig
50+
{
51+
ServiceURL = options.ServiceUrl,
52+
RegionEndpoint = RegionEndpoint.USEast1,
53+
};
54+
55+
return (hasCredentials, hasEndpoint) switch
56+
{
57+
(true, true) => new AmazonSimpleNotificationServiceClient(
58+
options.GetCredentials(),
59+
options.GetRegionEndpoint()
60+
),
61+
(true, false) => new AmazonSimpleNotificationServiceClient(
62+
options.GetCredentials(),
63+
config
64+
),
65+
(false, true) => new AmazonSimpleNotificationServiceClient(options.GetRegionEndpoint()),
66+
_ => throw new InvalidOperationException("Invalid ClientCreationMode."),
67+
};
68+
}
69+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace NetEvolve.HealthChecks.AWS.SNS;
2+
3+
using System;
4+
using Amazon;
5+
using Amazon.Runtime;
6+
7+
public sealed class SimpleNotificationServiceOptions
8+
{
9+
public string? AccessKey { get; set; }
10+
11+
public CreationMode CreationMode { get; set; }
12+
13+
public string? SecretKey { get; set; }
14+
15+
#pragma warning disable CA1056 // URI-like properties should not be strings
16+
public string? ServiceUrl { get; set; }
17+
#pragma warning restore CA1056 // URI-like properties should not be strings
18+
19+
public string? TopicName { get; set; }
20+
21+
public int Timeout { get; set; } = 100;
22+
23+
internal AWSCredentials? GetCredentials()
24+
{
25+
return CreationMode switch
26+
{
27+
CreationMode.BasicAuthentication => new BasicAWSCredentials(AccessKey, SecretKey),
28+
_ => null,
29+
};
30+
}
31+
32+
internal RegionEndpoint? GetRegionEndpoint() => CreationMode == CreationMode ? null : null;
33+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace NetEvolve.HealthChecks.Apache.ActiveMq;
2+
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.Options;
5+
using NetEvolve.Arguments;
6+
using static Microsoft.Extensions.Options.ValidateOptionsResult;
7+
8+
internal sealed class ActiveMqConfigure
9+
: IConfigureNamedOptions<ActiveMqOptions>,
10+
IValidateOptions<ActiveMqOptions>
11+
{
12+
private readonly IConfiguration _configuration;
13+
14+
public ActiveMqConfigure(IConfiguration configuration) => _configuration = configuration;
15+
16+
public void Configure(string? name, ActiveMqOptions options)
17+
{
18+
Argument.ThrowIfNullOrWhiteSpace(name);
19+
_configuration.Bind($"HealthChecks:ActiveMq:{name}", options);
20+
}
21+
22+
public void Configure(ActiveMqOptions options) => Configure(Options.DefaultName, options);
23+
24+
public ValidateOptionsResult Validate(string? name, ActiveMqOptions options)
25+
{
26+
if (string.IsNullOrWhiteSpace(name))
27+
{
28+
return Fail("The name cannot be null or whitespace.");
29+
}
30+
31+
if (options is null)
32+
{
33+
return Fail("The option cannot be null.");
34+
}
35+
36+
if (string.IsNullOrWhiteSpace(options.BrokerAddress))
37+
{
38+
return Fail("The broker address cannot be null or whitespace.");
39+
}
40+
41+
return Success;
42+
}
43+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace NetEvolve.HealthChecks.Apache.ActiveMq;
2+
3+
using System.Collections.Concurrent;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using global::Apache.NMS;
7+
using global::Apache.NMS.ActiveMQ;
8+
using Microsoft.Extensions.Diagnostics.HealthChecks;
9+
using Microsoft.Extensions.Options;
10+
using NetEvolve.Extensions.Tasks;
11+
using NetEvolve.HealthChecks.Abstractions;
12+
13+
internal sealed class ActiveMqHealthCheck : ConfigurableHealthCheckBase<ActiveMqOptions>
14+
{
15+
public ActiveMqHealthCheck(IOptionsMonitor<ActiveMqOptions> optionsMonitor)
16+
: base(optionsMonitor) { }
17+
18+
protected override async ValueTask<HealthCheckResult> ExecuteHealthCheckAsync(
19+
string name,
20+
HealthStatus failureStatus,
21+
ActiveMqOptions options,
22+
CancellationToken cancellationToken
23+
)
24+
{
25+
using var client = await ClientFactory
26+
.GetConnectionAsync(options, cancellationToken)
27+
.ConfigureAwait(false);
28+
29+
var isValid = await client
30+
.StartAsync()
31+
.WithTimeoutAsync(options.Timeout, cancellationToken)
32+
.ConfigureAwait(false);
33+
34+
return HealthCheckState(client.IsStarted && isValid, name);
35+
}
36+
}

0 commit comments

Comments
 (0)