Refactor notification support (#2363)

* Add teams notifications

* .

* Fix compilation isues

* Checkpoint

* Added Ado

* Fix some TODOs

* Teams messages work! 🎉

* fmt

* Bug fix container url generator

* Some small ado changes

* 🧹

* PR comments

* Fix packages

* Get more detailed restore information to debug errors

* Maybe fixes this issue?

* Undo CI change
This commit is contained in:
Teo Voinea 2022-09-14 11:07:52 -04:00 committed by GitHub
parent f375ee719e
commit ca7b6be43b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1382 additions and 330 deletions

View File

@ -1,16 +1,16 @@
# Azure Devops Work Item creation
Automatic creation of ADO Work Items from OneFuzz allows for the user to
customize any field using [jinja2](https://jinja.palletsprojects.com/)
customize any field using [scriban](https://github.com/scriban/scriban)
templates.
There are multiple Python objects provided via the template engine that
There are multiple objects provided via the template engine that
can be used such that any arbitrary component can be used to flesh out
the configuration:
* task (See [TaskConfig](../../src/pytypes/onefuzztypes/models.py))
* report (See [Report](../../src/pytypes/onefuzztypes/models.py))
* job (See [JobConfig](../../src/pytypes/onefuzztypes/models.py))
* task (See [TaskConfig](../../src/ApiService/ApiService/OneFuzzTypes/Model.cs))
* report (See [Report](../../src/ApiService/ApiService/OneFuzzTypes/Model.cs))
* job (See [JobConfig](../../src/ApiService/ApiService/OneFuzzTypes/Model.cs))
Using these objects allows dynamic configuration. As an example, the `project`
could be specified directly, or dynamically pulled from a template:
@ -49,7 +49,7 @@ clickable, make it a link.
"Microsoft.VSTS.Scheduling.StoryPoints": "1",
"System.IterationPath": "Iteration\\Path\\Here",
"System.Title": "{{ report.crash_site }} - {{ report.executable }}",
"Microsoft.VSTS.TCM.ReproSteps": "This is my call stack: <ul> {% for item in report.call_stack %} <li> {{ item }} </li> {% endfor %} </ul>"
"Microsoft.VSTS.TCM.ReproSteps": "This is my call stack: <ul> {{ for item in report.call_stack }} <li> {{ item }} </li> {{ endfor }} </ul>"
},
"comment": "This is my comment. {{ report.input_sha256 }} {{ input_url }} <br> <pre>{{ repro_cmd }}</pre>",
"unique_fields": ["System.Title", "System.AreaPath"],
@ -140,7 +140,7 @@ onefuzz notifications create_ado oft-my-demo-job-reports \
Microsoft.VSTS.Scheduling.StoryPoints=1 \
"System.IterationPath=Iteration\\Path\\Here" \
"System.Title={{ report.crash_site }} - {{ report.executable }}" \
"Microsoft.VSTS.TCM.ReproSteps=This is my call stack: <ul> {% for item in report.call_stack %} <li> {{ item }} </li> {% endfor %} </ul>" \
"Microsoft.VSTS.TCM.ReproSteps=This is my call stack: <ul> {{ for item in report.call_stack }} <li> {{ item }} </li> {{ end }} </ul>" \
--comment "This is my comment. {{ report.input_sha256 }} {{ input_url }}" \
--on_dup_comment "Another <a href='{{ input_url }}'>POC</a> was found in <a href='{{ target_url }}'>target</a>" \
--on_dup_set_state Resolved=Active \

View File

@ -42,6 +42,8 @@
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.SignalRService" Version="1.7.0" />
<PackageReference Include="TaskTupleAwaiter" Version="2.0.0" />
<PackageReference Include="Scriban" Version="5.5.0" />
<PackageReference Include="Octokit" Version="2.0.1" />
<PackageReference Include="Microsoft.TeamFoundationServer.Client" Version="19.209.0-preview" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">

View File

@ -22,9 +22,9 @@ public class QueueFileChanges {
_notificationOperations = notificationOperations;
}
//[Function("QueueFileChanges")]
[Function("QueueFileChanges")]
public async Async.Task Run(
[QueueTrigger("file-changes-refactored", Connection = "AzureWebJobsStorage")] string msg,
[QueueTrigger("file-changes", Connection = "AzureWebJobsStorage")] string msg,
int dequeueCount) {
var fileChangeEvent = JsonSerializer.Deserialize<JsonDocument>(msg, EntityConverter.GetJsonSerializerOptions());
var lastTry = dequeueCount == MAX_DEQUEUE_COUNT;
@ -44,10 +44,10 @@ public class QueueFileChanges {
return;
}
await file_added(_log, fileChangeEvent, lastTry);
await FileAdded(_log, fileChangeEvent, lastTry);
}
private async Async.Task file_added(ILogTracer log, JsonDocument fileChangeEvent, bool failTaskOnTransientError) {
private async Async.Task FileAdded(ILogTracer log, JsonDocument fileChangeEvent, bool failTaskOnTransientError) {
var data = fileChangeEvent.RootElement.GetProperty("data");
var url = data.GetProperty("url").GetString()!;
var parts = url.Split("/").Skip(3).ToList();

View File

@ -84,7 +84,11 @@ public record ProxyHeartbeat
Guid ProxyId,
List<Forward> Forwards,
DateTimeOffset TimeStamp
);
) {
public override string ToString() {
return JsonSerializer.Serialize(this);
}
};
public record Node
(
@ -412,7 +416,7 @@ public record Notification(
public record BlobRef(
string Account,
Container container,
Container Container,
string Name
);
@ -470,21 +474,43 @@ public class NotificationTemplateConverter : JsonConverter<NotificationTemplate>
public override NotificationTemplate? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
using var templateJson = JsonDocument.ParseValue(ref reader);
try {
return templateJson.Deserialize<AdoTemplate>(options);
} catch (JsonException) {
return ValidateDeserialization(templateJson.Deserialize<AdoTemplate>(options));
} catch (Exception ex) when (
ex is JsonException
|| ex is ArgumentNullException
|| ex is ArgumentOutOfRangeException
) {
}
try {
return templateJson.Deserialize<TeamsTemplate>(options);
} catch (JsonException) {
return ValidateDeserialization(templateJson.Deserialize<TeamsTemplate>(options));
} catch (Exception ex) when (
ex is JsonException
|| ex is ArgumentNullException
|| ex is ArgumentOutOfRangeException
) {
}
try {
return templateJson.Deserialize<GithubIssuesTemplate>(options);
} catch (JsonException) {
return ValidateDeserialization(templateJson.Deserialize<GithubIssuesTemplate>(options));
} catch (Exception ex) when (
ex is JsonException
|| ex is ArgumentNullException
|| ex is ArgumentOutOfRangeException
) {
}
throw new JsonException("Unsupported notification template");
var expectedTemplateTypes = new List<Type> {
typeof(AdoTemplate),
typeof(TeamsTemplate),
typeof(GithubIssuesTemplate)
}
.Select(type => type.ToString());
throw new JsonException($"Unsupported notification template. Could not deserialize {templateJson} into one of the following template types: {string.Join(", ", expectedTemplateTypes)}");
}
public override void Write(Utf8JsonWriter writer, NotificationTemplate value, JsonSerializerOptions options) {
@ -499,14 +525,38 @@ public class NotificationTemplateConverter : JsonConverter<NotificationTemplate>
}
}
private static T ValidateDeserialization<T>(T? obj) {
if (obj == null) {
throw new ArgumentNullException($"Failed to deserialize type: {typeof(T)}. It was null.");
}
var nonNullableParameters = obj.GetType().GetConstructors().First().GetParameters()
.Where(parameter => !parameter.HasDefaultValue)
.Select(parameter => parameter.Name)
.Where(pName => pName != null)
.ToHashSet();
var nullProperties = obj.GetType().GetProperties()
.Where(property => property.GetValue(obj) == null)
.Select(property => property.Name)
.ToHashSet<Endpoint>();
var nullNonNullableProperties = nonNullableParameters.Intersect(nullProperties);
if (nullNonNullableProperties.Any()) {
throw new ArgumentOutOfRangeException($"Failed to deserialize type: {obj.GetType()}. The following non nullable properties are missing values: {string.Join(", ", nullNonNullableProperties)}");
}
return obj;
}
}
public record ADODuplicateTemplate(
List<string> Increment,
string? Comment,
Dictionary<string, string> SetState,
Dictionary<string, string> AdoFields
Dictionary<string, string> AdoFields,
string? Comment = null
);
public record AdoTemplate(
@ -515,27 +565,26 @@ public record AdoTemplate(
string Project,
string Type,
List<string> UniqueFields,
string? Comment,
Dictionary<string, string> AdoFields,
ADODuplicateTemplate OnDuplicate
ADODuplicateTemplate OnDuplicate,
string? Comment = null
) : NotificationTemplate;
public record TeamsTemplate(SecretData<string> Url) : NotificationTemplate;
public record GithubAuth(string User, string PersonalAccessToken);
public record GithubIssueSearch(
string? Author,
GithubIssueState? State,
List<GithubIssueSearchMatch> FieldMatch,
[property: JsonPropertyName("string")] String str
[property: JsonPropertyName("string")] String str,
string? Author = null,
GithubIssueState? State = null
);
public record GithubIssueDuplicate(
string? Comment,
List<string> Labels,
bool Reopen
bool Reopen,
string? Comment = null
);

View File

@ -102,6 +102,9 @@ public class Program {
.AddScoped<INodeMessageOperations, NodeMessageOperations>()
.AddScoped<IRequestHandling, RequestHandling>()
.AddScoped<IImageOperations, ImageOperations>()
.AddScoped<ITeams, Teams>()
.AddScoped<IGithubIssues, GithubIssues>()
.AddScoped<IAdo, Ado>()
.AddScoped<IOnefuzzContext, OnefuzzContext>()
.AddScoped<IEndpointAuthorization, EndpointAuthorization>()
.AddScoped<INodeMessageOperations, NodeMessageOperations>()

View File

@ -29,6 +29,8 @@ public interface IContainers {
public Async.Task<Uri> AddContainerSasUrl(Uri uri, TimeSpan? duration = null);
public Async.Task<Dictionary<Container, IDictionary<string, string>>> GetContainers(StorageType corpus);
public string AuthDownloadUrl(Container container, string filename);
}
public class Containers : IContainers {
@ -215,4 +217,14 @@ public class Containers : IContainers {
return new(data.SelectMany(x => x));
}
public string AuthDownloadUrl(Container container, string filename) {
var instance = _config.OneFuzzInstance;
var queryString = System.Web.HttpUtility.ParseQueryString(string.Empty);
queryString.Add("container", container.String);
queryString.Add("filename", filename);
return $"{instance}/api/download?{queryString}";
}
}

View File

@ -395,7 +395,7 @@ public class Extensions : IExtensions {
BlobSasPermissions.Read
),
await _context.Containers.GetFileSasUrl(
report?.InputBlob?.container!,
report?.InputBlob?.Container!,
report?.InputBlob?.Name!,
StorageType.Corpus,
BlobSasPermissions.Read

View File

@ -1,6 +1,5 @@
using System.Text.Json;
using ApiService.OneFuzzLib.Orm;
using Azure.Data.Tables;
using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
@ -36,7 +35,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
done.Add(notification.Config);
if (notification.Config is TeamsTemplate teamsTemplate) {
NotifyTeams(teamsTemplate, container, filename, reportOrRegression!);
await _context.Teams.NotifyTeams(teamsTemplate, container, filename, reportOrRegression!);
}
if (reportOrRegression == null) {
@ -44,11 +43,11 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
}
if (notification.Config is AdoTemplate adoTemplate) {
NotifyAdo(adoTemplate, container, filename, reportOrRegression, failTaskOnTransientError);
await _context.Ado.NotifyAdo(adoTemplate, container, filename, reportOrRegression, failTaskOnTransientError);
}
if (notification.Config is GithubIssuesTemplate githubIssuesTemplate) {
GithubIssue(githubIssuesTemplate, container, filename, reportOrRegression);
await _context.GithubIssues.GithubIssue(githubIssuesTemplate, container, filename, reportOrRegression);
}
}
@ -78,7 +77,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
}
public IAsyncEnumerable<Notification> GetNotifications(Container container) {
return QueryAsync(filter: TableClient.CreateQueryFilter($"container eq {container.String}"));
return SearchByRowKeys(new[] { container.String });
}
public IAsyncEnumerable<(Task, IEnumerable<Container>)> GetQueueTasks() {
@ -140,16 +139,4 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
_logTracer.Error($"unable to find crash_report or no repro entry for report: {JsonSerializer.Serialize(report)}");
return null;
}
private void GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport report) {
throw new NotImplementedException();
}
private void NotifyAdo(AdoTemplate config, Container container, string filename, IReport report, bool failTaskOnTransientError) {
throw new NotImplementedException();
}
private void NotifyTeams(TeamsTemplate config, Container container, string filename, IReport report) {
throw new NotImplementedException();
}
}

