From ca7b6be43b7cf9d03257360e17fbef1fba8041ff Mon Sep 17 00:00:00 2001 From: Teo Voinea <58236992+tevoinea@users.noreply.github.com> Date: Wed, 14 Sep 2022 11:07:52 -0400 Subject: [PATCH] Refactor notification support (#2363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- docs/notifications/ado.md | 14 +- src/ApiService/ApiService/ApiService.csproj | 2 + .../ApiService/Functions/QueueFileChanges.cs | 8 +- .../ApiService/OneFuzzTypes/Model.cs | 87 ++++-- src/ApiService/ApiService/Program.cs | 3 + .../ApiService/onefuzzlib/Containers.cs | 12 + .../ApiService/onefuzzlib/Extension.cs | 2 +- .../onefuzzlib/NotificationOperations.cs | 21 +- .../ApiService/onefuzzlib/OnefuzzContext.cs | 6 + .../ApiService/onefuzzlib/Reports.cs | 12 - .../ApiService/onefuzzlib/Scheduler.cs | 2 +- .../onefuzzlib/notifications/Ado.cs | 283 ++++++++++++++++++ .../onefuzzlib/notifications/GithubIssues.cs | 160 ++++++++++ .../notifications/NotificationsBase.cs | 99 ++++++ .../onefuzzlib/notifications/Teams.cs | 124 ++++++++ src/ApiService/ApiService/packages.lock.json | 251 +++++++++------- .../IntegrationTests/Fakes/TestContext.cs | 3 + .../IntegrationTests/packages.lock.json | 258 +++++++++++----- src/ApiService/Tests/OrmModelsTest.cs | 92 +++++- src/ApiService/Tests/TemplateTests.cs | 15 + src/ApiService/Tests/packages.lock.json | 258 +++++++++++----- 21 files changed, 1382 insertions(+), 330 deletions(-) create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs create mode 100644 src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs diff --git a/docs/notifications/ado.md b/docs/notifications/ado.md index 892948826..3899dc8f0 100644 --- a/docs/notifications/ado.md +++ b/docs/notifications/ado.md @@ -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: " + "Microsoft.VSTS.TCM.ReproSteps": "This is my call stack: " }, "comment": "This is my comment. {{ report.input_sha256 }} {{ input_url }}
{{ repro_cmd }}
", "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: " \ + "Microsoft.VSTS.TCM.ReproSteps=This is my call stack: " \ --comment "This is my comment. {{ report.input_sha256 }} {{ input_url }}" \ --on_dup_comment "Another POC was found in target" \ --on_dup_set_state Resolved=Active \ diff --git a/src/ApiService/ApiService/ApiService.csproj b/src/ApiService/ApiService/ApiService.csproj index f38e51e64..1135ba821 100644 --- a/src/ApiService/ApiService/ApiService.csproj +++ b/src/ApiService/ApiService/ApiService.csproj @@ -42,6 +42,8 @@ + + diff --git a/src/ApiService/ApiService/Functions/QueueFileChanges.cs b/src/ApiService/ApiService/Functions/QueueFileChanges.cs index 22832d3a9..897ed2ce6 100644 --- a/src/ApiService/ApiService/Functions/QueueFileChanges.cs +++ b/src/ApiService/ApiService/Functions/QueueFileChanges.cs @@ -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(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(); diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs index 77c4f4435..f39d4f88f 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs @@ -84,7 +84,11 @@ public record ProxyHeartbeat Guid ProxyId, List 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 public override NotificationTemplate? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var templateJson = JsonDocument.ParseValue(ref reader); try { - return templateJson.Deserialize(options); - } catch (JsonException) { + return ValidateDeserialization(templateJson.Deserialize(options)); + } catch (Exception ex) when ( + ex is JsonException + || ex is ArgumentNullException + || ex is ArgumentOutOfRangeException + ) { } try { - return templateJson.Deserialize(options); - } catch (JsonException) { + return ValidateDeserialization(templateJson.Deserialize(options)); + } catch (Exception ex) when ( + ex is JsonException + || ex is ArgumentNullException + || ex is ArgumentOutOfRangeException + ) { + } try { - return templateJson.Deserialize(options); - } catch (JsonException) { + return ValidateDeserialization(templateJson.Deserialize(options)); + } catch (Exception ex) when ( + ex is JsonException + || ex is ArgumentNullException + || ex is ArgumentOutOfRangeException + ) { + } - throw new JsonException("Unsupported notification template"); + + var expectedTemplateTypes = new List { + 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 } } + + private static T ValidateDeserialization(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(); + + 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 Increment, - string? Comment, Dictionary SetState, - Dictionary AdoFields + Dictionary AdoFields, + string? Comment = null ); public record AdoTemplate( @@ -515,27 +565,26 @@ public record AdoTemplate( string Project, string Type, List UniqueFields, - string? Comment, Dictionary AdoFields, - ADODuplicateTemplate OnDuplicate + ADODuplicateTemplate OnDuplicate, + string? Comment = null ) : NotificationTemplate; - public record TeamsTemplate(SecretData Url) : NotificationTemplate; public record GithubAuth(string User, string PersonalAccessToken); public record GithubIssueSearch( - string? Author, - GithubIssueState? State, List FieldMatch, - [property: JsonPropertyName("string")] String str + [property: JsonPropertyName("string")] String str, + string? Author = null, + GithubIssueState? State = null ); public record GithubIssueDuplicate( - string? Comment, List Labels, - bool Reopen + bool Reopen, + string? Comment = null ); diff --git a/src/ApiService/ApiService/Program.cs b/src/ApiService/ApiService/Program.cs index 103a6c84a..94235d6f2 100644 --- a/src/ApiService/ApiService/Program.cs +++ b/src/ApiService/ApiService/Program.cs @@ -102,6 +102,9 @@ public class Program { .AddScoped() .AddScoped() .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() .AddScoped() .AddScoped() .AddScoped() diff --git a/src/ApiService/ApiService/onefuzzlib/Containers.cs b/src/ApiService/ApiService/onefuzzlib/Containers.cs index d2a99689a..8d3ce5a26 100644 --- a/src/ApiService/ApiService/onefuzzlib/Containers.cs +++ b/src/ApiService/ApiService/onefuzzlib/Containers.cs @@ -29,6 +29,8 @@ public interface IContainers { public Async.Task AddContainerSasUrl(Uri uri, TimeSpan? duration = null); public Async.Task>> 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}"; + } } diff --git a/src/ApiService/ApiService/onefuzzlib/Extension.cs b/src/ApiService/ApiService/onefuzzlib/Extension.cs index 7351094ce..622f1137a 100644 --- a/src/ApiService/ApiService/onefuzzlib/Extension.cs +++ b/src/ApiService/ApiService/onefuzzlib/Extension.cs @@ -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 diff --git a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs index 26d65075b..9c5f80cdd 100644 --- a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs @@ -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, 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, 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, INotificationOperations } public IAsyncEnumerable GetNotifications(Container container) { - return QueryAsync(filter: TableClient.CreateQueryFilter($"container eq {container.String}")); + return SearchByRowKeys(new[] { container.String }); } public IAsyncEnumerable<(Task, IEnumerable)> GetQueueTasks() { @@ -140,16 +139,4 @@ public class NotificationOperations : Orm, 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(); - } } diff --git a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs index 013e4b3bd..01c9e6dc7 100644 --- a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs +++ b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs @@ -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(); public IImageOperations ImageOperations => _serviceProvider.GetRequiredService(); public EntityConverter EntityConverter => _serviceProvider.GetRequiredService(); + public ITeams Teams => _serviceProvider.GetRequiredService(); + public IGithubIssues GithubIssues => _serviceProvider.GetRequiredService(); + public IAdo Ado => _serviceProvider.GetRequiredService(); } diff --git a/src/ApiService/ApiService/onefuzzlib/Reports.cs b/src/ApiService/ApiService/onefuzzlib/Reports.cs index 333b63726..636a4b407 100644 --- a/src/ApiService/ApiService/onefuzzlib/Reports.cs +++ b/src/ApiService/ApiService/onefuzzlib/Reports.cs @@ -59,18 +59,6 @@ public class Reports : IReports { } return regressionReport; } - - private IReport? ParseReportOrRegression(IEnumerable 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 { }; diff --git a/src/ApiService/ApiService/onefuzzlib/Scheduler.cs b/src/ApiService/ApiService/onefuzzlib/Scheduler.cs index 61261c171..2e23636e5 100644 --- a/src/ApiService/ApiService/onefuzzlib/Scheduler.cs +++ b/src/ApiService/ApiService/onefuzzlib/Scheduler.cs @@ -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) { diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs new file mode 100644 index 000000000..faa96c4e8 --- /dev/null +++ b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs @@ -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() + { + //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 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 Render(string template) { + return await _renderer.Render(template, _instanceUrl); + } + + public async IAsyncEnumerable ExistingWorkItems() { + var filters = new Dictionary(); + 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(); + /* + # 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(); + 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> GetValidFields(string? project) { + return (await _client.GetFieldsAsync(project, expand: GetFieldsExpand.ExtensionFields)) + .Select(field => field.ReferenceName.ToLowerInvariant()) + .ToList(); + } + + private async Async.Task 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}"); + } + } + } +} diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs b/src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs new file mode 100644 index 000000000..3ae188343 --- /dev/null +++ b/src/ApiService/ApiService/onefuzzlib/notifications/GithubIssues.cs @@ -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 GithubConnnectorCreator(GithubIssuesTemplate config, Container container, string filename, Renderer renderer, Uri instanceUrl, IOnefuzzContext context, ILogTracer logTracer) { + var auth = config.Auth.Secret switch { + SecretAddress sa => await context.SecretsOperations.GetSecretObj(sa.Url), + SecretValue 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 Render(string field) { + return await _renderer.Render(field, _instanceUrl); + } + + private async Async.Task> Existing() { + var query = new List() { + 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(); + 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 + ); + } + } +} + diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs b/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs new file mode 100644 index 000000000..8f83cf43e --- /dev/null +++ b/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs @@ -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 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 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; + } + } +} diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs new file mode 100644 index 000000000..c983541f8 --- /dev/null +++ b/src/ApiService/ApiService/onefuzzlib/notifications/Teams.cs @@ -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> facts, string? text) { + title = MarkdownEscape(title); + + var sections = new List>() { + new() { + {"activityTitle", title}, + {"facts", facts} + } + }; + if (text != null) { + sections.Add(new() { + { "text", text } + }); + } + + var message = new Dictionary() { + {"@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>(); + 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 { + $"[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> { + 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() { + {"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; + } +} diff --git a/src/ApiService/ApiService/packages.lock.json b/src/ApiService/ApiService/packages.lock.json index d03c5f2e9..198f430bd 100644 --- a/src/ApiService/ApiService/packages.lock.json +++ b/src/ApiService/ApiService/packages.lock.json @@ -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" + } } } } diff --git a/src/ApiService/IntegrationTests/Fakes/TestContext.cs b/src/ApiService/IntegrationTests/Fakes/TestContext.cs index bfcd778bd..60d2af0ca 100644 --- a/src/ApiService/IntegrationTests/Fakes/TestContext.cs +++ b/src/ApiService/IntegrationTests/Fakes/TestContext.cs @@ -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(); } diff --git a/src/ApiService/IntegrationTests/packages.lock.json b/src/ApiService/IntegrationTests/packages.lock.json index 2d61f054f..3d843d602 100644 --- a/src/ApiService/IntegrationTests/packages.lock.json +++ b/src/ApiService/IntegrationTests/packages.lock.json @@ -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" } } } diff --git a/src/ApiService/Tests/OrmModelsTest.cs b/src/ApiService/Tests/OrmModelsTest.cs index 3dbc03cbe..0c8de3617 100644 --- a/src/ApiService/Tests/OrmModelsTest.cs +++ b/src/ApiService/Tests/OrmModelsTest.cs @@ -368,11 +368,87 @@ namespace Tests { where Container.TryParse(nameString, out var _) select Container.Parse(nameString); + public static Gen AdoDuplicateTemplate() { + return Arb.Generate, Dictionary, string?>>().Select( + arg => + new ADODuplicateTemplate( + arg.Item1, + arg.Item2, + arg.Item2, + arg.Item3 + ) + ); + } + + public static Gen AdoTemplate() { + return Arb.Generate, NonEmptyString, List, Dictionary, 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() { + return Arb.Generate>>().Select( + arg => + new TeamsTemplate( + arg.Item1 + ) + ); + } + + public static Gen GithubAuth() { + return Arb.Generate>().Select( + arg => + new GithubAuth( + arg.Item1, + arg.Item1 + ) + ); + } + + public static Gen GithubIssueSearch() { + return Arb.Generate, string, string?, GithubIssueState?>>().Select( + arg => + new GithubIssueSearch( + arg.Item1, + arg.Item2, + arg.Item3, + arg.Item4 + ) + ); + } + + public static Gen GithubIssuesTemplate() { + return Arb.Generate, NonEmptyString, GithubIssueSearch, List, 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() { return Gen.OneOf(new[] { - Arb.Generate().Select(e => e as NotificationTemplate), - Arb.Generate().Select(e => e as NotificationTemplate), - Arb.Generate().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 }); } - */ } } diff --git a/src/ApiService/Tests/TemplateTests.cs b/src/ApiService/Tests/TemplateTests.cs index 7147ebcb8..d3b07e7d2 100644 --- a/src/ApiService/Tests/TemplateTests.cs +++ b/src/ApiService/Tests/TemplateTests.cs @@ -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(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", diff --git a/src/ApiService/Tests/packages.lock.json b/src/ApiService/Tests/packages.lock.json index 587dcd926..13dcea0c2 100644 --- a/src/ApiService/Tests/packages.lock.json +++ b/src/ApiService/Tests/packages.lock.json @@ -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" } } }