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."""
from __future__ import absolute_import
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
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))
        try:
            import gitlab
            self.gitlab = self.gitlab_connect()
            self.gitlab.auth()
        except ImportError:
            log.error("GitLab error reporting - Please install python-gitlab to submit bug reports to GitLab.", exc_info=True)
            self.gitlab = None
        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: %s - job tool version: %s - tool tool_shed: %s" % (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()
                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]:
                    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):
                        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)
                else:
                    # Add a comment to an issue...
                    self._append_issue(issue_cache_key, error_title, error_message, gitlab_urlencodedpath=gitlab_urlencodedpath)
                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 _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)
        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', )