Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Add support for feature flags #2620

Merged
merged 14 commits into from
Dec 5, 2022
3 changes: 3 additions & 0 deletions src/ApiService/ApiService/ApiService.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.0.0-preview3" />
<PackageReference Include="Semver" Version="2.1.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="5.1.0" />
<PackageReference Include="Microsoft.FeatureManagement" Version="2.5.1" />
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.EventGrid" Version="2.1.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
Expand Down
5 changes: 5 additions & 0 deletions src/ApiService/ApiService/FeatureFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Microsoft.OneFuzz.Service;

public static class FeatureFlagConstants {
public const string EnableScribanOnly = "EnableScribanOnly";
}
1 change: 1 addition & 0 deletions src/ApiService/ApiService/Functions/AgentCanSchedule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class AgentCanSchedule {
private readonly IEndpointAuthorization _auth;
private readonly IOnefuzzContext _context;


public AgentCanSchedule(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
_log = log;
_auth = auth;
Expand Down
11 changes: 11 additions & 0 deletions src/ApiService/ApiService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
using Microsoft.ApplicationInsights.DependencyCollector;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.FeatureManagement;
using Microsoft.Graph;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;

Expand Down Expand Up @@ -47,13 +49,22 @@ public static async Async.Task Main() {

using var host =
new HostBuilder()
.ConfigureAppConfiguration(builder => {
var _ = builder.AddAzureAppConfiguration(options => {
var _ = options
.Connect(new Uri(configuration.AppConfigurationEndpoint!), new DefaultAzureCredential())
.UseFeatureFlags(ffOptions => ffOptions.CacheExpirationInterval = TimeSpan.FromMinutes(1));
});
})
.ConfigureFunctionsWorkerDefaults(builder => {
builder.UseMiddleware<LoggingMiddleware>();
builder.AddApplicationInsights(options => {
options.ConnectionString = $"InstrumentationKey={configuration.ApplicationInsightsInstrumentationKey}";
});
})
.ConfigureServices((context, services) => {
services.AddAzureAppConfiguration();
tevoinea marked this conversation as resolved.
Show resolved Hide resolved
_ = services.AddFeatureManagement();
services.Configure<JsonSerializerOptions>(options => {
options = EntityConverter.GetJsonSerializerOptions();
});
Expand Down
3 changes: 3 additions & 0 deletions src/ApiService/ApiService/ServiceConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface IServiceConfig {

public string? ApplicationInsightsAppId { get; }
public string? ApplicationInsightsInstrumentationKey { get; }
public string? AppConfigurationEndpoint { get; }
public string? AzureSignalRConnectionString { get; }
public string? AzureSignalRServiceTransportType { get; }

Expand Down Expand Up @@ -82,6 +83,8 @@ public ServiceConfiguration() {
public string? ApplicationInsightsAppId => GetEnv("APPINSIGHTS_APPID");
public string? ApplicationInsightsInstrumentationKey => GetEnv("APPINSIGHTS_INSTRUMENTATIONKEY");

public string? AppConfigurationEndpoint => GetEnv("APPCONFIGURATION_ENDPOINT");
tevoinea marked this conversation as resolved.
Show resolved Hide resolved

public string? AzureSignalRConnectionString => GetEnv("AzureSignalRConnectionString");
public string? AzureSignalRServiceTransportType => GetEnv("AzureSignalRServiceTransportType");

Expand Down
10 changes: 10 additions & 0 deletions src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;


namespace Microsoft.OneFuzz.Service;

using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.FeatureManagement;

public interface IOnefuzzContext {
IAutoScaleOperations AutoScaleOperations { get; }
Expand Down Expand Up @@ -46,6 +49,9 @@ public interface IOnefuzzContext {
ITeams Teams { get; }
IGithubIssues GithubIssues { get; }
IAdo Ado { get; }

IFeatureManagerSnapshot FeatureManagerSnapshot { get; }
IConfigurationRefresher ConfigurationRefresher { get; }
}

public class OnefuzzContext : IOnefuzzContext {
Expand Down Expand Up @@ -95,4 +101,8 @@ public OnefuzzContext(IServiceProvider serviceProvider) {
public ITeams Teams => _serviceProvider.GetRequiredService<ITeams>();
public IGithubIssues GithubIssues => _serviceProvider.GetRequiredService<IGithubIssues>();
public IAdo Ado => _serviceProvider.GetRequiredService<IAdo>();

public IFeatureManagerSnapshot FeatureManagerSnapshot => _serviceProvider.GetRequiredService<IFeatureManagerSnapshot>();

public IConfigurationRefresher ConfigurationRefresher => _serviceProvider.GetRequiredService<IConfigurationRefresherProvider>().Refreshers.First();
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ protected class Renderer {
private readonly Uri _targetUrl;
private readonly Uri _inputUrl;
private readonly Uri _reportUrl;
private readonly bool _scribanOnly;

public static async Async.Task<Renderer> ConstructRenderer(
IOnefuzzContext context,
Expand Down Expand Up @@ -66,6 +67,9 @@ public static async Async.Task<Renderer> ConstructRenderer(
inputUrl = new Uri(context.Containers.AuthDownloadUrl(report.InputBlob.Container, report.InputBlob.Name));
}

await context.ConfigurationRefresher.TryRefreshAsync().IgnoreResult();
var scribanOnly = await context.FeatureManagerSnapshot.IsEnabledAsync(FeatureFlagConstants.EnableScribanOnly);

return new Renderer(
container,
filename,
Expand All @@ -74,7 +78,8 @@ public static async Async.Task<Renderer> ConstructRenderer(
checkedJob,
targetUrl,
inputUrl!, // TODO: incorrect
reportUrl);
reportUrl,
scribanOnly);
}
public Renderer(
Container container,
Expand All @@ -84,7 +89,8 @@ public Renderer(
Job job,
Uri targetUrl,
Uri inputUrl,
Uri reportUrl) {
Uri reportUrl,
bool scribanOnly) {
_report = report;
_container = container;
_filename = filename;
Expand All @@ -93,13 +99,17 @@ public Renderer(
_reportUrl = reportUrl;
_targetUrl = targetUrl;
_inputUrl = inputUrl;
_scribanOnly = scribanOnly;
}

// TODO: This function is fallible but the python
// implementation doesn't have that so I'm trying to match it.
// We should probably propagate any errors up
public async Async.Task<string> Render(string templateString, Uri instanceUrl) {
templateString = JinjaTemplateAdapter.IsJinjaTemplate(templateString) ? JinjaTemplateAdapter.AdaptForScriban(templateString) : templateString;
if (!_scribanOnly && JinjaTemplateAdapter.IsJinjaTemplate(templateString)) {
templateString = JinjaTemplateAdapter.AdaptForScriban(templateString);
}

var template = Template.Parse(templateString);
if (template != null) {
return await template.RenderAsync(new {
Expand Down
93 changes: 93 additions & 0 deletions src/ApiService/ApiService/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@
"resolved": "0.12.2",
"contentHash": "JgMAGj8ekeAzKkagubXqf1UqgfHq89GyA1UQYWbkAe441uRr2Rh2rktkx5Z0LPwmD/aOqu9cxjekD2GZjP8rbw=="
},
"Microsoft.Azure.Functions.Extensions": {
"type": "Direct",
"requested": "[1.1.0, )",
"resolved": "1.1.0",
"contentHash": "zYKtQQoS1fdzufxFApuMFiFtoi9QAGH6McXxntpylwLKgKjmCMWdgUd1dcekzTKNR9DPSDPRLiulvukqXnpWrQ==",
"dependencies": {
"Microsoft.Azure.WebJobs": "3.0.18",
"Microsoft.Extensions.DependencyInjection": "2.1.0"
}
},
"Microsoft.Azure.Functions.Worker": {
"type": "Direct",
"requested": "[1.10.0, )",
Expand Down Expand Up @@ -262,6 +272,31 @@
"System.Net.Http": "4.3.0"
}
},
"Microsoft.Extensions.Configuration.AzureAppConfiguration": {
"type": "Direct",
"requested": "[5.1.0, )",
"resolved": "5.1.0",
"contentHash": "FoAfgvT/rjL/+c7BP7q0LrJIdc4Hu6SH56BTIUbwCwVjHoUw4dpgGtLQULi5GmMjdbdAxyLQSnbwpOEWuBy+RA==",
"dependencies": {
"Azure.Data.AppConfiguration": "1.2.0",
"Azure.Messaging.EventGrid": "4.7.0",
"Azure.Security.KeyVault.Secrets": "4.0.1",
"Microsoft.Extensions.Configuration": "3.1.18",
"Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.18",
"Microsoft.Extensions.Logging": "3.1.18",
"System.Text.Json": "4.6.0"
}
},
"Microsoft.FeatureManagement": {
"type": "Direct",
"requested": "[2.5.1, )",
"resolved": "2.5.1",
"contentHash": "ERbRjk0etZs4d5Pv17unfogO4iBwV2c/HoBt4jqIJmfbKbmTLV+GbjBPYzidIg2RgYIFi8yA+EoEapSAIOp19g==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.1.10",
"Microsoft.Extensions.Logging": "2.1.1"
}
},
"Microsoft.Graph": {
"type": "Direct",
"requested": "[4.37.0, )",
Expand Down Expand Up @@ -355,6 +390,16 @@
"resolved": "2.0.0",
"contentHash": "rXkSI9t4vP2EaPhuchsWiD3elcLNth3UOZAlGohGmuckpkiOr57oMHuzM5WDzz7MJd+ZewE27/WfrZhhhFDHzA=="
},
"Azure.Data.AppConfiguration": {
"type": "Transitive",
"resolved": "1.2.0",
"contentHash": "KA1dAM9TuDsq0CRFd+3cJTYUAzA2z9N8t9/xKdDbP9URuReq/NDFcKYr7GW2W9xzVGDtCHlD5j5am/+zLLBdSg==",
"dependencies": {
"Azure.Core": "1.20.0",
"Microsoft.Bcl.AsyncInterfaces": "1.0.0",
"System.Text.Json": "4.6.0"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.12.0",
Expand Down Expand Up @@ -567,6 +612,33 @@
"Microsoft.CodeAnalysis.CSharp": "3.11.0"
}
},
"Microsoft.Azure.WebJobs": {
"type": "Transitive",
"resolved": "3.0.18",
"contentHash": "aYJ76yjPkIpsafqFp1Xz1sA06RvhUwqJnk4AqX4I0teuRjPyig9Sv7LTzxUMAppKXc4JyR/Asos2At/LMiblqg==",
"dependencies": {
"Microsoft.Azure.WebJobs.Core": "3.0.18",
"Microsoft.Extensions.Configuration": "2.1.0",
"Microsoft.Extensions.Configuration.Abstractions": "2.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "2.1.0",
"Microsoft.Extensions.Configuration.Json": "2.1.0",
"Microsoft.Extensions.Hosting": "2.1.0",
"Microsoft.Extensions.Logging": "2.1.0",
"Microsoft.Extensions.Logging.Abstractions": "2.1.0",
"Microsoft.Extensions.Logging.Configuration": "2.1.0",
"Newtonsoft.Json": "11.0.2",
"System.Threading.Tasks.Dataflow": "4.8.0"
}
},
"Microsoft.Azure.WebJobs.Core": {
"type": "Transitive",
"resolved": "3.0.18",
"contentHash": "ajYI8pPzPn4qq7FL8C2tz9WmFEG5PorUlkw8W9CF5M+5egnFJaF7yH48WYC+zBoQIzv2vHmFq0zhQpnv+O8v5Q==",
"dependencies": {
"System.ComponentModel.Annotations": "4.4.0",
"System.Diagnostics.TraceSource": "4.3.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "6.0.0",
Expand Down Expand Up @@ -1381,6 +1453,22 @@
"System.Runtime": "4.3.0"
}
},
"System.Diagnostics.TraceSource": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "VnYp1NxGx8Ww731y2LJ1vpfb/DKVNKEZ8Jsh5SgQTZREL/YpWRArgh9pI8CDLmgHspZmLL697CaLvH85qQpRiw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0",
"runtime.native.System": "4.3.0"
}
},
"System.Diagnostics.Tracing": {
"type": "Transitive",
"resolved": "4.3.0",
Expand Down Expand Up @@ -2077,6 +2165,11 @@
"System.Runtime": "4.3.0"
}
},
"System.Threading.Tasks.Dataflow": {
"type": "Transitive",
"resolved": "4.8.0",
"contentHash": "PSIdcgbyNv7FZvZ1I9Mqy6XZOwstYYMdZiXuHvIyc0gDyPjEhrrP9OvTGDHp+LAHp1RNSLjPYssyqox9+Kt9Ug=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
Expand Down
5 changes: 4 additions & 1 deletion src/ApiService/IntegrationTests/Fakes/TestContext.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Linq;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.Options;
using Microsoft.FeatureManagement;
using Microsoft.OneFuzz.Service;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using Async = System.Threading.Tasks;
Expand Down Expand Up @@ -120,6 +122,7 @@ public Async.Task InsertAll(params EntityBase[] objs)
public ITeams Teams => throw new NotImplementedException();
public IGithubIssues GithubIssues => throw new NotImplementedException();
public IAdo Ado => throw new NotImplementedException();
public IFeatureManagerSnapshot FeatureManagerSnapshot => throw new NotImplementedException();


public IConfigurationRefresher ConfigurationRefresher => throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ public TestServiceConfiguration(string tablePrefix) {
public string? OneFuzzResourceGroup => throw new NotImplementedException();

public string? OneFuzzAllowOutdatedAgent => throw new NotImplementedException();
public string? AppConfigurationEndpoint => throw new NotImplementedException();
}
Loading