Skip to content

Commit

Permalink
feat: Add OpenFeature.Extensions.Hosting package
Browse files Browse the repository at this point in the history
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <austin@austindrenski.io>
  • Loading branch information
austindrenski committed Jan 23, 2024
1 parent eba8848 commit 7fb289d
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 45 deletions.
47 changes: 2 additions & 45 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
7.0.x
- name: Run Tests
run: dotnet test test/OpenFeature.Tests/ --configuration Release --logger GitHubActions
run: dotnet test --logger GitHubActions

unit-tests-windows:
runs-on: windows-latest
Expand All @@ -43,47 +43,4 @@ jobs:
7.0.x
- name: Run Tests
run: dotnet test test\OpenFeature.Tests\ --configuration Release --logger GitHubActions

packaging:
needs:
- unit-tests-linux
- unit-tests-windows

permissions:
contents: read
packages: write

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
7.0.x
- name: Restore
run: dotnet restore

- name: Pack NuGet packages (CI versions)
if: startsWith(github.ref, 'refs/heads/')
run: dotnet pack --no-restore --version-suffix "ci.$(date -u +%Y%m%dT%H%M%S)+sha.${GITHUB_SHA:0:9}"

- name: Pack NuGet packages (PR versions)
if: startsWith(github.ref, 'refs/pull/')
run: dotnet pack --no-restore --version-suffix "pr.$(date -u +%Y%m%dT%H%M%S)+sha.${GITHUB_SHA:0:9}"

- name: Publish NuGet packages (base)
if: github.event.pull_request.head.repo.fork == false
run: dotnet nuget push "src/**/*.nupkg" --api-key "${{ secrets.GITHUB_TOKEN }}" --source https://nuget.pkg.github.com/open-feature/index.json