View File

@ -43,6 +43,9 @@ public interface IOnefuzzContext {
ISubnet Subnet { get; }
IImageOperations ImageOperations { get; }
EntityConverter EntityConverter { get; }
ITeams Teams { get; }
IGithubIssues GithubIssues { get; }
IAdo Ado { get; }
}
public class OnefuzzContext : IOnefuzzContext {
@ -89,4 +92,7 @@ public class OnefuzzContext : IOnefuzzContext {
public ISubnet Subnet => _serviceProvider.GetRequiredService<ISubnet>();
public IImageOperations ImageOperations => _serviceProvider.GetRequiredService<IImageOperations>();
public EntityConverter EntityConverter => _serviceProvider.GetRequiredService<EntityConverter>();
public ITeams Teams => _serviceProvider.GetRequiredService<ITeams>();
public IGithubIssues GithubIssues => _serviceProvider.GetRequiredService<IGithubIssues>();
public IAdo Ado => _serviceProvider.GetRequiredService<IAdo>();
}

View File

@ -59,18 +59,6 @@ public class Reports : IReports {
}
return regressionReport;
}
private IReport? ParseReportOrRegression(IEnumerable<byte> content, string? filePath, bool expectReports = false) {
try {
var str = System.Text.Encoding.UTF8.GetString(content.ToArray());
return ParseReportOrRegression(str, filePath, expectReports);
} catch (Exception e) {
if (expectReports) {
_log.Error($"unable to parse report ({filePath}): unicode decode of report failed - {e.Message} {e.StackTrace}");
}
return null;
}
}
}
public interface IReport { };

View File

