Commit 28a4e491 authored by Michał Zając's avatar Michał Zając

Determine Jira deployment_type based on URL

If API-based methods fail when determining the deployment_type we should
use the URL. Since Jira Cloud does not support custom domains yet we
can pretty much guess whether it's Jira Cloud or Jira Server based on
the domain. If it ends with atlassian.net then we're dealing with a Jira
Cloud, otherwise it's Jira Server.

Changelog: fixed
parent 91596934
...@@ -10,12 +10,7 @@ module Integrations ...@@ -10,12 +10,7 @@ module Integrations
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
PROJECTS_PER_PAGE = 50 PROJECTS_PER_PAGE = 50
JIRA_CLOUD_HOST = '.atlassian.net'
# TODO: use jira_service.deployment_type enum when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37003 is merged
DEPLOYMENT_TYPES = {
server: 'SERVER',
cloud: 'CLOUD'
}.freeze
validates :url, public_url: true, presence: true, if: :activated? validates :url, public_url: true, presence: true, if: :activated?
validates :api_url, public_url: true, allow_blank: true validates :api_url, public_url: true, allow_blank: true
...@@ -517,15 +512,38 @@ module Integrations ...@@ -517,15 +512,38 @@ module Integrations
def update_deployment_type def update_deployment_type
clear_memoization(:server_info) # ensure we run the request when we try to update deployment type clear_memoization(:server_info) # ensure we run the request when we try to update deployment type
results = server_info results = server_info
return data_fields.deployment_unknown! unless results.present?
case results['deploymentType'] unless results.present?
when 'Server' Gitlab::AppLogger.warn(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: results, url: client_url)
return set_deployment_type_from_url
end
if jira_cloud?
data_fields.deployment_cloud!
else
data_fields.deployment_server! data_fields.deployment_server!
when 'Cloud' end
end
def jira_cloud?
server_info['deploymentType'] == 'Cloud' || URI(client_url).hostname.end_with?(JIRA_CLOUD_HOST)
end
def set_deployment_type_from_url
# This shouldn't happen but of course it will happen when an integration is removed.
# Instead of deleting the integration we set all fields to null
# and mark it as inactive
return data_fields.deployment_unknown! unless client_url
# If API-based detection methods fail here then
# we can only assume it's either Cloud or Server
# based on the URL being *.atlassian.net
if URI(client_url).hostname.end_with?(JIRA_CLOUD_HOST)
data_fields.deployment_cloud! data_fields.deployment_cloud!
else else
data_fields.deployment_unknown! data_fields.deployment_server!
end end
end end
......
...@@ -31,21 +31,10 @@ module JiraImport ...@@ -31,21 +31,10 @@ module JiraImport
@users_mapper_service ||= user_mapper_service_factory @users_mapper_service ||= user_mapper_service_factory
end end
def deployment_type
# TODO: use project.jira_service.deployment_type value when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37003 is merged
@deployment_type ||= client.ServerInfo.all.deploymentType
end
def client
@client ||= project.jira_service.client
end
def user_mapper_service_factory def user_mapper_service_factory
# TODO: use deployment_type enum from jira service when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37003 is merged if project.jira_service.data_fields.deployment_server?
case deployment_type.upcase
when Integrations::Jira::DEPLOYMENT_TYPES[:server]
ServerUsersMapperService.new(user, project, start_at) ServerUsersMapperService.new(user, project, start_at)
when Integrations::Jira::DEPLOYMENT_TYPES[:cloud] elsif project.jira_service.data_fields.deployment_cloud?
CloudUsersMapperService.new(user, project, start_at) CloudUsersMapperService.new(user, project, start_at)
else else
raise ArgumentError raise ArgumentError
......
...@@ -117,7 +117,8 @@ RSpec.describe Integrations::Jira do ...@@ -117,7 +117,8 @@ RSpec.describe Integrations::Jira do
let(:params) do let(:params) do
{ {
project: project, project: project,
url: url, api_url: api_url, url: url,
api_url: api_url,
username: username, password: password, username: username, password: password,
jira_issue_transition_id: transition_id jira_issue_transition_id: transition_id
} }
...@@ -141,9 +142,9 @@ RSpec.describe Integrations::Jira do ...@@ -141,9 +142,9 @@ RSpec.describe Integrations::Jira do
end end
context 'when loading serverInfo' do context 'when loading serverInfo' do
let!(:jira_service) { subject } let(:jira_service) { subject }
context 'Cloud instance' do context 'from a Cloud instance' do
let(:server_info_results) { { 'deploymentType' => 'Cloud' } } let(:server_info_results) { { 'deploymentType' => 'Cloud' } }
it 'is detected' do it 'is detected' do
...@@ -151,7 +152,7 @@ RSpec.describe Integrations::Jira do ...@@ -151,7 +152,7 @@ RSpec.describe Integrations::Jira do
end end
end end
context 'Server instance' do context 'from a Server instance' do
let(:server_info_results) { { 'deploymentType' => 'Server' } } let(:server_info_results) { { 'deploymentType' => 'Server' } }
it 'is detected' do it 'is detected' do
...@@ -159,11 +160,45 @@ RSpec.describe Integrations::Jira do ...@@ -159,11 +160,45 @@ RSpec.describe Integrations::Jira do
end end
end end
context 'Unknown instance' do context 'from an Unknown instance' do
let(:server_info_results) { { 'deploymentType' => 'FutureCloud' } } let(:server_info_results) { { 'deploymentType' => 'FutureCloud' } }
it 'is detected' do context 'and URL ends in .atlassian.net' do
expect(jira_service.jira_tracker_data.deployment_unknown?).to be_truthy let(:api_url) { 'http://example-api.atlassian.net' }
it 'deployment_type is set to cloud' do
expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy
end
end
context 'and URL is something else' do
let(:api_url) { 'http://my-jira-api.someserver.com' }
it 'deployment_type is set to server' do
expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy
end
end
end
context 'and no ServerInfo response is received' do
let(:server_info_results) { {} }
context 'and URL ends in .atlassian.net' do
let(:api_url) { 'http://example-api.atlassian.net' }
it 'deployment_type is set to cloud' do
expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url)
expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy
end
end
context 'and URL is something else' do
let(:api_url) { 'http://my-jira-api.someserver.com' }
it 'deployment_type is set to server' do
expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url)
expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy
end
end end
end end
end end
...@@ -229,17 +264,31 @@ RSpec.describe Integrations::Jira do ...@@ -229,17 +264,31 @@ RSpec.describe Integrations::Jira do
end end
context 'when updating the url, api_url, username, or password' do context 'when updating the url, api_url, username, or password' do
it 'updates deployment type' do context 'when updating the integration' do
service.update!(url: 'http://first.url') it 'updates deployment type' do
service.jira_tracker_data.update!(deployment_type: 'server') service.update!(url: 'http://first.url')
service.jira_tracker_data.update!(deployment_type: 'server')
expect(service.jira_tracker_data.deployment_server?).to be_truthy expect(service.jira_tracker_data.deployment_server?).to be_truthy
service.update!(api_url: 'http://another.url') service.update!(api_url: 'http://another.url')
service.jira_tracker_data.reload service.jira_tracker_data.reload
expect(service.jira_tracker_data.deployment_cloud?).to be_truthy expect(service.jira_tracker_data.deployment_cloud?).to be_truthy
expect(WebMock).to have_requested(:get, /serverInfo/).twice expect(WebMock).to have_requested(:get, /serverInfo/).twice
end
end
context 'when removing the integration' do
let(:server_info_results) { {} }
it 'updates deployment type' do
service.update!(url: nil, api_url: nil, active: false)
service.jira_tracker_data.reload
expect(service.jira_tracker_data.deployment_unknown?).to be_truthy
end
end end
it 'calls serverInfo for url' do it 'calls serverInfo for url' do
......
...@@ -43,39 +43,37 @@ RSpec.describe JiraImport::UsersImporter do ...@@ -43,39 +43,37 @@ RSpec.describe JiraImport::UsersImporter do
end end
end end
RSpec.shared_examples 'maps jira users to gitlab users' do RSpec.shared_examples 'maps Jira users to GitLab users' do |users_mapper_service:|
context 'when Jira import is configured correctly' do context 'when Jira import is configured correctly' do
let_it_be(:jira_service) { create(:jira_service, project: project, active: true) } let_it_be(:jira_service) { create(:jira_service, project: project, active: true, url: "http://jira.example.net") }
let(:client) { double }
before do context 'when users mapper service raises an error' do
expect(importer).to receive(:client).at_least(1).and_return(client)
allow(client).to receive_message_chain(:ServerInfo, :all, :deploymentType).and_return(deployment_type)
end
context 'when jira client raises an error' do
let(:error) { Timeout::Error.new } let(:error) { Timeout::Error.new }
it 'returns an error response' do it 'returns an error response' do
expect(client).to receive(:get).and_raise(error) expect_next_instance_of(users_mapper_service) do |instance|
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(error, project_id: project.id) expect(instance).to receive(:execute).and_raise(error)
end
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(error, project_id: project.id)
expect(subject.error?).to be_truthy expect(subject.error?).to be_truthy
expect(subject.message).to include('There was an error when communicating to Jira') expect(subject.message).to include('There was an error when communicating to Jira')
end end
end end
context 'when jira client returns result' do context 'when users mapper service returns result' do
context 'when jira client returns an empty array' do context 'when users mapper service returns an empty array' do
let(:jira_users) { [] }
it 'returns nil payload' do it 'returns nil payload' do
expect_next_instance_of(users_mapper_service) do |instance|
expect(instance).to receive(:execute).and_return([])
end
expect(subject.success?).to be_truthy expect(subject.success?).to be_truthy
expect(subject.payload).to be_empty expect(subject.payload).to be_empty
end end
end end
context 'when jira client returns an results' do context 'when Jira client returns any users' do
let_it_be(:project_member) { create(:user, email: 'sample@jira.com') } let_it_be(:project_member) { create(:user, email: 'sample@jira.com') }
let_it_be(:group_member) { create(:user, name: 'user-name2') } let_it_be(:group_member) { create(:user, name: 'user-name2') }
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
...@@ -86,6 +84,10 @@ RSpec.describe JiraImport::UsersImporter do ...@@ -86,6 +84,10 @@ RSpec.describe JiraImport::UsersImporter do
end end
it 'returns the mapped users' do it 'returns the mapped users' do
expect_next_instance_of(users_mapper_service) do |instance|
expect(instance).to receive(:execute).and_return(mapped_users)
end
expect(subject.success?).to be_truthy expect(subject.success?).to be_truthy
expect(subject.payload).to eq(mapped_users) expect(subject.payload).to eq(mapped_users)
end end
...@@ -95,43 +97,23 @@ RSpec.describe JiraImport::UsersImporter do ...@@ -95,43 +97,23 @@ RSpec.describe JiraImport::UsersImporter do
end end
context 'when Jira instance is of Server deployment type' do context 'when Jira instance is of Server deployment type' do
let(:deployment_type) { 'Server' }
let(:url) { "/rest/api/2/user/search?username=''&maxResults=50&startAt=#{start_at}" }
let(:jira_users) do
[
{ 'key' => 'acc1', 'name' => 'user-name1', 'emailAddress' => 'sample@jira.com' },
{ 'key' => 'acc2', 'name' => 'user-name2' }
]
end
before do before do
allow_next_instance_of(JiraImport::ServerUsersMapperService) do |instance| allow(project).to receive(:jira_service).and_return(jira_service)
allow(instance).to receive(:client).and_return(client)
allow(client).to receive(:get).with(url).and_return(jira_users) jira_service.data_fields.deployment_server!
end
end end
it_behaves_like 'maps jira users to gitlab users' it_behaves_like 'maps Jira users to GitLab users', users_mapper_service: JiraImport::ServerUsersMapperService
end end
context 'when Jira instance is of Cloud deploymet type' do context 'when Jira instance is of Cloud deployment type' do
let(:deployment_type) { 'Cloud' }
let(:url) { "/rest/api/2/users?maxResults=50&startAt=#{start_at}" }
let(:jira_users) do
[
{ 'accountId' => 'acc1', 'displayName' => 'user-name1', 'emailAddress' => 'sample@jira.com' },
{ 'accountId' => 'acc2', 'displayName' => 'user-name2' }
]
end
before do before do
allow_next_instance_of(JiraImport::CloudUsersMapperService) do |instance| allow(project).to receive(:jira_service).and_return(jira_service)
allow(instance).to receive(:client).and_return(client)
allow(client).to receive(:get).with(url).and_return(jira_users) jira_service.data_fields.deployment_cloud!
end
end end
it_behaves_like 'maps jira users to gitlab users' it_behaves_like 'maps Jira users to GitLab users', users_mapper_service: JiraImport::CloudUsersMapperService
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment