gitlab 4.5.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +0 -267
- data/LICENSE.txt +1 -1
- data/README.md +40 -30
- data/exe/gitlab +5 -1
- data/lib/gitlab/api.rb +7 -3
- data/lib/gitlab/cli.rb +13 -9
- data/lib/gitlab/cli_helpers.rb +49 -45
- data/lib/gitlab/client/access_requests.rb +10 -1
- data/lib/gitlab/client/application_settings.rb +172 -0
- data/lib/gitlab/client/avatar.rb +21 -0
- data/lib/gitlab/client/award_emojis.rb +5 -3
- data/lib/gitlab/client/boards.rb +62 -4
- data/lib/gitlab/client/branches.rb +47 -8
- data/lib/gitlab/client/broadcast_messages.rb +75 -0
- data/lib/gitlab/client/build_variables.rb +19 -12
- data/lib/gitlab/client/builds.rb +13 -11
- data/lib/gitlab/client/commits.rb +73 -21
- data/lib/gitlab/client/container_registry.rb +85 -0
- data/lib/gitlab/client/deployments.rb +3 -1
- data/lib/gitlab/client/environments.rb +5 -3
- data/lib/gitlab/client/epic_issues.rb +23 -0
- data/lib/gitlab/client/epics.rb +73 -0
- data/lib/gitlab/client/events.rb +6 -4
- data/lib/gitlab/client/features.rb +48 -0
- data/lib/gitlab/client/group_badges.rb +88 -0
- data/lib/gitlab/client/group_boards.rb +141 -0
- data/lib/gitlab/client/group_labels.rb +88 -0
- data/lib/gitlab/client/group_milestones.rb +7 -6
- data/lib/gitlab/client/groups.rb +326 -12
- data/lib/gitlab/client/issue_links.rb +48 -0
- data/lib/gitlab/client/issues.rb +47 -13
- data/lib/gitlab/client/jobs.rb +96 -8
- data/lib/gitlab/client/keys.rb +13 -0
- data/lib/gitlab/client/labels.rb +6 -4
- data/lib/gitlab/client/lint.rb +19 -0
- data/lib/gitlab/client/markdown.rb +23 -0
- data/lib/gitlab/client/merge_request_approvals.rb +164 -9
- data/lib/gitlab/client/merge_requests.rb +148 -11
- data/lib/gitlab/client/merge_trains.rb +55 -0
- data/lib/gitlab/client/milestones.rb +19 -5
- data/lib/gitlab/client/namespaces.rb +4 -2
- data/lib/gitlab/client/notes.rb +38 -9
- data/lib/gitlab/client/packages.rb +95 -0
- data/lib/gitlab/client/pipeline_schedules.rb +36 -10
- data/lib/gitlab/client/pipeline_triggers.rb +10 -8
- data/lib/gitlab/client/pipelines.rb +65 -3
- data/lib/gitlab/client/project_badges.rb +85 -0
- data/lib/gitlab/client/project_clusters.rb +83 -0
- data/lib/gitlab/client/project_exports.rb +54 -0
- data/lib/gitlab/client/project_release_links.rb +76 -0
- data/lib/gitlab/client/project_releases.rb +90 -0
- data/lib/gitlab/client/projects.rb +307 -26
- data/lib/gitlab/client/protected_tags.rb +59 -0
- data/lib/gitlab/client/remote_mirrors.rb +51 -0
- data/lib/gitlab/client/repositories.rb +77 -6
- data/lib/gitlab/client/repository_files.rb +21 -3
- data/lib/gitlab/client/repository_submodules.rb +27 -0
- data/lib/gitlab/client/resource_label_events.rb +82 -0
- data/lib/gitlab/client/resource_state_events.rb +57 -0
- data/lib/gitlab/client/runners.rb +170 -18
- data/lib/gitlab/client/search.rb +66 -0
- data/lib/gitlab/client/services.rb +4 -1
- data/lib/gitlab/client/sidekiq.rb +2 -0
- data/lib/gitlab/client/snippets.rb +5 -3
- data/lib/gitlab/client/system_hooks.rb +9 -7
- data/lib/gitlab/client/tags.rb +10 -9
- data/lib/gitlab/client/templates.rb +100 -0
- data/lib/gitlab/client/todos.rb +7 -5
- data/lib/gitlab/client/user_snippets.rb +114 -0
- data/lib/gitlab/client/users.rb +302 -31
- data/lib/gitlab/client/versions.rb +18 -0
- data/lib/gitlab/client/wikis.rb +79 -0
- data/lib/gitlab/client.rb +48 -9
- data/lib/gitlab/configuration.rb +9 -6
- data/lib/gitlab/error.rb +73 -3
- data/lib/gitlab/file_response.rb +4 -2
- data/lib/gitlab/headers/page_links.rb +37 -0
- data/lib/gitlab/headers/total.rb +29 -0
- data/lib/gitlab/help.rb +16 -16
- data/lib/gitlab/objectified_hash.rb +27 -10
- data/lib/gitlab/paginated_response.rb +43 -25
- data/lib/gitlab/request.rb +51 -37
- data/lib/gitlab/shell.rb +6 -4
- data/lib/gitlab/shell_history.rb +11 -13
- data/lib/gitlab/version.rb +3 -1
- data/lib/gitlab.rb +23 -9
- metadata +59 -45
- data/.gitignore +0 -22
- data/CONTRIBUTING.md +0 -195
- data/Gemfile +0 -4
- data/Rakefile +0 -17
- data/bin/console +0 -10
- data/bin/setup +0 -6
- data/gitlab.gemspec +0 -33
- data/lib/gitlab/page_links.rb +0 -33
data/lib/gitlab/configuration.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'gitlab/cli_helpers'
|
2
4
|
module Gitlab
|
3
5
|
# Defines constants and methods related to configuration.
|
4
6
|
module Configuration
|
5
7
|
# An array of valid keys in the options hash when configuring a Gitlab::API.
|
6
|
-
VALID_OPTIONS_KEYS = %i
|
8
|
+
VALID_OPTIONS_KEYS = %i[endpoint private_token user_agent sudo httparty pat_prefix].freeze
|
7
9
|
|
8
10
|
# The user agent that will be sent to the API endpoint if none is set.
|
9
|
-
DEFAULT_USER_AGENT = "Gitlab Ruby Gem #{Gitlab::VERSION}"
|
11
|
+
DEFAULT_USER_AGENT = "Gitlab Ruby Gem #{Gitlab::VERSION}"
|
10
12
|
|
11
13
|
# @private
|
12
14
|
attr_accessor(*VALID_OPTIONS_KEYS)
|
13
15
|
# @private
|
14
|
-
|
16
|
+
alias auth_token= private_token=
|
15
17
|
|
16
18
|
# Sets all configuration options to their default values
|
17
19
|
# when this module is extended.
|
@@ -33,8 +35,9 @@ module Gitlab
|
|
33
35
|
|
34
36
|
# Resets all configuration options to the defaults.
|
35
37
|
def reset
|
36
|
-
self.endpoint = ENV['GITLAB_API_ENDPOINT']
|
38
|
+
self.endpoint = ENV['GITLAB_API_ENDPOINT'] || ENV['CI_API_V4_URL']
|
37
39
|
self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN'] || ENV['GITLAB_API_AUTH_TOKEN']
|
40
|
+
self.pat_prefix = nil
|
38
41
|
self.httparty = get_httparty_config(ENV['GITLAB_API_HTTPARTY_OPTIONS'])
|
39
42
|
self.sudo = nil
|
40
43
|
self.user_agent = DEFAULT_USER_AGENT
|
@@ -44,11 +47,11 @@ module Gitlab
|
|
44
47
|
|
45
48
|
# Allows HTTParty config to be specified in ENV using YAML hash.
|
46
49
|
def get_httparty_config(options)
|
47
|
-
return
|
50
|
+
return if options.nil?
|
48
51
|
|
49
52
|
httparty = Gitlab::CLI::Helpers.yaml_load(options)
|
50
|
-
|
51
53
|
raise ArgumentError, 'HTTParty config should be a Hash.' unless httparty.is_a? Hash
|
54
|
+
|
52
55
|
Gitlab::CLI::Helpers.symbolize_keys httparty
|
53
56
|
end
|
54
57
|
end
|
data/lib/gitlab/error.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gitlab
|
2
4
|
module Error
|
3
5
|
# Custom error class for rescuing from all Gitlab errors.
|
@@ -11,7 +13,7 @@ module Gitlab
|
|
11
13
|
|
12
14
|
# Custom error class for rescuing from HTTP response errors.
|
13
15
|
class ResponseError < Error
|
14
|
-
POSSIBLE_MESSAGE_KEYS = %i
|
16
|
+
POSSIBLE_MESSAGE_KEYS = %i[message error_description error].freeze
|
15
17
|
|
16
18
|
def initialize(response)
|
17
19
|
@response = response
|
@@ -32,13 +34,24 @@ module Gitlab
|
|
32
34
|
@response.parsed_response.message
|
33
35
|
end
|
34
36
|
|
37
|
+
# Additional error context returned by some API endpoints
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
def error_code
|
41
|
+
if @response.respond_to?(:error_code)
|
42
|
+
@response.error_code
|
43
|
+
else
|
44
|
+
''
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
35
48
|
private
|
36
49
|
|
37
50
|
# Human friendly message.
|
38
51
|
#
|
39
52
|
# @return [String]
|
40
53
|
def build_error_message
|
41
|
-
parsed_response =
|
54
|
+
parsed_response = classified_response
|
42
55
|
message = check_error_keys(parsed_response)
|
43
56
|
"Server responded with code #{@response.code}, message: " \
|
44
57
|
"#{handle_message(message)}. " \
|
@@ -52,12 +65,31 @@ module Gitlab
|
|
52
65
|
key ? resp.send(key) : resp
|
53
66
|
end
|
54
67
|
|
68
|
+
# Parse the body based on the classification of the body content type
|
69
|
+
#
|
70
|
+
# @return parsed response
|
71
|
+
def classified_response
|
72
|
+
if @response.respond_to?('headers')
|
73
|
+
@response.headers['content-type'] == 'text/plain' ? { message: @response.to_s } : @response.parsed_response
|
74
|
+
else
|
75
|
+
@response.parsed_response
|
76
|
+
end
|
77
|
+
rescue Gitlab::Error::Parsing
|
78
|
+
# Return stringified response when receiving a
|
79
|
+
# parsing error to avoid obfuscation of the
|
80
|
+
# api error.
|
81
|
+
#
|
82
|
+
# note: The Gitlab API does not always return valid
|
83
|
+
# JSON when there are errors.
|
84
|
+
@response.to_s
|
85
|
+
end
|
86
|
+
|
55
87
|
# Handle error response message in case of nested hashes
|
56
88
|
def handle_message(message)
|
57
89
|
case message
|
58
90
|
when Gitlab::ObjectifiedHash
|
59
91
|
message.to_h.sort.map do |key, val|
|
60
|
-
"'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : val).join(' ')}"
|
92
|
+
"'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : [val].flatten).join(' ')}"
|
61
93
|
end.join(', ')
|
62
94
|
when Array
|
63
95
|
message.join(' ')
|
@@ -82,12 +114,18 @@ module Gitlab
|
|
82
114
|
# Raised when API endpoint returns the HTTP status code 405.
|
83
115
|
class MethodNotAllowed < ResponseError; end
|
84
116
|
|
117
|
+
# Raised when API endpoint returns the HTTP status code 406.
|
118
|
+
class NotAcceptable < ResponseError; end
|
119
|
+
|
85
120
|
# Raised when API endpoint returns the HTTP status code 409.
|
86
121
|
class Conflict < ResponseError; end
|
87
122
|
|
88
123
|
# Raised when API endpoint returns the HTTP status code 422.
|
89
124
|
class Unprocessable < ResponseError; end
|
90
125
|
|
126
|
+
# Raised when API endpoint returns the HTTP status code 429.
|
127
|
+
class TooManyRequests < ResponseError; end
|
128
|
+
|
91
129
|
# Raised when API endpoint returns the HTTP status code 500.
|
92
130
|
class InternalServerError < ResponseError; end
|
93
131
|
|
@@ -96,5 +134,37 @@ module Gitlab
|
|
96
134
|
|
97
135
|
# Raised when API endpoint returns the HTTP status code 503.
|
98
136
|
class ServiceUnavailable < ResponseError; end
|
137
|
+
|
138
|
+
# Raised when API endpoint returns the HTTP status code 522.
|
139
|
+
class ConnectionTimedOut < ResponseError; end
|
140
|
+
|
141
|
+
# HTTP status codes mapped to error classes.
|
142
|
+
STATUS_MAPPINGS = {
|
143
|
+
400 => BadRequest,
|
144
|
+
401 => Unauthorized,
|
145
|
+
403 => Forbidden,
|
146
|
+
404 => NotFound,
|
147
|
+
405 => MethodNotAllowed,
|
148
|
+
406 => NotAcceptable,
|
149
|
+
409 => Conflict,
|
150
|
+
422 => Unprocessable,
|
151
|
+
429 => TooManyRequests,
|
152
|
+
500 => InternalServerError,
|
153
|
+
502 => BadGateway,
|
154
|
+
503 => ServiceUnavailable,
|
155
|
+
522 => ConnectionTimedOut
|
156
|
+
}.freeze
|
157
|
+
|
158
|
+
# Returns error class that should be raised for this response. Returns nil
|
159
|
+
# if the response status code is not 4xx or 5xx.
|
160
|
+
#
|
161
|
+
# @param [HTTParty::Response] response The response object.
|
162
|
+
# @return [Class<Error::ResponseError>, nil]
|
163
|
+
def self.klass(response)
|
164
|
+
error_klass = STATUS_MAPPINGS[response.code]
|
165
|
+
return error_klass if error_klass
|
166
|
+
|
167
|
+
ResponseError if response.server_error? || response.client_error?
|
168
|
+
end
|
99
169
|
end
|
100
170
|
end
|
data/lib/gitlab/file_response.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gitlab
|
2
4
|
# Wrapper class of file response.
|
3
5
|
class FileResponse
|
4
|
-
HEADER_CONTENT_DISPOSITION = 'Content-Disposition'
|
6
|
+
HEADER_CONTENT_DISPOSITION = 'Content-Disposition'
|
5
7
|
|
6
8
|
attr_reader :filename
|
7
9
|
|
@@ -18,7 +20,7 @@ module Gitlab
|
|
18
20
|
def to_hash
|
19
21
|
{ filename: @filename, data: @file }
|
20
22
|
end
|
21
|
-
|
23
|
+
alias to_h to_hash
|
22
24
|
|
23
25
|
# @return [String] Formatted string with the class name, object id and filename.
|
24
26
|
def inspect
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Headers
|
5
|
+
# Parses link header.
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
class PageLinks
|
9
|
+
HEADER_LINK = 'Link'
|
10
|
+
DELIM_LINKS = ','
|
11
|
+
LINK_REGEX = /<([^>]+)>; rel="([^"]+)"/
|
12
|
+
METAS = %w[last next first prev].freeze
|
13
|
+
|
14
|
+
attr_accessor(*METAS)
|
15
|
+
|
16
|
+
def initialize(headers)
|
17
|
+
link_header = headers[HEADER_LINK]
|
18
|
+
|
19
|
+
extract_links(link_header) if link_header && link_header =~ /(next|first|last|prev)/
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def extract_links(header)
|
25
|
+
header.split(DELIM_LINKS).each do |link|
|
26
|
+
LINK_REGEX.match(link.strip) do |match|
|
27
|
+
url = match[1]
|
28
|
+
meta = match[2]
|
29
|
+
next if !url || !meta || METAS.index(meta).nil?
|
30
|
+
|
31
|
+
send("#{meta}=", url)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Headers
|
5
|
+
# Parses total header.
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
class Total
|
9
|
+
HEADER_TOTAL = 'x-total'
|
10
|
+
TOTAL_REGEX = /^\d+$/
|
11
|
+
|
12
|
+
attr_accessor :total
|
13
|
+
|
14
|
+
def initialize(headers)
|
15
|
+
header_total = headers[HEADER_TOTAL]
|
16
|
+
|
17
|
+
extract_total(header_total) if header_total
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def extract_total(header_total)
|
23
|
+
TOTAL_REGEX.match(header_total.strip) do |match|
|
24
|
+
@total = match[0]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/gitlab/help.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'gitlab'
|
2
4
|
require 'gitlab/cli_helpers'
|
3
5
|
|
@@ -32,9 +34,7 @@ module Gitlab::Help
|
|
32
34
|
# @return [String]
|
33
35
|
def ri_cmd
|
34
36
|
which_ri = `which ri`.chomp
|
35
|
-
if which_ri.empty?
|
36
|
-
raise "'ri' tool not found in $PATH. Please install it to use the help."
|
37
|
-
end
|
37
|
+
raise "'ri' tool not found in $PATH. Please install it to use the help." if which_ri.empty?
|
38
38
|
|
39
39
|
which_ri
|
40
40
|
end
|
@@ -45,20 +45,19 @@ module Gitlab::Help
|
|
45
45
|
#
|
46
46
|
# @return [Hash<Array>]
|
47
47
|
def help_map
|
48
|
-
@help_map ||=
|
48
|
+
@help_map ||=
|
49
49
|
actions.each_with_object({}) do |action, hsh|
|
50
|
-
key = client.method(action)
|
51
|
-
|
50
|
+
key = client.method(action)
|
51
|
+
.owner.to_s.gsub(/Gitlab::(?:Client::)?/, '')
|
52
52
|
hsh[key] ||= []
|
53
53
|
hsh[key] << action.to_s
|
54
54
|
end
|
55
|
-
end
|
56
55
|
end
|
57
56
|
|
58
57
|
# Table with available commands.
|
59
58
|
#
|
60
59
|
# @return [Terminal::Table]
|
61
|
-
def actions_table(topic=nil)
|
60
|
+
def actions_table(topic = nil)
|
62
61
|
rows = topic ? help_map[topic] : help_map.keys
|
63
62
|
table do |t|
|
64
63
|
t.title = topic || 'Help Topics'
|
@@ -73,22 +72,23 @@ module Gitlab::Help
|
|
73
72
|
|
74
73
|
# Returns full namespace of a command (e.g. Gitlab::Client::Branches.cmd)
|
75
74
|
def namespace(cmd)
|
76
|
-
method_owners.select { |method| method[:name] == cmd }
|
77
|
-
|
78
|
-
|
75
|
+
method_owners.select { |method| method[:name] == cmd }
|
76
|
+
.map { |method| "#{method[:owner]}.#{method[:name]}" }
|
77
|
+
.shift
|
79
78
|
end
|
80
79
|
|
81
80
|
# Massage output from 'ri'.
|
82
81
|
def change_help_output!(cmd, output_str)
|
83
|
-
output_str
|
84
|
-
output_str.gsub!(
|
82
|
+
output_str = +output_str
|
83
|
+
output_str.gsub!(/#{cmd}(\(.*?\))/m, "#{cmd}\\1")
|
84
|
+
output_str.gsub!(/,\s*/, ', ')
|
85
85
|
|
86
86
|
# Ensure @option descriptions are on a single line
|
87
87
|
output_str.gsub!(/\n\[/, " \[")
|
88
88
|
output_str.gsub!(/\s(@)/, "\n@")
|
89
|
-
output_str.gsub!(/(\])\n(
|
90
|
-
output_str.gsub!(/(
|
91
|
-
output_str.gsub!(/\{(.+)\}/, '"{
|
89
|
+
output_str.gsub!(/(\])\n(:)/, '\\1 \\2')
|
90
|
+
output_str.gsub!(/(:.*)(\n)(.*\.)/, '\\1 \\3')
|
91
|
+
output_str.gsub!(/\{(.+)\}/, '"{\\1}"')
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
@@ -1,34 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gitlab
|
2
4
|
# Converts hashes to the objects.
|
3
5
|
class ObjectifiedHash
|
4
6
|
# Creates a new ObjectifiedHash object.
|
5
7
|
def initialize(hash)
|
6
8
|
@hash = hash
|
7
|
-
@data = hash.
|
8
|
-
value =
|
9
|
+
@data = hash.each_with_object({}) do |(key, value), data|
|
10
|
+
value = self.class.new(value) if value.is_a? Hash
|
11
|
+
value = value.map { |v| v.is_a?(Hash) ? self.class.new(v) : v } if value.is_a? Array
|
9
12
|
data[key.to_s] = value
|
10
|
-
data
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
# @return [Hash] The original hash.
|
15
17
|
def to_hash
|
16
|
-
|
18
|
+
hash
|
17
19
|
end
|
18
|
-
|
20
|
+
alias to_h to_hash
|
19
21
|
|
20
22
|
# @return [String] Formatted string with the class name, object id and original hash.
|
21
23
|
def inspect
|
22
|
-
"#<#{self.class}:#{object_id} {hash: #{
|
24
|
+
"#<#{self.class}:#{object_id} {hash: #{hash.inspect}}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
data[key]
|
23
29
|
end
|
24
30
|
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :hash, :data
|
34
|
+
|
35
|
+
# Respond to messages for which `self.data` has a key
|
36
|
+
def method_missing(method_name, *args, &block)
|
37
|
+
if data.key?(method_name.to_s)
|
38
|
+
data[method_name.to_s]
|
39
|
+
elsif data.respond_to?(method_name)
|
40
|
+
warn 'WARNING: Please convert ObjectifiedHash object to hash before calling Hash methods on it.'
|
41
|
+
data.send(method_name, *args, &block)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
28
45
|
end
|
29
46
|
|
30
47
|
def respond_to_missing?(method_name, include_private = false)
|
31
|
-
|
48
|
+
hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
|
32
49
|
end
|
33
50
|
end
|
34
51
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Gitlab
|
2
4
|
# Wrapper class of paginated response.
|
3
5
|
class PaginatedResponse
|
@@ -28,7 +30,8 @@ module Gitlab
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def parse_headers!(headers)
|
31
|
-
@links = PageLinks.new headers
|
33
|
+
@links = Headers::PageLinks.new headers
|
34
|
+
@total = Headers::Total.new headers
|
32
35
|
end
|
33
36
|
|
34
37
|
def each_page
|
@@ -40,58 +43,73 @@ module Gitlab
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
def lazy_paginate
|
47
|
+
to_enum(:each_page).lazy.flat_map(&:to_ary)
|
48
|
+
end
|
49
|
+
|
50
|
+
def auto_paginate(&block)
|
51
|
+
return lazy_paginate.to_a unless block
|
52
|
+
|
53
|
+
lazy_paginate.each(&block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def paginate_with_limit(limit, &block)
|
57
|
+
return lazy_paginate.take(limit).to_a unless block
|
58
|
+
|
59
|
+
lazy_paginate.take(limit).each(&block)
|
55
60
|
end
|
56
61
|
|
57
|
-
def
|
62
|
+
def total
|
63
|
+
@total.total
|
64
|
+
end
|
65
|
+
|
66
|
+
def last_page?
|
58
67
|
!(@links.nil? || @links.last.nil?)
|
59
68
|
end
|
69
|
+
alias has_last_page? last_page?
|
60
70
|
|
61
71
|
def last_page
|
62
72
|
return nil if @client.nil? || !has_last_page?
|
63
|
-
|
64
|
-
@client.get(
|
73
|
+
|
74
|
+
@client.get(client_relative_path(@links.last))
|
65
75
|
end
|
66
76
|
|
67
|
-
def
|
77
|
+
def first_page?
|
68
78
|
!(@links.nil? || @links.first.nil?)
|
69
79
|
end
|
80
|
+
alias has_first_page? first_page?
|
70
81
|
|
71
82
|
def first_page
|
72
83
|
return nil if @client.nil? || !has_first_page?
|
73
|
-
|
74
|
-
@client.get(
|
84
|
+
|
85
|
+
@client.get(client_relative_path(@links.first))
|
75
86
|
end
|
76
87
|
|
77
|
-
def
|
88
|
+
def next_page?
|
78
89
|
!(@links.nil? || @links.next.nil?)
|
79
90
|
end
|
91
|
+
alias has_next_page? next_page?
|
80
92
|
|
81
93
|
def next_page
|
82
94
|
return nil if @client.nil? || !has_next_page?
|
83
|
-
|
84
|
-
@client.get(
|
95
|
+
|
96
|
+
@client.get(client_relative_path(@links.next))
|
85
97
|
end
|
86
98
|
|
87
|
-
def
|
99
|
+
def prev_page?
|
88
100
|
!(@links.nil? || @links.prev.nil?)
|
89
101
|
end
|
102
|
+
alias has_prev_page? prev_page?
|
90
103
|
|
91
104
|
def prev_page
|
92
105
|
return nil if @client.nil? || !has_prev_page?
|
93
|
-
|
94
|
-
@client.get(
|
106
|
+
|
107
|
+
@client.get(client_relative_path(@links.prev))
|
108
|
+
end
|
109
|
+
|
110
|
+
def client_relative_path(link)
|
111
|
+
client_endpoint_path = URI.parse(@client.endpoint).request_uri # api/v4
|
112
|
+
URI.parse(link).request_uri.sub(client_endpoint_path, '')
|
95
113
|
end
|
96
114
|
end
|
97
115
|
end
|
data/lib/gitlab/request.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'httparty'
|
2
4
|
require 'json'
|
3
5
|
|
@@ -6,10 +8,11 @@ module Gitlab
|
|
6
8
|
class Request
|
7
9
|
include HTTParty
|
8
10
|
format :json
|
11
|
+
maintain_method_across_redirects true
|
9
12
|
headers 'Accept' => 'application/json', 'Content-Type' => 'application/x-www-form-urlencoded'
|
10
|
-
parser
|
13
|
+
parser(proc { |body, _| parse(body) })
|
11
14
|
|
12
|
-
attr_accessor :private_token, :endpoint
|
15
|
+
attr_accessor :private_token, :endpoint, :pat_prefix
|
13
16
|
|
14
17
|
# Converts the response body to an ObjectifiedHash.
|
15
18
|
def self.parse(body)
|
@@ -23,8 +26,6 @@ module Gitlab
|
|
23
26
|
true
|
24
27
|
elsif !body
|
25
28
|
false
|
26
|
-
elsif body.nil?
|
27
|
-
false
|
28
29
|
else
|
29
30
|
raise Error::Parsing, "Couldn't parse a response body"
|
30
31
|
end
|
@@ -32,35 +33,41 @@ module Gitlab
|
|
32
33
|
|
33
34
|
# Decodes a JSON response into Ruby object.
|
34
35
|
def self.decode(response)
|
35
|
-
|
36
|
+
response ? JSON.load(response) : {}
|
36
37
|
rescue JSON::ParserError
|
37
38
|
raise Error::Parsing, 'The response is not a valid JSON'
|
38
39
|
end
|
39
40
|
|
40
|
-
%w
|
41
|
-
define_method method do |path, options={}|
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
%w[get post put patch delete].each do |method|
|
42
|
+
define_method method do |path, options = {}|
|
43
|
+
params = options.dup
|
44
|
+
|
45
|
+
httparty_config(params)
|
46
|
+
|
47
|
+
unless params[:unauthenticated]
|
48
|
+
params[:headers] ||= {}
|
49
|
+
params[:headers].merge!(authorization_header)
|
50
|
+
end
|
51
|
+
|
52
|
+
retries_left = params[:ratelimit_retries] || 3
|
53
|
+
begin
|
54
|
+
response = self.class.send(method, endpoint + path, params)
|
55
|
+
validate response
|
56
|
+
rescue Gitlab::Error::TooManyRequests => e
|
57
|
+
retries_left -= 1
|
58
|
+
raise e if retries_left.zero?
|
59
|
+
|
60
|
+
wait_time = response.headers['Retry-After'] || 2
|
61
|
+
sleep(wait_time.to_i)
|
62
|
+
retry
|
63
|
+
end
|
45
64
|
end
|
46
65
|
end
|
47
66
|
|
48
67
|
# Checks the response code for common errors.
|
49
68
|
# Returns parsed response for successful requests.
|
50
69
|
def validate(response)
|
51
|
-
error_klass =
|
52
|
-
when 400 then Error::BadRequest
|
53
|
-
when 401 then Error::Unauthorized
|
54
|
-
when 403 then Error::Forbidden
|
55
|
-
when 404 then Error::NotFound
|
56
|
-
when 405 then Error::MethodNotAllowed
|
57
|
-
when 409 then Error::Conflict
|
58
|
-
when 422 then Error::Unprocessable
|
59
|
-
when 500 then Error::InternalServerError
|
60
|
-
when 502 then Error::BadGateway
|
61
|
-
when 503 then Error::ServiceUnavailable
|
62
|
-
end
|
63
|
-
|
70
|
+
error_klass = Error.klass(response)
|
64
71
|
raise error_klass, response if error_klass
|
65
72
|
|
66
73
|
parsed = response.parsed_response
|
@@ -71,28 +78,35 @@ module Gitlab
|
|
71
78
|
|
72
79
|
# Sets a base_uri and default_params for requests.
|
73
80
|
# @raise [Error::MissingCredentials] if endpoint not set.
|
74
|
-
def request_defaults(sudo=nil)
|
81
|
+
def request_defaults(sudo = nil)
|
82
|
+
raise Error::MissingCredentials, 'Please set an endpoint to API' unless endpoint
|
83
|
+
|
75
84
|
self.class.default_params sudo: sudo
|
76
|
-
raise Error::MissingCredentials, 'Please set an endpoint to API' unless @endpoint
|
77
85
|
self.class.default_params.delete(:sudo) if sudo.nil?
|
78
86
|
end
|
79
87
|
|
80
88
|
private
|
81
89
|
|
82
|
-
#
|
90
|
+
# Returns an Authorization header hash
|
83
91
|
#
|
84
|
-
# @param [Hash] options A customizable set of options.
|
85
|
-
# @option options [Boolean] :unauthenticated true if the API call does not require user authentication.
|
86
92
|
# @raise [Error::MissingCredentials] if private_token and auth_token are not set.
|
87
|
-
def authorization_header
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
def authorization_header
|
94
|
+
raise Error::MissingCredentials, 'Please provide a private_token or auth_token for user' unless private_token
|
95
|
+
|
96
|
+
# The Personal Access Token prefix can be at most 20 characters, and the
|
97
|
+
# generated part is of length 20 characters. Personal Access Tokens, thus
|
98
|
+
# can have a maximum size of 40 characters. GitLab uses
|
99
|
+
# `Doorkeeper::OAuth::Helpers::UniqueToken.generate` for generating
|
100
|
+
# OAuth2 tokens, and specified `hex` as token generator method. Thus, the
|
101
|
+
# OAuth2 tokens are of length more than 64. If the token length is below
|
102
|
+
# that, it is probably a Personal Access Token or CI_JOB_TOKEN.
|
103
|
+
if private_token.size >= 64
|
104
|
+
{ 'Authorization' => "Bearer #{private_token}" }
|
105
|
+
elsif private_token.start_with?(pat_prefix.to_s)
|
106
|
+
{ 'PRIVATE-TOKEN' => private_token }
|
107
|
+
else
|
108
|
+
{ 'JOB-TOKEN' => private_token }
|
109
|
+
end
|
96
110
|
end
|
97
111
|
|
98
112
|
# Set HTTParty configuration
|