Warning
This document is for an in-development version of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.
Source code for galaxy.tools.error_reports.plugins.gitlab
"""The module describes the ``gitlab`` error plugin plugin."""
import logging
import os
import sys
import requests
if sys.version_info[0] < 3:
import urllib as urllib
import urlparse as urlparse
else:
import urllib.parse as urllib
urlparse = urllib
try:
import gitlab
except ImportError:
gitlab = None
from galaxy.util import string_as_bool
from .base_git import BaseGitPlugin
log = logging.getLogger(__name__)
[docs]class GitLabPlugin(BaseGitPlugin):
"""Send error report to GitLab.
"""
plugin_type = "gitlab"
[docs] def __init__(self, **kwargs):
self.app = kwargs['app']
self.redact_user_details_in_bugreport = self.app.config.redact_user_details_in_bugreport
self.verbose = string_as_bool(kwargs.get('verbose', False))
self.user_submission = string_as_bool(kwargs.get('user_submission', False))
# GitLab settings
self.gitlab_base_url = kwargs.get('gitlab_base_url', 'https://gitlab.com')
self.gitlab_private_token = kwargs.get('gitlab_private_token', "")
self.git_default_repo_owner = kwargs.get('gitlab_default_repo_owner', False)
self.git_default_repo_name = kwargs.get('gitlab_default_repo_name', False)
self.git_default_repo_only = string_as_bool(kwargs.get('gitlab_default_repo_only', True))
self.gitlab_use_proxy = string_as_bool(kwargs.get('gitlab_allow_proxy', True))
self.gitlab_new_issue_on_closed = string_as_bool(kwargs.get("gitlab_new_issue_on_closed", False))
self.gitlab_labels = kwargs.get("gitlab_labels", [])
try:
if gitlab is None:
raise Exception("GitLab error reporting plugin is configured, but gitlab is not installed. Please install python-gitlab.")
self.gitlab = self.gitlab_connect()
self.gitlab.auth()
except gitlab.GitlabAuthenticationError:
log.error("GitLab error reporting - Could not authenticate with GitLab.", exc_info=True)
self.gitlab = None
except gitlab.GitlabParsingError:
log.error("GitLab error reporting - Could not parse GitLab message.", exc_info=True)
self.gitlab = None
except (gitlab.GitlabConnectionError, gitlab.GitlabHttpError):
log.error("GitLab error reporting - Could not connect to GitLab.", exc_info=True)
self.gitlab = None
except gitlab.GitlabError:
log.error("GitLab error reporting - General error communicating with GitLab.", exc_info=True)
self.gitlab = None
[docs] def gitlab_connect(self):
import gitlab
session = requests.Session()
if self.gitlab_use_proxy:
session.proxies = {
'https': os.environ.get('https_proxy'),
'http': os.environ.get('http_proxy'),
}
return gitlab.Gitlab(
# Allow running against GL enterprise deployments
self.gitlab_base_url,
private_token=self.gitlab_private_token,
session=session
)
[docs] def submit_report(self, dataset, job, tool, **kwargs):
"""Submit the error report to GitLab
"""
log.info(self.gitlab)
# Try to connect beforehand, as we might lose connection
if not self.gitlab:
self.gitlab = self.gitlab_connect()
self.gitlab.auth()
# Ensure we are connected to Gitlab
if self.gitlab:
# Import GitLab here for the error handling
import gitlab
try:
log.info("GitLab error reporting - submit report - job tool id: {} - job tool version: {} - tool tool_shed: {}".format(job.tool_id, job.tool_version, tool.tool_shed))
# Determine the ToolShed url, initially we connect with HTTP and if redirect to HTTPS is set up,
# this will be detected by requests and used further down the line. Also cache this so everything is
# as fast as possible
ts_url = self._determine_ts_url(tool)
log.info("GitLab error reporting - Determined ToolShed is %s", ts_url)
# Find the repo inside the ToolShed
ts_repourl = self._get_gitrepo_from_ts(job, ts_url)
# Remove .git from the repository URL if this was specified
if ts_repourl is not None and ts_repourl.endswith(".git"):
ts_repourl = ts_repourl[:-4]
log.info("GitLab error reporting - Determine ToolShed Repository URL: %s", ts_repourl)
# Determine the GitLab project URL and the issue cache key
gitlab_projecturl = urlparse.urlparse(ts_repourl).path[1:] if (ts_repourl and not self.git_default_repo_only)\
else "/".join((self.git_default_repo_owner, self.git_default_repo_name))
issue_cache_key = self._get_issue_cache_key(job, ts_repourl)
gitlab_urlencodedpath = urllib.quote_plus(gitlab_projecturl)
# Make sure we are always logged in, then retrieve the GitLab project if it isn't cached.
self.gitlab.auth()
try:
if gitlab_projecturl not in self.git_project_cache:
self.git_project_cache[gitlab_projecturl] = self.gitlab.projects.get(gitlab_urlencodedpath)
except gitlab.GitlabGetError:
# Handle scenario where the repository doesn't exist so we can still continue
log.warn("GitLab error reporting - Repository '%s' doesn't exist, using default repository." % gitlab_urlencodedpath)
# Redo some of the previous steps to recover from such an issue but continue issue creation
gitlab_projecturl = "/".join((self.git_default_repo_owner, self.git_default_repo_name))
gitlab_urlencodedpath = urllib.quote_plus(gitlab_projecturl)
if gitlab_projecturl not in self.git_project_cache:
self.git_project_cache[gitlab_projecturl] = self.gitlab.projects.get(gitlab_urlencodedpath)
gl_project = self.git_project_cache[gitlab_projecturl]
# Make sure we keep a cache of the issues, per tool in this case
if issue_cache_key not in self.issue_cache:
self._fill_issue_cache(gl_project, issue_cache_key)
# Generate information for the tool
error_title = self._generate_error_title(job)
# Generate the error message
error_message = self._generate_error_message(dataset, job, kwargs)
# Determine the user to assign to the issue
gl_userid = None
if len(gl_project.commits.list()) > 0:
gl_username = gl_project.commits.list()[0].attributes['author_name']
if not self.redact_user_details_in_bugreport:
log.debug("GitLab error reporting - Last commiter username: %s" % gl_username)
if gl_username not in self.git_username_id_cache:
gl_userquery = self.gitlab.users.list(username=gl_username)
log.debug("GitLab error reporting - User list: %s" % gl_userquery)
if len(gl_userquery) > 0:
log.debug("GitLab error reporting - Last Committer user ID: %d" %
gl_userquery[0].get_id())
self.git_username_id_cache[gl_username] = gl_userquery[0].get_id()
gl_userid = self.git_username_id_cache.get(gl_username, None)
log.info(error_title in self.issue_cache[issue_cache_key])
if error_title not in self.issue_cache[issue_cache_key]:
self._open_issue(error_message, error_title, gitlab_projecturl, gl_project, gl_userid,
issue_cache_key)
else:
if not self.gitlab_new_issue_on_closed:
# Check if issue is closed, otherwise reopen it
issue_id = self.issue_cache[issue_cache_key][error_title]
issue = gl_project.issues.get(issue_id)
log.info("GitLab error reporting - Issue state is %s" % issue.state)
if issue.state == 'closed':
# Reopen issue
issue.state_event = 'reopen'
issue.save()
log.info("GitLab error reporting - Reopened issue %s" % issue_id)
# Add a comment to an issue...
self._append_issue(issue_cache_key, error_title, error_message,
gitlab_urlencodedpath=gitlab_urlencodedpath)
else:
self._open_issue(error_message, error_title, gitlab_projecturl, gl_project, gl_userid,
issue_cache_key)
return ('Submitted error report to GitLab. Your issue number is <a href="%s/%s/issues/%s" '
'target="_blank">#%s</a>.' % (self.gitlab_base_url, gitlab_projecturl,
self.issue_cache[issue_cache_key][error_title],
self.issue_cache[issue_cache_key][error_title]), 'success')
except gitlab.GitlabCreateError:
log.error("GitLab error reporting - Could not create the issue on GitLab.", exc_info=True)
return ('Internal Error.', 'danger')
except gitlab.GitlabOwnershipError:
log.error("GitLab error reporting - Could not create the issue on GitLab due to ownership issues.", exc_info=True)
return ('Internal Error.', 'danger')
except gitlab.GitlabSearchError:
log.error("GitLab error reporting - Could not find repository on GitLab.", exc_info=True)
return ('Internal Error.', 'danger')
except gitlab.GitlabAuthenticationError:
log.error("GitLab error reporting - Could not authenticate with GitLab.", exc_info=True)
return ('Internal Error.', 'danger')
except gitlab.GitlabParsingError:
log.error("GitLab error reporting - Could not parse GitLab message.", exc_info=True)
return ('Internal Error.', 'danger')
except (gitlab.GitlabConnectionError, gitlab.GitlabHttpError):
log.error("GitLab error reporting - Could not connect to GitLab.", exc_info=True)
return ('Internal Error.', 'danger')
except gitlab.GitlabError:
log.error("GitLab error reporting - General error communicating with GitLab.", exc_info=True)
return ('Internal Error.', 'danger')
except Exception:
log.error("GitLab error reporting - Error reporting to GitLab had an exception that could not be "
"determined.", exc_info=True)
return ('Internal Error.', 'danger')
else:
log.error("GitLab error reporting - No connection to GitLab. Cannot report error to GitLab.")
return ('Internal Error.', 'danger')
def _open_issue(self, error_message, error_title, gitlab_projecturl, gl_project, gl_userid, issue_cache_key):
""" Open an issue """
try:
# Create a new issue.
self._create_issue(issue_cache_key, error_title, error_message, gl_project, gl_userid=gl_userid)
except (gitlab.GitlabOwnershipError, gitlab.GitlabGetError):
# Create an issue in the default location
gitlab_projecturl = "/".join((self.git_default_repo_owner, self.git_default_repo_name))
gitlab_urlencodedpath = urllib.quote_plus(gitlab_projecturl)
# Make sure we are always logged in, then retrieve the GitLab project if it isn't cached.
self.gitlab = self.gitlab_connect()
if gitlab_projecturl not in self.git_project_cache:
self.git_project_cache[gitlab_projecturl] = self.gitlab.projects.get(gitlab_urlencodedpath)
gl_project = self.git_project_cache[gitlab_projecturl]
# Submit issue to default project
self._create_issue(issue_cache_key, error_title, error_message, gl_project, gl_userid=gl_userid)
return gitlab_projecturl
def _create_issue(self, issue_cache_key, error_title, error_message, project, **kwargs):
# Set payload for the issue
issue_data = {
'title': error_title,
'description': error_message
}
# Assign the user to the issue
gl_userid = kwargs.get("gl_userid", None)
if gl_userid is not None:
issue_data['assignee_ids'] = [gl_userid]
# Create the issue on GitLab
issue = project.issues.create(issue_data)
# Set labels
issue.labels = self.gitlab_labels
issue.save()
# Store in cache
self.issue_cache[issue_cache_key][error_title] = issue.iid
def _append_issue(self, issue_cache_key, error_title, error_message, **kwargs):
# Add a comment to an existing issue
gl_url = "/".join((
self.gitlab_base_url,
"api",
"v4",
"projects",
kwargs.get('gitlab_urlencodedpath'),
"issues",
str(self.issue_cache[issue_cache_key][error_title]),
"notes"
))
self.gitlab.http_post(gl_url, post_data={'body': error_message})
def _fill_issue_cache(self, git_project, issue_cache_key):
self.issue_cache[issue_cache_key] = {}
# Loop over all open issues and add the issue iid to the cache
for issue in git_project.issues.list():
if issue.state != 'closed':
log.info("GitLab error reporting - Repo issue: %s", str(issue.iid))
self.issue_cache[issue_cache_key][issue.title] = issue.iid
__all__ = ('GitLabPlugin', )