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 urllib.parse
try:
import gitlab
except ImportError:
gitlab = None
from galaxy.util import (
requests,
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 v2.10.0 or higher."
)
self.gitlab = self.gitlab_connect()
self.gitlab.auth()
except gitlab.GitlabAuthenticationError:
log.exception("GitLab error reporting - Could not authenticate with GitLab.")
self.gitlab = None
except gitlab.GitlabParsingError:
log.exception("GitLab error reporting - Could not parse GitLab message.")
self.gitlab = None
except (gitlab.GitlabConnectionError, gitlab.GitlabHttpError):
log.exception("GitLab error reporting - Could not connect to GitLab.")
self.gitlab = None
except gitlab.GitlabError:
log.exception("GitLab error reporting - General error communicating with GitLab.")
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(
f"GitLab error reporting - submit report - job tool id: {job.tool_id} - job tool version: {job.tool_version} - tool tool_shed: {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(f"GitLab error reporting - Determined ToolShed is {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(f"GitLab error reporting - Determine ToolShed Repository URL: {ts_repourl}")
# Determine the GitLab project URL and the issue cache key
gitlab_projecturl = (
urllib.parse.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)
# 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_projecturl)
except gitlab.GitlabGetError:
# Handle scenario where the repository doesn't exist so we can still continue
log.warning(
f"GitLab error reporting - Repository '{gitlab_projecturl}' doesn't exist, using default repository."
)
# 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))
if gitlab_projecturl not in self.git_project_cache:
self.git_project_cache[gitlab_projecturl] = self.gitlab.projects.get(gitlab_projecturl)
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_useremail = gl_project.commits.list()[0].attributes["author_email"]
if not self.redact_user_details_in_bugreport:
log.debug(f"GitLab error reporting - Last author email: {gl_useremail}")
if gl_useremail not in self.git_username_id_cache:
gl_emailquery = self.gitlab.users.list(search=gl_useremail)
log.debug(f"GitLab error reporting - User list: {gl_emailquery}")
if len(gl_emailquery) > 0:
log.debug("GitLab error reporting - Last Committer user ID: %s", gl_emailquery[0].get_id())
self.git_username_id_cache[gl_useremail] = gl_emailquery[0].get_id()
gl_userid = self.git_username_id_cache.get(gl_useremail, 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(f"GitLab error reporting - Issue state is {issue.state}")
if issue.state == "closed":
# Reopen issue
issue.state_event = "reopen"
issue.save()
log.info(f"GitLab error reporting - Reopened issue {issue_id}")
# Add a comment to an issue...
self._append_issue(
issue_cache_key, error_title, error_message, gitlab_projecturl=gitlab_projecturl
)
else:
self._open_issue(
error_message, error_title, gitlab_projecturl, gl_project, gl_userid, issue_cache_key
)
return (
f"Submitted error report to GitLab. Your Issue number is [#{self.issue_cache[issue_cache_key][error_title]}]({self.gitlab_base_url}/{gitlab_projecturl}/issues/{self.issue_cache[issue_cache_key][error_title]})",
"success",
)
except gitlab.GitlabCreateError:
log.exception("GitLab error reporting - Could not create the issue on GitLab.")
return ("Internal Error.", "danger")
except gitlab.GitlabOwnershipError:
log.exception("GitLab error reporting - Could not create the issue on GitLab due to ownership issues.")
return ("Internal Error.", "danger")
except gitlab.GitlabSearchError:
log.exception("GitLab error reporting - Could not find repository on GitLab.")
return ("Internal Error.", "danger")
except gitlab.GitlabAuthenticationError:
log.exception("GitLab error reporting - Could not authenticate with GitLab.")
return ("Internal Error.", "danger")
except gitlab.GitlabParsingError:
log.exception("GitLab error reporting - Could not parse GitLab message.")
return ("Internal Error.", "danger")
except (gitlab.GitlabConnectionError, gitlab.GitlabHttpError):
log.exception("GitLab error reporting - Could not connect to GitLab.")
return ("Internal Error.", "danger")
except gitlab.GitlabError:
log.exception("GitLab error reporting - General error communicating with GitLab.")
return ("Internal Error.", "danger")
except Exception:
log.exception(
"GitLab error reporting - Error reporting to GitLab had an exception that could not be determined.",
)
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))
# 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_projecturl)
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
if (gl_userid := kwargs.get("gl_userid", None)) 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",
urllib.parse.quote(kwargs.get("gitlab_projecturl"), safe=""),
"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" or not self.gitlab_new_issue_on_closed:
log.info("GitLab error reporting - Repo issue: %s", str(issue.iid))
self.issue_cache[issue_cache_key][issue.title] = issue.iid
__all__ = ("GitLabPlugin",)