@ -248,7 +248,7 @@ public class Scheduler : IScheduler {
});
}
static Container GetSetupContainer(TaskConfig config) {
public static Container GetSetupContainer(TaskConfig config) {
foreach (var container in config.Containers ?? throw new Exception("Missing containers")) {
if (container.Type == ContainerType.Setup) {

View File

@ -0,0 +1,283 @@
using System.Text.Json;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
namespace Microsoft.OneFuzz.Service;
public interface IAdo {
public Async.Task NotifyAdo(AdoTemplate config, Container container, string filename, IReport reportable, bool failTaskOnTransientError);
}
public class Ado : NotificationsBase, IAdo {
public Ado(ILogTracer logTracer, IOnefuzzContext context) : base(logTracer, context) {
}
public async Async.Task NotifyAdo(AdoTemplate config, Container container, string filename, IReport reportable, bool failTaskOnTransientError) {
if (reportable is RegressionReport) {
_logTracer.Info($"ado integration does not support regression report. container:{container} filename:{filename}");
return;
}
var report = (Report)reportable;
var notificationInfo = @$"job_id:{report.JobId} task_id:{report.TaskId}
container:{container} filename:{filename}";
_logTracer.Info($"notify ado: {notificationInfo}");
try {
var ado = await AdoConnector.AdoConnectorCreator(_context, container, filename, config, report, _logTracer);
await ado.Process(notificationInfo);
} catch (Exception e) {
/*
TODO: Catch these
AzureDevOpsAuthenticationError,
AzureDevOpsClientError,
AzureDevOpsServiceError,
AzureDevOpsClientRequestError,
ValueError,
*/
if (!failTaskOnTransientError && IsTransient(e)) {
_logTracer.Error($"transient ADO notification failure {notificationInfo}");
throw;
} else {
await FailTask(report, e);
}
}
}
private static bool IsTransient(Exception e) {
var errorCodes = new List<string>()
{
//TF401349: An unexpected error has occurred, please verify your request and try again.
"TF401349",
//TF26071: This work item has been changed by someone else since you opened it. You will need to refresh it and discard your changes.
"TF26071",
};
var errorStr = e.ToString();
return errorCodes.Any(code => errorStr.Contains(code));
}
class AdoConnector {
private readonly AdoTemplate _config;
private readonly Renderer _renderer;
private readonly string _project;
private readonly WorkItemTrackingHttpClient _client;
private readonly Uri _instanceUrl;
private readonly ILogTracer _logTracer;
public static async Async.Task<AdoConnector> AdoConnectorCreator(IOnefuzzContext context, Container container, string filename, AdoTemplate config, Report report, ILogTracer logTracer, Renderer? renderer = null) {
renderer ??= await Renderer.ConstructRenderer(context, container, filename, report);
var instanceUrl = context.Creds.GetInstanceUrl();
var project = await renderer.Render(config.Project, instanceUrl);
var authToken = await context.SecretsOperations.GetSecretStringValue(config.AuthToken);
var client = GetAdoClient(config.BaseUrl, authToken!);
return new AdoConnector(container, filename, config, report, renderer, project!, client, instanceUrl, logTracer);
}
private static WorkItemTrackingHttpClient GetAdoClient(Uri baseUrl, string token) {
return new WorkItemTrackingHttpClient(baseUrl, new VssBasicCredential("PAT", token));
}
public AdoConnector(Container container, string filename, AdoTemplate config, Report report, Renderer renderer, string project, WorkItemTrackingHttpClient client, Uri instanceUrl, ILogTracer logTracer) {
_config = config;
_renderer = renderer;
_project = project;
_client = client;
_instanceUrl = instanceUrl;
_logTracer = logTracer;
}
public async Async.Task<string> Render(string template) {
return await _renderer.Render(template, _instanceUrl);
}
public async IAsyncEnumerable<WorkItem> ExistingWorkItems() {
var filters = new Dictionary<string, string>();
foreach (var key in _config.UniqueFields) {
var filter = string.Empty;
if (string.Equals("System.TeamProject", key)) {
filter = await Render(_config.Project);
} else {
filter = await Render(_config.AdoFields[key]);
}
filters.Add(key.ToLowerInvariant(), filter);
}
var validFields = await GetValidFields(filters["system.teamproject"]);
var postQueryFilter = new Dictionary<string, string>();
/*
# WIQL (Work Item Query Language) is an SQL like query language that
# doesn't support query params, safe quoting, or any other SQL-injection
# protection mechanisms.
#
# As such, build the WIQL with a those fields we can pre-determine are
# "safe" and otherwise use post-query filtering.
*/
var parts = new List<string>();
foreach (var key in filters.Keys) {
//# Only add pre-system approved fields to the query
if (!validFields.Contains(key)) {
postQueryFilter.Add(key, filters[key]);
continue;
}
/*
# WIQL supports wrapping values in ' or " and escaping ' by doubling it
#
# For this System.Title: hi'there
# use this query fragment: [System.Title] = 'hi''there'
#
# For this System.Title: hi"there
# use this query fragment: [System.Title] = 'hi"there'
#
# For this System.Title: hi'"there
# use this query fragment: [System.Title] = 'hi''"there'
*/
var single = "'";
parts.Add($"[{key}] = '{filters[key].Replace(single, single + single)}'");
}
var query = "select [System.Id] from WorkItems";
if (parts != null && parts.Any()) {
query += " where " + string.Join(" AND ", parts);
}
var wiql = new Wiql() {
Query = query
};
foreach (var workItemReference in (await _client.QueryByWiqlAsync(wiql)).WorkItems) {
var item = await _client.GetWorkItemAsync(_project, workItemReference.Id, expand: WorkItemExpand.Fields);
var loweredFields = item.Fields.ToDictionary(kvp => kvp.Key.ToLowerInvariant(), kvp => JsonSerializer.Serialize(kvp.Value));
if (postQueryFilter.Any() && !postQueryFilter.All(kvp => {
var lowerKey = kvp.Key.ToLowerInvariant();
return loweredFields.ContainsKey(lowerKey) && loweredFields[lowerKey] == postQueryFilter[kvp.Key];
})) {
continue;
}
yield return item;
}
}
public async Async.Task UpdateExisting(WorkItem item, string notificationInfo) {
if (_config.OnDuplicate.Comment != null) {
var comment = await Render(_config.OnDuplicate.Comment);
await _client.AddCommentAsync(
new CommentCreate() {
Text = comment
},
_project,
(int)(item.Id!)
);
}
var document = new JsonPatchDocument();
foreach (var field in _config.OnDuplicate.Increment) {
var value = item.Fields.ContainsKey(field) ? int.Parse(JsonSerializer.Serialize(item.Fields[field])) : 0;
value++;
document.Add(new JsonPatchOperation() {
Operation = VisualStudio.Services.WebApi.Patch.Operation.Replace,
Path = $"/fields/{field}",
Value = value.ToString()
});
}
foreach (var field in _config.OnDuplicate.AdoFields) {
var fieldValue = await Render(_config.OnDuplicate.AdoFields[field.Key]);
document.Add(new JsonPatchOperation() {
Operation = VisualStudio.Services.WebApi.Patch.Operation.Replace,
Path = $"/fields/{field}",
Value = fieldValue
});
}
var systemState = JsonSerializer.Serialize(item.Fields["System.State"]);
if (_config.OnDuplicate.SetState.ContainsKey(systemState)) {
document.Add(new JsonPatchOperation() {
Operation = VisualStudio.Services.WebApi.Patch.Operation.Replace,
Path = "/fields/System.State",
Value = _config.OnDuplicate.SetState[systemState]
});
}
if (document.Any()) {
await _client.UpdateWorkItemAsync(document, _project, (int)(item.Id!));
_logTracer.Info($"notify ado: updated work item {item.Id} - {notificationInfo}");
} else {
_logTracer.Info($"notify ado: no update for work item {item.Id} - {notificationInfo}");
}
}
private async Async.Task<List<string>> GetValidFields(string? project) {
return (await _client.GetFieldsAsync(project, expand: GetFieldsExpand.ExtensionFields))
.Select(field => field.ReferenceName.ToLowerInvariant())
.ToList();
}
private async Async.Task<WorkItem> CreateNew() {
var (taskType, document) = await RenderNew();
var entry = await _client.CreateWorkItemAsync(document, _project, taskType);
if (_config.Comment != null) {
var comment = await Render(_config.Comment);
await _client.AddCommentAsync(
new CommentCreate() {
Text = comment,
},
_project,
(int)(entry.Id!)
);
}
return entry;
}
private async Async.Task<(string, JsonPatchDocument)> RenderNew() {
var taskType = await Render(_config.Type);
var document = new JsonPatchDocument();
if (!_config.AdoFields.ContainsKey("System.Tags")) {
document.Add(new JsonPatchOperation() {
Operation = VisualStudio.Services.WebApi.Patch.Operation.Add,
Path = "/fields/System.Tags",
Value = "Onefuzz"
});
}
foreach (var field in _config.AdoFields.Keys) {
var value = await Render(_config.AdoFields[field]);
if (string.Equals(field, "System.Tags")) {
value += ";Onefuzz";
}
document.Add(new JsonPatchOperation() {
Operation = VisualStudio.Services.WebApi.Patch.Operation.Add,
Path = $"/fields/{field}",
Value = value
});
}
return (taskType, document);
}
public async Async.Task Process(string notificationInfo) {
var seen = false;
await foreach (var workItem in ExistingWorkItems()) {
await UpdateExisting(workItem, notificationInfo);
seen = true;
}
if (!seen) {
var entry = await CreateNew();
_logTracer.Info($"notify ado: created new work item {entry.Id} - {notificationInfo}");
}
}
}
}

View File

@ -0,0 +1,160 @@
using Octokit;
namespace Microsoft.OneFuzz.Service;
public interface IGithubIssues {
Async.Task GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport? reportable);
}
public class GithubIssues : NotificationsBase, IGithubIssues {
public GithubIssues(ILogTracer logTracer, IOnefuzzContext context)
: base(logTracer, context) { }
public async Async.Task GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport? reportable) {
if (reportable == null) {
return;
}
if (reportable is RegressionReport) {
_logTracer.Info($"github issue integration does not support regression reports. container:{container} filename:{filename}");
return;
}
var report = (Report)reportable;
try {
await Process(config, container, filename, report);
} catch (ApiException e) {
await FailTask(report, e);
}
}
private async Async.Task Process(GithubIssuesTemplate config, Container container, string filename, Report report) {
var renderer = await Renderer.ConstructRenderer(_context, container, filename, report);
var handler = await GithubConnnector.GithubConnnectorCreator(config, container, filename, renderer, _context.Creds.GetInstanceUrl(), _context, _logTracer);
await handler.Process();
}
class GithubConnnector {
private readonly GitHubClient _gh;
private readonly GithubIssuesTemplate _config;
private readonly Renderer _renderer;
private readonly Uri _instanceUrl;
private readonly ILogTracer _logTracer;
public static async Async.Task<GithubConnnector> GithubConnnectorCreator(GithubIssuesTemplate config, Container container, string filename, Renderer renderer, Uri instanceUrl, IOnefuzzContext context, ILogTracer logTracer) {
var auth = config.Auth.Secret switch {
SecretAddress<GithubAuth> sa => await context.SecretsOperations.GetSecretObj<GithubAuth>(sa.Url),
SecretValue<GithubAuth> sv => sv.Value,
_ => throw new ArgumentException($"Unexpected secret type {config.Auth.Secret.GetType()}")
};
return new GithubConnnector(config, container, filename, renderer, instanceUrl, auth!, logTracer);
}
public GithubConnnector(GithubIssuesTemplate config, Container container, string filename, Renderer renderer, Uri instanceUrl, GithubAuth auth, ILogTracer logTracer) {
_config = config;
_gh = new GitHubClient(new ProductHeaderValue("microsoft/OneFuzz")) {
Credentials = new Credentials(auth.User, auth.PersonalAccessToken)
};
_renderer = renderer;
_instanceUrl = instanceUrl;
_logTracer = logTracer;
}
public async Async.Task Process() {
var issues = await Existing();
if (issues.Any()) {
await Update(issues.First());
} else {
await Create();
}
}
private async Async.Task<string> Render(string field) {
return await _renderer.Render(field, _instanceUrl);
}
private async Async.Task<List<Issue>> Existing() {
var query = new List<string>() {
await Render(_config.UniqueSearch.str),
$"repo:{_config.Organization}/{_config.Repository}"
};
if (_config.UniqueSearch.Author != null) {
query.Add($"author:{await Render(_config.UniqueSearch.Author)}");
}
if (_config.UniqueSearch.State != null) {
query.Add($"state:{_config.UniqueSearch.State}");
}
var title = await Render(_config.Title);
var body = await Render(_config.Body);
var issues = new List<Issue>();
var t = await _gh.Search.SearchIssues(new SearchIssuesRequest(string.Join(' ', query)));
foreach (var issue in t.Items) {
var skip = false;
foreach (var field in _config.UniqueSearch.FieldMatch) {
if (field == GithubIssueSearchMatch.Title && issue.Title != title) {
skip = true;
break;
}
if (field == GithubIssueSearchMatch.Body && issue.Body != body) {
skip = true;
break;
}
}
if (!skip) {
issues.Add(issue);
}
}
return issues;
}
private async Async.Task Update(Issue issue) {
_logTracer.Info($"updating issue: {issue}");
if (_config.OnDuplicate.Comment != null) {
await _gh.Issue.Comment.Create(issue.Repository.Id, issue.Number, await Render(_config.OnDuplicate.Comment));
}
if (_config.OnDuplicate.Labels.Any()) {
var labels = await _config.OnDuplicate.Labels.ToAsyncEnumerable()
.SelectAwait(async label => await Render(label))
.ToArrayAsync();
await _gh.Issue.Labels.ReplaceAllForIssue(issue.Repository.Id, issue.Number, labels);
}
if (_config.OnDuplicate.Reopen && issue.State != ItemState.Open) {
await _gh.Issue.Update(issue.Repository.Id, issue.Number, new IssueUpdate() {
State = ItemState.Open
});
}
}
private async Async.Task Create() {
_logTracer.Info($"creating issue");
var assignees = await _config.Assignees.ToAsyncEnumerable()
.SelectAwait(async assignee => await Render(assignee))
.ToListAsync();
var labels = await _config.Labels.ToAsyncEnumerable()
.SelectAwait(async label => await Render(label))
.ToHashSetAsync();
labels.Add("OneFuzz");
var newIssue = new NewIssue(await Render(_config.Title)) {
Body = await Render(_config.Body),
};
labels.ToList().ForEach(label => newIssue.Labels.Add(label));
assignees.ForEach(assignee => newIssue.Assignees.Add(assignee));
await _gh.Issue.Create(
await Render(_config.Organization),
await Render(_config.Repository),
newIssue
);
}
}
}

View File

@ -0,0 +1,99 @@
using Scriban;
namespace Microsoft.OneFuzz.Service;
public abstract class NotificationsBase {
#pragma warning disable CA1051 // permit visible instance fields
protected readonly ILogTracer _logTracer;
protected readonly IOnefuzzContext _context;
#pragma warning restore CA1051
public NotificationsBase(ILogTracer logTracer, IOnefuzzContext context) {
_logTracer = logTracer;
_context = context;
}
public async Async.Task FailTask(Report report, Exception error) {
_logTracer.Error($"notification failed: job_id:{report.JobId} task_id:{report.TaskId} err:{error}");
var task = await _context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId);
if (task != null) {
await _context.TaskOperations.MarkFailed(task, new Error(ErrorCode.NOTIFICATION_FAILURE, new string[] {
"notification failed",
error.ToString()
}));
}
}
public static string ReplaceFirstSetup(string executable) {
var setup = "setup/";
int index = executable.IndexOf(setup, StringComparison.InvariantCultureIgnoreCase);
var setupFileName = (index < 0) ? executable : executable.Remove(index, setup.Length);
return setupFileName;
}
protected class Renderer {
private readonly Report _report;
private readonly Container _container;
private readonly string _filename;
private readonly TaskConfig _taskConfig;
private readonly JobConfig _jobConfig;
private readonly Uri _targetUrl;
private readonly Uri _inputUrl;
private readonly Uri _reportUrl;
public static async Async.Task<Renderer> ConstructRenderer(IOnefuzzContext context, Container container, string filename, Report report, Task? task = null, Job? job = null, Uri? targetUrl = null, Uri? inputUrl = null, Uri? reportUrl = null) {
task ??= await context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId);
task.EnsureNotNull($"invalid task {report.TaskId}");
job ??= await context.JobOperations.Get(report.JobId);
job.EnsureNotNull($"invalid job {report.JobId}");
if (targetUrl == null) {
var setupContainer = Scheduler.GetSetupContainer(task?.Config!);
targetUrl = new Uri(context.Containers.AuthDownloadUrl(setupContainer, ReplaceFirstSetup(report.Executable)));
}
if (reportUrl == null) {
reportUrl = new Uri(context.Containers.AuthDownloadUrl(container, filename));
}
if (inputUrl == null && report.InputBlob != null) {
inputUrl = new Uri(context.Containers.AuthDownloadUrl(report.InputBlob.Container, report.InputBlob.Name));
}
return new Renderer(container, filename, report, task!, job!, targetUrl, inputUrl!, reportUrl);
}
public Renderer(Container container, string filename, Report report, Task task, Job job, Uri targetUrl, Uri inputUrl, Uri reportUrl) {
_report = report;
_container = container;
_filename = filename;
_taskConfig = task.Config;
_jobConfig = job.Config;
_reportUrl = reportUrl;
_targetUrl = targetUrl;
_inputUrl = inputUrl;
}
// 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) {
var template = Template.Parse(templateString);
if (template != null) {
return await template.RenderAsync(new {
Report = this._report,
Task = this._taskConfig,
Job = this._jobConfig,
ReportUrl = this._reportUrl,
InputUrl = _inputUrl,
TargetUrl = _targetUrl,
ReportContainer = _container,
ReportFilename = _filename,
ReproCmd = $"onefuzz --endpoint {instanceUrl} repro create_and_connect {_container} {_filename}"
});
}
return string.Empty;
}
}
}

