You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2658 lines
92 KiB
2658 lines
92 KiB
From 611ca48b8d81e5edc0907885ece48cb0dce986fa Mon Sep 17 00:00:00 2001 |
|
From: Matej Habrnal <mhabrnal@redhat.com> |
|
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 <mhabrnal@redhat.com> |
|
--- |
|
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 <curl/curl.h> |
|
+ |
|
+#include <libxml/xmlreader.h> |
|
+ |
|
+#include "internal_libreport.h" |
|
+#include "libreport_curl.h" |
|
+#include "mantisbt.h" |
|
+#include "client.h" |
|
+ |
|
+/* |
|
+ * SOAP |
|
+*/ |
|
+ |
|
+#define XML_VERSION "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" |
|
+ |
|
+/* fprint string */ |
|
+#define SOAP_TEMPLATE \ |
|
+ "<SOAP-ENV:Envelope xmlns:ns3=\"http://schemas.xmlsoap.org/soap/encoding/\" " \ |
|
+ "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " \ |
|
+ "xmlns:ns0=\"http://schemas.xmlsoap.org/soap/encoding/\" " \ |
|
+ "xmlns:ns1=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ |
|
+ "xmlns:ns2=\"http://www.w3.org/2001/XMLSchema\" " \ |
|
+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " \ |
|
+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ |
|
+ "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" \ |
|
+ "<SOAP-ENV:Header/>" \ |
|
+ "<ns1:Body>" \ |
|
+ "<ns3:%s>" \ |
|
+ "</ns3:%s>" \ |
|
+ "</ns1:Body>" \ |
|
+ "</SOAP-ENV:Envelope>" |
|
+ |
|
+#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. |
|
+ * ... |
|
+ * <item xsi:type="ns1:IssueData"> |
|
+ * <id xsi:type="xsd:integer">10</id> <-- This is issue ID (required) |
|
+ * <view_state xsi:type="ns1:ObjectRef"> |
|
+ * <id xsi:type="xsd:integer">10</id> <-- This is view_state ID (not required) |
|
+ * <name xsi:type="xsd:string">public</name> |
|
+ * </view_state> |
|
+ * <project xsi:type="ns1:ObjectRef"> |
|
+ * <id xsi:type="xsd:integer">1</id> <-- This is project ID (not required) |
|
+ * <name xsi:type="xsd:string">test</name> |
|
+ * </project> |
|
+ * ... |
|
+ */ |
|
+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 |
|
+ * <elem> |
|
+ * <id>1</id> |
|
+ * <name>foo</name> |
|
+ * </elem> |
|
+ * |
|
+ * 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 <text> 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 <libxml/encoding.h> |
|
+#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 |
|
+# <element_name>: <contents> |
|
+# or |
|
+# <element_name>: |
|
+# :<contents> |
|
+# :<contents> |
|
+# :<contents> |
|
+# - problem directory element names prefixed by "%bare_", |
|
+# which is formatted as-is, without "<element_name>:" 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 |
|
+# <element_name>: <contents> |
|
+# or |
|
+# <element_name>: |
|
+# :<contents> |
|
+# :<contents> |
|
+# :<contents> |
|
+# - problem directory element names prefixed by "%bare_", |
|
+# which is formatted as-is, without "<element_name>:" 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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<event> |
|
+ <_name>CentOS Bug Tracker</_name> |
|
+ <_description>Report to CentOS Bug Tracker</_description> |
|
+ |
|
+ <requires-items>component,duphash,os_release</requires-items> |
|
+ <exclude-items-by-default>coredump,count,event_log,reported_to,vmcore</exclude-items-by-default> |
|
+ <exclude-items-always></exclude-items-always> |
|
+ <exclude-binary-items>no</exclude-binary-items> |
|
+ <include-items-by-default></include-items-by-default> |
|
+ <minimal-rating>3</minimal-rating> |
|
+ <gui-review-elements>yes</gui-review-elements> |
|
+ |
|
+ <options> |
|
+ <option type="text" name="Mantisbt_Login"> |
|
+ <_label>User name</_label> |
|
+ <allow-empty>no</allow-empty> |
|
+ <_description>CentOS Bug Tracker account user name</_description> |
|
+ <_note-html>You can create bugs.centos.org account <a href="https://bugs.centos.org/signup_page.php">here</a></_note-html> |
|
+ </option> |
|
+ <option type="password" name="Mantisbt_Password"> |
|
+ <_label>Password</_label> |
|
+ <allow-empty>no</allow-empty> |
|
+ <_description>CentOS Bug Tracker account password</_description> |
|
+ </option> |
|
+ <advanced-options> |
|
+ <option type="text" name="Mantisbt_MantisbtURL"> |
|
+ <_label>CentOS Bug Tracker URL</_label> |
|
+ <allow-empty>no</allow-empty> |
|
+ <_note-html>Address of CentOS Bug Tracker server</_note-html> |
|
+ <default-value>https://bugs.centos.org</default-value> |
|
+ </option> |
|
+ <option type="bool" name="Mantisbt_SSLVerify"> |
|
+ <_label>Verify SSL</_label> |
|
+ <_note-html>Check SSL key validity</_note-html> |
|
+ <default-value>yes</default-value> |
|
+ </option> |
|
+ <option type="text" name="Mantisbt_Project"> |
|
+ <_label>CentOS Bug Tracker project</_label> |
|
+ <allow-empty>yes</allow-empty> |
|
+ <_note-html>Specify this only if you needed different project than specified in /etc/os-release</_note-html> |
|
+ </option> |
|
+ <option type="text" name="Mantisbt_ProjectVersion"> |
|
+ <_label>CentOS Bug Tracker project version</_label> |
|
+ <allow-empty>yes</allow-empty> |
|
+ <_note-html>Specify this only if you needed different project version than specified in /etc/os-release</_note-html> |
|
+ </option> |
|
+ <option type="text" name="http_proxy"> |
|
+ <_label>HTTP Proxy</_label> |
|
+ <allow-empty>yes</allow-empty> |
|
+ <_note-html>Sets the proxy server to use for HTTP</_note-html> |
|
+ </option> |
|
+ <option type="text" name="HTTPS_PROXY"> |
|
+ <_label>HTTPS Proxy</_label> |
|
+ <allow-empty>yes</allow-empty> |
|
+ <_note-html>Sets the proxy server to use for HTTPS</_note-html> |
|
+ </option> |
|
+ <option type="bool" name="Mantisbt_CreatePrivate"> |
|
+ <_label>Restrict access</_label> |
|
+ <_note-html>Restrict access to the created CentOS Bug Tracker issue allowing only users from specified groups to view it (see advanced settings for more details)</_note-html> |
|
+ <default-value>no</default-value> |
|
+ </option> |
|
+ </advanced-options> |
|
+ </options> |
|
+</event> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the C/C++ crash using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>report_uReport</event> |
|
+ <event>collect_*</event> |
|
+ <event>analyze_CCpp</event> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the Java exception using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>report_uReport</event> |
|
+ <event>collect_*</event> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the kerneloops using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>report_uReport</event> |
|
+ <event>collect_*</event> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the problem using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the python exception using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>report_uReport</event> |
|
+ <event>collect_*</event> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the python 3 exception using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>report_uReport</event> |
|
+ <event>collect_*</event> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the kernel crash using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>analyze_VMcore</event> |
|
+ <event>report_uReport</event> |
|
+ <event>collect_*</event> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
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 @@ |
|
+<?xml version="1.0" encoding="UTF-8" ?> |
|
+<workflow> |
|
+ <_name>Report to CentOS Bug Tracker</_name> |
|
+ <_description>Process the X Server problem using the CentOS infrastructure</_description> |
|
+ |
|
+ <events> |
|
+ <event>report_CentOSBugTracker</event> |
|
+ </events> |
|
+</workflow> |
|
-- |
|
1.8.3.1 |
|
|
|
|