From 611ca48b8d81e5edc0907885ece48cb0dce986fa Mon Sep 17 00:00:00 2001 From: Matej Habrnal Date: Tue, 13 Jan 2015 19:30:27 -0500 Subject: [PATCH] reporter-mantisbt: first version of the reporter-mantisbt Related to #272 Signed-off-by: Matej Habrnal --- configure.ac | 33 + po/POTFILES.in | 11 + src/plugins/Makefile.am | 43 +- src/plugins/centos_report_event.conf | 37 + src/plugins/mantisbt.c | 1111 ++++++++++++++++++++++++ src/plugins/mantisbt.conf | 8 + src/plugins/mantisbt.h | 140 +++ src/plugins/mantisbt_format.conf | 59 ++ src/plugins/mantisbt_formatdup.conf | 65 ++ src/plugins/report_CentOSBugTracker.conf | 4 + src/plugins/report_CentOSBugTracker.xml.in | 65 ++ src/plugins/reporter-mantisbt.c | 696 +++++++++++++++ src/workflows/Makefile.am | 15 +- src/workflows/report_centos.conf | 31 + src/workflows/workflow_CentOSCCpp.xml.in | 12 + src/workflows/workflow_CentOSJava.xml.in | 11 + src/workflows/workflow_CentOSKerneloops.xml.in | 11 + src/workflows/workflow_CentOSLibreport.xml.in | 9 + src/workflows/workflow_CentOSPython.xml.in | 11 + src/workflows/workflow_CentOSPython3.xml.in | 11 + src/workflows/workflow_CentOSVmcore.xml.in | 12 + src/workflows/workflow_CentOSXorg.xml.in | 9 + 22 files changed, 2402 insertions(+), 2 deletions(-) create mode 100644 src/plugins/centos_report_event.conf create mode 100644 src/plugins/mantisbt.c create mode 100644 src/plugins/mantisbt.conf create mode 100644 src/plugins/mantisbt.h create mode 100644 src/plugins/mantisbt_format.conf create mode 100644 src/plugins/mantisbt_formatdup.conf create mode 100644 src/plugins/report_CentOSBugTracker.conf create mode 100644 src/plugins/report_CentOSBugTracker.xml.in create mode 100644 src/plugins/reporter-mantisbt.c create mode 100644 src/workflows/report_centos.conf create mode 100644 src/workflows/workflow_CentOSCCpp.xml.in create mode 100644 src/workflows/workflow_CentOSJava.xml.in create mode 100644 src/workflows/workflow_CentOSKerneloops.xml.in create mode 100644 src/workflows/workflow_CentOSLibreport.xml.in create mode 100644 src/workflows/workflow_CentOSPython.xml.in create mode 100644 src/workflows/workflow_CentOSPython3.xml.in create mode 100644 src/workflows/workflow_CentOSVmcore.xml.in create mode 100644 src/workflows/workflow_CentOSXorg.xml.in diff --git a/configure.ac b/configure.ac index 8aea410..66508c0 100644 --- a/configure.ac +++ b/configure.ac @@ -131,6 +131,39 @@ do done fi dnl end NO_BUGZILLA +AC_ARG_WITH(mantisbt, +AS_HELP_STRING([--with-mantisbt],[use MantisBT plugin (default is YES)]), +LIBREPORT_PARSE_WITH([mantisbt])) + +if test -z "$NO_MANTISBT"; then +AM_CONDITIONAL(BUILD_MANTISBT, true) + +# enable mantisbt & deps translations +for FILE in `grep -e "#.*antisbt.*" -e "#.*naconda.*" po/POTFILES.in` +do + sed -ie "s,$FILE,${FILE:1}," po/POTFILES.in + sed -ie "\,^${FILE:1}$,d" po/POTFILES.skip +done +else +AM_CONDITIONAL(BUILD_MANTISBT, false) + +# disablie mantisbt & deps translations +for FILE in `grep -e "antisbt" -e "naconda" po/POTFILES.in` +do + if test "${FILE:0:1}" = "#" + then + continue + fi + + sed -ie "s,$FILE,#$FILE," po/POTFILES.in + grep "$FILE" po/POTFILES.skip > /dev/null 2>&1 + if test $? + then + echo "$FILE" >> po/POTFILES.skip + fi +done +fi dnl end NO_MANTISBT + AC_PATH_PROG([PYTHON_CONFIG], [python-config], [no]) [if test "$PYTHON_CONFIG" = "no"] [then] diff --git a/po/POTFILES.in b/po/POTFILES.in index 5588540..3415b03 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -36,6 +36,8 @@ src/plugins/reporter-print.c src/plugins/reporter-rhtsupport.c src/plugins/reporter-rhtsupport-parse.c src/plugins/reporter-upload.c +src/plugins/reporter-mantisbt.c +src/plugins/report_CentOSBugTracker.xml.in src/plugins/report_Kerneloops.xml.in src/plugins/report_Logger.xml.in src/plugins/report_Mailx.xml.in @@ -44,12 +46,21 @@ src/plugins/report_Uploader.xml.in src/plugins/report_uReport.xml.in src/plugins/report_EmergencyAnalysis.xml.in src/plugins/rhbz.c +src/plugins/mantisbt.c src/plugins/reporter-ureport.c src/report-newt/report-newt.c src/workflows/workflow_AnacondaFedora.xml.in src/workflows/workflow_AnacondaRHEL.xml.in src/workflows/workflow_AnacondaRHELBugzilla.xml.in src/workflows/workflow_AnacondaUpload.xml.in +src/workflows/workflow_CentOSCCpp.xml.in +src/workflows/workflow_CentOSJava.xml.in +src/workflows/workflow_CentOSKerneloops.xml.in +src/workflows/workflow_CentOSLibreport.xml.in +src/workflows/workflow_CentOSPython.xml.in +src/workflows/workflow_CentOSPython3.xml.in +src/workflows/workflow_CentOSVmcore.xml.in +src/workflows/workflow_CentOSXorg.xml.in src/workflows/workflow_FedoraCCpp.xml.in src/workflows/workflow_FedoraKerneloops.xml.in src/workflows/workflow_FedoraPython.xml.in diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 7ec08d7..d70b4db 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -7,6 +7,11 @@ reporters_bin += \ report endif +if BUILD_MANTISBT +reporters_bin += \ + reporter-mantisbt +endif + if BUILD_UREPORT reporters_bin += reporter-ureport endif @@ -34,6 +39,12 @@ reporters_plugin_format_conf += bugzilla_format.conf \ bugzilla_formatdup_anaconda.conf endif +if BUILD_MANTISBT +reporters_plugin_conf += mantisbt.conf +reporters_plugin_format_conf += mantisbt_format.conf \ + mantisbt_formatdup.conf +endif + defaultreportpluginsconfdir = $(DEFAULT_REPORT_PLUGINS_CONF_DIR) dist_defaultreportpluginsconf_DATA = $(reporters_plugin_conf) \ rhtsupport.conf \ @@ -53,6 +64,12 @@ reporters_events += report_Bugzilla.xml reporters_events_conf += report_Bugzilla.conf endif +if BUILD_MANTISBT +reporters_events += report_CentOSBugTracker.xml + +reporters_events_conf += report_CentOSBugTracker.conf +endif + if BUILD_UREPORT reporters_events += report_uReport.xml endif @@ -77,7 +94,8 @@ dist_eventsdef_DATA = \ print_event.conf \ rhtsupport_event.conf \ uploader_event.conf \ - emergencyanalysis_event.conf + emergencyanalysis_event.conf \ + centos_report_event.conf reporters_extra_dist = if BUILD_BUGZILLA @@ -125,6 +143,29 @@ reporter_bugzilla_LDADD = \ ../lib/libreport.la endif +if BUILD_MANTISBT +reporter_mantisbt_SOURCES = \ + reporter-mantisbt.c mantisbt.c mantisbt.h +reporter_mantisbt_CPPFLAGS = \ + -I$(srcdir)/../include \ + -I$(srcdir)/../lib \ + -DBIN_DIR=\"$(bindir)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(REPORT_PLUGINS_CONF_DIR)\" \ + $(GLIB_CFLAGS) \ + $(LIBREPORT_CFLAGS) \ + $(LIBXML_CFLAGS) \ + -D_GNU_SOURCE +reporter_mantisbt_LDADD = \ + $(GLIB_LIBS) \ + ../lib/libreport-web.la \ + ../lib/libreport.la +endif + reporter_rhtsupport_SOURCES = \ abrt_rh_support.h abrt_rh_support.c \ reporter-rhtsupport.h \ diff --git a/src/plugins/centos_report_event.conf b/src/plugins/centos_report_event.conf new file mode 100644 index 0000000..53f12d8 --- /dev/null +++ b/src/plugins/centos_report_event.conf @@ -0,0 +1,37 @@ +EVENT=report_CentOSBugTracker analyzer=xorg + reporter-mantisbt + +EVENT=report_CentOSBugTracker analyzer=Kerneloops + reporter-mantisbt + +EVENT=report_CentOSBugTracker analyzer=vmcore + reporter-mantisbt + +EVENT=report_CentOSBugTracker analyzer=Python component!=anaconda + test -f component || abrt-action-save-package-data + reporter-mantisbt \ + -c /etc/libreport/plugins/mantisbt.conf \ + -F /etc/libreport/plugins/mantisbt_format.conf \ + -A /etc/libreport/plugins/mantisbt_formatdup.conf + +EVENT=report_CentOSBugTracker analyzer=Python3 component!=anaconda + test -f component || abrt-action-save-package-data + reporter-mantisbt \ + -c /etc/libreport/plugins/mantisbt.conf \ + -F /etc/libreport/plugins/mantisbt_format.conf \ + -A /etc/libreport/plugins/mantisbt_formatdup.conf + +EVENT=report_CentOSBugTracker analyzer=CCpp duphash!= + test -f component || abrt-action-save-package-data + component="`cat component`" + format="mantisbt_format.conf" + test -f "/etc/libreport/plugins/mantisbt_format_$component.conf" \ + && format="mantisbt_format_$component.conf" + formatdup="mantisbt_formatdup.conf" + test -f "/etc/libreport/plugins/mantisbt_formatdup_$component.conf" \ + && formatdup="mantisbt_formatdup_$component.conf" + reporter-mantisbt \ + -c /etc/libreport/plugins/mantisbt.conf \ + -F "/etc/libreport/plugins/$format" \ + -A "/etc/libreport/plugins/$formatdup" + diff --git a/src/plugins/mantisbt.c b/src/plugins/mantisbt.c new file mode 100644 index 0000000..1c496b4 --- /dev/null +++ b/src/plugins/mantisbt.c @@ -0,0 +1,1111 @@ +/* + Copyright (C) 2014 ABRT team + Copyright (C) 2014 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include + +#include + +#include "internal_libreport.h" +#include "libreport_curl.h" +#include "mantisbt.h" +#include "client.h" + +/* + * SOAP +*/ + +#define XML_VERSION "" + +/* fprint string */ +#define SOAP_TEMPLATE \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" + +#define MAX_SUMMARY_LENGTH 128 +#define CUSTOMFIELD_DUPHASH "abrt_hash" +#define CUSTOMFIELD_URL "URL" +#define MAX_HOPS 5 + +/* MantisBT limit is 2MB by default + */ +#define MANTISBT_MAX_FILE_UPLOAD_SIZE (2 * 1024 * 1024) + +typedef struct mantisbt_custom_fields +{ + char *cf_abrt_hash_id; + char *cf_url_id; +} mantisbt_custom_fields_t; + +/* + * MantisBT settings issue info + */ +void +mantisbt_settings_free(mantisbt_settings_t *s) +{ + if (s == NULL) + return; + + free(s->m_login); + free(s->m_password); + free(s->m_project); + free(s->m_project_id); + free(s->m_project_version); +} + +/* + * MantisBT issue info + */ +mantisbt_issue_info_t * +mantisbt_issue_info_new() +{ + mantisbt_issue_info_t *info = xzalloc(sizeof(mantisbt_issue_info_t)); + info->mii_id = -1; + info->mii_dup_id = -1; + + return info; +} + +void +mantisbt_issue_info_free(mantisbt_issue_info_t *info) +{ + if (info == NULL) + return; + + free(info->mii_status); + free(info->mii_resolution); + free(info->mii_reporter); + free(info->mii_project); + + list_free_with_free(info->mii_notes); + list_free_with_free(info->mii_attachments); + + free(info); +} + +mantisbt_issue_info_t * +mantisbt_find_origin_bug_closed_duplicate(mantisbt_settings_t *settings, mantisbt_issue_info_t *info) +{ + mantisbt_issue_info_t *info_tmp = mantisbt_issue_info_new(); + info_tmp->mii_id = info->mii_id; + info_tmp->mii_dup_id = info->mii_dup_id; + + for (int ii = 0; ii <= MAX_HOPS; ii++) + { + if (ii == MAX_HOPS) + error_msg_and_die(_("MantisBT couldn't find parent of issue %d"), info->mii_id); + + log("Issue %d is a duplicate, using parent issue %d", info_tmp->mii_id, info_tmp->mii_dup_id); + int issue_id = info_tmp->mii_dup_id; + + mantisbt_issue_info_free(info_tmp); + info_tmp = mantisbt_get_issue_info(settings, issue_id); + + // found a issue which is not CLOSED as DUPLICATE + if (info_tmp->mii_dup_id == -1) + break; + } + + return info_tmp; +} + +/* + * SOAP request + */ +static soap_request_t * +soap_request_new() +{ + soap_request_t *req = xzalloc(sizeof(*req)); + + return req; +} + +void +soap_request_free(soap_request_t *req) +{ + if (req == NULL) + return; + + if (req->sr_root != NULL) + xmlFreeDoc(req->sr_root->doc); + + free(req); + + return; +} + +static xmlNodePtr +soap_node_get_next_element_node(xmlNodePtr node) +{ + for (; node != NULL; node = node->next) + if (node->type == XML_ELEMENT_NODE) + break; + + return node; +} + +static xmlNodePtr +soap_node_get_child_element(xmlNodePtr node) +{ + if (node == NULL) + error_msg_and_die(_("SOAP: Failed to get child element because of no parent.")); + + return soap_node_get_next_element_node(node->xmlChildrenNode); +} + +static xmlNodePtr +soap_node_get_next_sibling(xmlNodePtr node) +{ + if (node == NULL) + error_msg_and_die(_("SOAP: Failed to get next element because of no node.")); + + return soap_node_get_next_element_node(node->next); +} + +static xmlNodePtr +soap_node_get_child_node(xmlNodePtr parent, const char *name) +{ + if (parent == NULL) + error_msg_and_die(_("SOAP: Failed to get child node because of no parent.")); + + xmlNodePtr node; + for (node = soap_node_get_child_element(parent); node != NULL; node = soap_node_get_next_sibling(node)) + { + if (xmlStrcmp(node->name, BAD_CAST name) == 0) + return node; + } + + return NULL; +} + +soap_request_t * +soap_request_new_for_method(const char *method) +{ + char *xml_str = xasprintf(SOAP_TEMPLATE, method, method); + + xmlDocPtr doc = xmlParseDoc(BAD_CAST xml_str); + free(xml_str); + + if (doc == NULL) + error_msg_and_die(_("SOAP: Failed to parse xml during creating request.")); + + soap_request_t *req = soap_request_new(); + + req->sr_root = xmlDocGetRootElement(doc); + if (req->sr_root == NULL) + { + soap_request_free(req); + error_msg_and_die(_("SOAP: Failed to get xml root element.")); + } + + req->sr_body = soap_node_get_child_node(req->sr_root, "Body"); + req->sr_method = soap_node_get_child_node(req->sr_body, method); + + return req; +} + +static xmlNodePtr +soap_node_add_child_node(xmlNodePtr node, const char *name, const char *type, const char *value) +{ + if (node == NULL || name == NULL) + error_msg_and_die(_("SOAP: Failed to add a new child node because of no node or no child name.")); + + xmlNodePtr new_node = xmlNewTextChild(node, /* namespace */ NULL, BAD_CAST name, BAD_CAST value); + + if (new_node == NULL) + error_msg_and_die(_("SOAP: Failed to create a new xml child item.")); + + if (type != NULL) + { + if (xmlNewProp(new_node, BAD_CAST "xsi:type", BAD_CAST type) == NULL) + error_msg_and_die(_("SOAP: Failed to create a new property.")); + } + + return new_node; +} + +void +soap_request_add_method_parameter(soap_request_t *req, const char *name, const char *type, const char *value) +{ + if (req == NULL || req->sr_method == NULL) + error_msg_and_die(_("SOAP: Failed to add method parametr.")); + + soap_node_add_child_node(req->sr_method, name, type, value); + return; +} + +void +soap_request_add_credentials_parameter(soap_request_t *req, const mantisbt_settings_t *settings) +{ + soap_request_add_method_parameter(req, "username", SOAP_STRING, settings->m_login); + soap_request_add_method_parameter(req, "password", SOAP_STRING, settings->m_password); + + return; +} + +static void +soap_add_new_issue_parameters(soap_request_t *req, + const char *project, + const char *version, + const char *category, + const char *summary, + const char *description, + const char *additional_information, + bool private, + mantisbt_custom_fields_t *fields, + const char *duphash, + const char *tracker_url) +{ + if (req == NULL || req->sr_method == NULL) + error_msg_and_die(_("SOAP: Failed to add new issue parametrs.")); + + if (project == NULL || category == NULL || summary == NULL || description == NULL) + error_msg_and_die(_("SOAP: Failed to add new issue parameters because the required items are missing.")); + + xmlNodePtr issue_node = soap_node_add_child_node(req->sr_method, "issue", SOAP_ISSUEDATA, /* content */ NULL); + + // project + xmlNodePtr project_node = soap_node_add_child_node(issue_node, "project", SOAP_OBJECTREF, /* content */ NULL); + soap_node_add_child_node(project_node, "name", SOAP_STRING, project); + + // view status + xmlNodePtr view_node = soap_node_add_child_node(issue_node, "view_state", SOAP_OBJECTREF, /* content */ NULL); + soap_node_add_child_node(view_node, "name", SOAP_STRING, (private) ? "private" : "public"); + + /* if any custom field exists */ + int custom_fields_count = 0; + xmlNodePtr duphash_node; + if (fields->cf_abrt_hash_id != NULL || fields->cf_url_id != NULL) + duphash_node = soap_node_add_child_node(issue_node, "custom_fields", SOAP_CUSTOMFIELD_ARRAY, /* content */ NULL); + + // custom fields (duphash and URL to tracker) + xmlNodePtr item_node, field_node; + if (fields->cf_abrt_hash_id != NULL) + { + item_node = soap_node_add_child_node(duphash_node, "item", SOAP_CUSTOMFIELD, /* content */ NULL); + field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL); + soap_node_add_child_node(field_node, "id", SOAP_INTEGER, /* custom_field id */ fields->cf_abrt_hash_id); + soap_node_add_child_node(item_node, "value", SOAP_STRING, duphash); + ++custom_fields_count; + } + + // if tracker url exists, attach it to the issue + if (tracker_url != NULL && fields->cf_url_id != NULL) + { + item_node = soap_node_add_child_node(duphash_node, "item", SOAP_CUSTOMFIELD, /* content */ NULL); + field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL); + soap_node_add_child_node(field_node, "id", SOAP_INTEGER, /* custom_field */ fields->cf_url_id); + soap_node_add_child_node(item_node, "value", SOAP_STRING, tracker_url); + ++custom_fields_count; + } + + if (custom_fields_count > 0) + { + char *type = xasprintf("%s[%i]", SOAP_CUSTOMFIELD, custom_fields_count); + + if (xmlNewProp(duphash_node, BAD_CAST "ns3:arrayType", BAD_CAST type) == NULL) + error_msg_and_die(_("SOAP: Failed to create a new property in custom fields.")); + + free(type); + } + + soap_node_add_child_node(issue_node, "os_build", SOAP_STRING, version); + soap_node_add_child_node(issue_node, "category", SOAP_STRING, category); + soap_node_add_child_node(issue_node, "summary", SOAP_STRING, summary); + soap_node_add_child_node(issue_node, "description", SOAP_STRING, description); + soap_node_add_child_node(issue_node, "additional_information", SOAP_STRING, additional_information); + + return; +} + +char * +soap_request_to_str(const soap_request_t *req) +{ + if (req == NULL || req->sr_root == NULL || req->sr_root->doc == NULL) + error_msg_and_die(_("SOAP: Failed to create SOAP string because of invalid function arguments.")); + + xmlBufferPtr buffer = xmlBufferCreate(); + int err = xmlNodeDump(buffer, req->sr_root->doc, req->sr_root, 1, /* formatting */ 0); + if (err == -1) + { + xmlBufferFree(buffer); + error_msg_and_die(_("SOAP: Failed to dump xml node.")); + } + + char *ret = xasprintf("%s%s", XML_VERSION, (const char *) xmlBufferContent(buffer)); + xmlBufferFree(buffer); + + return ret; +} + +#if 0 +void +soap_request_print(soap_request_t *req) +{ + if (req == NULL || req->sr_root == NULL || req->sr_root->doc == NULL) + error_msg_and_die(_("SOAP: Failed to print SOAP string.")); + + xmlBufferPtr buffer = xmlBufferCreate(); + int err = xmlNodeDump(buffer, req->sr_root->doc, req->sr_root, 1, /* formatting */ 0); + if (err == -1) + { + xmlBufferFree(buffer); + error_msg_and_die(_("Failed to dump xml node.")); + } + + puts((const char *) xmlBufferContent(buffer)); + + xmlBufferFree(buffer); + return; +} +#endif + +static bool +reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(xmlTextReaderPtr reader, const char *name) +{ + /* is not element node */ + if (xmlTextReaderNodeType(reader) != XML_ELEMENT_NODE) + return false; + + /* is not required name */ + if (xmlStrcmp(xmlTextReaderConstName(reader), BAD_CAST name) != 0) + return false; + + /* read next node */ + if (xmlTextReaderRead(reader) != 1) + return false; + + /* no value node */ + if (xmlTextReaderHasValue(reader) == 0) + return false; + + /* no text node */ + if (xmlTextReaderNodeType(reader) != XML_TEXT_NODE) + return false; + + return true; +} + +static void +reader_find_element_by_name(xmlTextReaderPtr reader, const char *name) +{ + while (xmlTextReaderRead(reader) == 1) + { + /* is not element node */ + if (xmlTextReaderNodeType(reader) != XML_ELEMENT_NODE) + continue; + + /* is not required name */ + if (xmlStrcmp(xmlTextReaderConstName(reader), BAD_CAST name) != 0) + continue; + + break; + } + + return; +} + +/* It is not possible to search only by name because the response contains + * different node with the same name. (e.g. id - user id, project id, issue id etc.) + * We are interested in only about issues id which is located at a different depth than others. + * ... + * + * 10 <-- This is issue ID (required) + * + * 10 <-- This is view_state ID (not required) + * public + * + * + * 1 <-- This is project ID (not required) + * test + * + * ... + */ +static GList * +response_values_at_depth_by_name(const char *xml, const char *name, int depth) +{ + xmlDocPtr doc = xmlParseDoc(BAD_CAST xml); + if (doc == NULL) + error_msg_and_die(_("SOAP: Failed to parse xml (searching value at depth by name).")); + + xmlTextReaderPtr reader = xmlReaderWalker(doc); + if (reader == NULL) + error_msg_and_die(_("SOAP: Failed to create xml text reader.")); + + GList *result = NULL; + + const xmlChar *value; + while (xmlTextReaderRead(reader) == 1) + { + /* is not right depth */ + if (depth != -1 && xmlTextReaderDepth(reader) != depth) + continue; + + if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, name) == false) + continue; + + if ((value = xmlTextReaderConstValue(reader)) != NULL) + result = g_list_append(result, xstrdup((const char *) value)); + } + xmlFreeTextReader(reader); + + return result; +} + +/* + * Finds an element named 'elem' and returns a text of a child named 'name' + * + * Example: + * For + * + * 1 + * foo + * + * + * returns "foo" + */ +static char * +response_get_name_value_of_element(const char *xml, const char *element) +{ + xmlDocPtr doc = xmlParseDoc(BAD_CAST xml); + if (doc == NULL) + error_msg_and_die(_("SOAP: Failed to parse xml.")); + + xmlTextReaderPtr reader = xmlReaderWalker(doc); + if (reader == NULL) + error_msg_and_die(_("SOAP: Failed to create xml text reader.")); + + const xmlChar *value = NULL; + + reader_find_element_by_name(reader, element); + + /* find 'name' element and return its text */ + while (xmlTextReaderRead(reader) == 1) + { + if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "name") == false) + continue; + + if ((value = xmlTextReaderConstValue(reader)) != NULL) + break; + } + xmlFreeTextReader(reader); + + return (char *) value; +} + +static int +response_get_id_of_relatedto_issue(const char *xml) +{ + xmlDocPtr doc = xmlParseDoc(BAD_CAST xml); + if (doc == NULL) + error_msg_and_die(_("SOAP: Failed to parse xml (get related to issue).")); + + xmlTextReaderPtr reader = xmlReaderWalker(doc); + if (reader == NULL) + error_msg_and_die(_("SOAP: Failed to create xml text reader.")); + + const xmlChar *value = NULL; + const xmlChar *id = NULL; + + /* find relationships section */ + reader_find_element_by_name(reader, "relationships"); + + /* find "name" value of 'name' element */ + while (xmlTextReaderRead(reader) == 1) + { + /* find type of relattionship */ + if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "name") == false) + continue; + + if ((value = xmlTextReaderConstValue(reader)) == NULL) + continue; + + /* we need 'duplicate of' realtionship type */ + if (xmlStrcmp(value, BAD_CAST "duplicate of") != 0) + continue; + + /* find id of duplicate issues */ + reader_find_element_by_name(reader, "target_id"); + + /* verify target_id node */ + if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "target_id") == false) + continue; + + /* get its value */ + if ((id = xmlTextReaderConstValue(reader)) != NULL) + break; + } + xmlFreeTextReader(reader); + + return (id == NULL) ? -1 : atoi((const char *) id); +} + +GList * +response_get_main_ids_list(const char *xml) +{ + return response_values_at_depth_by_name(xml, "id", 5); +} + +int +response_get_main_id(const char *xml) +{ + GList *l = response_values_at_depth_by_name(xml, "id", 5); + return (l != NULL) ? atoi(l->data) : -1; +} + +static int +response_get_return_value(const char *xml) +{ + GList *l = response_values_at_depth_by_name(xml, "return", 3); + return (l != NULL) ? atoi(l->data) : -1; +} + +static char* +response_get_return_value_as_string(const char *xml) +{ + GList *l = response_values_at_depth_by_name(xml, "return", 3); + return (l != NULL) ? l->data : NULL; +} + +static char * +response_get_error_msg(const char *xml) +{ + GList *l = response_values_at_depth_by_name(xml, "faultstring", 3); + return (l != NULL) ? xstrdup(l->data) : NULL; +} + +static char * +response_get_additioanl_information(const char *xml) +{ + GList *l = response_values_at_depth_by_name(xml, "additional_information", -1); + return (l != NULL) ? xstrdup(l->data) : NULL; +} + +void +response_values_free(GList *values) +{ + g_list_free_full(values, free); +} + +/* + * POST + */ + +void +mantisbt_result_free(mantisbt_result_t *result) +{ + if (result == NULL) + return; + + free(result->mr_url); + free(result->mr_msg); + free(result->mr_body); + free(result); +} + +mantisbt_result_t * +mantisbt_soap_call(const mantisbt_settings_t *settings, const soap_request_t *req) +{ + char *request = soap_request_to_str(req); + + const char *url = settings->m_mantisbt_soap_url; + + mantisbt_result_t *result = xzalloc(sizeof(*result)); + + if (url == NULL || request == NULL) + { + result->mr_error = -2; + result->mr_msg = xasprintf(_("Url or request isn't specified.")); + free(request); + + return result; + } + + char *url_copy = NULL; + + int redirect_count = 0; + char *errmsg; + post_state_t *post_state; + +redirect: + post_state = new_post_state(0 + + POST_WANT_HEADERS + + POST_WANT_BODY + + POST_WANT_ERROR_MSG + + (settings->m_ssl_verify ? POST_WANT_SSL_VERIFY : 0) + ); + + post_string(post_state, settings->m_mantisbt_soap_url, "text/xml", NULL, request); + + char *location = find_header_in_post_state(post_state, "Location:"); + + switch (post_state->http_resp_code) + { + case 404: + result->mr_error = -1; + result->mr_msg = xasprintf(_("Error in HTTP POST, " + "HTTP code: 404 (Not found), URL:'%s'"), url); + break; + case 500: + result->mr_error = -1; + result->mr_msg = response_get_error_msg(post_state->body); + + break; + case 301: /* "301 Moved Permanently" (for example, used to move http:// to https://) */ + case 302: /* "302 Found" (just in case) */ + case 305: /* "305 Use Proxy" */ + if (++redirect_count < 10 && location) + { + free(url_copy); + url = url_copy = xstrdup(location); + free_post_state(post_state); + goto redirect; + } + /* fall through */ + + default: + result->mr_error = -1; + errmsg = post_state->curl_error_msg; + if (errmsg && errmsg[0]) + result->mr_msg = xasprintf(_("Error in MantisBT request at '%s': %s"), url, errmsg); + else + result->mr_msg = xasprintf(_("Error in MantisBT request at '%s'"), url); + break; + + case 200: + case 201: + /* sent successfully */ + result->mr_url = xstrdup(location); /* note: xstrdup(NULL) returns NULL */ + } /* switch (HTTP code) */ + + result->mr_http_resp_code = post_state->http_resp_code; + result->mr_body = post_state->body; + post_state->body = NULL; + + free_post_state(post_state); + free(url_copy); + free(request); + + return result; +} + +int +mantisbt_attach_data(const mantisbt_settings_t *settings, const char *bug_id, + const char *att_name, const char *data, int size) +{ + soap_request_t *req = soap_request_new_for_method("mc_issue_attachment_add"); + soap_request_add_credentials_parameter(req, settings); + + soap_request_add_method_parameter(req, "issue_id", SOAP_INTEGER, bug_id); + soap_request_add_method_parameter(req, "name", SOAP_STRING, att_name); + + soap_request_add_method_parameter(req, "file_type", SOAP_STRING, "text"); + soap_request_add_method_parameter(req, "content", SOAP_BASE64, encode_base64(data, size)); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + soap_request_free(req); + + if (result->mr_http_resp_code != 200) + { + int ret = -1; + if (strcmp(result->mr_msg, "Duplicate filename.") == 0) + ret = -2; + + error_msg(_("Failed to attach file: '%s'"), result->mr_msg); + mantisbt_result_free(result); + return ret; + } + + int id = response_get_return_value(result->mr_body); + + mantisbt_result_free(result); + + return id; +} + +static int +mantisbt_attach_fd(const mantisbt_settings_t *settings, const char *bug_id, + const char *att_name, int fd) +{ + off_t size = lseek(fd, 0, SEEK_END); + if (size < 0) + { + perror_msg(_("Can't lseek '%s'"), att_name); + return -1; + } + + if (size >= MANTISBT_MAX_FILE_UPLOAD_SIZE) + { + error_msg(_("Can't upload '%s', it's too large (%llu bytes)"), att_name, (long long)size); + return -1; + } + lseek(fd, 0, SEEK_SET); + + char *data = xmalloc(size + 1); + ssize_t r = full_read(fd, data, size); + if (r < 0) + { + free(data); + perror_msg(_("Can't read '%s'"), att_name); + return -1; + } + + int res = mantisbt_attach_data(settings, bug_id, att_name, data, size); + free(data); + return res; +} + +int +mantisbt_attach_file(const mantisbt_settings_t *settings, const char *bug_id, + const char *att_name, const char *path) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) + { + perror_msg(_("Can't open '%s'"), path); + return 0; + } + errno = 0; + struct stat st; + if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) + { + perror_msg("'%s': not a regular file", path); + close(fd); + return 0; + } + log_debug("attaching '%s' as file", att_name); + int ret = mantisbt_attach_fd(settings, bug_id, att_name, fd); + close(fd); + return ret; +} + +static void +soap_filter_add_new_array_parameter(xmlNodePtr filter_node, const char *name, const char *type, const char *value) +{ + const char *array_type = NULL; + if( strcmp(type, SOAP_INTEGER) == 0 ) + array_type = SOAP_INTEGERARRAY; + else + array_type = SOAP_STRINGARRAY; + + xmlNodePtr filter_item = soap_node_add_child_node(filter_node, name, array_type, /* content */ NULL); + soap_node_add_child_node(filter_item, "item", type, value); +} + +static void +soap_filter_custom_fields_add_new_item(xmlNodePtr filter_node, const char *custom_field_name, const char *value) +{ + xmlNodePtr item_node = soap_node_add_child_node(filter_node, "item", SOAP_FILTER_CUSTOMFIELD, /* content */ NULL); + + xmlNodePtr field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL); + soap_node_add_child_node(field_node, "name", SOAP_STRING, custom_field_name); + + xmlNodePtr value_node = soap_node_add_child_node(item_node, "value", SOAP_STRINGARRAY, /* content */ NULL); + soap_node_add_child_node(value_node, "item", SOAP_STRING, value); +} + +GList * +mantisbt_search_by_abrt_hash(mantisbt_settings_t *settings, const char *abrt_hash) +{ + soap_request_t *req = soap_request_new_for_method("mc_filter_search_issues"); + soap_request_add_credentials_parameter(req, settings); + + xmlNodePtr filter_node = soap_node_add_child_node(req->sr_method, "filter", SOAP_FILTER_SEARCH_DATA, /* content */ NULL); + + /* 'hide_status_is : -2' means, searching within all status */ + soap_filter_add_new_array_parameter(filter_node, "hide_status_id", SOAP_INTEGERARRAY, "-2"); + + // custom fields + xmlNodePtr custom_fields_node = soap_node_add_child_node(filter_node, "custom_fields", SOAP_FILTER_CUSTOMFIELD_ARRAY, /* content */ NULL); + + // custom field 'abrt_hash' + soap_filter_custom_fields_add_new_item(custom_fields_node, "abrt_hash", abrt_hash); + + soap_request_add_method_parameter(req, "page_number", SOAP_INTEGER, "1"); + soap_request_add_method_parameter(req, "per_page", SOAP_INTEGER, /* -1 means get all issues */ "-1"); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + soap_request_free(req); + + if (result->mr_error == -1) + { + error_msg(_("Failed to search MantisBT issue by duphash: '%s'"), result->mr_msg); + mantisbt_result_free(result); + return NULL; + } + + GList *ids = response_get_main_ids_list(result->mr_body); + + return ids; +} + +GList * +mantisbt_search_duplicate_issues(mantisbt_settings_t *settings, const char *category, + const char *version, const char *abrt_hash) +{ + soap_request_t *req = soap_request_new_for_method("mc_filter_search_issues"); + soap_request_add_credentials_parameter(req, settings); + + xmlNodePtr filter_node = soap_node_add_child_node(req->sr_method, "filter", SOAP_FILTER_SEARCH_DATA, /* content */ NULL); + + soap_filter_add_new_array_parameter(filter_node, "project_id", SOAP_INTEGERARRAY, settings->m_project_id); + + /* 'hide_status_is : -2' means, searching within all status */ + soap_filter_add_new_array_parameter(filter_node, "hide_status_id", SOAP_INTEGERARRAY, "-2"); + + soap_filter_add_new_array_parameter(filter_node, "category", SOAP_STRINGARRAY, category); + + // custom fields + xmlNodePtr custom_fields_node = soap_node_add_child_node(filter_node, "custom_fields", SOAP_FILTER_CUSTOMFIELD_ARRAY, /* content */ NULL); + + // custom field 'abrt_hash' + soap_filter_custom_fields_add_new_item(custom_fields_node, "abrt_hash", abrt_hash); + + // version + if (version != NULL) + soap_filter_add_new_array_parameter(filter_node, "os_build", SOAP_STRINGARRAY, version); + + soap_request_add_method_parameter(req, "page_number", SOAP_INTEGER, "1"); + soap_request_add_method_parameter(req, "per_page", SOAP_INTEGER, /* -1 means get all issues */ "-1"); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + soap_request_free(req); + + if (result->mr_error == -1) + { + error_msg(_("Failed to search MantisBT duplicate issue: '%s'"), result->mr_msg); + mantisbt_result_free(result); + return NULL; + } + + GList *ids = response_get_main_ids_list(result->mr_body); + + return ids; +} + +static char * +custom_field_get_id_from_name(GList *ids, GList *names, const char *name) +{ + GList *i = ids; + GList *n = names; + for (; i != NULL; i = i->next, n = n->next) + { + if (strcmp(n->data, name) == 0) + return i->data; + } + + return NULL; +} + +static void +custom_field_ask(const char *name) +{ + char *msg = xasprintf(_("CentOS Bug Tracker doesn't contain custom field '%s', which is required for full functionality of the reporter. Do you still want to create a new issue?"), name); + int yes = ask_yes_no(msg); + free(msg); + + if (!yes) + { + set_xfunc_error_retval(EXIT_CANCEL_BY_USER); + xfunc_die(); + } + + return; +} + +static void +mantisbt_get_custom_fields(const mantisbt_settings_t *settings, mantisbt_custom_fields_t *fields, const char *project_id) +{ + soap_request_t *req = soap_request_new_for_method("mc_project_get_custom_fields"); + soap_request_add_credentials_parameter(req, settings); + soap_request_add_method_parameter(req, "project_id", SOAP_INTEGER, project_id); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + soap_request_free(req); + + if (result->mr_http_resp_code != 200) + error_msg_and_die(_("Failed to get custom fields for '%s' project"), settings->m_project); + + GList *ids = response_values_at_depth_by_name(result->mr_body, "id", -1); + GList *names = response_values_at_depth_by_name(result->mr_body, "name", -1); + + mantisbt_result_free(result); + + if ((fields->cf_abrt_hash_id = custom_field_get_id_from_name(ids, names, CUSTOMFIELD_DUPHASH)) == NULL) + custom_field_ask(CUSTOMFIELD_DUPHASH); + + if ((fields->cf_url_id = custom_field_get_id_from_name(ids, names, CUSTOMFIELD_URL)) == NULL) + custom_field_ask(CUSTOMFIELD_URL); + + return; +} + +int +mantisbt_create_new_issue(const mantisbt_settings_t *settings, + problem_data_t *problem_data, + const problem_report_t *pr, + const char *tracker_url) +{ + + const char *category = problem_data_get_content_or_NULL(problem_data, FILENAME_COMPONENT); + const char *duphash = problem_data_get_content_or_NULL(problem_data, FILENAME_DUPHASH); + + char *summary = shorten_string_to_length(problem_report_get_summary(pr), MAX_SUMMARY_LENGTH); + + const char *description = problem_report_get_description(pr); + const char *additional_information = problem_report_get_section(pr, PR_SEC_ADDITIONAL_INFO); + + mantisbt_custom_fields_t fields; + mantisbt_get_custom_fields(settings, &fields, settings->m_project_id); + + soap_request_t *req = soap_request_new_for_method("mc_issue_add"); + soap_request_add_credentials_parameter(req, settings); + soap_add_new_issue_parameters(req, settings->m_project, settings->m_project_version, category, summary, description, additional_information, settings->m_create_private, &fields, duphash, tracker_url); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + soap_request_free(req); + free(summary); + + if (result->mr_error == -1) + { + error_msg(_("Failed to create a new issue: '%s'"), result->mr_msg); + mantisbt_result_free(result); + return -1; + } + + int id = response_get_return_value(result->mr_body); + + mantisbt_result_free(result); + return id; +} + +mantisbt_issue_info_t * +mantisbt_get_issue_info(const mantisbt_settings_t *settings, int issue_id) +{ + soap_request_t *req = soap_request_new_for_method("mc_issue_get"); + soap_request_add_credentials_parameter(req, settings); + + char *issue_id_str = xasprintf("%d", issue_id); + soap_request_add_method_parameter(req, "issue_id", SOAP_INTEGER, issue_id_str); + free(issue_id_str); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + soap_request_free(req); + + if (result->mr_error == -1) + { + error_msg(_("Failed to get MantisBT issue: '%s'"), result->mr_msg); + mantisbt_result_free(result); + return NULL; + } + + mantisbt_issue_info_t *issue_info = mantisbt_issue_info_new(); + + issue_info->mii_id = issue_id; + issue_info->mii_status = response_get_name_value_of_element(result->mr_body, "status"); + issue_info->mii_resolution = response_get_name_value_of_element(result->mr_body, "resolution"); + issue_info->mii_reporter = response_get_name_value_of_element(result->mr_body, "reporter"); + issue_info->mii_project = response_get_name_value_of_element(result->mr_body, "project"); + + if (strcmp(issue_info->mii_status, "closed") == 0 && !issue_info->mii_resolution) + error_msg(_("Issue %i is CLOSED, but it has no RESOLUTION"), issue_info->mii_id); + + issue_info->mii_dup_id = response_get_id_of_relatedto_issue(result->mr_body); + + if (strcmp(issue_info->mii_status, "closed") == 0 + && strcmp(issue_info->mii_resolution, "duplicate") == 0 + && issue_info->mii_dup_id == -1 ) + { + error_msg(_("Issue %i is CLOSED as DUPLICATE, but it has no DUPLICATE_ID"), + issue_info->mii_id); + } + + /* notes are stored in element */ + issue_info->mii_notes = response_values_at_depth_by_name(result->mr_body, "text", -1); + + /* looking for bt rating in additional information too */ + char *add_info = response_get_additioanl_information(result->mr_body); + if (add_info != NULL) + issue_info->mii_notes = g_list_append (issue_info->mii_notes, add_info); + issue_info->mii_attachments = response_values_at_depth_by_name(result->mr_body, "filename", -1); + issue_info->mii_best_bt_rating = comments_find_best_bt_rating(issue_info->mii_notes); + + mantisbt_result_free(result); + return issue_info; +} + +int +mantisbt_add_issue_note(const mantisbt_settings_t *settings, int issue_id, const char *note) +{ + soap_request_t *req = soap_request_new_for_method("mc_issue_note_add"); + soap_request_add_credentials_parameter(req, settings); + + char *issue_id_str = xasprintf("%i", issue_id); + soap_node_add_child_node(req->sr_method, "issue_id", SOAP_INTEGER, issue_id_str); + + xmlNodePtr note_node = soap_node_add_child_node(req->sr_method, "note", SOAP_ISSUENOTE, /* content */ NULL); + soap_node_add_child_node(note_node, "text", SOAP_STRING, note); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + + free(issue_id_str); + soap_request_free(req); + + if (result->mr_error == -1) + { + error_msg(_("Failed to add MantisBT issue note: '%s'"), result->mr_msg); + mantisbt_result_free(result); + return -1; + } + int id = response_get_return_value(result->mr_body); + + mantisbt_result_free(result); + return id; +} + +void +mantisbt_get_project_id_from_name(mantisbt_settings_t *settings) +{ + if (settings->m_project == NULL) + error_msg_and_die(_("The MantisBT project has not been deretmined.")); + + soap_request_t *req = soap_request_new_for_method("mc_project_get_id_from_name"); + soap_request_add_credentials_parameter(req, settings); + soap_node_add_child_node(req->sr_method, "project_name", SOAP_STRING, settings->m_project); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + + if (result->mr_http_resp_code != 200) + error_msg_and_die(_("Failed to get project id from name")); + + settings->m_project_id = response_get_return_value_as_string(result->mr_body); + + return; +} diff --git a/src/plugins/mantisbt.conf b/src/plugins/mantisbt.conf new file mode 100644 index 0000000..15a1065 --- /dev/null +++ b/src/plugins/mantisbt.conf @@ -0,0 +1,8 @@ +# MantisBT URL +MantisbtURL = https://bugs.centos.org/ +# yes means that ssl certificates will be checked +SSLVerify = yes +# your login has to exist, if you don have any, please create one +Login = +# your password +Password = diff --git a/src/plugins/mantisbt.h b/src/plugins/mantisbt.h new file mode 100644 index 0000000..c31c174 --- /dev/null +++ b/src/plugins/mantisbt.h @@ -0,0 +1,140 @@ +/* + Copyright (C) 2014 ABRT team + Copyright (C) 2014 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef MANTISBT_H +#define MANTISBT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "problem_report.h" + +#define SOAP_STRING "ns2:string" +#define SOAP_INTEGER "ns2:integer" +#define SOAP_INTEGERARRAY "ns2:IntegerArray" +#define SOAP_STRINGARRAY "ns2:StringArray" +#define SOAP_ISSUEDATA "ns3:IssueData" +#define SOAP_OBJECTREF "ns3:ObjectRef" +#define SOAP_CUSTOMFIELD_ARRAY "ns2:CustomFieldValueForIssueDataArray" +#define SOAP_FILTER_CUSTOMFIELD "ns2:FilterCustomField" +#define SOAP_FILTER_CUSTOMFIELD_ARRAY "ns2:FilterCustomFieldArray" +#define SOAP_FILTER_SEARCH_DATA "ns2:FilterSearchData" +#define SOAP_CUSTOMFIELD "ns2:CustomFieldValueForIssueData" +#define SOAP_BASE64 "SOAP-ENC:base64" +#define SOAP_ISSUENOTE "ns3:IssueNoteData" + +#define PR_SEC_ADDITIONAL_INFO "Additional info" + +typedef struct soap_request +{ + xmlNodePtr sr_root; + xmlNodePtr sr_body; + xmlNodePtr sr_method; +} soap_request_t; + +typedef struct mantisbt_settings +{ + char *m_login; + char *m_password; + const char *m_mantisbt_url; + const char *m_mantisbt_soap_url; + char *m_project; + char *m_project_id; + char *m_project_version; + const char *m_DontMatchComponents; + int m_ssl_verify; + int m_create_private; +} mantisbt_settings_t; + +typedef struct mantisbt_result +{ + int mr_http_resp_code; + int mr_error; + char *mr_msg; + char *mr_url; + char *mr_body; +} mantisbt_result_t; + +typedef struct mantisbt_issue_info +{ + int mii_id; + int mii_dup_id; + unsigned mii_best_bt_rating; + + char *mii_status; + char *mii_resolution; + char *mii_reporter; + char *mii_project; + + GList *mii_notes; + GList *mii_attachments; +} mantisbt_issue_info_t; + +void mantisbt_settings_free(mantisbt_settings_t *settings); + +mantisbt_issue_info_t * mantisbt_issue_info_new(); +void mantisbt_issue_info_free(mantisbt_issue_info_t *info); +mantisbt_issue_info_t * mantisbt_find_origin_bug_closed_duplicate(mantisbt_settings_t *settings, mantisbt_issue_info_t *info); + +void soap_request_free(soap_request_t *req); + +soap_request_t *soap_request_new_for_method(const char *method); + +void soap_request_add_method_parameter(soap_request_t *req, const char *name, const char *type, const char *value); +void soap_request_add_credentials_parameter(soap_request_t *req, const mantisbt_settings_t *settings); + +char *soap_request_to_str(const soap_request_t *req); + +#if 0 +void soap_request_print(soap_request_t *req); +#endif + +GList * response_get_main_ids_list(const char *xml); +int response_get_main_id(const char *xml); +void response_values_free(GList *values); + +void mantisbt_result_free(mantisbt_result_t *result); +mantisbt_result_t *mantisbt_soap_call(const mantisbt_settings_t *settings, const soap_request_t *req); + +int mantisbt_attach_data(const mantisbt_settings_t *settings, const char *bug_id, + const char *att_name, const char *data, int size); + +int mantisbt_attach_file(const mantisbt_settings_t *settings, const char *bug_id, + const char *att_name, const char *data); + +GList * mantisbt_search_by_abrt_hash(mantisbt_settings_t *settings, const char *abrt_hash); +GList * mantisbt_search_duplicate_issues(mantisbt_settings_t *settings, const char *category, + const char *version, const char *abrt_hash); + +int mantisbt_create_new_issue(const mantisbt_settings_t *settings, problem_data_t *problem_data, + const problem_report_t *pr, const char *tracker_url); + +mantisbt_issue_info_t * mantisbt_get_issue_info(const mantisbt_settings_t *settings, int issue_id); +int mantisbt_add_issue_note(const mantisbt_settings_t *settings, int issue_id, const char *note); + +void mantisbt_get_project_id_from_name(mantisbt_settings_t *settings); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/plugins/mantisbt_format.conf b/src/plugins/mantisbt_format.conf new file mode 100644 index 0000000..da08aa6 --- /dev/null +++ b/src/plugins/mantisbt_format.conf @@ -0,0 +1,59 @@ +# Lines starting with # are ignored. +# Lines can be continued on the next line using trailing backslash. +# +# Format: +# %summary:: summary format +# section:: element1[,element2]... +# The literal text line to be added to Bugzilla comment. Can be empty. +# (IOW: empty lines are NOT ignored!) +# +# Summary format is a line of text, where %element% is replaced by +# text element's content, and [[...%element%...]] block is used only if +# %element% exists. [[...]] blocks can nest. +# +# Sections can be: +# - %summary: bug summary format string. +# - %attach: a list of elements to attach. +# - text, double colon (::) and the list of comma-separated elements. +# Text can be empty (":: elem1, elem2, elem3" works), +# in this case "Text:" header line will be omitted. +# +# Elements can be: +# - problem directory element names, which get formatted as +# : +# or +# : +# : +# : +# : +# - problem directory element names prefixed by "%bare_", +# which is formatted as-is, without ":" and colons +# - %oneline, %multiline, %text wildcards, which select all corresponding +# elements for output or attachment +# - %binary wildcard, valid only for %attach section, instructs to attach +# binary elements +# - problem directory element names prefixed by "-", +# which excludes given element from all wildcards +# +# Nonexistent elements are silently ignored. +# If none of elements exists, the section will not be created. + +%summary:: [abrt] %pkg_name%[[: %crash_function%()]][[: %reason%]][[: TAINTED %tainted_short%]] + +Description of problem:: %bare_comment + +Version-Release number of selected component:: %bare_package + +Truncated backtrace:: %bare_%short_backtrace + +%Additional info:: +:: -pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\ + -component,-architecture,\ + -analyzer,-count,-duphash,-uuid,-abrt_version,\ + -username,-hostname,-os_release,-os_info,\ + -time,-pid,-pwd,-last_occurrence,-ureports_counter,\ + %reporter,\ + %oneline + +%attach:: -comment,-reason,-reported_to,-event_log,%multiline,\ + -coredump,%binary diff --git a/src/plugins/mantisbt_formatdup.conf b/src/plugins/mantisbt_formatdup.conf new file mode 100644 index 0000000..b1552ac --- /dev/null +++ b/src/plugins/mantisbt_formatdup.conf @@ -0,0 +1,65 @@ +# Lines starting with # are ignored. +# Lines can be continued on the next line using trailing backslash. +# +# Format: +# %summary:: summary format +# section:: element1[,element2]... +# The literal text line to be added to Bugzilla comment. Can be empty. +# (IOW: empty lines are NOT ignored!) +# +# Summary format is a line of text, where %element% is replaced by +# text element's content, and [[...%element%...]] block is used only if +# %element% exists. [[...]] blocks can nest. +# +# Sections can be: +# - %summary: bug summary format string. +# - %attach: a list of elements to attach. +# - text, double colon (::) and the list of comma-separated elements. +# Text can be empty (":: elem1, elem2, elem3" works), +# in this case "Text:" header line will be omitted. +# +# Elements can be: +# - problem directory element names, which get formatted as +# : +# or +# : +# : +# : +# : +# - problem directory element names prefixed by "%bare_", +# which is formatted as-is, without ":" and colons +# - %oneline, %multiline, %text wildcards, which select all corresponding +# elements for output or attachment +# - %binary wildcard, valid only for %attach section, instructs to attach +# binary elements +# - problem directory element names prefixed by "-", +# which excludes given element from all wildcards +# +# Nonexistent elements are silently ignored. +# If none of elements exists, the section will not be created. + +# When we add a comment to an existing BZ, %summary is ignored +# (it specifies *new bug* summary field): +# %summary:: blah blah + +# When dup is detected, BZ reporter adds a comment to it. +# This comment may interrupt an ongoing conversation in the BZ. +# (Three people independently filed a bug against abrt about this). +# Need to clearly explain what this comment is, to prevent confusion. +# Hopefully, this line would suffice: +Another user experienced a similar problem: + +# If user filled out comment field, show it: +:: %bare_comment + +# var_log_messages has too much variance (time/date), +# we exclude it from message so that dup message elimination has more chances to work +:: \ + -pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\ + -component,-architecture,\ + -analyzer,-count,-duphash,-uuid,-abrt_version,\ + -username,-hostname,-os_release,-os_info,\ + -time,-pid,-pwd,-last_occurrence,-ureports_counter,\ + -var_log_messages,\ + %reporter,\ + %oneline diff --git a/src/plugins/report_CentOSBugTracker.conf b/src/plugins/report_CentOSBugTracker.conf new file mode 100644 index 0000000..04cab13 --- /dev/null +++ b/src/plugins/report_CentOSBugTracker.conf @@ -0,0 +1,4 @@ +Mantisbt_MantisbtURL = https://bugs.centos.org +Mantisbt_Login = +Mantisbt_Password +Mantisbt_SSLVerify = yes diff --git a/src/plugins/report_CentOSBugTracker.xml.in b/src/plugins/report_CentOSBugTracker.xml.in new file mode 100644 index 0000000..81f909b --- /dev/null +++ b/src/plugins/report_CentOSBugTracker.xml.in @@ -0,0 +1,65 @@ + + + <_name>CentOS Bug Tracker + <_description>Report to CentOS Bug Tracker + + component,duphash,os_release + coredump,count,event_log,reported_to,vmcore + + no + + 3 + yes + + + + + + + + + + + + + + + diff --git a/src/plugins/reporter-mantisbt.c b/src/plugins/reporter-mantisbt.c new file mode 100644 index 0000000..d281cdb --- /dev/null +++ b/src/plugins/reporter-mantisbt.c @@ -0,0 +1,696 @@ +/* + Copyright (C) 2014 ABRT team + Copyright (C) 2014 RedHat Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., 51 + Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "internal_libreport.h" +#include "client.h" +#include "mantisbt.h" +#include "problem_report.h" + +static void +parse_osinfo_for_mantisbt(map_string_t *osinfo, char** project, char** version) +{ + const char *name = get_map_string_item_or_NULL(osinfo, "CENTOS_MANTISBT_PROJECT"); + if (!name) + name = get_map_string_item_or_NULL(osinfo, OSINFO_NAME); + + const char *version_id = get_map_string_item_or_NULL(osinfo, "CENTOS_MANTISBT_PROJECT_VERSION"); + if (!version_id) + version_id = get_map_string_item_or_NULL(osinfo, OSINFO_VERSION_ID); + + if (name && version_id) + { + *project = xstrdup(name); + *version = xstrdup(version_id); + return; + } + + /* something bad happend */ + *project = NULL; + *version = NULL; +} + +static char * +ask_mantisbt_login(const char *message) +{ + char *login = ask(message); + if (login == NULL || login[0] == '\0') + { + set_xfunc_error_retval(EXIT_CANCEL_BY_USER); + error_msg_and_die(_("Can't continue without login")); + } + + return login; +} + +static char * +ask_mantisbt_password(const char *message) +{ + char *password = ask_password(message); + /* TODO: this should be fixed in ask_password() as other tools have the same problem */ + putchar('\n'); + if (password == NULL || password[0] == '\0') + { + set_xfunc_error_retval(EXIT_CANCEL_BY_USER); + error_msg_and_die(_("Can't continue without password")); + } + + return password; +} + +static void +ask_mantisbt_credentials(mantisbt_settings_t *settings, const char *pre_message) +{ + free(settings->m_login); + free(settings->m_password); + + char *question = xasprintf("%s %s", pre_message, _("Please enter your CentOS Bug Tracker login:")); + settings->m_login = ask_mantisbt_login(question); + free(question); + + question = xasprintf("%s %s '%s':", pre_message, _("Please enter the password for"), settings->m_login); + settings->m_password = ask_mantisbt_password(question); + free(question); + + return; +} + +static void +verify_credentials(mantisbt_settings_t *settings) +{ + if (settings->m_login[0] == '\0' || settings->m_password[0] == '\0') + ask_mantisbt_credentials(settings, _("Credentials are not provided by configuration.")); + + while (true) + { + soap_request_t *req = soap_request_new_for_method("mc_login"); + soap_request_add_credentials_parameter(req, settings); + + mantisbt_result_t *result = mantisbt_soap_call(settings, req); + soap_request_free(req); + + if (g_verbose > 2) + { + GList *ids = response_get_main_ids_list(result->mr_body); + if (ids != NULL) + log("%s", (char *)ids->data); + response_values_free(ids); + } + + int result_val = result->mr_http_resp_code; + mantisbt_result_free(result); + + if (result_val == 200) + return; + + ask_mantisbt_credentials(settings, _("Invalid password or login.")); + } +} + +static void +set_settings(mantisbt_settings_t *m, map_string_t *settings, struct dump_dir *dd) +{ + const char *environ; + + environ = getenv("Mantisbt_Login"); + m->m_login = xstrdup(environ ? environ : get_map_string_item_or_empty(settings, "Login")); + + environ = getenv("Mantisbt_Password"); + m->m_password = xstrdup(environ ? environ : get_map_string_item_or_empty(settings, "Password")); + + environ = getenv("Mantisbt_MantisbtURL"); + m->m_mantisbt_url = environ ? environ : get_map_string_item_or_empty(settings, "MantisbtURL"); + if (!m->m_mantisbt_url[0]) + m->m_mantisbt_url = "https://bugs.centos.org/"; + else + { + /* We don't want trailing '/': "https://host/dir/" -> "https://host/dir" */ + char *last_slash = strrchr(m->m_mantisbt_url, '/'); + if (last_slash && last_slash[1] == '\0') + *last_slash = '\0'; + } + m->m_mantisbt_soap_url = concat_path_file(m->m_mantisbt_url, "api/soap/mantisconnect.php"); + + environ = getenv("Mantisbt_Project"); + if (environ) + { + m->m_project = xstrdup(environ); + environ = getenv("Mantisbt_ProjectVersion"); + if (environ) + m->m_project_version = xstrdup(environ); + } + else + { + const char *option = get_map_string_item_or_NULL(settings, "Project"); + if (option) + m->m_project = xstrdup(option); + option = get_map_string_item_or_NULL(settings, "ProjectVersion"); + if (option) + m->m_project_version = xstrdup(option); + } + + if (!m->m_project || !*m->m_project) /* if not overridden or empty... */ + { + free(m->m_project); + free(m->m_project_version); + + if (dd != NULL) + { + map_string_t *osinfo = new_map_string(); + + char *os_info_data = dd_load_text(dd, FILENAME_OS_INFO); + parse_osinfo(os_info_data, osinfo); + free(os_info_data); + + parse_osinfo_for_mantisbt(osinfo, &m->m_project, &m->m_project_version); + free_map_string(osinfo); + } + } + + environ = getenv("Mantisbt_SSLVerify"); + m->m_ssl_verify = string_to_bool(environ ? environ : get_map_string_item_or_empty(settings, "SSLVerify")); + + environ = getenv("Mantisbt_DontMatchComponents"); + m->m_DontMatchComponents = environ ? environ : get_map_string_item_or_empty(settings, "DontMatchComponents"); + + environ = getenv(CREATE_PRIVATE_TICKET); + if (environ) + m->m_create_private = string_to_bool(environ); + + if (!m->m_create_private) + { + environ = getenv("Mantisbt_CreatePrivate"); + m->m_create_private = string_to_bool(environ ? environ : get_map_string_item_or_empty(settings, "CreatePrivate")); + } + log_notice("create private CentOS Bug Tracker ticket: '%s'", m->m_create_private ? "YES": "NO"); +} + +int main(int argc, char **argv) +{ + abrt_init(argv); + + /* I18n */ + setlocale(LC_ALL, ""); +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + const char *program_usage_string = _( + "\n& [-vf] [-c CONFFILE]... [-F FMTFILE] [-A FMTFILE2] -d DIR" + "\nor:" + "\n& [-v] [-c CONFFILE]... [-d DIR] -t[ID] FILE..." + "\nor:" + "\n& [-v] [-c CONFFILE]... [-d DIR] -t[ID] -w" + "\nor:" + "\n& [-v] [-c CONFFILE]... -h DUPHASH" + "\n" + "\nReports problem to CentOS Bug Tracker." + "\n" + "\nThe tool reads DIR. Then it tries to find an issue" + "\nwith the same abrt_hash in custom field 'abrt_hash'." + "\n" + "\nIf such issue is not found, then a new issue is created. Elements of DIR" + "\nare stored in the issue as part of issue description or as attachments," + "\ndepending on their type and size." + "\n" + "\nOtherwise, if such issue is found and it is marked as CLOSED DUPLICATE," + "\nthe tool follows the chain of duplicates until it finds a non-DUPLICATE issue." + "\nThe tool adds a new comment to found issue." + "\n" + "\nThe URL to new or modified issue is printed to stdout and recorded in" + "\n'reported_to' element." + "\n" + "\nOption -t uploads FILEs to the already created issue on CentOS Bug Tracker site." + "\nThe issue ID is retrieved from directory specified by -d DIR." + "\nIf problem data in DIR was never reported to CentOS Bug Tracker, upload will fail." + "\n" + "\nOption -tID uploads FILEs to the issue with specified ID on CentOS Bug Tracker site." + "\n-d DIR is ignored." + "\n" + "\nOption -r sets the last url from reporter_to element which is prefixed with" + "\nTRACKER_NAME to URL field. This option is applied only when a new issue is to be" + "\nfiled. The default value is 'ABRT Server'" + "\n" + "\nIf not specified, CONFFILE defaults to "CONF_DIR"/plugins/mantisb.conf" + "\nIts lines should have 'PARAM = VALUE' format." + "\nRecognized string parameters: MantisbtURL, Login, Password, Project, ProjectVersion." + "\nRecognized boolean parameter (VALUE should be 1/0, yes/no): SSLVerify, CreatePrivate." + "\nParameters can be overridden via $Mantisbt_PARAM environment variables." + "\n" + "\nFMTFILE and FMTFILE2 default to "CONF_DIR"/plugins/mantisbt_format.conf" + ); + + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_c = 1 << 2, + OPT_F = 1 << 3, + OPT_A = 1 << 4, + OPT_t = 1 << 5, + OPT_f = 1 << 6, + OPT_h = 1 << 7, + OPT_r = 1 << 8, + OPT_D = 1 << 9, + }; + + const char *dump_dir_name = "."; + GList *conf_file = NULL; + const char *fmt_file = CONF_DIR"/plugins/mantisbt_format.conf"; + const char *fmt_file2 = fmt_file; + char *abrt_hash = NULL; + char *ticket_no = NULL; + const char *tracker_str = "ABRT Server"; + char *debug_str = NULL; + mantisbt_settings_t mbt_settings = { 0 }; + /* Keep enum above and order of options below in sync! */ + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT_STRING( 'd', NULL, &dump_dir_name , "DIR" , _("Problem directory")), + OPT_LIST( 'c', NULL, &conf_file , "FILE" , _("Configuration file (may be given many times)")), + OPT_STRING( 'F', NULL, &fmt_file , "FILE" , _("Formatting file for initial comment")), + OPT_STRING( 'A', NULL, &fmt_file2 , "FILE" , _("Formatting file for duplicates")), + OPT_OPTSTRING('t', "ticket", &ticket_no , "ID" , _("Attach FILEs [to issue with this ID]")), + OPT_BOOL( 'f', NULL, NULL, _("Force reporting even if this problem is already reported")), + OPT_STRING( 'h', "duphash", &abrt_hash, "DUPHASH", _("Print BUG_ID which has given DUPHASH")), + OPT_STRING( 'r', "tracker", &tracker_str, "TRACKER_NAME", _("A name of bug tracker for an additional URL from 'reported_to'")), + + OPT_OPTSTRING('D', "debug", &debug_str , "STR" , _("Debug")), + OPT_END() + }; + + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); + argv += optind; + + export_abrt_envvars(0); + + map_string_t *settings = new_map_string(); + + { + if (!conf_file) + conf_file = g_list_append(conf_file, (char*) CONF_DIR"/plugins/mantisbt.conf"); + while (conf_file) + { + char *fn = (char *)conf_file->data; + log_notice("Loading settings from '%s'", fn); + load_conf_file(fn, settings, /*skip key w/o values:*/ false); + log_debug("Loaded '%s'", fn); + conf_file = g_list_delete_link(conf_file, conf_file); + } + + struct dump_dir *dd = NULL; + if (abrt_hash == NULL) + { + dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + error_msg_and_die(_("Can't open problem dir '%s'."), dump_dir_name); + } + + set_settings(&mbt_settings, settings, dd); + dd_close(dd); + /* WRONG! set_settings() does not copy the strings, it merely sets up pointers + * to settings[] dictionary: + */ + /*free_map_string(settings);*/ + } + + /* No connection is opened between client and server. Users authentication + * is performed on every SOAP method call. In the first step we verify the + * credentials by calling 'mc_login' method. In the case the credentials are + * correctly applies the reporter uses them in the next requests. It is not + * necessary to call 'mc_login' method because the method provides only + * verification of credentials. + */ + verify_credentials(&mbt_settings); + + if (abrt_hash) + { + log(_("Looking for similar problems in CentOS Bug Tracker")); + GList *ids = mantisbt_search_by_abrt_hash(&mbt_settings, abrt_hash); + mantisbt_settings_free(&mbt_settings); + + if (ids == NULL) + return EXIT_FAILURE; + + puts(ids->data); + response_values_free(ids); + return EXIT_SUCCESS; + } + + mantisbt_get_project_id_from_name(&mbt_settings); + + if (opts & OPT_t) + { + if (!argv[0]) + show_usage_and_die(program_usage_string, program_options); + + if (!ticket_no) + { + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + xfunc_die(); + report_result_t *reported_to = find_in_reported_to(dd, "CentOS Bug Tracker"); + dd_close(dd); + + if (!reported_to || !reported_to->url) + error_msg_and_die(_("Can't get MantisBT ID because this problem has not yet been reported to MantisBT.")); + + char *url = reported_to->url; + reported_to->url = NULL; + free_report_result(reported_to); + + if (prefixcmp(url, mbt_settings.m_mantisbt_url) != 0) + error_msg_and_die(_("This problem has been reported to MantisBT '%s' which differs from the configured MantisBT '%s'."), url, mbt_settings.m_mantisbt_url); + + ticket_no = strrchr(url, '='); + if (!ticket_no) + error_msg_and_die(_("Malformed url to MantisBT '%s'."), url); + + /* won't ever call free on it - it simplifies the code a lot */ + ticket_no = xstrdup(ticket_no + 1); + log(_("Using CentOS Bug Tracker ID '%s'"), ticket_no); + } + + /* Attach files to existing MantisBT issues */ + while (*argv) + { + const char *path = *argv++; + char *filename = basename(path); + log(_("Attaching file '%s' to issue %s"), filename, ticket_no); + mantisbt_attach_file(&mbt_settings, ticket_no, filename, path); + } + + return 0; + } + + /* Create new issue in MantisBT */ + + if (!(opts & OPT_f)) + { + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (!dd) + xfunc_die(); + report_result_t *reported_to = find_in_reported_to(dd, "CentOS Bug Tracker"); + dd_close(dd); + + if (reported_to && reported_to->url) + { + char *msg = xasprintf(_("This problem was already reported to CentOS Bug Tracker (see '%s')." + " Do you still want to create a new issue?"), + reported_to->url); + int yes = ask_yes_no(msg); + free(msg); + if (!yes) + return 0; + } + free_report_result(reported_to); + } + + problem_data_t *problem_data = create_problem_data_for_reporting(dump_dir_name); + if (!problem_data) + xfunc_die(); /* create_problem_data_for_reporting already emitted error msg */ + + const char *category = problem_data_get_content_or_die(problem_data, FILENAME_COMPONENT); + const char *duphash = problem_data_get_content_or_die(problem_data, FILENAME_DUPHASH); + + if (opts & OPT_D) + { + problem_formatter_t *pf = problem_formatter_new(); + problem_formatter_add_section(pf, PR_SEC_ADDITIONAL_INFO, /* optional section */ 0); + + if (problem_formatter_load_file(pf, fmt_file)) + error_msg_and_die("Invalid format file: %s", fmt_file); + + problem_report_t *pr = NULL; + if (problem_formatter_generate_report(pf, problem_data, &pr)) + error_msg_and_die("Failed to format issue report from problem data"); + + printf("summary: %s\n" + "\n" + "Description:\n%s\n" + "Additional info:\n%s\n" + , problem_report_get_summary(pr) + , problem_report_get_description(pr) + , problem_report_get_section(pr, PR_SEC_ADDITIONAL_INFO) + ); + + puts("attachments:"); + for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) + printf(" %s\n", (const char *)a->data); + + problem_report_free(pr); + problem_formatter_free(pf); + exit(0); + } + + int bug_id = 0; + + /* If REMOTE_RESULT contains "DUPLICATE 12345", we consider it a dup of 12345 + * and won't search on MantisBT server. + */ + char *remote_result; + remote_result = problem_data_get_content_or_NULL(problem_data, FILENAME_REMOTE_RESULT); + if (remote_result) + { + char *cmd = strtok(remote_result, " \n"); + char *id = strtok(NULL, " \n"); + + if (!prefixcmp(cmd, "DUPLICATE")) + { + errno = 0; + char *e; + bug_id = strtoul(id, &e, 10); + if (errno || id == e || *e != '\0' || bug_id > INT_MAX) + { + /* error / no digits / illegal trailing chars / too big a number */ + bug_id = 0; + } + } + } + + mantisbt_issue_info_t *ii; + if (!bug_id) + { + log(_("Checking for duplicates")); + + int existing_id = -1; + int crossver_id = -1; + { + /* Figure out whether we want to match category + * when doing dup search. + */ + const char *category_substitute = is_in_comma_separated_list(category, mbt_settings.m_DontMatchComponents) ? NULL : category; + + /* We don't do dup detection across versions (see below why), + * but we do add a note if cross-version potential dup exists. + * For that, we search for cross version dups first: + */ + // SOAP API searching method is not in the final version, it's possible the project will be string + GList *crossver_bugs_ids = mantisbt_search_duplicate_issues(&mbt_settings, category_substitute, /*version*/ NULL, duphash); + + unsigned crossver_bugs_count = g_list_length(crossver_bugs_ids); + log_debug("CentOS Bug Tracker has %i reports with duphash '%s' including cross-version ones", + crossver_bugs_count, duphash); + if (crossver_bugs_count > 0) + crossver_id = atoi(g_list_first(crossver_bugs_ids)->data); + + if (crossver_bugs_count > 0) + { + // SOAP API searching method is not in the final version, it's possible the project will be string + GList *dup_bugs_ids = mantisbt_search_duplicate_issues(&mbt_settings, category_substitute, mbt_settings.m_project_version, duphash); + + unsigned dup_bugs_count = g_list_length(dup_bugs_ids); + log_debug("CentOS Bug Tracker has %i reports with duphash '%s'", + dup_bugs_count, duphash); + if (dup_bugs_count > 0) + existing_id = atoi(g_list_first(dup_bugs_ids)->data); + } + } + + if (existing_id < 0) + { + /* Create new issue */ + log(_("Creating a new issue")); + problem_formatter_t *pf = problem_formatter_new(); + problem_formatter_add_section(pf, PR_SEC_ADDITIONAL_INFO, 0); + + if (problem_formatter_load_file(pf, fmt_file)) + error_msg_and_die(_("Invalid format file: %s"), fmt_file); + + problem_report_t *pr = NULL; + if (problem_formatter_generate_report(pf, problem_data, &pr)) + error_msg_and_die(_("Failed to format problem data")); + + if (crossver_id >= 0) + problem_report_buffer_printf( + problem_report_get_buffer(pr, PR_SEC_DESCRIPTION), + "\nPotential duplicate: issue %u\n", crossver_id); + + problem_formatter_free(pf); + + /* get tracker URL if exists */ + struct dump_dir *dd = dd_opendir(dump_dir_name, 0); + char *tracker_url = NULL; + if (dd) + { + report_result_t *reported_to = find_in_reported_to(dd, tracker_str); + dd_close(dd); + + if (reported_to && reported_to->url) + { + log(_("Adding External URL to issue")); + tracker_url = xstrdup(reported_to->url); + free_report_result(reported_to); + } + } + + int new_id = mantisbt_create_new_issue(&mbt_settings, problem_data, pr, tracker_url); + + free(tracker_url); + + if (new_id == -1) + return EXIT_FAILURE; + + log(_("Adding attachments to issue %i"), new_id); + char *new_id_str = xasprintf("%u", new_id); + + for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) + { + const char *item_name = (const char *)a->data; + struct problem_item *item = problem_data_get_item_or_NULL(problem_data, item_name); + if (!item) + continue; + else if (item->flags & CD_FLAG_TXT) + mantisbt_attach_data(&mbt_settings, new_id_str, item_name, item->content, strlen(item->content)); + else if (item->flags & CD_FLAG_BIN) + mantisbt_attach_file(&mbt_settings, new_id_str, item_name, item->content); + } + + free(new_id_str); + problem_report_free(pr); + ii = mantisbt_issue_info_new(); + ii->mii_id = new_id; + ii->mii_status = xstrdup("new"); + + goto finish; + } + + bug_id = existing_id; + } + + ii = mantisbt_get_issue_info(&mbt_settings, bug_id); + + log(_("Bug is already reported: %i"), ii->mii_id); + + /* Follow duplicates */ + if ((strcmp(ii->mii_status, "closed") == 0) + && (strcmp(ii->mii_resolution, "duplicate") == 0) + ) { + mantisbt_issue_info_t *origin = mantisbt_find_origin_bug_closed_duplicate(&mbt_settings, ii); + if (origin) + { + mantisbt_issue_info_free(ii); + ii = origin; + } + } + + /* TODO CC list + * Is no MantisBT SOAP API method which allows adding users to CC list + * without updating issue. + */ + + /* Add comment and bt */ + const char *comment = problem_data_get_content_or_NULL(problem_data, FILENAME_COMMENT); + if (comment && comment[0]) + { + problem_formatter_t *pf = problem_formatter_new(); + + if (problem_formatter_load_file(pf, fmt_file2)) + error_msg_and_die(_("Invalid duplicate format file: '%s"), fmt_file2); + + problem_report_t *pr; + if (problem_formatter_generate_report(pf, problem_data, &pr)) + error_msg_and_die(_("Failed to format duplicate comment from problem data")); + + const char *mbtcomment = problem_report_get_description(pr); + + int dup_comment = is_comment_dup(ii->mii_notes, mbtcomment); + if (!dup_comment) + { + log(_("Adding new comment to issue %d"), ii->mii_id); + mantisbt_add_issue_note(&mbt_settings, ii->mii_id, mbtcomment); + + const char *bt = problem_data_get_content_or_NULL(problem_data, FILENAME_BACKTRACE); + unsigned rating = 0; + const char *rating_str = problem_data_get_content_or_NULL(problem_data, FILENAME_RATING); + /* python doesn't have rating file */ + if (rating_str) + rating = xatou(rating_str); + if (bt && rating > ii->mii_best_bt_rating) + { + char *bug_id_str = xasprintf("%i", ii->mii_id); + + log(_("Attaching better backtrace")); + + // find unique filename of attachment + char *name = NULL; + for (int i = 0;; ++i) + { + if (i == 0) + name = xasprintf("%s", FILENAME_BACKTRACE); + else + name = xasprintf("%s%d", FILENAME_BACKTRACE, i); + + if (g_list_find_custom(ii->mii_attachments, name, (GCompareFunc) strcmp) == NULL) + break; + + free(name); + } + mantisbt_attach_data(&mbt_settings, bug_id_str, name, bt, strlen(bt)); + + free(name); + free(bug_id_str); + } + } + else + log(_("Found the same comment in the issue history, not adding a new one")); + + problem_report_free(pr); + problem_formatter_free(pf); + } + +finish: + log(_("Status: %s%s%s %s/view.php?id=%u"), + ii->mii_status, + ii->mii_resolution ? " " : "", + ii->mii_resolution ? ii->mii_resolution : "", + mbt_settings.m_mantisbt_url, + ii->mii_id); + + struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); + if (dd) + { + char *msg = xasprintf("CentOS Bug Tracker: URL=%s/view.php?id=%u", mbt_settings.m_mantisbt_url, ii->mii_id); + add_reported_to(dd, msg); + free(msg); + dd_close(dd); + } + + mantisbt_settings_free(&mbt_settings); + return 0; +} diff --git a/src/workflows/Makefile.am b/src/workflows/Makefile.am index 0fc1019..5b8376a 100644 --- a/src/workflows/Makefile.am +++ b/src/workflows/Makefile.am @@ -22,6 +22,18 @@ dist_workflows_DATA = \ workflow_Logger.xml \ workflow_LoggerCCpp.xml +if BUILD_MANTISBT +dist_workflows_DATA += \ + workflow_CentOSCCpp.xml \ + workflow_CentOSKerneloops.xml \ + workflow_CentOSPython.xml \ + workflow_CentOSPython3.xml \ + workflow_CentOSVmcore.xml \ + workflow_CentOSXorg.xml \ + workflow_CentOSLibreport.xml \ + workflow_CentOSJava.xml +endif + if BUILD_BUGZILLA dist_workflows_DATA += \ workflow_AnacondaFedora.xml \ @@ -44,7 +56,8 @@ dist_workflowsdef_DATA =\ report_rhel.conf \ report_mailx.conf \ report_logger.conf \ - report_uploader.conf + report_uploader.conf \ + report_centos.conf if BUILD_BUGZILLA dist_workflowsdef_DATA += \ diff --git a/src/workflows/report_centos.conf b/src/workflows/report_centos.conf new file mode 100644 index 0000000..07b0d40 --- /dev/null +++ b/src/workflows/report_centos.conf @@ -0,0 +1,31 @@ +EVENT=workflow_CentOSLibreport analyzer=libreport +# this is just a meta event which consists of other events +# the list is defined in the xml file + +EVENT=workflow_CentOSCCpp analyzer=CCpp +# this is just a meta event which consists of other events +# the list is defined in the xml file + +EVENT=workflow_CentOSPython analyzer=Python component!=anaconda +# this is just a meta event which consists of other events +# the list is defined in the xml file + +EVENT=workflow_CentOSPython3 analyzer=Python3 component!=anaconda +# this is just a meta event which consists of other events +# the list is defined in the xml file + +EVENT=workflow_CentOSKerneloops analyzer=Kerneloops +# this is just a meta event which consists of other events +# the list is defined in the xml file + +EVENT=workflow_CentOSVmcore analyzer=vmcore +# this is just a meta event which consists of other events +# the list is defined in the xml file + +EVENT=workflow_CentOSXorg analyzer=xorg +# this is just a meta event which consists of other events +# the list is defined in the xml file + +EVENT=workflow_CentOSJava analyzer=Java +# this is just a meta event which consists of other events +# the list is defined in the xml file diff --git a/src/workflows/workflow_CentOSCCpp.xml.in b/src/workflows/workflow_CentOSCCpp.xml.in new file mode 100644 index 0000000..ea94d56 --- /dev/null +++ b/src/workflows/workflow_CentOSCCpp.xml.in @@ -0,0 +1,12 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the C/C++ crash using the CentOS infrastructure + + + report_uReport + collect_* + analyze_CCpp + report_CentOSBugTracker + + diff --git a/src/workflows/workflow_CentOSJava.xml.in b/src/workflows/workflow_CentOSJava.xml.in new file mode 100644 index 0000000..89f9295 --- /dev/null +++ b/src/workflows/workflow_CentOSJava.xml.in @@ -0,0 +1,11 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the Java exception using the CentOS infrastructure + + + report_uReport + collect_* + report_CentOSBugTracker + + diff --git a/src/workflows/workflow_CentOSKerneloops.xml.in b/src/workflows/workflow_CentOSKerneloops.xml.in new file mode 100644 index 0000000..c43c681 --- /dev/null +++ b/src/workflows/workflow_CentOSKerneloops.xml.in @@ -0,0 +1,11 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the kerneloops using the CentOS infrastructure + + + report_uReport + collect_* + report_CentOSBugTracker + + diff --git a/src/workflows/workflow_CentOSLibreport.xml.in b/src/workflows/workflow_CentOSLibreport.xml.in new file mode 100644 index 0000000..2f6ed82 --- /dev/null +++ b/src/workflows/workflow_CentOSLibreport.xml.in @@ -0,0 +1,9 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the problem using the CentOS infrastructure + + + report_CentOSBugTracker + + diff --git a/src/workflows/workflow_CentOSPython.xml.in b/src/workflows/workflow_CentOSPython.xml.in new file mode 100644 index 0000000..7e26c6c --- /dev/null +++ b/src/workflows/workflow_CentOSPython.xml.in @@ -0,0 +1,11 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the python exception using the CentOS infrastructure + + + report_uReport + collect_* + report_CentOSBugTracker + + diff --git a/src/workflows/workflow_CentOSPython3.xml.in b/src/workflows/workflow_CentOSPython3.xml.in new file mode 100644 index 0000000..f1dd8a9 --- /dev/null +++ b/src/workflows/workflow_CentOSPython3.xml.in @@ -0,0 +1,11 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the python 3 exception using the CentOS infrastructure + + + report_uReport + collect_* + report_CentOSBugTracker + + diff --git a/src/workflows/workflow_CentOSVmcore.xml.in b/src/workflows/workflow_CentOSVmcore.xml.in new file mode 100644 index 0000000..c876594 --- /dev/null +++ b/src/workflows/workflow_CentOSVmcore.xml.in @@ -0,0 +1,12 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the kernel crash using the CentOS infrastructure + + + analyze_VMcore + report_uReport + collect_* + report_CentOSBugTracker + + diff --git a/src/workflows/workflow_CentOSXorg.xml.in b/src/workflows/workflow_CentOSXorg.xml.in new file mode 100644 index 0000000..bf52221 --- /dev/null +++ b/src/workflows/workflow_CentOSXorg.xml.in @@ -0,0 +1,9 @@ + + + <_name>Report to CentOS Bug Tracker + <_description>Process the X Server problem using the CentOS infrastructure + + + report_CentOSBugTracker + + -- 1.8.3.1