View File

@ -0,0 +1,124 @@
using System.Net.Http;
using System.Text.Json;
namespace Microsoft.OneFuzz.Service;
public interface ITeams {
Async.Task NotifyTeams(TeamsTemplate config, Container container, string filename, IReport reportOrRegression);
}
public class Teams : ITeams {
private readonly ILogTracer _logTracer;
private readonly IOnefuzzContext _context;
private readonly IHttpClientFactory _httpFactory;
public Teams(IHttpClientFactory httpFactory, ILogTracer logTracer, IOnefuzzContext context) {
_logTracer = logTracer;
_context = context;
_httpFactory = httpFactory;
}
private static string CodeBlock(string data) {
data = data.Replace("`", "``");
return $"\n```\n{data}\n```\n";
}
private async Async.Task SendTeamsWebhook(TeamsTemplate config, string title, IList<Dictionary<string, string>> facts, string? text) {
title = MarkdownEscape(title);
var sections = new List<Dictionary<string, object>>() {
new() {
{"activityTitle", title},
{"facts", facts}
}
};
if (text != null) {
sections.Add(new() {
{ "text", text }
});
}
var message = new Dictionary<string, object>() {
{"@type", "MessageCard"},
{"@context", "https://schema.org/extensions"},
{"summary", title},
{"sections", sections}
};
var configUrl = await _context.SecretsOperations.GetSecretStringValue(config.Url);
var client = new Request(_httpFactory.CreateClient());
var response = await client.Post(url: new Uri(configUrl!), JsonSerializer.Serialize(message));
if (response == null || !response.IsSuccessStatusCode) {
_logTracer.Error($"webhook failed {response?.StatusCode} {response?.Content}");
}
}
public async Async.Task NotifyTeams(TeamsTemplate config, Container container, string filename, IReport reportOrRegression) {
var facts = new List<Dictionary<string, string>>();
string? text = null;
var title = string.Empty;
if (reportOrRegression is Report report) {
var task = await _context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId);
if (task == null) {
_logTracer.Error($"report with invalid task {report.JobId}:{report.TaskId}");
return;
}
title = $"new crash in {report.Executable}: {report.CrashType} @ {report.CrashSite}";
var links = new List<string> {
$"[report]({_context.Containers.AuthDownloadUrl(container, filename)})"
};
var setupContainer = Scheduler.GetSetupContainer(task.Config);
if (setupContainer != null) {
var setupFileName = NotificationsBase.ReplaceFirstSetup(report.Executable);
links.Add(
$"[executable]({_context.Containers.AuthDownloadUrl(setupContainer, setupFileName)})"
);
}
if (report.InputBlob != null) {
links.Add(
$"[input]({_context.Containers.AuthDownloadUrl(report.InputBlob.Container, report.InputBlob.Name)})"
);
}
facts.AddRange(new List<Dictionary<string, string>> {
new() {{"name", "Files"}, {"value", string.Join(" | ", links)}},
new() {
{"name", "Task"},
{"value", MarkdownEscape(
$"job_id: {report.JobId} task_id: {report.TaskId}"
)}
},
new() {
{"name", "Repro"},
{"value", CodeBlock($"onefuzz repro create_and_connect {container} {filename}")}
}
});
text = "## Call Stack\n" + string.Join("\n", report.CallStack.Select(cs => CodeBlock(cs)));
} else {
title = "new file found";
var fileUrl = _context.Containers.AuthDownloadUrl(container, filename);
facts.Add(new Dictionary<string, string>() {
{"name", "file"},
{"value", $"[{MarkdownEscape(container.String)}/{MarkdownEscape(filename)}]({fileUrl})"}
});
}
await SendTeamsWebhook(config, title, facts, text);
}
private static string MarkdownEscape(string data) {
var values = "\\*_{}[]()#+-.!";
foreach (var c in values) {
data = data.Replace(c.ToString(), "\\" + c);
}
data = data.Replace("`", "``");
return data;
}
}

