From 87c031f6cbad42fa928736cf29dfab6911b7325b Mon Sep 17 00:00:00 2001 From: Michal Srb Date: Mon, 12 Feb 2024 22:07:20 +0100 Subject: [PATCH] reporter-bugzilla: Reduce number of duplicate bug reports Private bugs are problematic, because we might end up with many duplicates. We simply don't know if the problem has been reported already, if the previously reported bug is private. We are going to open a public bug instead, but the bug description, which might contain sensitive information, will be private. We cannot make attachments private though. Attachments in Bugzilla can be "Red Hat private", but we need users from certain groups (like "fedora_contrib_private") to have access to them. Therefore, we are going to create a separate supplementary bug that will be private and will only contain the attachments. This supplementary bug will be closed immediately, and the links to the attachments will be added to the first public bug in a private comment. Signed-off-by: Michal Srb --- .../reportclient/internal/bz_connection.py | 23 ++- src/plugins/python/reporter_bugzilla.py | 159 +++++++++--------- 2 files changed, 101 insertions(+), 81 deletions(-) diff --git a/src/client-python/reportclient/internal/bz_connection.py b/src/client-python/reportclient/internal/bz_connection.py index 8d5d301d..eab08888 100644 --- a/src/client-python/reportclient/internal/bz_connection.py +++ b/src/client-python/reportclient/internal/bz_connection.py @@ -175,7 +175,11 @@ def bug_create(self, summary: str, comment: str, private: bool, - group: List) -> int: + group: List, + status: Optional[str] = None, + resolution: Optional[str] = None, + comment_is_private: bool = False, + extra_private_groups: Optional[List[str]] = None) -> int: self.logger.debug('-- %s', inspect.getframeinfo(inspect.currentframe()).function) if group: @@ -200,6 +204,15 @@ def bug_create(self, 'status_whiteboard': whiteboard } + if status and resolution: + data['status'] = status + data['resolution'] = resolution + + if comment_is_private: + data['comment_is_private'] = comment_is_private + if extra_private_groups: + data['extra_private_groups'] = extra_private_groups + sub_component = None if product.startswith('Red Hat Enterprise Linux'): default_sub_components = {'binutils': 'system-version', @@ -270,10 +283,16 @@ def bug_get_comments(self, bug_id: int): return comments - def bug_add_comment(self, bug_id: int, comment: str): + def bug_add_comment(self, bug_id: int, comment: str, is_private: bool = False, extra_private_groups: Optional[List[str]] = None): self.logger.debug('-- %s', inspect.getframeinfo(inspect.currentframe()).function) params = self.params.copy() params.update({'id': bug_id, 'comment': comment}) + + if is_private: + params.update({'is_private': True}) + if extra_private_groups: + params.update({'extra_private_groups': extra_private_groups}) + response = requests.post(os.path.join(self.url, f'rest.cgi/bug/{bug_id}/comment'), headers=self.headers, params=params, diff --git a/src/plugins/python/reporter_bugzilla.py b/src/plugins/python/reporter_bugzilla.py index c19fba52..a40434d6 100755 --- a/src/plugins/python/reporter_bugzilla.py +++ b/src/plugins/python/reporter_bugzilla.py @@ -739,47 +739,65 @@ def log_out(bug_info, rhbz, dump_dir_name): # } - if existing_id < 0 or rhbz['b_create_private']: + if existing_id < 0: pf = ProblemFormatter(fmt_file, problem_data, logger) report = pf.generate_report() # TODO: if not report (how would that happen?) if existing_id > 0: - msg = _( - 'You have requested to make your data accessible only to a ' - 'specific group and this bug is a duplicate of bug: ' - '{}/{}' - ' ' - 'In case of bug duplicates a new comment is added to the ' - 'original bug report but access to the comments cannot be ' - 'restricted to a specific group.' - ' ' - 'Would you like to open a new bug report and close it as ' - 'DUPLICATE of the original one?' - ' ' - 'Otherwise, the bug reporting procedure will be terminated.' - ).format(rhbz['b_bugzilla_url'], existing_id) - if not ask_yes_no(msg): - sys.exit(const.EXIT_CANCEL_BY_USER) - msg = _( - "\nThis is a private, duplicate bug report of bug {}. " - "The report has been created because Bugzilla cannot " - "grant access to a comment for a specific group.\n" - ).format(existing_id) - report['text'] += msg + pass # Create new bug - logger.warning(_('Creating a new bug')) + logger.warning(_('Creating a new bug...')) if existing_id < 0 <= crossver_id: report['text'] += f"\nPotential duplicate: bug {crossver_id}\n" - new_id = bz_conn.bug_create(problem_data, rhbz['b_product'], rhbz['b_product_version'], - report['summary'], report['text'], rhbz['b_create_private'], - rhbz['b_private_groups']) + new_id = bz_conn.bug_create( + problem_data, + rhbz['b_product'], + rhbz['b_product_version'], + report['summary'], + report['text'], + False, + None, + comment_is_private = rhbz['b_create_private'], + extra_private_groups = rhbz['b_private_groups'] + ) if new_id == -1: - # bug_create logged an error + # bug_create logged the error sys.exit(1) + attachments_bug_id = new_id + + if rhbz['b_create_private']: + # User wants to create a new private bug + text = _( + f"This is a supplementary private bug for uploading attachments for the problem reported in {new_id}. " + f"These attachments are intended to be accessible only to members of the following group(s):\n\n" + f"{",".join(rhbz['b_private_groups'])}" + ).format(rhbz['b_private_groups']) + attachments_bug_id = bz_conn.bug_create( + problem_data, + rhbz['b_product'], + rhbz['b_product_version'], + report['summary'], + text, + True, + rhbz['b_private_groups'] + ) + if attachments_bug_id == -1: + # bug_create logged the error + sys.exit(1) + + # Immediately close the supplementary private bug with attachments + update_data = { + 'ids': [attachments_bug_id], + 'status': 'CLOSED', + 'resolution': 'NOTABUG', + 'minor_update': True + } + bz_conn.bug_update(attachments_bug_id, update_data) + dump_dir_obj = DumpDir(logger) dd = dump_dir_obj.dd_opendir(dump_dir_name, 0) if dd: @@ -802,12 +820,37 @@ def log_out(bug_info, rhbz, dump_dir_name): dump_dir_obj.dd_close(dd) - logger.warning(_('Adding attachments to bug %i'), new_id) + logger.warning(_('Adding attachments to bug %i'), attachments_bug_id) + bug_attachments = [] for attachment in report['attach']: - response = bz_conn.attachment_create_from_problem_data(new_id, attachment, problem_data) + response = bz_conn.attachment_create_from_problem_data(attachments_bug_id, attachment, problem_data) logger.info("Attached '%s' to bug no. %s with id %s", - attachment, new_id, response.json()['ids'][0]) + attachment, attachments_bug_id, response.json()['ids'][0]) + if response.status_code == 201: + bug_attachment = { + 'name': attachment, + 'id': response.json()['ids'][0] + } + bug_attachments.append(bug_attachment) + + if rhbz['b_create_private'] and new_id != attachments_bug_id: + text = _( + "Bugzilla does not offer support for private attachments accessible only to specific groups, " + "such as 'fedora_contrib_private'. As a result, a separate private bug has been created, " + "and the following attachments have been added there:\n\n" + ) + for bug_attachment in bug_attachments: + # We are hardcoding the URL here. The code already relies on certain "Red Hat Bugzilla API customizations", + # so it wouldn't reliably work with any other Bugzilla instance anyway... + text += f"* {bug_attachment['name']}: https://bugzilla-attachments.redhat.com/attachment.cgi?id={bug_attachment['id']}\n" + # We created a public bug FIXME + bz_conn.bug_add_comment( + new_id, + text, + is_private=rhbz['b_create_private'], + extra_private_groups=rhbz['b_private_groups'] + ) # We just created a new bug, let's make sure that we are in CC bug_info = bz_conn.bug_info(new_id) @@ -820,15 +863,6 @@ def log_out(bug_info, rhbz, dump_dir_name): 'bi_status': 'NEW', 'bi_dup_id': -1} - if existing_id >= 0: - logger.warning(_("Closing bug %i as duplicate of bug %i"), new_id, existing_id) - update_data = {'ids': [new_id], - 'status': 'CLOSED', - 'resolution': 'DUPLICATE', - 'dupe_of': existing_id, - 'minor_update': True} - bz_conn.bug_update(new_id, update_data) - log_out(bug_info, rhbz, dump_dir_name) bug_id = existing_id @@ -843,30 +877,6 @@ def log_out(bug_info, rhbz, dump_dir_name): if origin: bug_info = origin - # Original author's note: - # We used to skip adding the comment to CLOSED bugs: - # - # if (strcmp(bug_info['bi_status'], "CLOSED") != 0) - # { - # - # But that condition has been added without a good explanation of the - # reason for doing so: - # - # ABRT commit 1bf37ad93e87f065347fdb7224578d55cca8d384 - # - # - if (bug_id > 0) - # + if (strcmp(bz.bug_status, "CLOSED") != 0) - # - # - # From my point of view, there is no good reason to not add the comment to - # such a bug. The reporter spent several minutes waiting for the backtrace - # and we don't want to make the reporters feel that they spent their time - # in vain and I think that adding comments to already closed bugs doesn't - # hurt the maintainers (at least not me). - # - # Plenty of new comments might convince the maintainer to reconsider the - # bug's status. - # Add user's login to CC if not there already, but only if the login is known if ( rhbz.get('b_login') @@ -901,21 +911,12 @@ def log_out(bug_info, rhbz, dump_dir_name): if not dup_comment: logger.warning(_("Adding new comment to bug %d"), bug_info['bi_id']) - bz_conn.bug_add_comment(int(bug_info['bi_id']), bzcomment) - - bt = pd_get_item_content(const.FILENAME_BACKTRACE, problem_data) - rating_str = pd_get_item_content(const.FILENAME_RATING, problem_data) - rating = 0 - # python doesn't have rating file - if rating_str: - rtg = re.search(r'^\d+', rating_str) - if rtg: - rating = int(rtg.group(0)) - if rating > G_MAXUINT64: - logger.error("expected number in range <0, %lu>: '%s'", G_MAXUINT64, rating_str) - if bt and rating > bug_info.get('bi_best_bt_rating', 0): - logger.warning(_("Attaching better backtrace")) - bz_conn.attachment_create(int(bug_info['bi_id']), const.FILENAME_BACKTRACE, True) + bz_conn.bug_add_comment( + int(bug_info['bi_id']), + bzcomment, + is_private = rhbz['b_create_private'], + extra_private_groups = rhbz['b_private_groups'] + ) else: logger.warning(_('Found the same comment in the bug history, not adding a new one'))