This repository has been archived by the owner on Nov 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add github issues integration (#110)
- Loading branch information
Showing
12 changed files
with
276 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# Notifications via Github Issues | ||
|
||
OneFuzz can create or update [Github Issues](https://guides.github.com/features/issues/) | ||
upon creation of crash reports in OneFuzz managed [containers](../containers.md). | ||
|
||
Nearly every field can be customized using [jinja2](https://jinja.palletsprojects.com/) | ||
templates. There are multiple python 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)) | ||
|
||
Using these objects allows dynamic configuration. As an example, the `repository` | ||
could be specified directly, or dynamically pulled from the task configuration: | ||
|
||
```json | ||
{ | ||
"repository": "{{ task.tags['repository'] }}" | ||
} | ||
``` | ||
|
||
There are additional values that can be used in any template: | ||
|
||
* report_url: This will link to an authenticated download link for the report | ||
* input_url: This will link to an authenticated download link for crashing input | ||
* target_url: This will link to an authenticated download link for the target | ||
executable | ||
* repro_cmd: This will give an example command to initiate a live reproduction | ||
of a crash | ||
* report_container: This will give the name of the report storage container | ||
* report_filename: This will give the container relative path to the report | ||
|
||
## Example Config | ||
|
||
```json | ||
{ | ||
"config": { | ||
"auth": { | ||
"user": "INSERT_YOUR_USERNAME_HERE", | ||
"personal_access_token": "INSERT_YOUR_PERSONAL_ACCESS_TOKEN_HERE" | ||
}, | ||
"organization": "contoso", | ||
"repository": "sample-project", | ||
"title": "{{ report.executable }} - {{report.crash_site}}", | ||
"body": "## Files\n\n* input: [{{ report.input_blob.name }}]({{ input_url }})\n* exe: [{{ report.executable }}]( {{ target_url }})\n* report: [{{ report_filename }}]({{ report_url }})\n\n## Repro\n\n `{{ repro_cmd }}`\n\n## Call Stack\n\n```{% for item in report.call_stack %}{{ item }}\n{% endfor %}```\n\n## ASAN Log\n\n```{{ report.asan_log }}```", | ||
"unique_search": { | ||
"field_match": ["title"], | ||
"string": "{{ report.executable }}" | ||
}, | ||
"assignees": [], | ||
"labels": ["bug", "{{ report.crash_type }}"], | ||
"on_duplicate": { | ||
"comment": "Duplicate found.\n\n* input: [{{ report.input_blob.name }}]({{ input_url }})\n* exe: [{{ report.executable }}]( {{ target_url }})\n* report: [{{ report_filename }}]({{ report_url }})", | ||
"labels": ["{{ report.crash_type }}"], | ||
"reopen": true | ||
} | ||
} | ||
} | ||
``` | ||
|
||
For full documentation on the syntax, see [GithubIssueTemplate](../../src/pytypes/onefuzztypes/models.py)) | ||
|
||
## Integration | ||
|
||
1. Create a [Personal access token](https://github.com/settings/tokens). | ||
2. Update your config to specify your user and personal access token. | ||
1. Add a notification to your OneFuzz instance. | ||
|
||
``` | ||
onefuzz notifications create <CONTAINER> @./config.json | ||
``` | ||
Until the integration is deleted, when a crash report is written to the indicated container, | ||
issues will be created and updated based on the reports. | ||
The OneFuzz SDK provides an example tool [fake-report.py](../../src/cli/examples/fake-report.py), | ||
which can be used to generate a synthetic crash report to verify the integration | ||
is functional. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
src/api-service/__app__/onefuzzlib/notifications/github_issues.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.