1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
# frozen_string_literal: true
module BitbucketServer
class Connection
include ActionView::Helpers::SanitizeHelper
include BitbucketServer::RetryWithDelay
DEFAULT_API_VERSION = '1.0'
SEPARATOR = '/'
NETWORK_ERRORS = [
SocketError,
OpenSSL::SSL::SSLError,
Errno::ECONNRESET,
Errno::ECONNREFUSED,
Errno::EHOSTUNREACH,
Net::OpenTimeout,
Net::ReadTimeout,
URI::InvalidURIError,
Gitlab::HTTP::BlockedUrlError
].freeze
attr_reader :api_version, :base_uri, :username, :token
ConnectionError = Class.new(StandardError)
def initialize(options = {})
@api_version = options.fetch(:api_version, DEFAULT_API_VERSION)
@base_uri = options[:base_uri]
@username = options[:user]
@token = options[:password]
end
def get(path, extra_query = {})
response = if Feature.enabled?(:bitbucket_server_importer_exponential_backoff)
retry_with_delay do
Gitlab::HTTP.get(build_url(path), basic_auth: auth, headers: accept_headers, query: extra_query)
end
else
Gitlab::HTTP.get(build_url(path), basic_auth: auth, headers: accept_headers, query: extra_query)
end
check_errors!(response)
response.parsed_response
rescue *NETWORK_ERRORS => e
raise ConnectionError, e
end
def post(path, body)
response = if Feature.enabled?(:bitbucket_server_importer_exponential_backoff)
retry_with_delay do
Gitlab::HTTP.post(build_url(path), basic_auth: auth, headers: post_headers, body: body)
end
else
Gitlab::HTTP.post(build_url(path), basic_auth: auth, headers: post_headers, body: body)
end
check_errors!(response)
response.parsed_response
rescue *NETWORK_ERRORS => e
raise ConnectionError, e
end
# We need to support two different APIs for deletion:
#
# /rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/branches/default
# /rest/branch-utils/1.0/projects/{projectKey}/repos/{repositorySlug}/branches
def delete(resource, path, body)
url = delete_url(resource, path)
response = if Feature.enabled?(:bitbucket_server_importer_exponential_backoff)
retry_with_delay do
Gitlab::HTTP.delete(url, basic_auth: auth, headers: post_headers, body: body)
end
else
Gitlab::HTTP.delete(url, basic_auth: auth, headers: post_headers, body: body)
end
check_errors!(response)
response.parsed_response
rescue *NETWORK_ERRORS => e
raise ConnectionError, e
end
private
def check_errors!(response)
return if ActionDispatch::Response::NO_CONTENT_CODES.include?(response.code)
raise ConnectionError, "Response is not valid JSON" unless response.parsed_response.is_a?(Hash)
return if response.code >= 200 && response.code < 300
details = sanitize(response.parsed_response.dig('errors', 0, 'message'))
message = "Error #{response.code}"
message += ": #{details}" if details
raise ConnectionError, message
rescue JSON::ParserError
raise ConnectionError, "Unable to parse the server response as JSON"
end
def auth
@auth ||= { username: username, password: token }
end
def accept_headers
@accept_headers ||= { 'Accept' => 'application/json' }
end
def post_headers
@post_headers ||= accept_headers.merge({ 'Content-Type' => 'application/json' })
end
def build_url(path)
return path if path.starts_with?(root_url)
Gitlab::Utils.append_path(root_url, path)
end
def root_url
Gitlab::Utils.append_path(base_uri, "rest/api/#{api_version}")
end
def delete_url(resource, path)
if resource == :branches
Gitlab::Utils.append_path(base_uri, "rest/branch-utils/#{api_version}#{path}")
else
build_url(path)
end
end
def logger
Gitlab::BitbucketServerImport::Logger
end
end
end
|