# frozen_string_literal: true
require 'gitlab'
module Gitlab
# Monkey patch the Gitlab client to use the correct API path and add required methods
class Client
def team_member(project, id)
get("/projects/#{url_encode(project)}/members/all/#{id}")
end
def issue_discussions(project, issue_id, options = {})
get("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions", query: options)
end
def add_note_to_issue_discussion_as_thread(project, issue_id, discussion_id, options = {})
post("/projects/#{url_encode(project)}/issues/#{issue_id}/discussions/#{discussion_id}/notes", query: options)
end
end
module QA
module Report
# The GitLab client is used for API access: https://github.com/NARKOZ/gitlab
class GitlabIssueClient
MAINTAINER_ACCESS_LEVEL = 40
RETRY_BACK_OFF_DELAY = 60
MAX_RETRY_ATTEMPTS = 3
def initialize(token:, project:)
@token = token
@project = project
@retry_backoff = 0
configure_gitlab_client
end
def assert_user_permission!
handle_gitlab_client_exceptions do
user = Gitlab.user
member = Gitlab.team_member(project, user.id)
abort_not_permitted if member.access_level < MAINTAINER_ACCESS_LEVEL
end
rescue Gitlab::Error::NotFound
abort_not_permitted
end
def find_issues(iid: nil, options: {}, &select)
select ||= :itself
handle_gitlab_client_exceptions do
return [Gitlab.issue(project, iid)].select(&select) if iid
Gitlab.issues(project, options)
.auto_paginate
.select(&select)
end
end
def find_issue_discussions(iid:)
handle_gitlab_client_exceptions do
Gitlab.issue_discussions(project, iid, order_by: 'created_at', sort: 'asc').auto_paginate
end
end
def create_issue(title:, description:, labels:, issue_type: 'issue')
attrs = { issue_type: issue_type, description: description, labels: labels }
handle_gitlab_client_exceptions do
Gitlab.create_issue(project, title, attrs)
end
end
def edit_issue(iid:, options: {})
handle_gitlab_client_exceptions do
Gitlab.edit_issue(project, iid, options)
end
end
def find_issue_notes(iid:)
handle_gitlab_client_exceptions do
Gitlab.issue_notes(project, iid, order_by: 'created_at', sort: 'asc')&.auto_paginate
end
end
def create_issue_note(iid:, note:)
handle_gitlab_client_exceptions do
Gitlab.create_issue_note(project, iid, note)
end
end
def edit_issue_note(issue_iid:, note_id:, note:)
handle_gitlab_client_exceptions do
Gitlab.edit_issue_note(project, issue_iid, note_id, note)
end
end
def add_note_to_issue_discussion_as_thread(iid:, discussion_id:, body:)
handle_gitlab_client_exceptions do
Gitlab.add_note_to_issue_discussion_as_thread(project, iid, discussion_id, body: body)
end
end
def find_user_id(username:)
handle_gitlab_client_exceptions do
user = Gitlab.users(username: username)&.first
user['id'] unless user.nil?
end
end
def upload_file(file_fullpath:)
ignore_gitlab_client_exceptions do
Gitlab.upload_file(project, file_fullpath)
end
end
def ignore_gitlab_client_exceptions
yield
rescue StandardError, SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout, Gitlab::Error::Error => e
puts "Ignoring the following error: #{e}"
end
def handle_gitlab_client_exceptions
yield
rescue Gitlab::Error::NotFound
# This error could be raised in assert_user_permission!
# If so, we want it to terminate at that point
raise
rescue SystemCallError, OpenSSL::SSL::SSLError, Net::OpenTimeout, Net::ReadTimeout, Gitlab::Error::InternalServerError, Gitlab::Error::Parsing => e
@retry_backoff += RETRY_BACK_OFF_DELAY
raise if @retry_backoff > RETRY_BACK_OFF_DELAY * MAX_RETRY_ATTEMPTS
warn_exception(e)
warn("Sleeping for #{@retry_backoff} seconds before retrying...")
sleep @retry_backoff
retry
rescue StandardError => e
pipeline = QA::Runtime::Env.pipeline_from_project_name
channel = case pipeline
when "canary"
"qa-production"
when "staging-canary"
"qa-staging"
else
"qa-#{pipeline}"
end
error_msg = warn_exception(e)
return unless QA::Runtime::Env.ci_commit_ref_name == QA::Runtime::Env.default_branch
slack_options = {
channel: channel,
icon_emoji: ':ci_failing:',
message: <<~MSG
An unexpected error occurred while reporting test results in issues.
The error occurred in job: #{QA::Runtime::Env.ci_job_url}
`#{error_msg}`
MSG
}
puts "Posting Slack message to channel: #{channel}"
Gitlab::QA::Slack::PostToSlack.new(**slack_options).invoke!
end
private
attr_reader :token, :project
def configure_gitlab_client
Gitlab.configure do |config|
config.endpoint = Runtime::Env.gitlab_api_base
config.private_token = token
end
end
def abort_not_permitted
abort "You must have at least Maintainer access to the project to use this feature."
end
def warn_exception(error)
error_msg = "#{error.class.name} #{error.message}"
warn(error_msg)
error_msg
end
end
end
end
end