"""
Functionality for dealing with tool errors.
"""
import string
import markupsafe
from galaxy import (
model,
util,
)
from galaxy.security.validate_user_input import validate_email_str
from galaxy.util import unicodify
error_report_template = """
GALAXY TOOL ERROR REPORT
------------------------
This error report was sent from the Galaxy instance hosted on the server
"${host}"
-----------------------------------------------------------------------------
This is in reference to dataset id ${dataset_id} (${dataset_id_encoded}) from history id ${history_id} (${history_id_encoded})
-----------------------------------------------------------------------------
You should be able to view the history containing the related history item (${hda_id_encoded})
${hid}: ${history_item_name}
by logging in as a Galaxy admin user to the Galaxy instance referenced above
and pointing your browser to the following link.
${history_view_link}
-----------------------------------------------------------------------------
The user ${email_str} provided the following information:
${message}
-----------------------------------------------------------------------------
info url: ${hda_show_params_link}
job id: ${job_id} (${job_id_encoded})
tool id: ${job_tool_id}
tool version: ${tool_version}
job pid or drm id: ${job_runner_external_id}
job tool version: ${job_tool_version}
-----------------------------------------------------------------------------
job command line:
${job_command_line}
-----------------------------------------------------------------------------
job stderr:
${job_stderr}
-----------------------------------------------------------------------------
job stdout:
${job_stdout}
-----------------------------------------------------------------------------
job info:
${job_info}
-----------------------------------------------------------------------------
job traceback:
${job_traceback}
-----------------------------------------------------------------------------
(This is an automated message).
"""
error_report_template_html = """
<html>
<body>
<h1>Galaxy Tool Error Report</h1>
<span class="sub"><i>from</i> <span style="font-family: monospace;"><a href="${host}">${host}</a></span>
<h3>Error Localization</h3>
<table style="margin:1em">
<tbody>
<tr><td>Dataset</td><td><a href="${hda_show_params_link}">${dataset_id} (${dataset_id_encoded})</a></td></tr>
<tr style="background-color: #f2f2f2"><td>History</td><td><a href="${history_view_link}">${history_id} (${history_id_encoded})</a></td></tr>
<tr><td>Failed Job</td><td>${hid}: ${history_item_name} (${hda_id_encoded})</td></tr>
</tbody>
</table>
<h3>User Provided Information</h3>
The user <span style="font-family: monospace;">${email_str}</span> provided the following information:
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${message}
</pre>
<h3>Detailed Job Information</h3>
Job environment and execution information is available at the job <a href="${hda_show_params_link}">info page</a>.
<table style="margin:1em">
<tbody>
<tr><td>Job ID</td><td>${job_id} (${job_id_encoded})</td></tr>
<tr style="background-color: #f2f2f2"><td>Tool ID</td><td>${job_tool_id}</td></tr>
<tr><td>Tool Version</td><td>${tool_version}</td></tr>
<tr style="background-color: #f2f2f2"><td>Job PID or DRM id</td><td>${job_runner_external_id}</td></tr>
<tr><td>Job Tool Version</td><td>${job_tool_version}</td></tr>
</tbody>
</table>
<h3>Job Execution and Failure Information</h3>
<h4>Command Line</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_command_line}
</pre>
<h4>stderr</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_stderr}
</pre>
<h4>stdout</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_stdout}
</pre>
<h4>Job Information</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_info}
</pre>
<h4>Job Traceback</h4>
<pre style="white-space: pre-wrap;background: #eeeeee;border:1px solid black;padding:1em;">
${job_traceback}
</pre>
This is an automated message. Do not reply to this address.
</body></html>
"""
[docs]class ErrorReporter:
[docs] def __init__(self, hda, app):
# Get the dataset
sa_session = app.model.context
if not isinstance(hda, model.HistoryDatasetAssociation):
hda_id = hda
try:
hda = sa_session.get(model.HistoryDatasetAssociation, hda_id)
assert hda is not None, ValueError("No HDA yet")
except Exception:
hda = sa_session.get(model.HistoryDatasetAssociation, app.security.decode_id(hda_id))
assert isinstance(hda, model.HistoryDatasetAssociation), ValueError(f"Bad value provided for HDA ({hda}).")
self.hda = hda
# Get the associated job
self.job = hda.creating_job
self.app = app
self.tool_id = self.job.tool_id
self.report = None
def _can_access_dataset(self, user):
if user:
roles = user.all_roles()
else:
roles = []
return self.app.security_agent.can_access_dataset(roles, self.hda.dataset)
[docs] def create_report(self, user, email="", message="", redact_user_details_in_bugreport=False, **kwd):
hda = self.hda
job = self.job
host = self.app.url_for("/", qualified=True)
history_id_encoded = self.app.security.encode_id(hda.history_id)
history_view_link = self.app.url_for("/histories/view", id=history_id_encoded, qualified=True)
hda_id_encoded = self.app.security.encode_id(hda.id)
hda_show_params_link = self.app.url_for(
controller="dataset", action="details", dataset_id=hda_id_encoded, qualified=True
)
# Build the email message
if redact_user_details_in_bugreport:
# This is sub-optimal but it is hard to solve fully. This affects
# the GitHub posting method more than the traditional email plugin.
# There is no way around CCing the person with the traditional
# email bug report plugin, however with the GitHub plugin we can
# submit to GitHub without putting the email in the bug report.
#
# A secondary system with access to the GitHub issue and access to
# the Galaxy database can shuttle email back and forth between
# GitHub comments and user-emails.
# Thus preventing issue helpers from every knowing the identity of
# the bug reporter (and preventing information about the bug
# reporter from leaving the EU until it hits email directly to the
# user.)
email_str = "redacted"
if user:
email_str += f" (user: {user.id})"
else:
if user:
email_str = f"'{user.email}'"
if email and user.email != email:
email_str += f" (providing preferred contact email '{email}')"
else:
email_str = "'%s'" % (email or "anonymous")
report_variables = dict(
host=host,
dataset_id_encoded=self.app.security.encode_id(hda.dataset_id),
dataset_id=hda.dataset_id,
history_id_encoded=history_id_encoded,
history_id=hda.history_id,
hda_id_encoded=hda_id_encoded,
hid=hda.hid,
history_item_name=hda.get_display_name(),
history_view_link=history_view_link,
hda_show_params_link=hda_show_params_link,
job_id_encoded=self.app.security.encode_id(job.id),
job_id=job.id,
tool_version=job.tool_version,
job_tool_id=job.tool_id,
job_tool_version=hda.tool_version,
job_runner_external_id=job.job_runner_external_id,
job_command_line=job.command_line,
job_stderr=util.unicodify(job.stderr),
job_stdout=util.unicodify(job.stdout),
job_info=util.unicodify(job.info),
job_traceback=util.unicodify(job.traceback),
email_str=email_str,
message=util.unicodify(message),
)
self.report = string.Template(error_report_template).safe_substitute(report_variables)
# Escape all of the content for use in the HTML report
for parameter in report_variables.keys():
if report_variables[parameter] is not None:
report_variables[parameter] = markupsafe.escape(unicodify(report_variables[parameter]))
self.html_report = string.Template(error_report_template_html).safe_substitute(report_variables)
def _send_report(self, user, email=None, message=None, **kwd):
return self.report
[docs] def send_report(self, user, email=None, message=None, **kwd):
if self.report is None:
self.create_report(user, email=email, message=message, **kwd)
return self._send_report(user, email=email, message=message, **kwd)
[docs]class EmailErrorReporter(ErrorReporter):
def _send_report(self, user, email=None, message=None, **kwd):
smtp_server = self.app.config.smtp_server
assert smtp_server, ValueError("Mail is not configured for this Galaxy instance")
to = self.app.config.error_email_to
assert to, ValueError("Error reporting has been disabled for this Galaxy instance")
error_msg = validate_email_str(email)
if not error_msg and self._can_access_dataset(user):
to += f", {email.strip()}"
subject = f"Galaxy tool error report from {email}"
try:
subject = f"{subject} ({self.app.toolbox.get_tool(self.job.tool_id, self.job.tool_version).old_id})"
except Exception:
pass
reply_to = user.email if user else None
return util.send_mail(
self.app.config.email_from,
to,
subject,
self.report,
self.app.config,
html=self.html_report,
reply_to=reply_to,
)