From 371ec172de645c93f98b4c74da0755baba25b97d Mon Sep 17 00:00:00 2001 From: Wes Kocher Date: Wed, 30 Dec 2015 16:07:14 -0800 Subject: [PATCH] Bug 1117583 - Add an API endpoint that creates a new bug in Bugzilla. This endpoint receives a http POST request from the UI containing information about the bug to be filed (product, component, summary, version, description), then formats it properly as a submission to Bugzilla's REST API, using a server-side Bugzilla API key, and adding a "treeherder" comment tag. The API then passes back either the bug ID (if the submission was successful) or Bugzilla's failure response if something went wrong. --- tests/settings.py | 4 + tests/webapp/api/test_bugzilla.py | 108 ++++++++++++++++++++ treeherder/config/settings.py | 1 + treeherder/config/settings_local.example.py | 3 + treeherder/webapp/api/bugzilla.py | 49 +++++++++ treeherder/webapp/api/urls.py | 3 + 6 files changed, 168 insertions(+) create mode 100644 tests/webapp/api/test_bugzilla.py create mode 100644 treeherder/webapp/api/bugzilla.py diff --git a/tests/settings.py b/tests/settings.py index f5741ebfa..aa9e0eb06 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -10,3 +10,7 @@ CELERY_EAGER_PROPAGATES_EXCEPTIONS = True # Reconfigure pulse to operate on default vhost of rabbitmq PULSE_URI = BROKER_URL PULSE_EXCHANGE_NAMESPACE = 'test' + +# Set a fake api key for testing bug filing +BZ_API_KEY = "12345helloworld" +BZ_API_URL = "https://thisisnotbugzilla.org" diff --git a/tests/webapp/api/test_bugzilla.py b/tests/webapp/api/test_bugzilla.py new file mode 100644 index 000000000..f3e6ec2a2 --- /dev/null +++ b/tests/webapp/api/test_bugzilla.py @@ -0,0 +1,108 @@ +import json + +import responses +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from rest_framework.test import APIClient + + +def test_create_bug(webapp, eleven_jobs_stored, activate_responses): + """ + test successfully creating a bug in bugzilla + """ + + def request_callback(request): + headers = {} + requestdata = json.loads(request.body) + requestheaders = request.headers + print requestdata + print requestheaders + assert requestheaders['x-bugzilla-api-key'] == "12345helloworld" + assert requestdata['product'] == "Bugzilla" + assert requestdata['description'] == "Filed by: MyName\n\nIntermittent Description" + assert requestdata['component'] == "Administration" + assert requestdata['summary'] == "Intermittent summary" + assert requestdata['comment_tags'] == "treeherder" + assert requestdata['version'] == "4.0.17" + assert requestdata['keywords'] == "intermittent-failure" + resp_body = {"id": 323} + return(200, headers, json.dumps(resp_body)) + + responses.add_callback( + responses.POST, "https://thisisnotbugzilla.org/rest/bug", + callback=request_callback, match_querystring=False, + content_type="application/json", + ) + + client = APIClient() + user = User.objects.create(username="MyName", email="foo@bar.com") + client.force_authenticate(user=user) + + resp = client.post( + reverse("bugzilla-create-bug"), + { + "product": "Bugzilla", + "component": "Administration", + "summary": "Intermittent summary", + "version": "4.0.17", + "description": "Intermittent Description", + "comment_tags": "treeherder", + "keywords": "intermittent-failure", + } + ) + + user.delete() + + content = json.loads(resp.content) + + print content + assert content['success'] == 323 + + +def test_create_unauthenticated_bug(webapp, eleven_jobs_stored, activate_responses): + """ + test successfully creating a bug in bugzilla + """ + + def request_callback(request): + headers = {} + requestdata = json.loads(request.body) + requestheaders = request.headers + print requestdata + print requestheaders + assert requestheaders['x-bugzilla-api-key'] == "12345helloworld" + assert requestdata['product'] == "Bugzilla" + assert requestdata['description'] == "Filed by: MyName\n\nIntermittent Description" + assert requestdata['component'] == "Administration" + assert requestdata['summary'] == "Intermittent summary" + assert requestdata['comment_tags'] == "treeherder" + assert requestdata['version'] == "4.0.17" + assert requestdata['keywords'] == "intermittent-failure" + resp_body = {"id": 323} + return(200, headers, json.dumps(resp_body)) + + responses.add_callback( + responses.POST, "https://thisisnotbugzilla.org/rest/bug", + callback=request_callback, match_querystring=False, + content_type="application/json", + ) + + client = APIClient() + + resp = client.post( + reverse("bugzilla-create-bug"), + { + "product": "Bugzilla", + "component": "Administration", + "summary": "Intermittent summary", + "version": "4.0.17", + "description": "Intermittent Description", + "comment_tags": "treeherder", + "keywords": "intermittent-failure", + } + ) + + content = json.loads(resp.content) + + print content + assert content['detail'] == "Authentication credentials were not provided." diff --git a/treeherder/config/settings.py b/treeherder/config/settings.py index 6127a33eb..aff182b03 100644 --- a/treeherder/config/settings.py +++ b/treeherder/config/settings.py @@ -327,6 +327,7 @@ PARSER_MAX_SUMMARY_LINES = 200 FAILURE_LINES_CUTOFF = 35 BZ_API_URL = "https://bugzilla.mozilla.org" +BZ_API_KEY = env("BUGZILLA_API_KEY", default=None) ORANGEFACTOR_SUBMISSION_URL = "https://brasstacks.mozilla.com/orangefactor/api/saveclassification" ORANGEFACTOR_HAWK_ID = "treeherder" diff --git a/treeherder/config/settings_local.example.py b/treeherder/config/settings_local.example.py index 139d6ea11..646454cb7 100644 --- a/treeherder/config/settings_local.example.py +++ b/treeherder/config/settings_local.example.py @@ -1,3 +1,6 @@ +# Switch to using a different bugzilla instance +BZ_API_URL = "http://bugzilla-dev.allizom.org/" + # Applications useful for development, e.g. debug_toolbar, django_extensions. # Always empty in production LOCAL_APPS = [] diff --git a/treeherder/webapp/api/bugzilla.py b/treeherder/webapp/api/bugzilla.py new file mode 100644 index 000000000..a4ef1bbc4 --- /dev/null +++ b/treeherder/webapp/api/bugzilla.py @@ -0,0 +1,49 @@ +import requests +from django.conf import settings +from rest_framework import (status, + viewsets) +from rest_framework.decorators import list_route +from rest_framework.permissions import IsAuthenticatedOrReadOnly +from rest_framework.response import Response + +from treeherder.etl.common import make_request + + +class BugzillaViewSet(viewsets.ViewSet): + permission_classes = (IsAuthenticatedOrReadOnly,) + + @list_route(methods=['post']) + def create_bug(self, request): + """ + Create a bugzilla bug with passed params + """ + + if settings.BZ_API_KEY is None: + return Response({"failure": "Bugzilla API key not defined. This shouldn't happen."}, status=status.HTTP_400_BAD_REQUEST) + else: + params = request.data + url = settings.BZ_API_URL + "/rest/bug" + headers = { + 'x-bugzilla-api-key': settings.BZ_API_KEY + } + data = { + 'product': params["product"], + 'component': params["component"], + 'summary': params["summary"], + 'keywords': params["keywords"], + 'version': params["version"], + 'description': "Filed by: " + request.user.username + "\n\n" + params["description"], + 'comment_tags': "treeherder", + } + + try: + response = make_request(url, method='POST', headers=headers, json=data) + except requests.exceptions.HTTPError as e: + response = e.response + try: + rsperror = response.json()['message'] + except: + rsperror = response + return Response({"failure": rsperror}, status=status.HTTP_400_BAD_REQUEST) + + return Response({"success": response.json()["id"]}) diff --git a/treeherder/webapp/api/urls.py b/treeherder/webapp/api/urls.py index 2ddd0e5ba..342a50350 100644 --- a/treeherder/webapp/api/urls.py +++ b/treeherder/webapp/api/urls.py @@ -4,6 +4,7 @@ from rest_framework import routers from treeherder.webapp.api import (artifact, bug, + bugzilla, job_log_url, jobs, logslice, @@ -108,6 +109,8 @@ default_router.register(r'performance/alert', default_router.register(r'performance/framework', performance_data.PerformanceFrameworkViewSet, base_name='performance-frameworks') +default_router.register(r'bugzilla', bugzilla.BugzillaViewSet, + base_name='bugzilla') urlpatterns = [ url(r'^project/(?P[\w-]{0,50})/',