View File

@ -302,6 +302,25 @@
"System.Text.Encodings.Web": "5.0.1"
}
},
"Microsoft.TeamFoundationServer.Client": {
"type": "Direct",
"requested": "[19.209.0-preview, )",
"resolved": "19.209.0-preview",
"contentHash": "dglVgITWfsps8pWA//2mBGVt/keD3UGdAVBXd50k9nVZiThUwWnaAoUzRf4fay/avLGXdvfkz6x9dBf6zGtfxg==",
"dependencies": {
"Microsoft.AspNet.WebApi.Client": "5.2.7",
"Microsoft.TeamFoundation.DistributedTask.Common.Contracts": "[19.209.0-preview]",
"Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]",
"Newtonsoft.Json": "12.0.3",
"System.ComponentModel.Annotations": "4.4.1"
}
},
"Octokit": {
"type": "Direct",
"requested": "[2.0.1, )",
"resolved": "2.0.1",
"contentHash": "JVlfUY+sfItl6RSyVKDJTutuy28cDydUwKKfzcelwNMor2Sa18pYVKna6phO8lug1b+ep+pcuFh/FPayuImsQw=="
},
"Scriban": {
"type": "Direct",
"requested": "[5.5.0, )",
@ -399,6 +418,15 @@
"System.Diagnostics.DiagnosticSource": "5.0.0"
}
},
"Microsoft.AspNet.WebApi.Client": {
"type": "Transitive",
"resolved": "5.2.7",
"contentHash": "/76fAHknzvFqbznS6Uj2sOyE9rJB3PltY+f53TH8dX9RiGhk02EhuFCWljSj5nnqKaTsmma8DFR50OGyQ4yJ1g==",
"dependencies": {
"Newtonsoft.Json": "10.0.1",
"Newtonsoft.Json.Bson": "1.0.1"
}
},
"Microsoft.AspNetCore.Cryptography.Internal": {
"type": "Transitive",
"resolved": "5.0.8",
@ -869,6 +897,30 @@
"Newtonsoft.Json": "10.0.3"
}
},
"Microsoft.TeamFoundation.DistributedTask.Common.Contracts": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "32lLZPU8pZg+mVfA2smHso6fhWPSFXJPPyawvOFsFoNz9Yj5y2fsAR7O4zPwE3c/z2zzi8BMfiXRKOcbW6cdIg==",
"dependencies": {
"Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]"
}
},
"Microsoft.VisualStudio.Services.Client": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "eQWZb5BhtOgywARvfHGGZsYuuZvFmJiXyE7P/EqKTLUplrUFmSVxo0J/KUC8GWJWmdarxH2vXZTAz9uW7BwRDQ==",
"dependencies": {
"Microsoft.AspNet.WebApi.Client": "5.2.7",
"Newtonsoft.Json": "12.0.3",
"System.Configuration.ConfigurationManager": "4.4.1",
"System.Data.SqlClient": "4.4.2",
"System.Security.Cryptography.Cng": "4.4.0",
"System.Security.Cryptography.OpenSsl": "4.4.0",
"System.Security.Cryptography.ProtectedData": "4.4.0",
"System.Security.Principal.Windows": "4.4.1",
"System.Xml.XPath.XmlDocument": "4.3.0"
}
},
"Microsoft.Win32.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@ -949,15 +1001,16 @@
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "10.0.3",
"contentHash": "hSXaFmh7hNCuEoC4XNY5DrRkLDzYHqPx/Ik23R4J86Z7PE/Y6YidhG602dFVdLBRSdG6xp9NabH3dXpcoxWvww==",
"resolved": "12.0.3",
"contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg=="
},
"Newtonsoft.Json.Bson": {
"type": "Transitive",
"resolved": "1.0.1",
"contentHash": "5PYT/IqQ+UK31AmZiSS102R6EsTo+LGTSI8bp7WAUqDKaF4wHXD8U9u4WxTI1vc64tYi++8p3dk3WWNqPFgldw==",
"dependencies": {
"Microsoft.CSharp": "4.3.0",
"NETStandard.Library": "1.6.1",
"System.ComponentModel.TypeConverter": "4.3.0",
"System.Runtime.Serialization.Formatters": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
"Newtonsoft.Json": "10.0.1"
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
@ -984,6 +1037,16 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==",
"dependencies": {
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
}
},
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1067,6 +1130,21 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
},
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
},
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
},
"System.AppContext": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1114,71 +1192,17 @@
"System.Threading.Tasks": "4.3.0"
}
},
"System.Collections.NonGeneric": {
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==",
"dependencies": {
"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"
}
"resolved": "4.4.1",
"contentHash": "ToiYqSCioqhtspq2O/jYKtyTC/T0uwWHBTYlzCi6PRbSSHArN1IaRWeHffDamvms5sye5FDUWCfNZgubQpNRsA=="
},
"System.Collections.Specialized": {
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==",
"resolved": "4.4.1",
"contentHash": "jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"dependencies": {
"System.Collections.NonGeneric": "4.3.0",
"System.Globalization": "4.3.0",
"System.Globalization.Extensions": "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"
}
},
"System.ComponentModel": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==",
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.ComponentModel.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==",
"dependencies": {
"System.ComponentModel": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.ComponentModel.TypeConverter": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Collections.NonGeneric": "4.3.0",
"System.Collections.Specialized": "4.3.0",
"System.ComponentModel": "4.3.0",
"System.ComponentModel.Primitives": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "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"
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"System.Console": {
@ -1193,6 +1217,17 @@
"System.Text.Encoding": "4.3.0"
}
},
"System.Data.SqlClient": {
"type": "Transitive",
"resolved": "4.4.2",
"contentHash": "Bv5J2EBAdP7FSgehKYN4O6iw1AaZrw4rFFqwt9vZSjRvC70FpwP2d9UG4aTaI2wh3vfrBKK+tjewowGM2Y6c1w==",
"dependencies": {
"Microsoft.Win32.Registry": "4.4.0",
"System.Security.Principal.Windows": "4.4.0",
"System.Text.Encoding.CodePages": "4.4.0",
"runtime.native.System.Data.SqlClient.sni": "4.4.0"
}
},
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1646,27 +1681,6 @@
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Runtime.Serialization.Formatters": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0"
}
},
"System.Runtime.Serialization.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==",
"dependencies": {
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
@ -1760,22 +1774,10 @@
},
"System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
"resolved": "4.4.0",
"contentHash": "is11qLXIHKIvbTipyB1an8FT1ZKavmgf/qJUSIz7ZP830ALRRhPSt5NhplW0/wMk0tNDQWQLluVap6HsQN4HMg==",
"dependencies": {
"System.Collections": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.Numerics": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
"Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
@ -1879,6 +1881,14 @@
"System.Runtime": "4.3.0"
}
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
@ -2014,6 +2024,39 @@
"System.Threading": "4.3.0",
"System.Xml.ReaderWriter": "4.3.0"
}
},
"System.Xml.XPath": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "v1JQ5SETnQusqmS3RwStF7vwQ3L02imIzl++sewmt23VGygix04pEH+FCj1yWb+z4GDzKiljr1W7Wfvrx0YwgA==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "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",
"System.Xml.ReaderWriter": "4.3.0"
}
},
"System.Xml.XPath.XmlDocument": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "A/uxsWi/Ifzkmd4ArTLISMbfFs6XpRPsXZonrIqyTY70xi8t+mDtvSM5Os0RqyRDobjMBwIDHDL4NOIbkDwf7A==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "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",
"System.Xml.ReaderWriter": "4.3.0",
"System.Xml.XPath": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
}
}
}
}

View File

@ -117,6 +117,9 @@ public sealed class TestContext : IOnefuzzContext {
public ISubnet Subnet => throw new NotImplementedException();
public IImageOperations ImageOperations => throw new NotImplementedException();
public ITeams Teams => throw new NotImplementedException();
public IGithubIssues GithubIssues => throw new NotImplementedException();
public IAdo Ado => throw new NotImplementedException();
}

View File

