mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-13 02:28:10 +00:00
instance config (#1791)
* instance config * address PR comments * make logs scoped * make logs scoped Co-authored-by: stas <statis@microsoft.com>
This commit is contained in:
@ -5,6 +5,7 @@
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningLevel>5</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.0" />
|
||||
|
@ -22,6 +22,9 @@ public static class EnvironmentVariables
|
||||
//TODO: Add environment variable to control where to write logs to
|
||||
public static LogDestination[] LogDestinations { get; set; }
|
||||
|
||||
//TODO: Get this from Environment variable
|
||||
public static ApplicationInsights.DataContracts.SeverityLevel LogSeverityLevel() { return ApplicationInsights.DataContracts.SeverityLevel.Verbose; }
|
||||
|
||||
public static class AppInsights
|
||||
{
|
||||
public static string? AppId { get => Environment.GetEnvironmentVariable("APPINSIGHTS_APPID"); }
|
||||
|
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public record FunctionInfo(string Name, string ResourceGroup, string? SlotName);
|
||||
|
||||
|
||||
public class Info
|
||||
{
|
||||
|
||||
private readonly ILogTracerFactory _loggerFactory;
|
||||
|
||||
|
||||
public Info(ILogTracerFactory loggerFactory)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
[Function("Info")]
|
||||
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequestData req)
|
||||
{
|
||||
var log = _loggerFactory.MakeLogTracer(Guid.NewGuid());
|
||||
log.Info("Creating function info response");
|
||||
var response = req.CreateResponse();
|
||||
FunctionInfo info = new(
|
||||
$"{EnvironmentVariables.OneFuzz.InstanceName}",
|
||||
$"{EnvironmentVariables.OneFuzz.ResourceGroup}",
|
||||
Environment.GetEnvironmentVariable("WEBSITE_SLOT_NAME"));
|
||||
|
||||
|
||||
log.Info("Returning function info");
|
||||
await response.WriteAsJsonAsync(info);
|
||||
log.Info("Returned function info");
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
@ -132,22 +132,34 @@ public interface ILogTracer
|
||||
void ForceFlush();
|
||||
void Info(string message);
|
||||
void Warning(string message);
|
||||
void Verbose(string message);
|
||||
|
||||
ILogTracer AddTags((string, string)[]? tags);
|
||||
ILogTracer WithTag(string k, string v);
|
||||
ILogTracer WithTags((string, string)[]? tags);
|
||||
}
|
||||
|
||||
public class LogTracer : ILogTracer
|
||||
internal interface ILogTracerInternal : ILogTracer
|
||||
{
|
||||
void ReplaceCorrelationId(Guid newCorrelationId);
|
||||
void AddTags((string, string)[] tags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class LogTracer : ILogTracerInternal
|
||||
{
|
||||
private string? GetCaller()
|
||||
{
|
||||
return new StackTrace()?.GetFrame(2)?.GetMethod()?.DeclaringType?.FullName;
|
||||
}
|
||||
|
||||
private Guid _correlationId;
|
||||
private List<ILog> _loggers;
|
||||
private Dictionary<string, string> _tags;
|
||||
private SeverityLevel _logSeverityLevel;
|
||||
|
||||
public Guid CorrelationId { get; }
|
||||
public IReadOnlyDictionary<string, string> Tags { get; }
|
||||
|
||||
public Guid CorrelationId => _correlationId;
|
||||
public IReadOnlyDictionary<string, string> Tags => _tags;
|
||||
|
||||
private static List<KeyValuePair<string, string>> ConvertTags((string, string)[]? tags)
|
||||
{
|
||||
@ -166,17 +178,43 @@ public class LogTracer : ILogTracer
|
||||
}
|
||||
}
|
||||
|
||||
public LogTracer(Guid correlationId, (string, string)[]? tags, List<ILog> loggers) : this(correlationId, new Dictionary<string, string>(ConvertTags(tags)), loggers) { }
|
||||
public LogTracer(Guid correlationId, (string, string)[]? tags, List<ILog> loggers, SeverityLevel logSeverityLevel) :
|
||||
this(correlationId, new Dictionary<string, string>(ConvertTags(tags)), loggers, logSeverityLevel)
|
||||
{ }
|
||||
|
||||
|
||||
public LogTracer(Guid correlationId, IReadOnlyDictionary<string, string> tags, List<ILog> loggers)
|
||||
public LogTracer(Guid correlationId, IReadOnlyDictionary<string, string> tags, List<ILog> loggers, SeverityLevel logSeverityLevel)
|
||||
{
|
||||
CorrelationId = correlationId;
|
||||
Tags = tags;
|
||||
_correlationId = correlationId;
|
||||
_tags = new(tags);
|
||||
_loggers = loggers;
|
||||
_logSeverityLevel = logSeverityLevel;
|
||||
}
|
||||
|
||||
public ILogTracer AddTags((string, string)[]? tags)
|
||||
//Single threaded only
|
||||
public void ReplaceCorrelationId(Guid newCorrelationId)
|
||||
{
|
||||
_correlationId = newCorrelationId;
|
||||
}
|
||||
|
||||
//single threaded only
|
||||
public void AddTags((string, string)[] tags)
|
||||
{
|
||||
if (tags is not null)
|
||||
{
|
||||
foreach (var (k, v) in tags)
|
||||
{
|
||||
_tags[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ILogTracer WithTag(string k, string v)
|
||||
{
|
||||
return WithTags(new[] { (k, v) });
|
||||
}
|
||||
|
||||
public ILogTracer WithTags((string, string)[]? tags)
|
||||
{
|
||||
var newTags = new Dictionary<string, string>(Tags);
|
||||
if (tags is not null)
|
||||
@ -186,42 +224,66 @@ public class LogTracer : ILogTracer
|
||||
newTags[k] = v;
|
||||
}
|
||||
}
|
||||
return new LogTracer(CorrelationId, newTags, _loggers);
|
||||
return new LogTracer(CorrelationId, newTags, _loggers, _logSeverityLevel);
|
||||
}
|
||||
|
||||
public void Verbose(string message)
|
||||
{
|
||||
if (_logSeverityLevel >= SeverityLevel.Verbose)
|
||||
{
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Verbose, Tags, caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
{
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
if (_logSeverityLevel >= SeverityLevel.Information)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Information, Tags, caller);
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Information, Tags, caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Warning(string message)
|
||||
{
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
if (_logSeverityLevel >= SeverityLevel.Warning)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Warning, Tags, caller);
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Warning, Tags, caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Error(string message)
|
||||
{
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
if (_logSeverityLevel >= SeverityLevel.Error)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Error, Tags, caller);
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Error, Tags, caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Critical(string message)
|
||||
{
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
if (_logSeverityLevel >= SeverityLevel.Critical)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Critical, Tags, caller);
|
||||
var caller = GetCaller();
|
||||
foreach (var logger in _loggers)
|
||||
{
|
||||
logger.Log(CorrelationId, message, SeverityLevel.Critical, Tags, caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,7 +316,7 @@ public class LogTracer : ILogTracer
|
||||
|
||||
public interface ILogTracerFactory
|
||||
{
|
||||
LogTracer MakeLogTracer(Guid correlationId, (string, string)[]? tags = null);
|
||||
LogTracer CreateLogTracer(Guid correlationId, (string, string)[]? tags = null, SeverityLevel severityLevel = SeverityLevel.Verbose);
|
||||
}
|
||||
|
||||
public class LogTracerFactory : ILogTracerFactory
|
||||
@ -266,9 +328,9 @@ public class LogTracerFactory : ILogTracerFactory
|
||||
_loggers = loggers;
|
||||
}
|
||||
|
||||
public LogTracer MakeLogTracer(Guid correlationId, (string, string)[]? tags = null)
|
||||
public LogTracer CreateLogTracer(Guid correlationId, (string, string)[]? tags = null, SeverityLevel severityLevel = SeverityLevel.Verbose)
|
||||
{
|
||||
return new(correlationId, tags, _loggers);
|
||||
return new(correlationId, tags, _loggers, severityLevel);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
public enum ErrorCode
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
public enum ErrorCode
|
||||
{
|
||||
INVALID_REQUEST = 450,
|
||||
INVALID_PERMISSION = 451,
|
||||
|
@ -43,6 +43,7 @@ namespace Microsoft.OneFuzz.Service
|
||||
this switch
|
||||
{
|
||||
EventNodeHeartbeat _ => EventType.NodeHeartbeat,
|
||||
EventInstanceConfigUpdated _ => EventType.InstanceConfigUpdated,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
@ -243,7 +244,7 @@ namespace Microsoft.OneFuzz.Service
|
||||
// ) : BaseEvent();
|
||||
|
||||
|
||||
// record EventInstanceConfigUpdated(
|
||||
// InstanceConfig Config
|
||||
// ) : BaseEvent();
|
||||
record EventInstanceConfigUpdated(
|
||||
InstanceConfig Config
|
||||
) : BaseEvent();
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PoolName = System.String;
|
||||
using Region = System.String;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Container = System.String;
|
||||
using Region = System.String;
|
||||
using PoolName = System.String;
|
||||
using Endpoint = System.String;
|
||||
using GroupId = System.Guid;
|
||||
using PrincipalId = System.Guid;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
|
||||
/// Convention for database entities:
|
||||
/// All entities are represented by immutable records
|
||||
/// All database entities need to derive from EntityBase
|
||||
@ -17,6 +21,7 @@ namespace Microsoft.OneFuzz.Service;
|
||||
/// the "partion key" and "row key" are identified by the [PartitionKey] and [RowKey] attributes
|
||||
/// Guids are mapped to string in the db
|
||||
|
||||
|
||||
public record Authentication
|
||||
(
|
||||
string Password,
|
||||
@ -253,5 +258,118 @@ public record Task(
|
||||
{
|
||||
List<TaskEventSummary> Events { get; set; } = new List<TaskEventSummary>();
|
||||
List<NodeAssignment> Nodes { get; set; } = new List<NodeAssignment>();
|
||||
}
|
||||
public record AzureSecurityExtensionConfig();
|
||||
public record GenevaExtensionConfig();
|
||||
|
||||
}
|
||||
|
||||
public record KeyvaultExtensionConfig(
|
||||
string KeyVaultName,
|
||||
string CertName,
|
||||
string CertPath,
|
||||
string ExtensionStore
|
||||
);
|
||||
|
||||
public record AzureMonitorExtensionConfig(
|
||||
string ConfigVersion,
|
||||
string Moniker,
|
||||
string Namespace,
|
||||
[property: JsonPropertyName("monitoringGSEnvironment")] string MonitoringGSEnvironment,
|
||||
[property: JsonPropertyName("monitoringGCSAccount")] string MonitoringGCSAccount,
|
||||
[property: JsonPropertyName("monitoringGCSAuthId")] string MonitoringGCSAuthId,
|
||||
[property: JsonPropertyName("monitoringGCSAuthIdType")] string MonitoringGCSAuthIdType
|
||||
);
|
||||
|
||||
public record AzureVmExtensionConfig(
|
||||
KeyvaultExtensionConfig? Keyvault,
|
||||
AzureMonitorExtensionConfig AzureMonitor
|
||||
);
|
||||
|
||||
public record NetworkConfig(
|
||||
string AddressSpace,
|
||||
string Subnet
|
||||
)
|
||||
{
|
||||
public NetworkConfig() : this("10.0.0.0/8", "10.0.0.0/16") { }
|
||||
}
|
||||
|
||||
public record NetworkSecurityGroupConfig(
|
||||
string[] AllowedServiceTags,
|
||||
string[] AllowedIps
|
||||
)
|
||||
{
|
||||
public NetworkSecurityGroupConfig() : this(Array.Empty<string>(), Array.Empty<string>()) { }
|
||||
}
|
||||
|
||||
public record ApiAccessRule(
|
||||
string[] Methods,
|
||||
Guid[] AllowedGroups
|
||||
);
|
||||
|
||||
public record InstanceConfig
|
||||
(
|
||||
[PartitionKey, RowKey] string InstanceName,
|
||||
//# initial set of admins can only be set during deployment.
|
||||
//# if admins are set, only admins can update instance configs.
|
||||
Guid[]? Admins,
|
||||
//# if set, only admins can manage pools or scalesets
|
||||
bool AllowPoolManagement,
|
||||
string[] AllowedAadTenants,
|
||||
NetworkConfig NetworkConfig,
|
||||
NetworkSecurityGroupConfig ProxyNsgConfig,
|
||||
AzureVmExtensionConfig? Extensions,
|
||||
string ProxyVmSku,
|
||||
IDictionary<Endpoint, ApiAccessRule>? ApiAccessRules,
|
||||
IDictionary<PrincipalId, GroupId[]>? GroupMembership,
|
||||
|
||||
IDictionary<string, string>? VmTags,
|
||||
IDictionary<string, string>? VmssTags
|
||||
) : EntityBase()
|
||||
{
|
||||
public InstanceConfig(string instanceName) : this(
|
||||
instanceName,
|
||||
null,
|
||||
true,
|
||||
Array.Empty<string>(),
|
||||
new NetworkConfig(),
|
||||
new NetworkSecurityGroupConfig(),
|
||||
null,
|
||||
"Standard_B2s",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null)
|
||||
{ }
|
||||
|
||||
public List<Guid>? CheckAdmins(List<Guid>? value)
|
||||
{
|
||||
if (value is not null && value.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("admins must be null or contain at least one UUID");
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//# At the moment, this only checks allowed_aad_tenants, however adding
|
||||
//# support for 3rd party JWT validation is anticipated in a future release.
|
||||
public ResultOk<List<string>> CheckInstanceConfig()
|
||||
{
|
||||
List<string> errors = new();
|
||||
if (AllowedAadTenants.Length == 0)
|
||||
{
|
||||
errors.Add("allowed_aad_tenants must not be empty");
|
||||
}
|
||||
if (errors.Count == 0)
|
||||
{
|
||||
return ResultOk<List<string>>.Ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultOk<List<string>>.Error(errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,24 @@
|
||||
namespace Microsoft.OneFuzz.Service
|
||||
{
|
||||
|
||||
public struct ResultOk<T_Error>
|
||||
{
|
||||
public static ResultOk<T_Error> Ok() => new();
|
||||
public static ResultOk<T_Error> Error(T_Error err) => new(err);
|
||||
|
||||
readonly T_Error? error;
|
||||
readonly bool isOk;
|
||||
|
||||
public ResultOk() => (error, isOk) = (default, true);
|
||||
|
||||
public ResultOk(T_Error error) => (this.error, isOk) = (error, false);
|
||||
|
||||
public bool IsOk => isOk;
|
||||
|
||||
public T_Error? ErrorV => error;
|
||||
}
|
||||
|
||||
|
||||
public struct Result<T_Ok, T_Error>
|
||||
{
|
||||
public static Result<T_Ok, T_Error> Ok(T_Ok ok) => new(ok);
|
||||
@ -14,7 +32,10 @@
|
||||
|
||||
public Result(T_Error error) => (this.error, ok, isOk) = (error, default, false);
|
||||
|
||||
public bool IsOk => IsOk;
|
||||
public bool IsOk => isOk;
|
||||
|
||||
public T_Error? ErrorV => error;
|
||||
public T_Ok? OkV => ok;
|
||||
}
|
||||
|
||||
|
||||
|
@ -6,13 +6,36 @@ using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ApiService.OneFuzzLib;
|
||||
|
||||
|
||||
using Microsoft.Azure.Functions.Worker.Middleware;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public class LoggingMiddleware : IFunctionsWorkerMiddleware
|
||||
{
|
||||
public async Async.Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
|
||||
{
|
||||
//TODO
|
||||
//if correlation ID is available in HTTP request
|
||||
//if correlation ID is available in Queue message
|
||||
//log.ReplaceCorrelationId
|
||||
|
||||
var log = (ILogTracerInternal?)context.InstanceServices.GetService<ILogTracer>();
|
||||
if (log is not null)
|
||||
{
|
||||
log.AddTags(new[] {
|
||||
("InvocationId", context.InvocationId.ToString())
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<ILog> GetLoggers()
|
||||
{
|
||||
List<ILog> loggers = new List<ILog>();
|
||||
@ -23,7 +46,7 @@ public class Program
|
||||
{
|
||||
LogDestination.AppInsights => new AppInsights(),
|
||||
LogDestination.Console => new Console(),
|
||||
_ => throw new Exception(string.Format("Unhandled Log Destination type: {0}", dest)),
|
||||
_ => throw new Exception($"Unhandled Log Destination type: {dest}"),
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -34,19 +57,25 @@ public class Program
|
||||
public static void Main()
|
||||
{
|
||||
var host = new HostBuilder()
|
||||
.ConfigureFunctionsWorkerDefaults()
|
||||
.ConfigureFunctionsWorkerDefaults(
|
||||
builder =>
|
||||
{
|
||||
builder.UseMiddleware<LoggingMiddleware>();
|
||||
}
|
||||
)
|
||||
.ConfigureServices((context, services) =>
|
||||
services
|
||||
.AddSingleton<ILogTracerFactory>(_ => new LogTracerFactory(GetLoggers()))
|
||||
.AddScoped<ILogTracer>(_ => new LogTracerFactory(GetLoggers()).CreateLogTracer(Guid.NewGuid(), severityLevel: EnvironmentVariables.LogSeverityLevel()))
|
||||
.AddSingleton<INodeOperations, NodeOperations>()
|
||||
.AddSingleton<IEvents, Events>()
|
||||
.AddSingleton<IWebhookOperations, WebhookOperations>()
|
||||
.AddSingleton<IWebhookMessageLogOperations, WebhookMessageLogOperations>()
|
||||
.AddSingleton<ITaskOperations, TaskOperations>()
|
||||
.AddSingleton<IQueue, Queue>()
|
||||
.AddSingleton<ICreds>(_ => new Creds())
|
||||
.AddSingleton<ICreds, Creds>()
|
||||
.AddSingleton<IStorage, Storage>()
|
||||
.AddSingleton<IProxyOperations, ProxyOperations>()
|
||||
.AddSingleton<IConfigOperations, ConfigOperations>()
|
||||
)
|
||||
.Build();
|
||||
|
||||
|
@ -13,13 +13,13 @@ public class QueueFileChanges
|
||||
// https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=csharp#poison-messages
|
||||
const int MAX_DEQUEUE_COUNT = 5;
|
||||
|
||||
private readonly ILogTracerFactory _loggerFactory;
|
||||
private readonly ILogTracer _log;
|
||||
|
||||
private readonly IStorage _storage;
|
||||
|
||||
public QueueFileChanges(ILogTracerFactory loggerFactory, IStorage storage)
|
||||
public QueueFileChanges(ILogTracer log, IStorage storage)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_log = log;
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
@ -28,7 +28,6 @@ public class QueueFileChanges
|
||||
[QueueTrigger("file-changes-refactored", Connection = "AzureWebJobsStorage")] string msg,
|
||||
int dequeueCount)
|
||||
{
|
||||
var log = _loggerFactory.MakeLogTracer(Guid.NewGuid());
|
||||
var fileChangeEvent = JsonSerializer.Deserialize<Dictionary<string, string>>(msg, EntityConverter.GetJsonSerializerOptions());
|
||||
var lastTry = dequeueCount == MAX_DEQUEUE_COUNT;
|
||||
|
||||
@ -44,12 +43,12 @@ public class QueueFileChanges
|
||||
|
||||
const string topic = "topic";
|
||||
if (!fileChangeEvent.ContainsKey(topic)
|
||||
|| !_storage.CorpusAccounts(log).Contains(fileChangeEvent[topic]))
|
||||
|| !_storage.CorpusAccounts().Contains(fileChangeEvent[topic]))
|
||||
{
|
||||
return Async.Task.CompletedTask;
|
||||
}
|
||||
|
||||
file_added(log, fileChangeEvent, lastTry);
|
||||
file_added(_log, fileChangeEvent, lastTry);
|
||||
return Async.Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
@ -8,14 +8,14 @@ namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public class QueueNodeHearbeat
|
||||
{
|
||||
private readonly ILogTracerFactory _loggerFactory;
|
||||
private readonly ILogTracer _log;
|
||||
|
||||
private readonly IEvents _events;
|
||||
private readonly INodeOperations _nodes;
|
||||
|
||||
public QueueNodeHearbeat(ILogTracerFactory loggerFactory, INodeOperations nodes, IEvents events)
|
||||
public QueueNodeHearbeat(ILogTracer log, INodeOperations nodes, IEvents events)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_log = log;
|
||||
_nodes = nodes;
|
||||
_events = events;
|
||||
}
|
||||
@ -23,13 +23,14 @@ public class QueueNodeHearbeat
|
||||
[Function("QueueNodeHearbeat")]
|
||||
public async Async.Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string msg)
|
||||
{
|
||||
var log = _loggerFactory.MakeLogTracer(Guid.NewGuid());
|
||||
log.Info($"heartbeat: {msg}");
|
||||
_log.Info($"heartbeat: {msg}");
|
||||
|
||||
var hb = JsonSerializer.Deserialize<NodeHeartbeatEntry>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}");
|
||||
|
||||
var node = await _nodes.GetByMachineId(hb.NodeId);
|
||||
|
||||
var log = _log.WithTag("NodeId", hb.NodeId.ToString());
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
log.Warning($"invalid node id: {hb.NodeId}");
|
||||
@ -38,8 +39,15 @@ public class QueueNodeHearbeat
|
||||
|
||||
var newNode = node with { Heartbeat = DateTimeOffset.UtcNow };
|
||||
|
||||
await _nodes.Replace(newNode);
|
||||
var r = await _nodes.Replace(newNode);
|
||||
|
||||
if (!r.IsOk)
|
||||
{
|
||||
var (status, reason) = r.ErrorV;
|
||||
log.Error($"Failed to replace heartbeat info due to [{status}] {reason}");
|
||||
}
|
||||
|
||||
// TODO: do we still send event if we fail do update the table ?
|
||||
await _events.SendEvent(new EventNodeHeartbeat(node.MachineId, node.ScalesetId, node.PoolName));
|
||||
}
|
||||
}
|
||||
|
@ -7,36 +7,40 @@ namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public class QueueProxyHearbeat
|
||||
{
|
||||
private readonly ILogTracerFactory _loggerFactory;
|
||||
private readonly ILogTracer _log;
|
||||
|
||||
private readonly IProxyOperations _proxy;
|
||||
|
||||
public QueueProxyHearbeat(ILogTracerFactory loggerFactory, IProxyOperations proxy)
|
||||
public QueueProxyHearbeat(ILogTracer log, IProxyOperations proxy)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_log = log;
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
[Function("QueueProxyHearbeat")]
|
||||
public async Async.Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string msg)
|
||||
{
|
||||
var log = _loggerFactory.MakeLogTracer(Guid.NewGuid());
|
||||
|
||||
log.Info($"heartbeat: {msg}");
|
||||
_log.Info($"heartbeat: {msg}");
|
||||
|
||||
var hb = JsonSerializer.Deserialize<ProxyHeartbeat>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}"); ;
|
||||
var newHb = hb with { TimeStamp = DateTimeOffset.UtcNow };
|
||||
|
||||
var proxy = await _proxy.GetByProxyId(newHb.ProxyId);
|
||||
|
||||
var log = _log.WithTag("ProxyId", newHb.ProxyId.ToString());
|
||||
|
||||
if (proxy == null)
|
||||
{
|
||||
log.AddTags(new[] { ("Proxy ID", newHb.ProxyId.ToString()) }).Warning($"invalid proxy id: {newHb.ProxyId}");
|
||||
log.Warning($"invalid proxy id: {newHb.ProxyId}");
|
||||
return;
|
||||
}
|
||||
var newProxy = proxy with { heartbeat = newHb };
|
||||
|
||||
await _proxy.Replace(newProxy);
|
||||
|
||||
var r = await _proxy.Replace(newProxy);
|
||||
if (!r.IsOk)
|
||||
{
|
||||
var (status, reason) = r.ErrorV;
|
||||
log.Error($"Failed to replace proxy heartbeat record due to [{status}] {reason}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
65
src/ApiService/ApiService/TestHooks.cs
Normal file
65
src/ApiService/ApiService/TestHooks.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public record FunctionInfo(string Name, string ResourceGroup, string? SlotName);
|
||||
|
||||
|
||||
public class TestHooks
|
||||
{
|
||||
|
||||
private readonly ILogTracer _log;
|
||||
private readonly IConfigOperations _configOps;
|
||||
private readonly IEvents _events;
|
||||
|
||||
public TestHooks(ILogTracer log, IConfigOperations configOps, IEvents events)
|
||||
{
|
||||
_log = log;
|
||||
_configOps = configOps;
|
||||
_events = events;
|
||||
}
|
||||
|
||||
[Function("Info")]
|
||||
public async Task<HttpResponseData> Info([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/info")] HttpRequestData req)
|
||||
{
|
||||
_log.Info("Creating function info response");
|
||||
var response = req.CreateResponse();
|
||||
FunctionInfo info = new(
|
||||
$"{EnvironmentVariables.OneFuzz.InstanceName}",
|
||||
$"{EnvironmentVariables.OneFuzz.ResourceGroup}",
|
||||
Environment.GetEnvironmentVariable("WEBSITE_SLOT_NAME"));
|
||||
|
||||
_log.Info("Returning function info");
|
||||
await response.WriteAsJsonAsync(info);
|
||||
_log.Info("Returned function info");
|
||||
return response;
|
||||
}
|
||||
|
||||
[Function("InstanceConfig")]
|
||||
public async Task<HttpResponseData> InstanceConfig([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/instance-config")] HttpRequestData req)
|
||||
{
|
||||
_log.Info("Fetching instance config");
|
||||
var config = await _configOps.Fetch();
|
||||
|
||||
if (config is null)
|
||||
{
|
||||
_log.Error("Instance config is null");
|
||||
Error err = new(ErrorCode.INVALID_REQUEST, new[] { "Instance config is null" });
|
||||
var resp = req.CreateResponse(HttpStatusCode.InternalServerError);
|
||||
await resp.WriteAsJsonAsync(err);
|
||||
return resp;
|
||||
}
|
||||
else
|
||||
{
|
||||
await _events.SendEvent(new EventInstanceConfigUpdated(config));
|
||||
|
||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||
await resp.WriteAsJsonAsync(config);
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@ namespace Microsoft.OneFuzz.Service
|
||||
);
|
||||
|
||||
|
||||
|
||||
public interface IEvents
|
||||
{
|
||||
public Async.Task SendEvent(BaseEvent anEvent);
|
||||
@ -27,14 +26,14 @@ namespace Microsoft.OneFuzz.Service
|
||||
public class Events : IEvents
|
||||
{
|
||||
private readonly IQueue _queue;
|
||||
private readonly ILogTracerFactory _loggerFactory;
|
||||
private readonly IWebhookOperations _webhook;
|
||||
private ILogTracer _log;
|
||||
|
||||
public Events(IQueue queue, ILogTracerFactory loggerFactory, IWebhookOperations webhook)
|
||||
public Events(IQueue queue, IWebhookOperations webhook, ILogTracer log)
|
||||
{
|
||||
_queue = queue;
|
||||
_loggerFactory = loggerFactory;
|
||||
_webhook = webhook;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public async Async.Task QueueSignalrEvent(EventMessage eventMessage)
|
||||
@ -46,7 +45,6 @@ namespace Microsoft.OneFuzz.Service
|
||||
|
||||
public async Async.Task SendEvent(BaseEvent anEvent)
|
||||
{
|
||||
var log = _loggerFactory.MakeLogTracer(Guid.NewGuid());
|
||||
var eventType = anEvent.GetEventType();
|
||||
|
||||
var eventMessage = new EventMessage(
|
||||
@ -58,16 +56,16 @@ namespace Microsoft.OneFuzz.Service
|
||||
);
|
||||
await QueueSignalrEvent(eventMessage);
|
||||
await _webhook.SendEvent(eventMessage);
|
||||
LogEvent(log, anEvent, eventType);
|
||||
LogEvent(anEvent, eventType);
|
||||
}
|
||||
|
||||
public void LogEvent(ILogTracer log, BaseEvent anEvent, EventType eventType)
|
||||
public void LogEvent(BaseEvent anEvent, EventType eventType)
|
||||
{
|
||||
var options = EntityConverter.GetJsonSerializerOptions();
|
||||
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
||||
options.Converters.Add(new RemoveUserInfo());
|
||||
var serializedEvent = JsonSerializer.Serialize(anEvent, options);
|
||||
log.AddTags(new[] { ("Event Type", eventType.ToString()) }).Info($"sending event: {eventType} - {serializedEvent}");
|
||||
_log.WithTag("Event Type", eventType.ToString()).Info($"sending event: {eventType} - {serializedEvent}");
|
||||
}
|
||||
}
|
||||
|
||||
|
65
src/ApiService/ApiService/onefuzzlib/InstanceConfig.cs
Normal file
65
src/ApiService/ApiService/onefuzzlib/InstanceConfig.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
|
||||
public interface IConfigOperations : IOrm<InstanceConfig>
|
||||
{
|
||||
Task<InstanceConfig> Fetch();
|
||||
|
||||
Async.Task Save(InstanceConfig config, bool isNew, bool requireEtag);
|
||||
}
|
||||
|
||||
public class ConfigOperations : Orm<InstanceConfig>, IConfigOperations
|
||||
{
|
||||
private readonly IEvents _events;
|
||||
private readonly ILogTracer _log;
|
||||
public ConfigOperations(IStorage storage, IEvents events, ILogTracer log) : base(storage)
|
||||
{
|
||||
_events = events;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public async Task<InstanceConfig> Fetch()
|
||||
{
|
||||
var key = EnvironmentVariables.OneFuzz.InstanceName ?? throw new Exception("Environment variable ONEFUZZ_INSTANCE_NAME is not set");
|
||||
var config = await GetEntityAsync(key, key);
|
||||
return config;
|
||||
}
|
||||
|
||||
public async Async.Task Save(InstanceConfig config, bool isNew = false, bool requireEtag = false)
|
||||
{
|
||||
ResultOk<(int, string)> r;
|
||||
if (isNew)
|
||||
{
|
||||
r = await Insert(config);
|
||||
if (!r.IsOk)
|
||||
{
|
||||
var (status, reason) = r.ErrorV;
|
||||
_log.Error($"Failed to save new instance config record with result [{status}] {reason}");
|
||||
}
|
||||
}
|
||||
else if (requireEtag && config.ETag.HasValue)
|
||||
{
|
||||
r = await Update(config);
|
||||
if (!r.IsOk)
|
||||
{
|
||||
var (status, reason) = r.ErrorV;
|
||||
_log.Error($"Failed to update instance config record with result: [{status}] {reason}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
r = await Replace(config);
|
||||
if (!r.IsOk)
|
||||
{
|
||||
var (status, reason) = r.ErrorV;
|
||||
_log.Error($"Failed to replace instance config record with result [{status}] {reason}");
|
||||
}
|
||||
}
|
||||
|
||||
await _events.SendEvent(new EventInstanceConfigUpdated(config));
|
||||
}
|
||||
}
|
@ -11,12 +11,12 @@ public interface IProxyOperations : IOrm<Proxy>
|
||||
}
|
||||
public class ProxyOperations : Orm<Proxy>, IProxyOperations
|
||||
{
|
||||
private readonly ILogTracerFactory _logger;
|
||||
private readonly ILogTracer _log;
|
||||
|
||||
public ProxyOperations(ILogTracerFactory loggerFactory, IStorage storage)
|
||||
public ProxyOperations(ILogTracer log, IStorage storage)
|
||||
: base(storage)
|
||||
{
|
||||
_logger = loggerFactory;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public async Task<Proxy?> GetByProxyId(Guid proxyId)
|
||||
|
@ -16,12 +16,12 @@ public interface IQueue
|
||||
public class Queue : IQueue
|
||||
{
|
||||
IStorage _storage;
|
||||
ILogTracerFactory _loggerFactory;
|
||||
ILogTracer _log;
|
||||
|
||||
public Queue(IStorage storage, ILogTracerFactory loggerFactory)
|
||||
public Queue(IStorage storage, ILogTracer log)
|
||||
{
|
||||
_storage = storage;
|
||||
_loggerFactory = loggerFactory;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@ public interface IStorage
|
||||
{
|
||||
public ArmClient GetMgmtClient();
|
||||
|
||||
public IEnumerable<string> CorpusAccounts(ILogTracer log);
|
||||
public IEnumerable<string> CorpusAccounts();
|
||||
string GetPrimaryAccount(StorageType storageType);
|
||||
public (string?, string?) GetStorageAccountNameAndKey(string accountId);
|
||||
}
|
||||
@ -27,11 +27,13 @@ public class Storage : IStorage
|
||||
{
|
||||
private ICreds _creds;
|
||||
private ArmClient _armClient;
|
||||
private ILogTracer _log;
|
||||
|
||||
public Storage(ICreds creds)
|
||||
public Storage(ICreds creds, ILogTracer log)
|
||||
{
|
||||
_creds = creds;
|
||||
_armClient = new ArmClient(credential: _creds.GetIdentity(), defaultSubscriptionId: _creds.GetSubcription());
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public static string GetFuncStorage()
|
||||
@ -52,7 +54,7 @@ public class Storage : IStorage
|
||||
}
|
||||
|
||||
// TODO: @cached
|
||||
public IEnumerable<string> CorpusAccounts(ILogTracer log)
|
||||
public IEnumerable<string> CorpusAccounts()
|
||||
{
|
||||
var skip = GetFuncStorage();
|
||||
var results = new List<string> { GetFuzzStorage() };
|
||||
@ -89,7 +91,7 @@ public class Storage : IStorage
|
||||
results.Add(account.Id!);
|
||||
}
|
||||
|
||||
log.Info($"corpus accounts: {JsonSerializer.Serialize(results)}");
|
||||
_log.Info($"corpus accounts: {JsonSerializer.Serialize(results)}");
|
||||
return results;
|
||||
}
|
||||
|
||||
|
@ -20,17 +20,16 @@ public class WebhookMessageLogOperations : Orm<WebhookMessageLog>, IWebhookMessa
|
||||
);
|
||||
|
||||
private readonly IQueue _queue;
|
||||
private readonly ILogTracerFactory _loggerFactory;
|
||||
public WebhookMessageLogOperations(IStorage storage, IQueue queue, ILogTracerFactory loggerFactory) : base(storage)
|
||||
private readonly ILogTracer _log;
|
||||
public WebhookMessageLogOperations(IStorage storage, IQueue queue, ILogTracer log) : base(storage)
|
||||
{
|
||||
_queue = queue;
|
||||
_loggerFactory = loggerFactory;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
|
||||
public async Async.Task QueueWebhook(WebhookMessageLog webhookLog)
|
||||
{
|
||||
var log = _loggerFactory.MakeLogTracer(Guid.NewGuid());
|
||||
var obj = new WebhookMessageQueueObj(webhookLog.WebhookId, webhookLog.EventId);
|
||||
|
||||
TimeSpan? visibilityTimeout = webhookLog.State switch
|
||||
@ -42,7 +41,7 @@ public class WebhookMessageLogOperations : Orm<WebhookMessageLog>, IWebhookMessa
|
||||
|
||||
if (visibilityTimeout == null)
|
||||
{
|
||||
log.AddTags(
|
||||
_log.WithTags(
|
||||
new[] {
|
||||
("WebhookId", webhookLog.WebhookId.ToString()),
|
||||
("EventId", webhookLog.EventId.ToString()) }
|
||||
@ -70,10 +69,12 @@ public interface IWebhookOperations
|
||||
public class WebhookOperations : Orm<Webhook>, IWebhookOperations
|
||||
{
|
||||
private readonly IWebhookMessageLogOperations _webhookMessageLogOperations;
|
||||
public WebhookOperations(IStorage storage, IWebhookMessageLogOperations webhookMessageLogOperations)
|
||||
private readonly ILogTracer _log;
|
||||
public WebhookOperations(IStorage storage, IWebhookMessageLogOperations webhookMessageLogOperations, ILogTracer log)
|
||||
: base(storage)
|
||||
{
|
||||
_webhookMessageLogOperations = webhookMessageLogOperations;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
async public Async.Task SendEvent(EventMessage eventMessage)
|
||||
@ -99,7 +100,12 @@ public class WebhookOperations : Orm<Webhook>, IWebhookOperations
|
||||
WebhookId: webhook.WebhookId
|
||||
);
|
||||
|
||||
await _webhookMessageLogOperations.Replace(message);
|
||||
var r = await _webhookMessageLogOperations.Replace(message);
|
||||
if (!r.IsOk)
|
||||
{
|
||||
var (status, reason) = r.ErrorV;
|
||||
_log.Error($"Failed to replace webhook message log due to [{status}] {reason}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -198,10 +198,16 @@ public class EntityConverter
|
||||
{
|
||||
throw new Exception("invalid ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var fieldName = ef.columnName;
|
||||
var obj = entity[fieldName];
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var objType = obj.GetType();
|
||||
|
||||
if (ef.type == typeof(string))
|
||||
{
|
||||
return entity.GetString(fieldName);
|
||||
@ -245,10 +251,6 @@ public class EntityConverter
|
||||
else
|
||||
{
|
||||
var value = entity.GetString(fieldName);
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return JsonSerializer.Deserialize(value, ef.type, options: _options); ;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,10 @@ namespace ApiService.OneFuzzLib.Orm
|
||||
{
|
||||
Task<TableClient> GetTableClient(string table, string? accountId = null);
|
||||
IAsyncEnumerable<T> QueryAsync(string filter);
|
||||
Task<bool> Replace(T entity);
|
||||
Task<ResultOk<(int, string)>> Replace(T entity);
|
||||
|
||||
Task<T> GetEntityAsync(string partitionKey, string rowKey);
|
||||
Task<ResultOk<(int, string)>> Insert(T entity);
|
||||
}
|
||||
|
||||
public class Orm<T> : IOrm<T> where T : EntityBase
|
||||
@ -36,13 +39,65 @@ namespace ApiService.OneFuzzLib.Orm
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Replace(T entity)
|
||||
public async Task<ResultOk<(int, string)>> Insert(T entity)
|
||||
{
|
||||
var tableClient = await GetTableClient(typeof(T).Name);
|
||||
var tableEntity = _entityConverter.ToTableEntity(entity);
|
||||
var response = await tableClient.AddEntityAsync(tableEntity);
|
||||
|
||||
if (response.IsError)
|
||||
{
|
||||
return ResultOk<(int, string)>.Error((response.Status, response.ReasonPhrase));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultOk<(int, string)>.Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ResultOk<(int, string)>> Replace(T entity)
|
||||
{
|
||||
var tableClient = await GetTableClient(typeof(T).Name);
|
||||
var tableEntity = _entityConverter.ToTableEntity(entity);
|
||||
var response = await tableClient.UpsertEntityAsync(tableEntity);
|
||||
return !response.IsError;
|
||||
if (response.IsError)
|
||||
{
|
||||
return ResultOk<(int, string)>.Error((response.Status, response.ReasonPhrase));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultOk<(int, string)>.Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ResultOk<(int, string)>> Update(T entity)
|
||||
{
|
||||
var tableClient = await GetTableClient(typeof(T).Name);
|
||||
var tableEntity = _entityConverter.ToTableEntity(entity);
|
||||
|
||||
if (entity.ETag is null)
|
||||
{
|
||||
return ResultOk<(int, string)>.Error((0, "ETag must be set when updating an entity"));
|
||||
}
|
||||
else
|
||||
{
|
||||
var response = await tableClient.UpdateEntityAsync(tableEntity, entity.ETag.Value);
|
||||
if (response.IsError)
|
||||
{
|
||||
return ResultOk<(int, string)>.Error((response.Status, response.ReasonPhrase));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultOk<(int, string)>.Ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> GetEntityAsync(string partitionKey, string rowKey)
|
||||
{
|
||||
var tableClient = await GetTableClient(typeof(T).Name);
|
||||
var tableEntity = await tableClient.GetEntityAsync<TableEntity>(partitionKey, rowKey);
|
||||
return _entityConverter.ToRecord<T>(tableEntity);
|
||||
}
|
||||
|
||||
public async Task<TableClient> GetTableClient(string table, string? accountId = null)
|
||||
|
@ -214,6 +214,7 @@ module pythonFunction 'bicep-templates/function.bicep' = {
|
||||
functions_extension_version: '~3'
|
||||
name: name
|
||||
|
||||
instance_name: name
|
||||
app_logs_sas_url: storage.outputs.FuncSasUrlBlobAppLogs
|
||||
app_func_audiences: app_func_audiences
|
||||
app_func_issuer: app_func_issuer
|
||||
@ -244,6 +245,7 @@ module netFunction 'bicep-templates/function.bicep' = {
|
||||
functions_extension_version: '~4'
|
||||
name: '${name}-net'
|
||||
|
||||
instance_name: name
|
||||
app_logs_sas_url: storage.outputs.FuncSasUrlBlobAppLogs
|
||||
app_func_audiences: app_func_audiences
|
||||
app_func_issuer: app_func_issuer
|
||||
|
@ -1,4 +1,5 @@
|
||||
param name string
|
||||
param instance_name string
|
||||
param location string
|
||||
param owner string
|
||||
|
||||
@ -137,7 +138,7 @@ resource pythonFunctionSettings 'Microsoft.Web/sites/config@2021-03-01' = {
|
||||
'AzureWebJobsDisableHomepage': 'true'
|
||||
'AzureSignalRConnectionString': signal_r_connection_string
|
||||
'AzureSignalRServiceTransportType': 'Transient'
|
||||
'ONEFUZZ_INSTANCE_NAME': name
|
||||
'ONEFUZZ_INSTANCE_NAME': instance_name
|
||||
'ONEFUZZ_INSTANCE': 'https://${name}.azurewebsites.net'
|
||||
'ONEFUZZ_RESOURCE_GROUP': resourceGroup().id
|
||||
'ONEFUZZ_DATA_STORAGE': fuzz_storage_resource_id
|
||||
|
Reference in New Issue
Block a user