mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-15 03:18:07 +00:00
Add github issues integration (#110)
This commit is contained in:
@ -24,11 +24,9 @@ from azure.devops.v6_0.work_item_tracking.work_item_tracking_client import (
|
||||
WorkItemTrackingClient,
|
||||
)
|
||||
from memoization import cached
|
||||
from onefuzztypes.enums import ErrorCode
|
||||
from onefuzztypes.models import ADOTemplate, Error, Report
|
||||
from onefuzztypes.models import ADOTemplate, Report
|
||||
|
||||
from ..tasks.main import Task
|
||||
from .common import Render
|
||||
from .common import Render, fail_task
|
||||
|
||||
|
||||
@cached(ttl=60)
|
||||
@ -201,21 +199,6 @@ class ADO:
|
||||
self.create_new()
|
||||
|
||||
|
||||
def fail_task(report: Report, error: Exception) -> None:
|
||||
logging.error(
|
||||
"ADO report failed: job_id:%s task_id:%s err:%s",
|
||||
report.job_id,
|
||||
report.task_id,
|
||||
error,
|
||||
)
|
||||
|
||||
task = Task.get(report.job_id, report.task_id)
|
||||
if task:
|
||||
task.mark_failed(
|
||||
Error(code=ErrorCode.NOTIFICATION_FAILURE, errors=[str(error)])
|
||||
)
|
||||
|
||||
|
||||
def notify_ado(
|
||||
config: ADOTemplate, container: str, filename: str, report: Report
|
||||
) -> None:
|
||||
|
@ -3,10 +3,12 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
from onefuzztypes.models import Report
|
||||
from onefuzztypes.enums import ErrorCode
|
||||
from onefuzztypes.models import Error, Report
|
||||
|
||||
from ..azure.containers import auth_download_url
|
||||
from ..jobs import Job
|
||||
@ -14,6 +16,21 @@ from ..tasks.config import get_setup_container
|
||||
from ..tasks.main import Task
|
||||
|
||||
|
||||
def fail_task(report: Report, error: Exception) -> None:
|
||||
logging.error(
|
||||
"notification failed: job_id:%s task_id:%s err:%s",
|
||||
report.job_id,
|
||||
report.task_id,
|
||||
error,
|
||||
)
|
||||
|
||||
task = Task.get(report.job_id, report.task_id)
|
||||
if task:
|
||||
task.mark_failed(
|
||||
Error(code=ErrorCode.NOTIFICATION_FAILURE, errors=[str(error)])
|
||||
)
|
||||
|
||||
|
||||
class Render:
|
||||
def __init__(self, container: str, filename: str, report: Report):
|
||||
self.report = report
|
||||
|
@ -0,0 +1,102 @@
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from github3 import login
|
||||
from github3.exceptions import GitHubException
|
||||
from github3.issues import Issue
|
||||
from onefuzztypes.enums import GithubIssueSearchMatch
|
||||
from onefuzztypes.models import GithubIssueTemplate, Report
|
||||
|
||||
from .common import Render, fail_task
|
||||
|
||||
|
||||
class GithubIssue:
|
||||
def __init__(
|
||||
self, config: GithubIssueTemplate, container: str, filename: str, report: Report
|
||||
):
|
||||
self.config = config
|
||||
self.report = report
|
||||
self.gh = login(
|
||||
username=config.auth.user, password=config.auth.personal_access_token
|
||||
)
|
||||
self.renderer = Render(container, filename, report)
|
||||
|
||||
def render(self, field: str) -> str:
|
||||
return self.renderer.render(field)
|
||||
|
||||
def existing(self) -> List[Issue]:
|
||||
query = [
|
||||
self.render(self.config.unique_search.string),
|
||||
"repo:%s/%s"
|
||||
% (
|
||||
self.render(self.config.organization),
|
||||
self.render(self.config.repository),
|
||||
),
|
||||
]
|
||||
if self.config.unique_search.author:
|
||||
query.append("author:%s" % self.render(self.config.unique_search.author))
|
||||
|
||||
if self.config.unique_search.state:
|
||||
query.append("state:%s" % self.config.unique_search.state.name)
|
||||
|
||||
issues = []
|
||||
title = self.render(self.config.title)
|
||||
body = self.render(self.config.body)
|
||||
for issue in self.gh.search_issues(" ".join(query)):
|
||||
skip = False
|
||||
for field in self.config.unique_search.field_match:
|
||||
if field == GithubIssueSearchMatch.title and issue.title != title:
|
||||
skip = True
|
||||
break
|
||||
if field == GithubIssueSearchMatch.body and issue.body != body:
|
||||
skip = True
|
||||
break
|
||||
if not skip:
|
||||
issues.append(issue)
|
||||
|
||||
return issues
|
||||
|
||||
def update(self, issue: Issue) -> None:
|
||||
logging.info("updating issue: %s", issue)
|
||||
if self.config.on_duplicate.comment:
|
||||
issue.issue.create_comment(self.render(self.config.on_duplicate.comment))
|
||||
if self.config.on_duplicate.labels:
|
||||
labels = [self.render(x) for x in self.config.on_duplicate.labels]
|
||||
issue.issue.edit(labels=labels)
|
||||
if self.config.on_duplicate.reopen and issue.state != "open":
|
||||
issue.issue.edit(state="open")
|
||||
|
||||
def create(self) -> None:
|
||||
logging.info("creating issue")
|
||||
|
||||
assignees = [self.render(x) for x in self.config.assignees]
|
||||
labels = list(set(["OneFuzz"] + [self.render(x) for x in self.config.labels]))
|
||||
|
||||
self.gh.create_issue(
|
||||
self.render(self.config.organization),
|
||||
self.render(self.config.repository),
|
||||
self.render(self.config.title),
|
||||
body=self.render(self.config.body),
|
||||
labels=labels,
|
||||
assignees=assignees,
|
||||
)
|
||||
|
||||
def process(self) -> None:
|
||||
issues = self.existing()
|
||||
if issues:
|
||||
self.update(issues[0])
|
||||
else:
|
||||
self.create()
|
||||
|
||||
|
||||
def github_issue(
|
||||
config: GithubIssueTemplate, container: str, filename: str, report: Optional[Report]
|
||||
) -> None:
|
||||
if report is None:
|
||||
return
|
||||
|
||||
try:
|
||||
handler = GithubIssue(config, container, filename, report)
|
||||
handler.process()
|
||||
except GitHubException as err:
|
||||
fail_task(report, err)
|
@ -10,7 +10,13 @@ from uuid import UUID
|
||||
from memoization import cached
|
||||
from onefuzztypes import models
|
||||
from onefuzztypes.enums import ErrorCode, TaskState
|
||||
from onefuzztypes.models import ADOTemplate, Error, NotificationTemplate, TeamsTemplate
|
||||
from onefuzztypes.models import (
|
||||
ADOTemplate,
|
||||
Error,
|
||||
GithubIssueTemplate,
|
||||
NotificationTemplate,
|
||||
TeamsTemplate,
|
||||
)
|
||||
from onefuzztypes.primitives import Container, Event
|
||||
|
||||
from ..azure.containers import get_container_metadata, get_file_sas_url
|
||||
@ -22,6 +28,7 @@ from ..reports import get_report
|
||||
from ..tasks.config import get_input_container_queues
|
||||
from ..tasks.main import Task
|
||||
from .ado import notify_ado
|
||||
from .github_issues import github_issue
|
||||
from .teams import notify_teams
|
||||
|
||||
|
||||
@ -111,6 +118,9 @@ def new_files(container: Container, filename: str) -> None:
|
||||
if isinstance(notification.config, ADOTemplate):
|
||||
notify_ado(notification.config, container, filename, report)
|
||||
|
||||
if isinstance(notification.config, GithubIssueTemplate):
|
||||
github_issue(notification.config, container, filename, report)
|
||||
|
||||
for (task, containers) in get_queue_tasks():
|
||||
if container in containers:
|
||||
logging.info("queuing input %s %s %s", container, filename, task.task_id)
|
||||
|
Reference in New Issue
Block a user