Skip to content

Commit

Permalink
Custom Provider (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
derekmckinnon authored Dec 7, 2023
1 parent e3ebed6 commit 557a098
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 46 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v3
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json

- run: dotnet restore
- run: dotnet build --no-restore
- run: dotnet build --no-restore --configuration Release

- run: |
dotnet pack src/CatConsult.AppConfigConfigurationProvider \
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ jobs:
dotnet-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v3
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./global.json

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# AWS AppConfig Configuration Provider for .NET

An opinionated [.NET Configuration Provider](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers)
for AWS AppConfig that wraps [Amazon.Extensions.Configuration.SystemsManager](https://www.nuget.org/packages/Amazon.Extensions.Configuration.SystemsManager/).
An opinionated [.NET Configuration Provider](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration-providers) for AWS AppConfig.

## Usage

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using Amazon.AppConfigData;
using Amazon.AppConfigData.Model;

using CatConsult.ConfigurationParsers;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;

namespace CatConsult.AppConfigConfigurationProvider;

public sealed class AppConfigConfigurationProvider : ConfigurationProvider, IDisposable
{
private const int LockReleaseTimeout = 3_000;

private readonly IAmazonAppConfigData _client;
private readonly AppConfigProfile _profile;
private readonly SemaphoreSlim _lock;

private IDisposable? _reloadChangeToken;

public AppConfigConfigurationProvider(IAmazonAppConfigData client, AppConfigProfile profile)
{
_profile = profile;
_client = client;
_lock = new SemaphoreSlim(1, 1);
}

public AppConfigConfigurationProvider(AppConfigProfile profile) : this(new AmazonAppConfigDataClient(), profile) { }

private string? ConfigurationToken { get; set; }

private DateTimeOffset NextPollingTime { get; set; }

public override void Load()
{
LoadAsync().GetAwaiter().GetResult();

if (_profile.ReloadAfter.HasValue)
{
_reloadChangeToken = ChangeToken.OnChange(
() => new CancellationChangeToken(
new CancellationTokenSource(_profile.ReloadAfter.Value).Token
),
Load
);
}
}

private async Task LoadAsync()
{
if (!await _lock.WaitAsync(LockReleaseTimeout))
{
return;
}

try
{
if (DateTimeOffset.UtcNow < NextPollingTime)
{
return;
}

if (string.IsNullOrEmpty(ConfigurationToken))
{
await InitializeAppConfigSessionAsync();
}

var request = new GetLatestConfigurationRequest
{
ConfigurationToken = ConfigurationToken
};

var response = await _client.GetLatestConfigurationAsync(request);
ConfigurationToken = response.NextPollConfigurationToken;
NextPollingTime = DateTimeOffset.UtcNow.AddSeconds(response.NextPollIntervalInSeconds);

// If the remote configuration has changed, the API will send back data and we re-parse
if (response.ContentLength > 0)
{
Data = ParseConfig(response.Configuration, response.ContentType);
}
}
finally
{
_lock.Release();
}
}

private async Task InitializeAppConfigSessionAsync()
{
var session = await _client.StartConfigurationSessionAsync(new StartConfigurationSessionRequest
{
ApplicationIdentifier = _profile.ApplicationId,
EnvironmentIdentifier = _profile.EnvironmentId,
ConfigurationProfileIdentifier = _profile.ProfileId,
});

ConfigurationToken = session.InitialConfigurationToken;
}

private static IDictionary<string, string?> ParseConfig(Stream stream, string? contentType)
{
if (!string.IsNullOrEmpty(contentType))
{
contentType = contentType.Split(";")[0];
}

return contentType switch
{
"application/json" => JsonConfigurationParser.Parse(stream),
"application/x-yaml" => YamlConfigurationParser.Parse(stream),
_ => throw new FormatException($"This configuration provider does not support: {contentType ?? "Unknown"}")
};
}

public void Dispose() => _reloadChangeToken?.Dispose();
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,51 @@
using Amazon.AppConfigData;

using CatConsult.AppConfigConfigurationProvider.Utilities;

using Microsoft.Extensions.Configuration;

namespace CatConsult.AppConfigConfigurationProvider;

// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable MemberCanBePrivate.Global
public static class AppConfigConfigurationProviderExtensions
{
public const string DefaultSectionName = "AppConfig";

public static IConfigurationBuilder AddAppConfig(
this IConfigurationBuilder builder,
string sectionName = "AppConfig"
string sectionName = DefaultSectionName
)
{
var options = builder.Build()
.GetSection(sectionName)
.Get<AppConfigOptions>();

if (options is null)
foreach (var profile in LoadProfiles(builder, sectionName))
{
return builder;
builder.Add(new AppConfigConfigurationSource(profile));
}

var profiles = options.Profiles.Select(AppConfigProfileParser.Parse);
return builder;
}

foreach (var profile in profiles)
public static IConfigurationBuilder AddAppConfig(
this IConfigurationBuilder builder,
IAmazonAppConfigData client,
string sectionName = DefaultSectionName
)
{
foreach (var profile in LoadProfiles(builder, sectionName))
{
builder.AddAppConfig(
profile.ApplicationId,
profile.EnvironmentId,
profile.ProfileId,
TimeSpan.FromSeconds(profile.ReloadAfter ?? options.Defaults.ReloadAfter)
);
builder.Add(new AppConfigConfigurationSource(client, profile));
}

return builder;
}

private static IEnumerable<AppConfigProfile> LoadProfiles(IConfigurationBuilder builder, string sectionName)
{
var options = builder.Build()
.GetSection(sectionName)
.Get<AppConfigOptions>() ?? new AppConfigOptions();

return options.Profiles.Select(AppConfigProfileParser.Parse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Amazon.AppConfigData;

using Microsoft.Extensions.Configuration;

namespace CatConsult.AppConfigConfigurationProvider;

public sealed class AppConfigConfigurationSource : IConfigurationSource
{
private readonly AppConfigConfigurationProvider _provider;

public AppConfigConfigurationSource(IAmazonAppConfigData client, AppConfigProfile profile) =>
_provider = new AppConfigConfigurationProvider(client, profile);

public AppConfigConfigurationSource(AppConfigProfile profile) =>
_provider = new AppConfigConfigurationProvider(profile);

public IConfigurationProvider Build(IConfigurationBuilder builder) => _provider;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageTags>Configuration</PackageTags>

<Description>
An opinionated .NET Configuration Provider for AWS AppConfig that wraps Amazon.Extensions.Configuration.SystemsManager
An opinionated .NET Configuration Provider for AWS AppConfig
</Description>
<Authors>Derek McKinnon</Authors>
<Company>Catalyst Consulting Group, Inc.</Company>
Expand All @@ -22,7 +22,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Amazon.Extensions.Configuration.SystemsManager" Version="5.1.0" />
<PackageReference Include="AWSSDK.AppConfigData" Version="3.7.300.16" />
<PackageReference Include="CatConsult.ConfigurationParsers" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
Expand Down

This file was deleted.

Loading

0 comments on commit 557a098

Please sign in to comment.