- name: Publish NuGet packages (fork)
if: github.event.pull_request.head.repo.fork == true
uses: actions/upload-artifact@v4.2.0
with:
name: nupkgs
path: src/**/*.nupkg
run: dotnet test --logger GitHubActions
14 changes: 14 additions & 0 deletions OpenFeature.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature", "src\OpenFeature\OpenFeature.csproj", "{07A6E6BD-FB7E-4B3B-9CBE-65AE9D0EB223}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Extensions.Hosting", "src\OpenFeature.Extensions.Hosting\OpenFeature.Extensions.Hosting.csproj", "{F2DB11D0-15E7-4C1F-936A-37D23EECECC0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{72005F60-C2E8-40BF-AE95-893635134D7D}"
ProjectSection(SolutionItems) = preProject
.github\workflows\code-coverage.yml = .github\workflows\code-coverage.yml
Expand Down Expand Up @@ -38,6 +40,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Benchmarks", "t
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.E2ETests", "test\OpenFeature.E2ETests\OpenFeature.E2ETests.csproj", "{90E7EAD3-251E-4490-AF78-E758E33518E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenFeature.Extensions.Hosting.Tests", "test\OpenFeature.Extensions.Hosting.Tests\OpenFeature.Extensions.Hosting.Tests.csproj", "{4588FE3C-EB7E-4BA5-BD77-E15131DB3B29}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -48,6 +52,10 @@ Global
{07A6E6BD-FB7E-4B3B-9CBE-65AE9D0EB223}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07A6E6BD-FB7E-4B3B-9CBE-65AE9D0EB223}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07A6E6BD-FB7E-4B3B-9CBE-65AE9D0EB223}.Release|Any CPU.Build.0 = Release|Any CPU
{F2DB11D0-15E7-4C1F-936A-37D23EECECC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2DB11D0-15E7-4C1F-936A-37D23EECECC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2DB11D0-15E7-4C1F-936A-37D23EECECC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2DB11D0-15E7-4C1F-936A-37D23EECECC0}.Release|Any CPU.Build.0 = Release|Any CPU
{49BB42BA-10A6-4DA3-A7D5-38C968D57837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49BB42BA-10A6-4DA3-A7D5-38C968D57837}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49BB42BA-10A6-4DA3-A7D5-38C968D57837}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -56,14 +64,20 @@ Global
{90E7EAD3-251E-4490-AF78-E758E33518E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90E7EAD3-251E-4490-AF78-E758E33518E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90E7EAD3-251E-4490-AF78-E758E33518E5}.Release|Any CPU.Build.0 = Release|Any CPU
{4588FE3C-EB7E-4BA5-BD77-E15131DB3B29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4588FE3C-EB7E-4BA5-BD77-E15131DB3B29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4588FE3C-EB7E-4BA5-BD77-E15131DB3B29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4588FE3C-EB7E-4BA5-BD77-E15131DB3B29}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{07A6E6BD-FB7E-4B3B-9CBE-65AE9D0EB223} = {C97E9975-E10A-4817-AE2C-4DD69C3C02D4}
{F2DB11D0-15E7-4C1F-936A-37D23EECECC0} = {C97E9975-E10A-4817-AE2C-4DD69C3C02D4}
{49BB42BA-10A6-4DA3-A7D5-38C968D57837} = {65FBA159-23E0-4CF9-881B-F78DBFF198E9}
{90E7EAD3-251E-4490-AF78-E758E33518E5} = {65FBA159-23E0-4CF9-881B-F78DBFF198E9}
{4588FE3C-EB7E-4BA5-BD77-E15131DB3B29} = {65FBA159-23E0-4CF9-881B-F78DBFF198E9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {41F01B78-FB06-404F-8AD0-6ED6973F948F}
Expand Down
3 changes: 3 additions & 0 deletions build/Common.prod.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@
<None Include="$(MSBuildThisFileDirectory)openfeature-icon.png" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)../src/Shared/**" LinkBase="Shared" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions build/Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
Refer to https://docs.microsoft.com/nuget/concepts/package-versioning for semver syntax.
-->
<MicrosoftBclAsyncInterfacesVer>[8.0.0,)</MicrosoftBclAsyncInterfacesVer>
<MicrosoftExtensionsHostingAbstractionsVer>[8.0.0,)</MicrosoftExtensionsHostingAbstractionsVer>
<MicrosoftExtensionsLoggerVer>[2.0,)</MicrosoftExtensionsLoggerVer>
</PropertyGroup>

Expand Down
1 change: 1 addition & 0 deletions build/Common.tests.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<CoverletCollectorVer>[3.1.2]</CoverletCollectorVer>
<FluentAssertionsVer>[6.7.0]</FluentAssertionsVer>
<GitHubActionsTestLoggerVer>[2.3.3]</GitHubActionsTestLoggerVer>
<MicrosoftExtensionsHostingVer>[8.0.0]</MicrosoftExtensionsHostingVer>
<MicrosoftNETTestSdkPkgVer>[17.2.0]</MicrosoftNETTestSdkPkgVer>
<NSubstituteVer>[5.0.0]</NSubstituteVer>
<XUnitRunnerVisualStudioPkgVer>[2.4.3,3.0)</XUnitRunnerVisualStudioPkgVer>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;

namespace OpenFeature.Internal;

/// <summary>
///
/// </summary>
public sealed class OpenFeatureHostedService(Api api, IEnumerable<FeatureProvider> providers) : IHostedLifecycleService
{
readonly Api _api = Check.NotNull(api);
readonly IEnumerable<FeatureProvider> _providers = Check.NotNull(providers);

async Task IHostedLifecycleService.StartingAsync(CancellationToken cancellationToken)
{
foreach (var provider in this._providers)
{
await this._api.SetProviderAsync(provider.GetMetadata().Name, provider).ConfigureAwait(false);

if (this._api.GetProviderMetadata() is { Name: "No-op Provider" })
await this._api.SetProviderAsync(provider).ConfigureAwait(false);
}
}

Task IHostedService.StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;

Task IHostedLifecycleService.StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;

Task IHostedLifecycleService.StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask;

Task IHostedService.StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

Task IHostedLifecycleService.StoppedAsync(CancellationToken cancellationToken) => this._api.Shutdown();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Nullable>enable</Nullable>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0;net462</TargetFrameworks>
</PropertyGroup>

<PropertyGroup>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RootNamespace>OpenFeature</RootNamespace>
</PropertyGroup>

<ItemGroup>
<None Include="../../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftExtensionsHostingAbstractionsVer)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../OpenFeature/OpenFeature.csproj" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions src/OpenFeature.Extensions.Hosting/OpenFeatureBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.Extensions.DependencyInjection;

namespace OpenFeature;

/// <summary>
///
/// </summary>
/// <param name="Services"></param>
public sealed record OpenFeatureBuilder(IServiceCollection Services);
145 changes: 145 additions & 0 deletions src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using OpenFeature.Internal;
using OpenFeature.Model;

namespace OpenFeature;

/// <summary>
///
/// </summary>
public static class OpenFeatureBuilderExtensions
{
/// <summary>
///
/// </summary>
/// <param name="builder"></param>
/// <param name="configure"></param>
/// <returns>
///
/// </returns>
public static OpenFeatureBuilder AddContext(
this OpenFeatureBuilder builder,
Action<EvaluationContextBuilder> configure)
{
Check.NotNull(builder);
Check.NotNull(configure);

Check warning on line 28 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L26-L28

Added lines #L26 - L28 were not covered by tests

AddContext(builder, null, (b, _, _) => configure(b));

Check warning on line 30 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L30

Added line #L30 was not covered by tests

return builder;
}

Check warning on line 33 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L32-L33

Added lines #L32 - L33 were not covered by tests

/// <summary>
///
/// </summary>
/// <param name="builder"></param>
/// <param name="configure"></param>
/// <returns>
///
/// </returns>
public static OpenFeatureBuilder AddContext(
this OpenFeatureBuilder builder,
Action<EvaluationContextBuilder, IServiceProvider> configure)
{
Check.NotNull(builder);
Check.NotNull(configure);

Check warning on line 48 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L46-L48

Added lines #L46 - L48 were not covered by tests

AddContext(builder, null, (b, _, s) => configure(b, s));

Check warning on line 50 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L50

Added line #L50 was not covered by tests

return builder;
}

Check warning on line 53 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L52-L53

Added lines #L52 - L53 were not covered by tests

/// <summary>
///
/// </summary>
/// <param name="builder"></param>
/// <param name="providerName"></param>
/// <param name="configure"></param>
/// <returns>
///
/// </returns>
public static OpenFeatureBuilder AddContext(
this OpenFeatureBuilder builder,
string? providerName,
Action<EvaluationContextBuilder, string?, IServiceProvider> configure)
{
Check.NotNull(builder);
Check.NotNull(configure);

Check warning on line 70 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L68-L70

Added lines #L68 - L70 were not covered by tests

builder.Services.AddKeyedSingleton(providerName, (services, key) =>
{
var b = EvaluationContext.Builder();
configure(b, key as string, services);
return b.Build();
});

Check warning on line 79 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L72-L79

Added lines #L72 - L79 were not covered by tests

return builder;
}

Check warning on line 82 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L81-L82

Added lines #L81 - L82 were not covered by tests

/// <summary>
///
/// </summary>
/// <param name="builder"></param>
/// <param name="providerName"></param>
public static void TryAddOpenFeatureClient(this OpenFeatureBuilder builder, string? providerName = null)
{
Check.NotNull(builder);

builder.Services.AddHostedService<OpenFeatureHostedService>();

builder.Services.TryAddKeyedSingleton(providerName, static (services, providerName) =>
{
var api = providerName switch
{
null => Api.Instance,
not null => services.GetRequiredKeyedService<Api>(null)

Check warning on line 100 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L100

Added line #L100 was not covered by tests
};
api.AddHooks(services.GetKeyedServices<Hook>(providerName));
api.SetContext(services.GetRequiredKeyedService<EvaluationContextBuilder>(providerName).Build());
return api;
});

builder.Services.TryAddKeyedSingleton(providerName, static (services, providerName) => providerName switch
{
null => services.GetRequiredService<ILogger<FeatureClient>>(),
not null => services.GetRequiredService<ILoggerFactory>().CreateLogger($"OpenFeature.FeatureClient.{providerName}")
});

builder.Services.TryAddKeyedTransient(providerName, static (services, providerName) =>
{
var builder = providerName switch
{
null => EvaluationContext.Builder(),
not null => services.GetRequiredKeyedService<EvaluationContextBuilder>(null)
};
foreach (var c in services.GetKeyedServices<EvaluationContext>(providerName))
{
builder.Merge(c);
}

Check warning on line 126 in src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/OpenFeature.Extensions.Hosting/OpenFeatureBuilderExtensions.cs#L124-L126

Added lines #L124 - L126 were not covered by tests
return builder;
});

builder.Services.TryAddKeyedTransient<IFeatureClient>(providerName, static (services, providerName) =>
{
var api = services.GetRequiredService<Api>();
return api.GetClient(
api.GetProviderMetadata(providerName as string).Name,
null,
services.GetRequiredKeyedService<ILogger>(providerName),
services.GetRequiredKeyedService<EvaluationContextBuilder>(providerName).Build());
});

if (providerName is not null)
builder.Services.Replace(ServiceDescriptor.Transient(services => services.GetRequiredKeyedService<IFeatureClient>(providerName)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using OpenFeature;

#pragma warning disable IDE0130 // Namespace does not match folder structure
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
///
/// </summary>
public static class OpenFeatureServiceCollectionExtensions
{
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <param name="configure"></param>
/// <returns></returns>
public static IServiceCollection AddOpenFeature(this IServiceCollection services, Action<OpenFeatureBuilder> configure)
{
Check.NotNull(services);
Check.NotNull(configure);

configure(AddOpenFeature(services));

return services;
}

/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static OpenFeatureBuilder AddOpenFeature(this IServiceCollection services)
{
Check.NotNull(services);

var builder = new OpenFeatureBuilder(services);

builder.TryAddOpenFeatureClient();

return builder;
}
}
Loading

0 comments on commit 7fb289d

Please sign in to comment.