Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apis/bugzilla_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ def task_id_comment_tag():
return os.environ.get('TASK_ID', 'missing')


def task_log_url():
if "TASK_ID" not in os.environ:
return None
root = os.environ.get('TASKCLUSTER_ROOT_URL', 'https://firefox-ci-tc.services.mozilla.com')
return root.rstrip('/') + '/tasks/' + os.environ['TASK_ID']


class CommentRejectedError(Exception):
"""Bugzilla's web filter (not Bugzilla itself) refused the comment, with an
empty 406 response. It has done this for comments quoting shell trace output."""
pass


def task_id_whiteboard():
if "TASK_ID" in os.environ:
return sq('task_id: ' + os.environ['TASK_ID'])
Expand Down Expand Up @@ -119,6 +132,9 @@ def commentOnBug(url, apikey, bugID, comment, needinfo=None, assignee=None):
json=data
)

if r.status_code == 406:
raise CommentRejectedError("Bugzilla rejected the comment on Bug %s (status=406)" % bugID)

j = _load_json_or_raise(r, "commentOnBug")

if 'bugs' in j:
Expand Down
22 changes: 19 additions & 3 deletions components/bugzilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from apis.bugzilla_api import fileBug, commentOnBug, closeBug, openBugsMetadata, markFFVersionAffected, getBugComments
from apis.bugzilla_api import fileBug, commentOnBug, closeBug, openBugsMetadata, markFFVersionAffected, getBugComments, CommentRejectedError, task_log_url
from components.providerbase import BaseProvider, INeedsLoggingProvider
from components.logging import LogLevel, logEntryExit

Expand Down Expand Up @@ -207,6 +207,16 @@ def MISSING_COMPONENT(library, filed_bug_id):
filed_bug_id, FALLBACK_PRODUCT_COMPONENT[0], FALLBACK_PRODUCT_COMPONENT[1],
library.yaml_path, filed_bug_id)

@staticmethod
def COMMENT_REJECTED(log_url):
s = "Updatebot tried to post a comment here, but Bugzilla rejected it, probably because "
s += "it quoted command output that Bugzilla's web filter considered suspicious. "
if log_url:
s += "The full message is in the Updatebot log: " + log_url
else:
s += "The full message is in the log of the Updatebot run that tried to post it."
return s

@staticmethod
def TRY_RUN_SUBMITTED(revision, another=False):
return "I've submitted a" + ("nother" if another else "") + " try run for this commit: https://treeherder.mozilla.org/jobs?repo=try&revision=" + revision
Expand Down Expand Up @@ -305,7 +315,7 @@ def _file_missing_component_bug(self, library, filed_bug_id):
self.logger.log("Could not file a bug about the missing component in %s: %s" % (library.yaml_path, e), level=LogLevel.Error)

@logEntryExit
def comment_on_bug(self, bug_id, comment, needinfo=None, assignee=None):
def comment_on_bug(self, bug_id, comment, needinfo=None, assignee=None, _is_fallback=False):
if len(comment) > 65535:
self.logger.log("Comment on Bug %s is too long: %s characters; truncating." % (bug_id, len(comment)), level=LogLevel.Warning)
suffix = "\n\nOops - the above comment was too long. I was unable to shrink it nicely, so I had to truncate it to fit Bugzilla's limits."
Expand All @@ -314,10 +324,16 @@ def comment_on_bug(self, bug_id, comment, needinfo=None, assignee=None):
try:
commentOnBug(
self.config['url'], self.config['apikey'], bug_id, comment, needinfo=needinfo, assignee=assignee)
except CommentRejectedError:
if _is_fallback:
raise
self.logger.log("Bugzilla rejected the comment on Bug %s; posting a short replacement instead. The rejected comment was:\n%s" % (bug_id, comment), level=LogLevel.Warning)
self.comment_on_bug(bug_id, CommentTemplates.COMMENT_REJECTED(task_log_url()), needinfo, assignee, _is_fallback=True)
return
except Exception as e:
if is_needinfo_exception(e):
self.logger.log("Developer is not accepting needinfos, trying again without...", level=LogLevel.Info)
self.comment_on_bug(bug_id, comment, None, assignee)
self.comment_on_bug(bug_id, comment, None, assignee, _is_fallback=_is_fallback)
else:
raise e

Expand Down
33 changes: 32 additions & 1 deletion tests/bugzilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import re
import sys
import unittest
Expand All @@ -18,14 +19,15 @@
from components.bugzilla import BugzillaProvider, CommentTemplates
from components.logging import SimpleLoggerConfig

from apis.bugzilla_api import task_id_whiteboard
from apis.bugzilla_api import task_id_whiteboard, CommentRejectedError

TRY_REVISION = "this-is-my-try-link"


class MockBugzillaServer(server.BaseHTTPRequestHandler):
closed_bug_ids = []
filed_bugs = []
received_comments = {}

def do_POST(self):
expectedPath_file = "/bug?api_key=bob"
Expand Down Expand Up @@ -105,6 +107,14 @@ def do_PUT(self):
size = int(self.headers.get('content-length'))
content = json.loads(self.rfile.read(size).decode("utf-8"))
bug_id = re.match(r"/bug/([0-9]+)\?api_key=bob", self.path).groups(0)[0]
MockBugzillaServer.received_comments.setdefault(bug_id, []).append(content)

# Like Bugzilla's web filter: an empty 406. Bug 1238 rejects everything
# but Updatebot's short replacement comment; bug 1239 rejects everything.
if bug_id == "1239" or (bug_id == "1238" and "Bugzilla rejected it" not in content['comment']['body']):
self.send_response(406)
self.end_headers()
return

self.send_response(200)
self.send_header("Content-type", "application/json")
Expand Down Expand Up @@ -237,6 +247,27 @@ def testFileMissingComponentFollowUpFails(self):
# Failing to file the follow-up must not lose the library's bug.
self.assertEqual(self.bugzillaProvider.file_bug(library, "Update broken to new version V1", "Details", []), 457)

def testCommentRejected(self):
os.environ['TASK_ID'] = 'abc123'
try:
self.bugzillaProvider.comment_on_bug(1238, "> ++ export VIRTUAL_ENV\n> ++ '[' -n /bin/bash ']'", needinfo='Jon')
finally:
del os.environ['TASK_ID']

sent = MockBugzillaServer.received_comments["1238"]
self.assertEqual(len(sent), 2, "Expected the rejected comment, then a replacement")
self.assertIn("Bugzilla rejected it", sent[1]['comment']['body'])
self.assertIn("https://firefox-ci-tc.services.mozilla.com/tasks/abc123", sent[1]['comment']['body'])
self.assertEqual(sent[1]['flags'][0]['requestee'], 'Jon', "The replacement should keep the needinfo")

def testCommentRejectedWithoutTaskId(self):
self.assertIn("log of the Updatebot run", CommentTemplates.COMMENT_REJECTED(None))

def testCommentAndReplacementRejected(self):
with self.assertRaises(CommentRejectedError):
self.bugzillaProvider.comment_on_bug(1239, "> ++ export VIRTUAL_ENV")
self.assertEqual(len(MockBugzillaServer.received_comments["1239"]), 2, "Should give up after the replacement is rejected too")

def testStatus(self):
self.bugzillaProvider.mark_ff_version_affected(456, 76)

Expand Down
Loading