mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-17 12:28:07 +00:00
update the ado logic to consume the list of existing items once (#3014)
* update the ado logic to consume the list of existing items once * format * Update src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs Co-authored-by: Teo Voinea <58236992+tevoinea@users.noreply.github.com> * Adding a notification testing endpoint * fix tests * format * regen docs * update logic * format * fix dummy name * mypy fix * make mypy happy * bandit fix * renaming * address PR Comment --------- Co-authored-by: Teo Voinea <58236992+tevoinea@users.noreply.github.com>
This commit is contained in:
@ -238,6 +238,10 @@ If webhook is set to have Event Grid message format then the payload will look a
|
||||
"title": "Onefuzz Version",
|
||||
"type": "string"
|
||||
},
|
||||
"report_url": {
|
||||
"title": "Report Url",
|
||||
"type": "string"
|
||||
},
|
||||
"scariness_description": {
|
||||
"title": "Scariness Description",
|
||||
"type": "string"
|
||||
@ -2158,6 +2162,10 @@ If webhook is set to have Event Grid message format then the payload will look a
|
||||
"title": "Onefuzz Version",
|
||||
"type": "string"
|
||||
},
|
||||
"report_url": {
|
||||
"title": "Report Url",
|
||||
"type": "string"
|
||||
},
|
||||
"scariness_description": {
|
||||
"title": "Scariness Description",
|
||||
"type": "string"
|
||||
@ -6578,6 +6586,10 @@ If webhook is set to have Event Grid message format then the payload will look a
|
||||
"title": "Onefuzz Version",
|
||||
"type": "string"
|
||||
},
|
||||
"report_url": {
|
||||
"title": "Report Url",
|
||||
"type": "string"
|
||||
},
|
||||
"scariness_description": {
|
||||
"title": "Scariness Description",
|
||||
"type": "string"
|
||||
|
@ -22,7 +22,7 @@ public class Notifications {
|
||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "notification search");
|
||||
}
|
||||
|
||||
var entries = request.OkV switch { { Container: null, NotificationId: null } => _context.NotificationOperations.SearchAll(), { Container: var c, NotificationId: null } => _context.NotificationOperations.SearchByRowKeys(c.Select(x => x.String)), { Container: var _, NotificationId: var n } => new[] { await _context.NotificationOperations.GetNotification(n.Value) }.ToAsyncEnumerable(),
|
||||
var entries = request.OkV switch { { Container: null, NotificationId: null } => _context.NotificationOperations.SearchAll(), { Container: var c, NotificationId: null } => _context.NotificationOperations.SearchByRowKeys(c.Select(x => x.String)), { Container: var _, NotificationId: var n } => new[] { await _context.NotificationOperations.GetNotification(n.Value) }.ToAsyncEnumerable()
|
||||
};
|
||||
|
||||
var response = req.CreateResponse(HttpStatusCode.OK);
|
||||
|
41
src/ApiService/ApiService/Functions/NotificationsTest.cs
Normal file
41
src/ApiService/ApiService/Functions/NotificationsTest.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.Net;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service.Functions;
|
||||
|
||||
public class NotificationsTest {
|
||||
private readonly ILogTracer _log;
|
||||
private readonly IEndpointAuthorization _auth;
|
||||
private readonly IOnefuzzContext _context;
|
||||
|
||||
public NotificationsTest(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
|
||||
_log = log;
|
||||
_auth = auth;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
|
||||
_log.WithTag("HttpRequest", "GET").Info($"Notification test");
|
||||
var request = await RequestHandling.ParseRequest<NotificationTest>(req);
|
||||
if (!request.IsOk) {
|
||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "notification search");
|
||||
}
|
||||
|
||||
var notificationTest = request.OkV;
|
||||
var result = await _context.NotificationOperations.TriggerNotification(notificationTest.Notification.Container, notificationTest.Notification,
|
||||
notificationTest.Report, isLastRetryAttempt: true);
|
||||
var response = req.CreateResponse(HttpStatusCode.OK);
|
||||
await response.WriteAsJsonAsync(new NotificationTestResponse(result.IsOk, result.ErrorV?.ToString()));
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
[Function("NotificationsTest")]
|
||||
public Async.Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "notifications/test")] HttpRequestData req) {
|
||||
return _auth.CallIfUser(req, r => r.Method switch {
|
||||
"POST" => Post(r),
|
||||
_ => throw new InvalidOperationException("Unsupported HTTP method"),
|
||||
});
|
||||
}
|
||||
}
|
@ -527,6 +527,10 @@ public record RegressionReport(
|
||||
}
|
||||
}
|
||||
|
||||
public record UnknownReportType(
|
||||
Uri? ReportUrl
|
||||
) : IReport;
|
||||
|
||||
[JsonConverter(typeof(NotificationTemplateConverter))]
|
||||
#pragma warning disable CA1715
|
||||
public interface NotificationTemplate {
|
||||
|
@ -129,6 +129,12 @@ public record NotificationSearch(
|
||||
Guid? NotificationId
|
||||
) : BaseRequest;
|
||||
|
||||
|
||||
public record NotificationTest(
|
||||
[property: Required] Report Report,
|
||||
[property: Required] Notification Notification
|
||||
) : BaseRequest;
|
||||
|
||||
public record NotificationGet(
|
||||
[property: Required] Guid NotificationId
|
||||
) : BaseRequest;
|
||||
|
@ -205,3 +205,8 @@ public record JinjaToScribanMigrationResponse(
|
||||
public record JinjaToScribanMigrationDryRunResponse(
|
||||
List<Guid> NotificationIdsToUpdate
|
||||
) : BaseResponse();
|
||||
|
||||
public record NotificationTestResponse(
|
||||
bool Success,
|
||||
string? Error = null
|
||||
) : BaseResponse();
|
||||
|
@ -10,6 +10,9 @@ public interface INotificationOperations : IOrm<Notification> {
|
||||
IAsyncEnumerable<(Task, IEnumerable<Container>)> GetQueueTasks();
|
||||
Async.Task<OneFuzzResult<Notification>> Create(Container container, NotificationTemplate config, bool replaceExisting);
|
||||
Async.Task<Notification?> GetNotification(Guid notifificationId);
|
||||
|
||||
System.Threading.Tasks.Task<OneFuzzResultVoid> TriggerNotification(Container container,
|
||||
Notification notification, IReport? reportOrRegression, bool isLastRetryAttempt = false);
|
||||
}
|
||||
|
||||
public class NotificationOperations : Orm<Notification>, INotificationOperations {
|
||||
@ -30,22 +33,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
||||
}
|
||||
|
||||
done.Add(notification.Config);
|
||||
|
||||
if (notification.Config is TeamsTemplate teamsTemplate) {
|
||||
await _context.Teams.NotifyTeams(teamsTemplate, container, filename, reportOrRegression!, notification.NotificationId);
|
||||
}
|
||||
|
||||
if (reportOrRegression == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (notification.Config is AdoTemplate adoTemplate) {
|
||||
await _context.Ado.NotifyAdo(adoTemplate, container, filename, reportOrRegression, isLastRetryAttempt, notification.NotificationId);
|
||||
}
|
||||
|
||||
if (notification.Config is GithubIssuesTemplate githubIssuesTemplate) {
|
||||
await _context.GithubIssues.GithubIssue(githubIssuesTemplate, container, filename, reportOrRegression, notification.NotificationId);
|
||||
}
|
||||
_ = await TriggerNotification(container, notification, reportOrRegression, isLastRetryAttempt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +62,25 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
||||
}
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task<OneFuzzResultVoid> TriggerNotification(Container container,
|
||||
Notification notification, IReport? reportOrRegression, bool isLastRetryAttempt = false) {
|
||||
switch (notification.Config) {
|
||||
case TeamsTemplate teamsTemplate:
|
||||
await _context.Teams.NotifyTeams(teamsTemplate, container, reportOrRegression!,
|
||||
notification.NotificationId);
|
||||
break;
|
||||
case AdoTemplate adoTemplate when reportOrRegression is not null:
|
||||
return await _context.Ado.NotifyAdo(adoTemplate, container, reportOrRegression, isLastRetryAttempt,
|
||||
notification.NotificationId);
|
||||
case GithubIssuesTemplate githubIssuesTemplate when reportOrRegression is not null:
|
||||
await _context.GithubIssues.GithubIssue(githubIssuesTemplate, container, reportOrRegression,
|
||||
notification.NotificationId);
|
||||
break;
|
||||
}
|
||||
|
||||
return OneFuzzResultVoid.Ok;
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<Notification> GetNotifications(Container container) {
|
||||
return SearchByRowKeys(new[] { container.String });
|
||||
}
|
||||
|
@ -35,7 +35,17 @@ public class Reports : IReports {
|
||||
return null;
|
||||
}
|
||||
|
||||
var blob = await _containers.GetBlob(container, fileName, StorageType.Corpus);
|
||||
var containerClient = await _containers.FindContainer(container, StorageType.Corpus);
|
||||
if (containerClient == null) {
|
||||
if (expectReports) {
|
||||
_log.Error($"get_report invalid container: {filePath:Tag:FilePath}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri reportUrl = containerClient.GetBlobClient(fileName).Uri;
|
||||
|
||||
var blob = (await containerClient.GetBlobClient(fileName).DownloadContentAsync()).Value.Content;
|
||||
|
||||
if (blob == null) {
|
||||
if (expectReports) {
|
||||
@ -44,11 +54,9 @@ public class Reports : IReports {
|
||||
return null;
|
||||
}
|
||||
|
||||
var reportUrl = await _containers.GetFileUrl(container, fileName, StorageType.Corpus);
|
||||
|
||||
var reportOrRegression = ParseReportOrRegression(blob.ToString(), reportUrl);
|
||||
|
||||
if (reportOrRegression == null && expectReports) {
|
||||
if (reportOrRegression is UnknownReportType && expectReports) {
|
||||
_log.Error($"unable to parse report ({filePath:Tag:FilePath}) as a report or regression");
|
||||
}
|
||||
|
||||
@ -64,7 +72,7 @@ public class Reports : IReports {
|
||||
}
|
||||
}
|
||||
|
||||
public static IReport? ParseReportOrRegression(string content, Uri? reportUrl) {
|
||||
public static IReport ParseReportOrRegression(string content, Uri reportUrl) {
|
||||
var regressionReport = TryDeserialize<RegressionReport>(content);
|
||||
if (regressionReport is { CrashTestResult: { } }) {
|
||||
return regressionReport with { ReportUrl = reportUrl };
|
||||
@ -73,12 +81,17 @@ public class Reports : IReports {
|
||||
if (report is { CrashType: { } }) {
|
||||
return report with { ReportUrl = reportUrl };
|
||||
}
|
||||
return null;
|
||||
return new UnknownReportType(reportUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IReport {
|
||||
Uri? ReportUrl {
|
||||
init;
|
||||
get;
|
||||
}
|
||||
public string FileName() {
|
||||
var segments = (this.ReportUrl ?? throw new ArgumentException()).Segments.Skip(2);
|
||||
return string.Concat(segments);
|
||||
}
|
||||
};
|
||||
|
@ -56,13 +56,11 @@ public class SecretsOperations : ISecretsOperations {
|
||||
}
|
||||
|
||||
public async Task<string?> GetSecretStringValue<T>(SecretData<T> data) {
|
||||
|
||||
if (data.Secret is SecretAddress<T> secretAddress) {
|
||||
var secret = await GetSecret(secretAddress.Url);
|
||||
return secret.Value;
|
||||
} else {
|
||||
return data.Secret.ToString();
|
||||
}
|
||||
return (data.Secret) switch {
|
||||
SecretAddress<T> secretAddress => (await GetSecret(secretAddress.Url)).Value,
|
||||
SecretValue<T> sValue => sValue.Value?.ToString(),
|
||||
_ => data.Secret.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
public Uri GetKeyvaultAddress() {
|
||||
|
@ -8,17 +8,18 @@ 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 isLastRetryAttempt, Guid notificationId);
|
||||
public Async.Task<OneFuzzResultVoid> NotifyAdo(AdoTemplate config, Container container, IReport reportable, bool isLastRetryAttempt, Guid notificationId);
|
||||
}
|
||||
|
||||
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 isLastRetryAttempt, Guid notificationId) {
|
||||
public async Async.Task<OneFuzzResultVoid> NotifyAdo(AdoTemplate config, Container container, IReport reportable, bool isLastRetryAttempt, Guid notificationId) {
|
||||
var filename = reportable.FileName();
|
||||
if (reportable is RegressionReport) {
|
||||
_logTracer.Info($"ado integration does not support regression report. container:{container:Tag:Container} filename:{filename:Tag:Filename}");
|
||||
return;
|
||||
return OneFuzzResultVoid.Ok;
|
||||
}
|
||||
|
||||
var report = (Report)reportable;
|
||||
@ -44,8 +45,11 @@ public class Ado : NotificationsBase, IAdo {
|
||||
} else {
|
||||
_logTracer.WithTags(notificationInfo).Exception(e, $"Failed to process ado notification");
|
||||
await LogFailedNotification(report, e, notificationId);
|
||||
return OneFuzzResultVoid.Error(ErrorCode.NOTIFICATION_FAILURE,
|
||||
$"Failed to process ado notification : exception: {e}");
|
||||
}
|
||||
}
|
||||
return OneFuzzResultVoid.Ok;
|
||||
}
|
||||
|
||||
private static bool IsTransient(Exception e) {
|
||||
@ -205,7 +209,7 @@ public class Ado : NotificationsBase, IAdo {
|
||||
}
|
||||
}
|
||||
|
||||
var query = "select [System.Id] from WorkItems";
|
||||
var query = "select [System.Id] from WorkItems order by [System.Id]";
|
||||
if (parts != null && parts.Any()) {
|
||||
query += " where " + string.Join(" AND ", parts);
|
||||
}
|
||||
@ -331,47 +335,42 @@ public class Ado : NotificationsBase, IAdo {
|
||||
}
|
||||
|
||||
public async Async.Task Process((string, string)[] notificationInfo) {
|
||||
var matchingWorkItems = await ExistingWorkItems(notificationInfo).ToListAsync();
|
||||
|
||||
var nonDuplicateWorkItems = matchingWorkItems
|
||||
.Where(wi => !IsADODuplicateWorkItem(wi))
|
||||
.ToList();
|
||||
|
||||
if (nonDuplicateWorkItems.Count > 1) {
|
||||
var nonDuplicateWorkItemIds = nonDuplicateWorkItems.Select(wi => wi.Id);
|
||||
var matchingWorkItemIds = matchingWorkItems.Select(wi => wi.Id);
|
||||
|
||||
var extraTags = new List<(string, string)> {
|
||||
("NonDuplicateWorkItemIds", JsonSerializer.Serialize(nonDuplicateWorkItemIds)),
|
||||
("MatchingWorkItemIds", JsonSerializer.Serialize(matchingWorkItemIds))
|
||||
};
|
||||
extraTags.AddRange(notificationInfo);
|
||||
|
||||
_logTracer.WithTags(extraTags).Info($"Found more than 1 matching, non-duplicate work item");
|
||||
foreach (var workItem in nonDuplicateWorkItems) {
|
||||
_ = await UpdateExisting(workItem, notificationInfo);
|
||||
var updated = false;
|
||||
WorkItem? oldestWorkItem = null;
|
||||
await foreach (var workItem in ExistingWorkItems(notificationInfo)) {
|
||||
// work items are ordered by id, so the oldest one is the first one
|
||||
oldestWorkItem ??= workItem;
|
||||
_logTracer.WithTags(new List<(string, string)> { ("MatchingWorkItemIds", $"{workItem.Id}") }).Info($"Found matching work item");
|
||||
if (IsADODuplicateWorkItem(workItem)) {
|
||||
continue;
|
||||
}
|
||||
} else if (nonDuplicateWorkItems.Count == 1) {
|
||||
_ = await UpdateExisting(nonDuplicateWorkItems.Single(), notificationInfo);
|
||||
} else if (matchingWorkItems.Any()) {
|
||||
// We have matching work items but all are duplicates
|
||||
_logTracer.WithTags(notificationInfo).Info($"All matching work items were duplicates, re-opening the oldest one");
|
||||
var oldestWorkItem = matchingWorkItems.OrderBy(wi => wi.Id).First();
|
||||
var stateChanged = await UpdateExisting(oldestWorkItem, notificationInfo);
|
||||
if (stateChanged) {
|
||||
// add a comment if we re-opened the bug
|
||||
_ = await _client.AddCommentAsync(
|
||||
new CommentCreate() {
|
||||
Text = "This work item was re-opened because OneFuzz could only find related work items that are marked as duplicate."
|
||||
},
|
||||
_project,
|
||||
(int)oldestWorkItem.Id!);
|
||||
_logTracer.WithTags(new List<(string, string)> { ("NonDuplicateWorkItemId", $"{workItem.Id}") }).Info($"Found matching non-duplicate work item");
|
||||
_ = await UpdateExisting(workItem, notificationInfo);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (!updated) {
|
||||
if (oldestWorkItem != null) {
|
||||
// We have matching work items but all are duplicates
|
||||
_logTracer.WithTags(notificationInfo)
|
||||
.Info($"All matching work items were duplicates, re-opening the oldest one");
|
||||
var stateChanged = await UpdateExisting(oldestWorkItem, notificationInfo);
|
||||
if (stateChanged) {
|
||||
// add a comment if we re-opened the bug
|
||||
_ = await _client.AddCommentAsync(
|
||||
new CommentCreate() {
|
||||
Text =
|
||||
"This work item was re-opened because OneFuzz could only find related work items that are marked as duplicate."
|
||||
},
|
||||
_project,
|
||||
(int)oldestWorkItem.Id!);
|
||||
}
|
||||
} else {
|
||||
// We never saw a work item like this before, it must be new
|
||||
var entry = await CreateNew();
|
||||
var adoEventType = "AdoNewItem";
|
||||
_logTracer.WithTags(notificationInfo).Event($"{adoEventType} {entry.Id:Tag:WorkItemId}");
|
||||
}
|
||||
} else {
|
||||
// We never saw a work item like this before, it must be new
|
||||
var entry = await CreateNew();
|
||||
var adoEventType = "AdoNewItem";
|
||||
_logTracer.WithTags(notificationInfo).Event($"{adoEventType} {entry.Id:Tag:WorkItemId}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public interface IGithubIssues {
|
||||
Async.Task GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport? reportable, Guid notificationId);
|
||||
Async.Task GithubIssue(GithubIssuesTemplate config, Container container, IReport reportable, Guid notificationId);
|
||||
}
|
||||
|
||||
public class GithubIssues : NotificationsBase, IGithubIssues {
|
||||
@ -11,10 +11,8 @@ 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, Guid notificationId) {
|
||||
if (reportable == null) {
|
||||
return;
|
||||
}
|
||||
public async Async.Task GithubIssue(GithubIssuesTemplate config, Container container, IReport reportable, Guid notificationId) {
|
||||
var filename = reportable.FileName();
|
||||
|
||||
if (reportable is RegressionReport) {
|
||||
_logTracer.Info($"github issue integration does not support regression reports. {container:Tag:Container} - {filename:Tag:Filename}");
|
||||
|
@ -4,7 +4,7 @@ using System.Text.Json;
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public interface ITeams {
|
||||
Async.Task NotifyTeams(TeamsTemplate config, Container container, string filename, IReport reportOrRegression, Guid notificationId);
|
||||
Async.Task NotifyTeams(TeamsTemplate config, Container container, IReport reportOrRegression, Guid notificationId);
|
||||
}
|
||||
|
||||
public class Teams : ITeams {
|
||||
@ -53,10 +53,11 @@ public class Teams : ITeams {
|
||||
}
|
||||
}
|
||||
|
||||
public async Async.Task NotifyTeams(TeamsTemplate config, Container container, string filename, IReport reportOrRegression, Guid notificationId) {
|
||||
public async Async.Task NotifyTeams(TeamsTemplate config, Container container, IReport reportOrRegression, Guid notificationId) {
|
||||
var facts = new List<Dictionary<string, string>>();
|
||||
string? text = null;
|
||||
var title = string.Empty;
|
||||
var filename = reportOrRegression.FileName();
|
||||
|
||||
if (reportOrRegression is Report report) {
|
||||
var task = await _context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId);
|
||||
|
@ -84,7 +84,7 @@ public class ReportTests {
|
||||
_ = Assert.IsType<RegressionReport>(regression);
|
||||
|
||||
var noReport = Reports.ParseReportOrRegression("{}", new Uri("http://test"));
|
||||
Assert.Null(noReport);
|
||||
_ = Assert.IsType<UnknownReportType>(noReport);
|
||||
|
||||
|
||||
|
||||
|
@ -8,6 +8,7 @@ import logging
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
from urllib.parse import urlparse
|
||||
@ -18,13 +19,13 @@ from azure.applicationinsights import ApplicationInsightsDataClient
|
||||
from azure.applicationinsights.models import QueryBody
|
||||
from azure.identity import AzureCliCredential
|
||||
from azure.storage.blob import ContainerClient
|
||||
from onefuzztypes import models, requests
|
||||
from onefuzztypes import models, requests, responses
|
||||
from onefuzztypes.enums import ContainerType, TaskType
|
||||
from onefuzztypes.models import BlobRef, Job, NodeAssignment, Report, Task, TaskConfig
|
||||
from onefuzztypes.primitives import Container, Directory, PoolName
|
||||
from onefuzztypes.responses import TemplateValidationResponse
|
||||
|
||||
from onefuzz.api import UUID_EXPANSION, Command, Onefuzz
|
||||
from onefuzz.api import UUID_EXPANSION, Command, Endpoint, Onefuzz
|
||||
|
||||
from .azure_identity_credential_adapter import AzureIdentityCredentialAdapter
|
||||
from .backend import wait
|
||||
@ -775,6 +776,7 @@ class DebugNotification(Command):
|
||||
"""Inject a report into the specified crash reporting task"""
|
||||
|
||||
task = self.onefuzz.tasks.get(task_id)
|
||||
|
||||
crashes = self._get_container(task, ContainerType.crashes)
|
||||
reports = self._get_container(task, report_container_type)
|
||||
|
||||
@ -792,26 +794,15 @@ class DebugNotification(Command):
|
||||
handle.write("")
|
||||
self.onefuzz.containers.files.upload_file(crashes, file_path, crash_name)
|
||||
|
||||
report = Report(
|
||||
input_blob=BlobRef(
|
||||
account=self._get_storage_account(crashes),
|
||||
container=crashes,
|
||||
name=crash_name,
|
||||
),
|
||||
executable=task.config.task.target_exe,
|
||||
crash_type="fake crash report",
|
||||
crash_site="fake crash site",
|
||||
call_stack=["#0 fake", "#1 call", "#2 stack"],
|
||||
call_stack_sha256=ZERO_SHA256,
|
||||
input_sha256=EMPTY_SHA256,
|
||||
asan_log="fake asan log",
|
||||
task_id=task_id,
|
||||
job_id=task.job_id,
|
||||
minimized_stack=[],
|
||||
minimized_stack_function_names=[],
|
||||
tool_name="libfuzzer",
|
||||
tool_version="1.2.3",
|
||||
onefuzz_version="1.2.3",
|
||||
input_blob_ref = BlobRef(
|
||||
account=self._get_storage_account(crashes),
|
||||
container=crashes,
|
||||
name=crash_name,
|
||||
)
|
||||
|
||||
target_exe = task.config.task.target_exe if task.config.task.target_exe else ""
|
||||
report = self._create_report(
|
||||
task.job_id, task.task_id, target_exe, input_blob_ref
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
@ -823,6 +814,58 @@ class DebugNotification(Command):
|
||||
reports, file_path, crash_name + ".json"
|
||||
)
|
||||
|
||||
def test_template(
|
||||
self, task_id: UUID_EXPANSION, notificationConfig: models.NotificationConfig
|
||||
) -> responses.NotificationTestResponse:
|
||||
"""Test a notification template"""
|
||||
endpoint = Endpoint(self.onefuzz)
|
||||
task = self.onefuzz.tasks.get(task_id)
|
||||
input_blob_ref = BlobRef(
|
||||
account="dummy-storage-account",
|
||||
container="test-notification-crashes",
|
||||
name="fake-crash-sample",
|
||||
)
|
||||
|
||||
report = self._create_report(
|
||||
task.job_id, task.task_id, "fake_target.exe", input_blob_ref
|
||||
)
|
||||
report.report_url = "https://dummy-container.blob.core.windows.net/dummy-reports/dummy-report.json"
|
||||
|
||||
return endpoint._req_model(
|
||||
"POST",
|
||||
responses.NotificationTestResponse,
|
||||
data=requests.NotificationTest(
|
||||
report=report,
|
||||
notification=models.Notification(
|
||||
container=Container("test-notification-reports"),
|
||||
notification_id=uuid.uuid4(),
|
||||
config=notificationConfig.config,
|
||||
),
|
||||
),
|
||||
alternate_endpoint="notifications/test",
|
||||
)
|
||||
|
||||
def _create_report(
|
||||
self, job_id: UUID, task_id: UUID, target_exe: str, input_blob_ref: BlobRef
|
||||
) -> Report:
|
||||
return Report(
|
||||
input_blob=input_blob_ref,
|
||||
executable=target_exe,
|
||||
crash_type="fake crash report",
|
||||
crash_site="fake crash site",
|
||||
call_stack=["#0 fake", "#1 call", "#2 stack"],
|
||||
call_stack_sha256=ZERO_SHA256,
|
||||
input_sha256=EMPTY_SHA256,
|
||||
asan_log="fake asan log",
|
||||
task_id=task_id,
|
||||
job_id=job_id,
|
||||
minimized_stack=[],
|
||||
minimized_stack_function_names=[],
|
||||
tool_name="libfuzzer",
|
||||
tool_version="1.2.3",
|
||||
onefuzz_version="1.2.3",
|
||||
)
|
||||
|
||||
|
||||
class Debug(Command):
|
||||
"""Debug running jobs"""
|
||||
|
@ -231,6 +231,7 @@ class Report(BaseModel):
|
||||
minimized_stack_function_names_sha256: Optional[str]
|
||||
minimized_stack_function_lines: Optional[List[str]]
|
||||
minimized_stack_function_lines_sha256: Optional[str]
|
||||
report_url: Optional[str]
|
||||
|
||||
|
||||
class NoReproReport(BaseModel):
|
||||
|
@ -8,6 +8,8 @@ from uuid import UUID
|
||||
|
||||
from pydantic import AnyHttpUrl, BaseModel, Field, root_validator
|
||||
|
||||
from onefuzztypes import models
|
||||
|
||||
from ._monkeypatch import _check_hotfix
|
||||
from .consts import ONE_HOUR, SEVEN_DAYS
|
||||
from .enums import (
|
||||
@ -24,6 +26,7 @@ from .models import (
|
||||
AutoScaleConfig,
|
||||
InstanceConfig,
|
||||
NotificationConfig,
|
||||
Report,
|
||||
TemplateRenderContext,
|
||||
)
|
||||
from .primitives import Container, PoolName, Region
|
||||
@ -267,4 +270,9 @@ class JinjaToScribanMigrationPost(BaseModel):
|
||||
dry_run: bool = Field(default=False)
|
||||
|
||||
|
||||
class NotificationTest(BaseModel):
|
||||
report: Report
|
||||
notification: models.Notification
|
||||
|
||||
|
||||
_check_hotfix()
|
||||
|
@ -99,3 +99,8 @@ class JinjaToScribanMigrationResponse(BaseResponse):
|
||||
|
||||
class JinjaToScribanMigrationDryRunResponse(BaseResponse):
|
||||
notification_ids_to_update: List[UUID]
|
||||
|
||||
|
||||
class NotificationTestResponse(BaseResponse):
|
||||
success: bool
|
||||
error: Optional[str]
|
||||
|
Reference in New Issue
Block a user