@ -281,6 +281,15 @@
"System.Diagnostics.DiagnosticSource": "5.0.0"
}
},
"Microsoft.AspNet.WebApi.Client": {
"type": "Transitive",
"resolved": "5.2.7",
"contentHash": "/76fAHknzvFqbznS6Uj2sOyE9rJB3PltY+f53TH8dX9RiGhk02EhuFCWljSj5nnqKaTsmma8DFR50OGyQ4yJ1g==",
"dependencies": {
"Newtonsoft.Json": "10.0.1",
"Newtonsoft.Json.Bson": "1.0.1"
}
},
"Microsoft.AspNetCore.Cryptography.Internal": {
"type": "Transitive",
"resolved": "5.0.8",
@ -878,6 +887,26 @@
"Newtonsoft.Json": "10.0.3"
}
},
"Microsoft.TeamFoundation.DistributedTask.Common.Contracts": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "32lLZPU8pZg+mVfA2smHso6fhWPSFXJPPyawvOFsFoNz9Yj5y2fsAR7O4zPwE3c/z2zzi8BMfiXRKOcbW6cdIg==",
"dependencies": {
"Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]"
}
},
"Microsoft.TeamFoundationServer.Client": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "dglVgITWfsps8pWA//2mBGVt/keD3UGdAVBXd50k9nVZiThUwWnaAoUzRf4fay/avLGXdvfkz6x9dBf6zGtfxg==",
"dependencies": {
"Microsoft.AspNet.WebApi.Client": "5.2.7",
"Microsoft.TeamFoundation.DistributedTask.Common.Contracts": "[19.209.0-preview]",
"Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]",
"Newtonsoft.Json": "12.0.3",
"System.ComponentModel.Annotations": "4.4.1"
}
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.1.0",
@ -896,6 +925,22 @@
"Newtonsoft.Json": "9.0.1"
}
},
"Microsoft.VisualStudio.Services.Client": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "eQWZb5BhtOgywARvfHGGZsYuuZvFmJiXyE7P/EqKTLUplrUFmSVxo0J/KUC8GWJWmdarxH2vXZTAz9uW7BwRDQ==",
"dependencies": {
"Microsoft.AspNet.WebApi.Client": "5.2.7",
"Newtonsoft.Json": "12.0.3",
"System.Configuration.ConfigurationManager": "4.4.1",
"System.Data.SqlClient": "4.4.2",
"System.Security.Cryptography.Cng": "4.4.0",
"System.Security.Cryptography.OpenSsl": "4.4.0",
"System.Security.Cryptography.ProtectedData": "4.4.0",
"System.Security.Principal.Windows": "4.4.1",
"System.Xml.XPath.XmlDocument": "4.3.0"
}
},
"Microsoft.Win32.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@ -976,15 +1021,16 @@
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "10.0.3",
"contentHash": "hSXaFmh7hNCuEoC4XNY5DrRkLDzYHqPx/Ik23R4J86Z7PE/Y6YidhG602dFVdLBRSdG6xp9NabH3dXpcoxWvww==",
"resolved": "12.0.3",
"contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg=="
},
"Newtonsoft.Json.Bson": {
"type": "Transitive",
"resolved": "1.0.1",
"contentHash": "5PYT/IqQ+UK31AmZiSS102R6EsTo+LGTSI8bp7WAUqDKaF4wHXD8U9u4WxTI1vc64tYi++8p3dk3WWNqPFgldw==",
"dependencies": {
"Microsoft.CSharp": "4.3.0",
"NETStandard.Library": "1.6.1",
"System.ComponentModel.TypeConverter": "4.3.0",
"System.Runtime.Serialization.Formatters": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
"Newtonsoft.Json": "10.0.1"
}
},
"NuGet.Frameworks": {
@ -992,6 +1038,11 @@
"resolved": "5.11.0",
"contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q=="
},
"Octokit": {
"type": "Transitive",
"resolved": "2.0.1",
"contentHash": "JVlfUY+sfItl6RSyVKDJTutuy28cDydUwKKfzcelwNMor2Sa18pYVKna6phO8lug1b+ep+pcuFh/FPayuImsQw=="
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.2",
@ -1016,6 +1067,16 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==",
"dependencies": {
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
}
},
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1099,6 +1160,21 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
},
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
},
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
},
"Scriban": {
"type": "Transitive",
"resolved": "5.5.0",
@ -1191,6 +1267,11 @@
"System.Runtime": "4.3.0"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.4.1",
"contentHash": "ToiYqSCioqhtspq2O/jYKtyTC/T0uwWHBTYlzCi6PRbSSHArN1IaRWeHffDamvms5sye5FDUWCfNZgubQpNRsA=="
},
"System.ComponentModel.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1225,8 +1306,8 @@
},
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==",
"resolved": "4.4.1",
"contentHash": "jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"dependencies": {
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
@ -1243,6 +1324,17 @@
"System.Text.Encoding": "4.3.0"
}
},
"System.Data.SqlClient": {
"type": "Transitive",
"resolved": "4.4.2",
"contentHash": "Bv5J2EBAdP7FSgehKYN4O6iw1AaZrw4rFFqwt9vZSjRvC70FpwP2d9UG4aTaI2wh3vfrBKK+tjewowGM2Y6c1w==",
"dependencies": {
"Microsoft.Win32.Registry": "4.4.0",
"System.Security.Principal.Windows": "4.4.0",
"System.Text.Encoding.CodePages": "4.4.0",
"runtime.native.System.Data.SqlClient.sni": "4.4.0"
}
},
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1755,27 +1847,6 @@
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Runtime.Serialization.Formatters": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0"
}
},
"System.Runtime.Serialization.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==",
"dependencies": {
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
@ -1869,22 +1940,10 @@
},
"System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
"resolved": "4.4.0",
"contentHash": "is11qLXIHKIvbTipyB1an8FT1ZKavmgf/qJUSIz7ZP830ALRRhPSt5NhplW0/wMk0tNDQWQLluVap6HsQN4HMg==",
"dependencies": {
"System.Collections": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.Numerics": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
"Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
@ -1988,6 +2047,14 @@
"System.Runtime": "4.3.0"
}
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
@ -2124,6 +2191,39 @@
"System.Xml.ReaderWriter": "4.3.0"
}
},
"System.Xml.XPath": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "v1JQ5SETnQusqmS3RwStF7vwQ3L02imIzl++sewmt23VGygix04pEH+FCj1yWb+z4GDzKiljr1W7Wfvrx0YwgA==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "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",
"System.Xml.ReaderWriter": "4.3.0"
}
},
"System.Xml.XPath.XmlDocument": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "A/uxsWi/Ifzkmd4ArTLISMbfFs6XpRPsXZonrIqyTY70xi8t+mDtvSM5Os0RqyRDobjMBwIDHDL4NOIbkDwf7A==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "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",
"System.Xml.ReaderWriter": "4.3.0",
"System.Xml.XPath": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
}
},
"TaskTupleAwaiter": {
"type": "Transitive",
"resolved": "2.0.0",
@ -2177,39 +2277,41 @@
"apiservice": {
"type": "Project",
"dependencies": {
"Azure.Core": "[1.25.0, )",
"Azure.Data.Tables": "[12.5.0, )",
"Azure.Identity": "[1.6.0, )",
"Azure.Messaging.EventGrid": "[4.10.0, )",
"Azure.ResourceManager": "[1.3.1, )",
"Azure.ResourceManager.Compute": "[1.0.0-beta.8, )",
"Azure.ResourceManager.Monitor": "[1.0.0-beta.2, )",
"Azure.ResourceManager.Network": "[1.0.0, )",
"Azure.ResourceManager.Resources": "[1.3.0, )",
"Azure.ResourceManager.Storage": "[1.0.0-beta.11, )",
"Azure.Security.KeyVault.Secrets": "[4.3.0, )",
"Azure.Storage.Blobs": "[12.13.0, )",
"Azure.Storage.Queues": "[12.11.0, )",
"Faithlife.Utility": "[0.12.2, )",
"Microsoft.ApplicationInsights.DependencyCollector": "[2.21.0, )",
"Microsoft.Azure.Functions.Worker": "[1.6.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "[2.1.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.Http": "[3.0.13, )",
"Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "[1.7.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.Storage": "[5.0.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.Timer": "[4.1.0, )",
"Microsoft.Azure.Functions.Worker.Sdk": "[1.3.0, )",
"Microsoft.Azure.Management.Monitor": "[0.28.0-preview, )",
"Microsoft.Azure.Management.OperationalInsights": "[0.24.0-preview, )",
"Microsoft.Extensions.Logging.ApplicationInsights": "[2.21.0, )",
"Microsoft.Graph": "[4.37.0, )",
"Microsoft.Identity.Client": "[4.46.2, )",
"Microsoft.Identity.Web.TokenCache": "[1.23.1, )",
"Scriban": "[5.5.0, )",
"Semver": "[2.1.0, )",
"System.IdentityModel.Tokens.Jwt": "[6.22.1, )",
"System.Linq.Async": "[6.0.1, )",
"TaskTupleAwaiter": "[2.0.0, )"
"Azure.Core": "1.25.0",
"Azure.Data.Tables": "12.5.0",
"Azure.Identity": "1.6.0",
"Azure.Messaging.EventGrid": "4.10.0",
"Azure.ResourceManager": "1.3.1",
"Azure.ResourceManager.Compute": "1.0.0-beta.8",
"Azure.ResourceManager.Monitor": "1.0.0-beta.2",
"Azure.ResourceManager.Network": "1.0.0",
"Azure.ResourceManager.Resources": "1.3.0",
"Azure.ResourceManager.Storage": "1.0.0-beta.11",
"Azure.Security.KeyVault.Secrets": "4.3.0",
"Azure.Storage.Blobs": "12.13.0",
"Azure.Storage.Queues": "12.11.0",
"Faithlife.Utility": "0.12.2",
"Microsoft.ApplicationInsights.DependencyCollector": "2.21.0",
"Microsoft.Azure.Functions.Worker": "1.6.0",
"Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "2.1.0",
"Microsoft.Azure.Functions.Worker.Extensions.Http": "3.0.13",
"Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "1.7.0",
"Microsoft.Azure.Functions.Worker.Extensions.Storage": "5.0.0",
"Microsoft.Azure.Functions.Worker.Extensions.Timer": "4.1.0",
"Microsoft.Azure.Functions.Worker.Sdk": "1.3.0",
"Microsoft.Azure.Management.Monitor": "0.28.0-preview",
"Microsoft.Azure.Management.OperationalInsights": "0.24.0-preview",
"Microsoft.Extensions.Logging.ApplicationInsights": "2.21.0",
"Microsoft.Graph": "4.37.0",
"Microsoft.Identity.Client": "4.46.2",
"Microsoft.Identity.Web.TokenCache": "1.23.1",
"Microsoft.TeamFoundationServer.Client": "19.209.0-preview",
"Octokit": "2.0.1",
"Scriban": "5.5.0",
"Semver": "2.1.0",
"System.IdentityModel.Tokens.Jwt": "6.22.1",
"System.Linq.Async": "6.0.1",
"TaskTupleAwaiter": "2.0.0"
}
}
}

View File

@ -368,11 +368,87 @@ namespace Tests {
where Container.TryParse(nameString, out var _)
select Container.Parse(nameString);
public static Gen<ADODuplicateTemplate> AdoDuplicateTemplate() {
return Arb.Generate<Tuple<List<string>, Dictionary<string, string>, string?>>().Select(
arg =>
new ADODuplicateTemplate(
arg.Item1,
arg.Item2,
arg.Item2,
arg.Item3
)
);
}
public static Gen<AdoTemplate> AdoTemplate() {
return Arb.Generate<Tuple<Uri, SecretData<string>, NonEmptyString, List<string>, Dictionary<string, string>, ADODuplicateTemplate, string?>>().Select(
arg =>
new AdoTemplate(
arg.Item1,
arg.Item2,
arg.Item3.Item,
arg.Item3.Item,
arg.Item4,
arg.Item5,
AdoDuplicateTemplate().Sample(1, 1).First(),
arg.Item7
)
);
}
public static Gen<TeamsTemplate> TeamsTemplate() {
return Arb.Generate<Tuple<SecretData<string>>>().Select(
arg =>
new TeamsTemplate(
arg.Item1
)
);
}
public static Gen<GithubAuth> GithubAuth() {
return Arb.Generate<Tuple<string>>().Select(
arg =>
new GithubAuth(
arg.Item1,
arg.Item1
)
);
}
public static Gen<GithubIssueSearch> GithubIssueSearch() {
return Arb.Generate<Tuple<List<GithubIssueSearchMatch>, string, string?, GithubIssueState?>>().Select(
arg =>
new GithubIssueSearch(
arg.Item1,
arg.Item2,
arg.Item3,
arg.Item4
)
);
}
public static Gen<GithubIssuesTemplate> GithubIssuesTemplate() {
return Arb.Generate<Tuple<SecretData<GithubAuth>, NonEmptyString, GithubIssueSearch, List<string>, GithubIssueDuplicate>>().Select(
arg =>
new GithubIssuesTemplate(
arg.Item1,
arg.Item2.Item,
arg.Item2.Item,
arg.Item2.Item,
arg.Item2.Item,
arg.Item3,
arg.Item4,
arg.Item4,
arg.Item5
)
);
}
public static Gen<NotificationTemplate> NotificationTemplate() {
return Gen.OneOf(new[] {
Arb.Generate<AdoTemplate>().Select(e => e as NotificationTemplate),
Arb.Generate<TeamsTemplate>().Select(e => e as NotificationTemplate),
Arb.Generate<GithubIssuesTemplate>().Select(e => e as NotificationTemplate)
AdoTemplate().Select(a => a as NotificationTemplate),
TeamsTemplate().Select(e => e as NotificationTemplate),
GithubIssuesTemplate().Select(e => e as NotificationTemplate)
});
}
@ -1006,17 +1082,15 @@ namespace Tests {
}
/*
//Sample function on how repro a failing test run, using Replay
//functionality of FsCheck. Feel free to
[Property]
void Replay()
{
var seed = FsCheck.Random.StdGen.NewStdGen(4570702, 297027754);
var p = Prop.ForAll((WebhookMessageEventGrid x) => WebhookMessageEventGrid(x) );
void Replay() {
var seed = FsCheck.Random.StdGen.NewStdGen(811038773, 297085737);
var p = Prop.ForAll((NotificationTemplate x) => NotificationTemplate(x));
p.Check(new Configuration { Replay = seed });
}
*/
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using FluentAssertions;
using Microsoft.OneFuzz.Service;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using Scriban;
using Xunit;
@ -115,6 +117,19 @@ public class TemplateTests {
output.Should().NotContainAny(report.CallStack);
}
[Fact]
public void TemplatesShouldDeserializeAppropriately() {
var teamsTemplate = @"{""url"": {""secret"": {""url"": ""https://example.com""}}}";
var template = JsonSerializer.Deserialize<NotificationTemplate>(teamsTemplate, EntityConverter.GetJsonSerializerOptions());
var a = template is AdoTemplate;
var t = template is TeamsTemplate;
var g = template is GithubIssuesTemplate;
a.Should().BeFalse();
t.Should().BeTrue();
g.Should().BeFalse();
}
private static Report GetReport() {
return new Report(
"https://example.com",

View File

@ -330,6 +330,15 @@
"System.Diagnostics.DiagnosticSource": "5.0.0"
}
},
"Microsoft.AspNet.WebApi.Client": {
"type": "Transitive",
"resolved": "5.2.7",
"contentHash": "/76fAHknzvFqbznS6Uj2sOyE9rJB3PltY+f53TH8dX9RiGhk02EhuFCWljSj5nnqKaTsmma8DFR50OGyQ4yJ1g==",
"dependencies": {
"Newtonsoft.Json": "10.0.1",
"Newtonsoft.Json.Bson": "1.0.1"
}
},
"Microsoft.AspNetCore.Cryptography.Internal": {
"type": "Transitive",
"resolved": "5.0.8",
@ -927,6 +936,26 @@
"Newtonsoft.Json": "10.0.3"
}
},
"Microsoft.TeamFoundation.DistributedTask.Common.Contracts": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "32lLZPU8pZg+mVfA2smHso6fhWPSFXJPPyawvOFsFoNz9Yj5y2fsAR7O4zPwE3c/z2zzi8BMfiXRKOcbW6cdIg==",
"dependencies": {
"Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]"
}
},
"Microsoft.TeamFoundationServer.Client": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "dglVgITWfsps8pWA//2mBGVt/keD3UGdAVBXd50k9nVZiThUwWnaAoUzRf4fay/avLGXdvfkz6x9dBf6zGtfxg==",
"dependencies": {
"Microsoft.AspNet.WebApi.Client": "5.2.7",
"Microsoft.TeamFoundation.DistributedTask.Common.Contracts": "[19.209.0-preview]",
"Microsoft.VisualStudio.Services.Client": "[19.209.0-preview]",
"Newtonsoft.Json": "12.0.3",
"System.ComponentModel.Annotations": "4.4.1"
}
},
"Microsoft.TestPlatform.ObjectModel": {
"type": "Transitive",
"resolved": "17.1.0",
@ -945,6 +974,22 @@
"Newtonsoft.Json": "9.0.1"
}
},
"Microsoft.VisualStudio.Services.Client": {
"type": "Transitive",
"resolved": "19.209.0-preview",
"contentHash": "eQWZb5BhtOgywARvfHGGZsYuuZvFmJiXyE7P/EqKTLUplrUFmSVxo0J/KUC8GWJWmdarxH2vXZTAz9uW7BwRDQ==",
"dependencies": {
"Microsoft.AspNet.WebApi.Client": "5.2.7",
"Newtonsoft.Json": "12.0.3",
"System.Configuration.ConfigurationManager": "4.4.1",
"System.Data.SqlClient": "4.4.2",
"System.Security.Cryptography.Cng": "4.4.0",
"System.Security.Cryptography.OpenSsl": "4.4.0",
"System.Security.Cryptography.ProtectedData": "4.4.0",
"System.Security.Principal.Windows": "4.4.1",
"System.Xml.XPath.XmlDocument": "4.3.0"
}
},
"Microsoft.Win32.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1025,15 +1070,16 @@
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "10.0.3",
"contentHash": "hSXaFmh7hNCuEoC4XNY5DrRkLDzYHqPx/Ik23R4J86Z7PE/Y6YidhG602dFVdLBRSdG6xp9NabH3dXpcoxWvww==",
"resolved": "12.0.3",
"contentHash": "6mgjfnRB4jKMlzHSl+VD+oUc1IebOZabkbyWj2RiTgWwYPPuaK1H97G1sHqGwPlS5npiF5Q0OrxN1wni2n5QWg=="
},
"Newtonsoft.Json.Bson": {
"type": "Transitive",
"resolved": "1.0.1",
"contentHash": "5PYT/IqQ+UK31AmZiSS102R6EsTo+LGTSI8bp7WAUqDKaF4wHXD8U9u4WxTI1vc64tYi++8p3dk3WWNqPFgldw==",
"dependencies": {
"Microsoft.CSharp": "4.3.0",
"NETStandard.Library": "1.6.1",
"System.ComponentModel.TypeConverter": "4.3.0",
"System.Runtime.Serialization.Formatters": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
"Newtonsoft.Json": "10.0.1"
}
},
"NuGet.Frameworks": {
@ -1041,6 +1087,11 @@
"resolved": "5.11.0",
"contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q=="
},
"Octokit": {
"type": "Transitive",
"resolved": "2.0.1",
"contentHash": "JVlfUY+sfItl6RSyVKDJTutuy28cDydUwKKfzcelwNMor2Sa18pYVKna6phO8lug1b+ep+pcuFh/FPayuImsQw=="
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.2",
@ -1065,6 +1116,16 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "A8v6PGmk+UGbfWo5Ixup0lPM4swuSwOiayJExZwKIOjTlFFQIsu3QnDXECosBEyrWSPryxBVrdqtJyhK3BaupQ==",
"dependencies": {
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
}
},
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1148,6 +1209,21 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
},
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
},
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
},
"Scriban": {
"type": "Transitive",
"resolved": "5.5.0",
@ -1240,6 +1316,11 @@
"System.Runtime": "4.3.0"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.4.1",
"contentHash": "ToiYqSCioqhtspq2O/jYKtyTC/T0uwWHBTYlzCi6PRbSSHArN1IaRWeHffDamvms5sye5FDUWCfNZgubQpNRsA=="
},
"System.ComponentModel.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1274,8 +1355,8 @@
},
"System.Configuration.ConfigurationManager": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "gWwQv/Ug1qWJmHCmN17nAbxJYmQBM/E94QxKLksvUiiKB1Ld3Sc/eK1lgmbSjDFxkQhVuayI/cGFZhpBSodLrg==",
"resolved": "4.4.1",
"contentHash": "jz3TWKMAeuDEyrPCK5Jyt4bzQcmzUIMcY9Ud6PkElFxTfnsihV+9N/UCqvxe1z5gc7jMYAnj7V1COMS9QKIuHQ==",
"dependencies": {
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
@ -1292,6 +1373,17 @@
"System.Text.Encoding": "4.3.0"
}
},
"System.Data.SqlClient": {
"type": "Transitive",
"resolved": "4.4.2",
"contentHash": "Bv5J2EBAdP7FSgehKYN4O6iw1AaZrw4rFFqwt9vZSjRvC70FpwP2d9UG4aTaI2wh3vfrBKK+tjewowGM2Y6c1w==",
"dependencies": {
"Microsoft.Win32.Registry": "4.4.0",
"System.Security.Principal.Windows": "4.4.0",
"System.Text.Encoding.CodePages": "4.4.0",
"runtime.native.System.Data.SqlClient.sni": "4.4.0"
}
},
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1850,27 +1942,6 @@
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Runtime.Serialization.Formatters": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0"
}
},
"System.Runtime.Serialization.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==",
"dependencies": {
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",
@ -1964,22 +2035,10 @@
},
"System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
"resolved": "4.4.0",
"contentHash": "is11qLXIHKIvbTipyB1an8FT1ZKavmgf/qJUSIz7ZP830ALRRhPSt5NhplW0/wMk0tNDQWQLluVap6HsQN4HMg==",
"dependencies": {
"System.Collections": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.Numerics": "4.3.0",
"System.Security.Cryptography.Algorithms": "4.3.0",
"System.Security.Cryptography.Encoding": "4.3.0",
"System.Security.Cryptography.Primitives": "4.3.0",
"System.Text.Encoding": "4.3.0",
"runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
"Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
@ -2083,6 +2142,14 @@
"System.Runtime": "4.3.0"
}
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "6JX7ZdaceBiLKLkYt8zJcp4xTJd1uYyXXEkPw6mnlUIjh1gZPIVKPtRXPmY5kLf6DwZmf5YLwR3QUrRonl7l0A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "2.0.0"
}
},
"System.Text.Encoding.Extensions": {
"type": "Transitive",
"resolved": "4.3.0",
@ -2251,6 +2318,39 @@
"System.Xml.ReaderWriter": "4.3.0"
}
},
"System.Xml.XPath": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "v1JQ5SETnQusqmS3RwStF7vwQ3L02imIzl++sewmt23VGygix04pEH+FCj1yWb+z4GDzKiljr1W7Wfvrx0YwgA==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "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",
"System.Xml.ReaderWriter": "4.3.0"
}
},
"System.Xml.XPath.XmlDocument": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "A/uxsWi/Ifzkmd4ArTLISMbfFs6XpRPsXZonrIqyTY70xi8t+mDtvSM5Os0RqyRDobjMBwIDHDL4NOIbkDwf7A==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "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",
"System.Xml.ReaderWriter": "4.3.0",
"System.Xml.XPath": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
}
},
"TaskTupleAwaiter": {
"type": "Transitive",
"resolved": "2.0.0",
@ -2304,39 +2404,41 @@
"apiservice": {
"type": "Project",
"dependencies": {
"Azure.Core": "[1.25.0, )",
"Azure.Data.Tables": "[12.5.0, )",
"Azure.Identity": "[1.6.0, )",
"Azure.Messaging.EventGrid": "[4.10.0, )",
"Azure.ResourceManager": "[1.3.1, )",
"Azure.ResourceManager.Compute": "[1.0.0-beta.8, )",
"Azure.ResourceManager.Monitor": "[1.0.0-beta.2, )",
"Azure.ResourceManager.Network": "[1.0.0, )",
"Azure.ResourceManager.Resources": "[1.3.0, )",
"Azure.ResourceManager.Storage": "[1.0.0-beta.11, )",
"Azure.Security.KeyVault.Secrets": "[4.3.0, )",
"Azure.Storage.Blobs": "[12.13.0, )",
"Azure.Storage.Queues": "[12.11.0, )",
"Faithlife.Utility": "[0.12.2, )",
"Microsoft.ApplicationInsights.DependencyCollector": "[2.21.0, )",
"Microsoft.Azure.Functions.Worker": "[1.6.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "[2.1.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.Http": "[3.0.13, )",
"Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "[1.7.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.Storage": "[5.0.0, )",
"Microsoft.Azure.Functions.Worker.Extensions.Timer": "[4.1.0, )",
"Microsoft.Azure.Functions.Worker.Sdk": "[1.3.0, )",
"Microsoft.Azure.Management.Monitor": "[0.28.0-preview, )",
"Microsoft.Azure.Management.OperationalInsights": "[0.24.0-preview, )",
"Microsoft.Extensions.Logging.ApplicationInsights": "[2.21.0, )",
"Microsoft.Graph": "[4.37.0, )",
"Microsoft.Identity.Client": "[4.46.2, )",
"Microsoft.Identity.Web.TokenCache": "[1.23.1, )",
"Scriban": "[5.5.0, )",
"Semver": "[2.1.0, )",
"System.IdentityModel.Tokens.Jwt": "[6.22.1, )",
"System.Linq.Async": "[6.0.1, )",
"TaskTupleAwaiter": "[2.0.0, )"
"Azure.Core": "1.25.0",
"Azure.Data.Tables": "12.5.0",
"Azure.Identity": "1.6.0",
"Azure.Messaging.EventGrid": "4.10.0",
"Azure.ResourceManager": "1.3.1",
"Azure.ResourceManager.Compute": "1.0.0-beta.8",
"Azure.ResourceManager.Monitor": "1.0.0-beta.2",
"Azure.ResourceManager.Network": "1.0.0",
"Azure.ResourceManager.Resources": "1.3.0",
"Azure.ResourceManager.Storage": "1.0.0-beta.11",
"Azure.Security.KeyVault.Secrets": "4.3.0",
"Azure.Storage.Blobs": "12.13.0",
"Azure.Storage.Queues": "12.11.0",
"Faithlife.Utility": "0.12.2",
"Microsoft.ApplicationInsights.DependencyCollector": "2.21.0",
"Microsoft.Azure.Functions.Worker": "1.6.0",
"Microsoft.Azure.Functions.Worker.Extensions.EventGrid": "2.1.0",
"Microsoft.Azure.Functions.Worker.Extensions.Http": "3.0.13",
"Microsoft.Azure.Functions.Worker.Extensions.SignalRService": "1.7.0",
"Microsoft.Azure.Functions.Worker.Extensions.Storage": "5.0.0",
"Microsoft.Azure.Functions.Worker.Extensions.Timer": "4.1.0",
"Microsoft.Azure.Functions.Worker.Sdk": "1.3.0",
"Microsoft.Azure.Management.Monitor": "0.28.0-preview",
"Microsoft.Azure.Management.OperationalInsights": "0.24.0-preview",
"Microsoft.Extensions.Logging.ApplicationInsights": "2.21.0",
"Microsoft.Graph": "4.37.0",
"Microsoft.Identity.Client": "4.46.2",
"Microsoft.Identity.Web.TokenCache": "1.23.1",
"Microsoft.TeamFoundationServer.Client": "19.209.0-preview",
"Octokit": "2.0.1",
"Scriban": "5.5.0",
"Semver": "2.1.0",
"System.IdentityModel.Tokens.Jwt": "6.22.1",
"System.Linq.Async": "6.0.1",
"TaskTupleAwaiter": "2.0.0"
}
}
}