gitlab-faraday 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 +7 -0
- data/lib/gitlab/api.rb +16 -0
- data/lib/gitlab/client/access_requests.rb +103 -0
- data/lib/gitlab/client/application_settings.rb +172 -0
- data/lib/gitlab/client/avatar.rb +21 -0
- data/lib/gitlab/client/award_emojis.rb +137 -0
- data/lib/gitlab/client/boards.rb +146 -0
- data/lib/gitlab/client/branches.rb +135 -0
- data/lib/gitlab/client/broadcast_messages.rb +75 -0
- data/lib/gitlab/client/build_variables.rb +135 -0
- data/lib/gitlab/client/builds.rb +108 -0
- data/lib/gitlab/client/commits.rb +216 -0
- data/lib/gitlab/client/container_registry.rb +90 -0
- data/lib/gitlab/client/deployments.rb +34 -0
- data/lib/gitlab/client/environments.rb +89 -0
- data/lib/gitlab/client/epic_issues.rb +23 -0
- data/lib/gitlab/client/epics.rb +73 -0
- data/lib/gitlab/client/events.rb +60 -0
- 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 +94 -0
- data/lib/gitlab/client/groups.rb +526 -0
- data/lib/gitlab/client/issue_links.rb +48 -0
- data/lib/gitlab/client/issues.rb +242 -0
- data/lib/gitlab/client/jobs.rb +250 -0
- data/lib/gitlab/client/keys.rb +29 -0
- data/lib/gitlab/client/labels.rb +88 -0
- data/lib/gitlab/client/lint.rb +19 -0
- data/lib/gitlab/client/markdown.rb +23 -0
- data/lib/gitlab/client/merge_request_approvals.rb +265 -0
- data/lib/gitlab/client/merge_requests.rb +415 -0
- data/lib/gitlab/client/merge_trains.rb +55 -0
- data/lib/gitlab/client/milestones.rb +106 -0
- data/lib/gitlab/client/namespaces.rb +22 -0
- data/lib/gitlab/client/notes.rb +313 -0
- data/lib/gitlab/client/packages.rb +95 -0
- data/lib/gitlab/client/pipeline_schedules.rb +159 -0
- data/lib/gitlab/client/pipeline_triggers.rb +103 -0
- data/lib/gitlab/client/pipelines.rb +130 -0
- 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 +792 -0
- data/lib/gitlab/client/protected_tags.rb +59 -0
- data/lib/gitlab/client/remote_mirrors.rb +90 -0
- data/lib/gitlab/client/repositories.rb +130 -0
- data/lib/gitlab/client/repository_files.rb +131 -0
- 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 +278 -0
- data/lib/gitlab/client/search.rb +66 -0
- data/lib/gitlab/client/services.rb +53 -0
- data/lib/gitlab/client/sidekiq.rb +39 -0
- data/lib/gitlab/client/snippets.rb +95 -0
- data/lib/gitlab/client/system_hooks.rb +64 -0
- data/lib/gitlab/client/tags.rb +97 -0
- data/lib/gitlab/client/templates.rb +100 -0
- data/lib/gitlab/client/todos.rb +46 -0
- data/lib/gitlab/client/user_snippets.rb +114 -0
- data/lib/gitlab/client/users.rb +521 -0
- data/lib/gitlab/client/versions.rb +18 -0
- data/lib/gitlab/client/wikis.rb +79 -0
- data/lib/gitlab/client.rb +96 -0
- data/lib/gitlab/configuration.rb +36 -0
- data/lib/gitlab/error.rb +114 -0
- data/lib/gitlab/file_response.rb +43 -0
- data/lib/gitlab/headers/page_links.rb +32 -0
- data/lib/gitlab/headers/total.rb +24 -0
- data/lib/gitlab/objectified_hash.rb +44 -0
- data/lib/gitlab/paginated_response.rb +114 -0
- data/lib/gitlab/request.rb +144 -0
- data/lib/gitlab/version.rb +5 -0
- data/lib/gitlab.rb +36 -0
- metadata +156 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi/escape'
|
|
4
|
+
|
|
5
|
+
module Gitlab
|
|
6
|
+
class Client < API
|
|
7
|
+
Dir[File.expand_path('client/*.rb', __dir__)].each { |f| require f }
|
|
8
|
+
|
|
9
|
+
include AccessRequests
|
|
10
|
+
include ApplicationSettings
|
|
11
|
+
include Avatar
|
|
12
|
+
include AwardEmojis
|
|
13
|
+
include Boards
|
|
14
|
+
include Branches
|
|
15
|
+
include BroadcastMessages
|
|
16
|
+
include BuildVariables
|
|
17
|
+
include Builds
|
|
18
|
+
include Commits
|
|
19
|
+
include ContainerRegistry
|
|
20
|
+
include Deployments
|
|
21
|
+
include Environments
|
|
22
|
+
include EpicIssues
|
|
23
|
+
include Epics
|
|
24
|
+
include Events
|
|
25
|
+
include Features
|
|
26
|
+
include GroupBadges
|
|
27
|
+
include GroupBoards
|
|
28
|
+
include GroupLabels
|
|
29
|
+
include GroupMilestones
|
|
30
|
+
include Groups
|
|
31
|
+
include IssueLinks
|
|
32
|
+
include Issues
|
|
33
|
+
include Jobs
|
|
34
|
+
include Keys
|
|
35
|
+
include Labels
|
|
36
|
+
include Lint
|
|
37
|
+
include Markdown
|
|
38
|
+
include MergeRequestApprovals
|
|
39
|
+
include MergeRequests
|
|
40
|
+
include MergeTrains
|
|
41
|
+
include Milestones
|
|
42
|
+
include Namespaces
|
|
43
|
+
include Notes
|
|
44
|
+
include Packages
|
|
45
|
+
include PipelineSchedules
|
|
46
|
+
include PipelineTriggers
|
|
47
|
+
include Pipelines
|
|
48
|
+
include ProjectBadges
|
|
49
|
+
include ProjectClusters
|
|
50
|
+
include ProjectExports
|
|
51
|
+
include ProjectReleaseLinks
|
|
52
|
+
include ProjectReleases
|
|
53
|
+
include Projects
|
|
54
|
+
include ProtectedTags
|
|
55
|
+
include RemoteMirrors
|
|
56
|
+
include Repositories
|
|
57
|
+
include RepositoryFiles
|
|
58
|
+
include RepositorySubmodules
|
|
59
|
+
include ResourceLabelEvents
|
|
60
|
+
include ResourceStateEvents
|
|
61
|
+
include Runners
|
|
62
|
+
include Search
|
|
63
|
+
include Services
|
|
64
|
+
include Sidekiq
|
|
65
|
+
include Snippets
|
|
66
|
+
include SystemHooks
|
|
67
|
+
include Tags
|
|
68
|
+
include Templates
|
|
69
|
+
include Todos
|
|
70
|
+
include Users
|
|
71
|
+
include UserSnippets
|
|
72
|
+
include Versions
|
|
73
|
+
include Wikis
|
|
74
|
+
|
|
75
|
+
def inspect
|
|
76
|
+
inspected = super
|
|
77
|
+
inspected = redact_private_token(inspected, @private_token) if @private_token
|
|
78
|
+
inspected
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def url_encode(url)
|
|
82
|
+
CGI.escapeURIComponent(url.to_s)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def redact_private_token(inspected, private_token)
|
|
86
|
+
redacted = only_show_last_four_chars(private_token)
|
|
87
|
+
inspected.sub(%(@private_token="#{private_token}"), %(@private_token="#{redacted}"))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def only_show_last_four_chars(token)
|
|
91
|
+
return '****' if token.size <= 4
|
|
92
|
+
|
|
93
|
+
"#{'*' * (token.size - 4)}#{token[-4..]}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Configuration
|
|
5
|
+
VALID_OPTIONS_KEYS = %i[endpoint private_token user_agent sudo httparty pat_prefix body_as_json].freeze
|
|
6
|
+
|
|
7
|
+
DEFAULT_USER_AGENT = "Gitlab Ruby Gem #{Gitlab::VERSION}"
|
|
8
|
+
|
|
9
|
+
attr_accessor(*VALID_OPTIONS_KEYS)
|
|
10
|
+
alias auth_token= private_token=
|
|
11
|
+
|
|
12
|
+
def self.extended(base)
|
|
13
|
+
base.reset
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def configure
|
|
17
|
+
yield self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def options
|
|
21
|
+
VALID_OPTIONS_KEYS.each_with_object({}) do |key, option|
|
|
22
|
+
option[key] = send(key)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def reset
|
|
27
|
+
self.endpoint = ENV['GITLAB_API_ENDPOINT'] || ENV.fetch('CI_API_V4_URL', nil)
|
|
28
|
+
self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN'] || ENV.fetch('GITLAB_API_AUTH_TOKEN', nil)
|
|
29
|
+
self.pat_prefix = nil
|
|
30
|
+
self.httparty = nil
|
|
31
|
+
self.sudo = nil
|
|
32
|
+
self.user_agent = DEFAULT_USER_AGENT
|
|
33
|
+
self.body_as_json = false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/gitlab/error.rb
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Error
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
class MissingCredentials < Error; end
|
|
8
|
+
|
|
9
|
+
class Parsing < Error; end
|
|
10
|
+
|
|
11
|
+
class ResponseError < Error
|
|
12
|
+
POSSIBLE_MESSAGE_KEYS = %i[message error_description error].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(response)
|
|
15
|
+
@response = response
|
|
16
|
+
super(build_error_message)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def response_status
|
|
20
|
+
@response.status
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def response_message
|
|
24
|
+
parsed = parsed_response
|
|
25
|
+
parsed.respond_to?(:message) ? parsed.message : parsed.to_s
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def error_code
|
|
29
|
+
''
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def build_error_message
|
|
33
|
+
parsed = parsed_response
|
|
34
|
+
message = check_error_keys(parsed)
|
|
35
|
+
"Server responded with code #{@response.status}, message: " \
|
|
36
|
+
"#{handle_message(message)}. " \
|
|
37
|
+
"Request URI: #{@response.env.url}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def check_error_keys(resp)
|
|
41
|
+
return resp unless resp.is_a?(Gitlab::ObjectifiedHash)
|
|
42
|
+
|
|
43
|
+
key = POSSIBLE_MESSAGE_KEYS.find { |k| resp.respond_to?(k) }
|
|
44
|
+
key ? resp.send(key) : resp
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parsed_response
|
|
48
|
+
return @parsed_response if defined?(@parsed_response)
|
|
49
|
+
|
|
50
|
+
body = @response.body
|
|
51
|
+
return {} if body.nil? || body.empty?
|
|
52
|
+
|
|
53
|
+
parsed = JSON.parse(body)
|
|
54
|
+
@parsed_response = if parsed.is_a?(Hash)
|
|
55
|
+
Gitlab::ObjectifiedHash.new(parsed)
|
|
56
|
+
else
|
|
57
|
+
parsed
|
|
58
|
+
end
|
|
59
|
+
rescue JSON::ParserError
|
|
60
|
+
@parsed_response = @response.body
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def handle_message(message)
|
|
64
|
+
case message
|
|
65
|
+
when Gitlab::ObjectifiedHash
|
|
66
|
+
message.to_h.sort.map do |key, val|
|
|
67
|
+
"'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : [val].flatten).join(' ')}"
|
|
68
|
+
end.join(', ')
|
|
69
|
+
when Array
|
|
70
|
+
message.join(' ')
|
|
71
|
+
else
|
|
72
|
+
message
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class BadRequest < ResponseError; end
|
|
78
|
+
class Unauthorized < ResponseError; end
|
|
79
|
+
class Forbidden < ResponseError; end
|
|
80
|
+
class NotFound < ResponseError; end
|
|
81
|
+
class MethodNotAllowed < ResponseError; end
|
|
82
|
+
class NotAcceptable < ResponseError; end
|
|
83
|
+
class Conflict < ResponseError; end
|
|
84
|
+
class Unprocessable < ResponseError; end
|
|
85
|
+
class TooManyRequests < ResponseError; end
|
|
86
|
+
class InternalServerError < ResponseError; end
|
|
87
|
+
class BadGateway < ResponseError; end
|
|
88
|
+
class ServiceUnavailable < ResponseError; end
|
|
89
|
+
class ConnectionTimedOut < ResponseError; end
|
|
90
|
+
|
|
91
|
+
STATUS_MAPPINGS = {
|
|
92
|
+
400 => BadRequest,
|
|
93
|
+
401 => Unauthorized,
|
|
94
|
+
403 => Forbidden,
|
|
95
|
+
404 => NotFound,
|
|
96
|
+
405 => MethodNotAllowed,
|
|
97
|
+
406 => NotAcceptable,
|
|
98
|
+
409 => Conflict,
|
|
99
|
+
422 => Unprocessable,
|
|
100
|
+
429 => TooManyRequests,
|
|
101
|
+
500 => InternalServerError,
|
|
102
|
+
502 => BadGateway,
|
|
103
|
+
503 => ServiceUnavailable,
|
|
104
|
+
522 => ConnectionTimedOut
|
|
105
|
+
}.freeze
|
|
106
|
+
|
|
107
|
+
def self.klass(response)
|
|
108
|
+
error_klass = STATUS_MAPPINGS[response.status]
|
|
109
|
+
return error_klass if error_klass
|
|
110
|
+
|
|
111
|
+
ResponseError if response.status >= 400
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
class FileResponse
|
|
5
|
+
HEADER_CONTENT_DISPOSITION = 'content-disposition'
|
|
6
|
+
|
|
7
|
+
attr_reader :filename
|
|
8
|
+
|
|
9
|
+
def initialize(file)
|
|
10
|
+
@file = file
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def empty?
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_hash
|
|
18
|
+
{ filename: @filename, data: @file }
|
|
19
|
+
end
|
|
20
|
+
alias to_h to_hash
|
|
21
|
+
|
|
22
|
+
def inspect
|
|
23
|
+
"#<#{self.class}:#{object_id} {filename: #{filename.inspect}}>"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def method_missing(name, *, &)
|
|
27
|
+
if @file.respond_to?(name)
|
|
28
|
+
@file.send(name, *, &)
|
|
29
|
+
else
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
35
|
+
super || @file.respond_to?(method_name, include_private)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_headers!(headers)
|
|
39
|
+
@filename = headers[HEADER_CONTENT_DISPOSITION].split('filename=')[1]
|
|
40
|
+
@filename = @filename[1...-1] if @filename[0] == '"'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Headers
|
|
5
|
+
class PageLinks
|
|
6
|
+
HEADER_LINK = 'link'
|
|
7
|
+
DELIM_LINKS = ','
|
|
8
|
+
LINK_REGEX = /<([^>]+)>; rel="([^"]+)"/
|
|
9
|
+
METAS = %w[last next first prev].freeze
|
|
10
|
+
|
|
11
|
+
attr_accessor(*METAS)
|
|
12
|
+
|
|
13
|
+
def initialize(headers)
|
|
14
|
+
link_header = headers[HEADER_LINK]
|
|
15
|
+
|
|
16
|
+
extract_links(link_header) if link_header && link_header =~ /(next|first|last|prev)/
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def extract_links(header)
|
|
20
|
+
header.split(DELIM_LINKS).each do |link|
|
|
21
|
+
LINK_REGEX.match(link.strip) do |match|
|
|
22
|
+
url = match[1]
|
|
23
|
+
meta = match[2]
|
|
24
|
+
next if !url || !meta || METAS.index(meta).nil?
|
|
25
|
+
|
|
26
|
+
send("#{meta}=", url)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module Headers
|
|
5
|
+
class Total
|
|
6
|
+
HEADER_TOTAL = 'x-total'
|
|
7
|
+
TOTAL_REGEX = /^\d+$/
|
|
8
|
+
|
|
9
|
+
attr_accessor :total
|
|
10
|
+
|
|
11
|
+
def initialize(headers)
|
|
12
|
+
header_total = headers[HEADER_TOTAL]
|
|
13
|
+
|
|
14
|
+
extract_total(header_total) if header_total
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def extract_total(header_total)
|
|
18
|
+
TOTAL_REGEX.match(header_total.strip) do |match|
|
|
19
|
+
@total = match[0]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
class ObjectifiedHash
|
|
5
|
+
def initialize(hash)
|
|
6
|
+
@hash = hash
|
|
7
|
+
@data = hash.each_with_object({}) do |(key, value), data|
|
|
8
|
+
value = self.class.new(value) if value.is_a?(Hash)
|
|
9
|
+
value = value.map { |v| v.is_a?(Hash) ? self.class.new(v) : v } if value.is_a?(Array)
|
|
10
|
+
data[key.to_s] = value
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_hash
|
|
15
|
+
hash
|
|
16
|
+
end
|
|
17
|
+
alias to_h to_hash
|
|
18
|
+
|
|
19
|
+
def inspect
|
|
20
|
+
"#<#{self.class}:#{object_id} {hash: #{hash.inspect}}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def [](key)
|
|
24
|
+
data[key]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_reader :hash, :data
|
|
28
|
+
|
|
29
|
+
def method_missing(method_name, *, &)
|
|
30
|
+
if data.key?(method_name.to_s)
|
|
31
|
+
data[method_name.to_s]
|
|
32
|
+
elsif data.respond_to?(method_name)
|
|
33
|
+
warn 'WARNING: Please convert ObjectifiedHash object to hash before calling Hash methods on it.'
|
|
34
|
+
data.send(method_name, *, &)
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
41
|
+
hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
class PaginatedResponse
|
|
5
|
+
attr_accessor :client
|
|
6
|
+
|
|
7
|
+
def initialize(array)
|
|
8
|
+
@array = array
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ==(other)
|
|
12
|
+
@array == other
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def inspect
|
|
16
|
+
@array.inspect
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def method_missing(name, *, &)
|
|
20
|
+
if @array.respond_to?(name)
|
|
21
|
+
@array.send(name, *, &)
|
|
22
|
+
else
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
28
|
+
super || @array.respond_to?(method_name, include_private)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_headers!(headers)
|
|
32
|
+
@links = Headers::PageLinks.new(headers)
|
|
33
|
+
@total = Headers::Total.new(headers)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def each_page
|
|
37
|
+
current = self
|
|
38
|
+
yield current
|
|
39
|
+
while current.has_next_page?
|
|
40
|
+
current = current.next_page
|
|
41
|
+
yield current
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def lazy_paginate
|
|
46
|
+
to_enum(:each_page).lazy.flat_map(&:to_ary)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def auto_paginate(&block)
|
|
50
|
+
return lazy_paginate.to_a unless block
|
|
51
|
+
|
|
52
|
+
lazy_paginate.each(&block)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def paginate_with_limit(limit, &block)
|
|
56
|
+
return lazy_paginate.take(limit).to_a unless block
|
|
57
|
+
|
|
58
|
+
lazy_paginate.take(limit).each(&block)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def total
|
|
62
|
+
@total.total
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def last_page?
|
|
66
|
+
!(@links.nil? || @links.last.nil?)
|
|
67
|
+
end
|
|
68
|
+
alias has_last_page? last_page?
|
|
69
|
+
|
|
70
|
+
def last_page
|
|
71
|
+
return nil if @client.nil? || !has_last_page?
|
|
72
|
+
|
|
73
|
+
@client.get(client_relative_path(@links.last))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def first_page?
|
|
77
|
+
!(@links.nil? || @links.first.nil?)
|
|
78
|
+
end
|
|
79
|
+
alias has_first_page? first_page?
|
|
80
|
+
|
|
81
|
+
def first_page
|
|
82
|
+
return nil if @client.nil? || !has_first_page?
|
|
83
|
+
|
|
84
|
+
@client.get(client_relative_path(@links.first))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def next_page?
|
|
88
|
+
!(@links.nil? || @links.next.nil?)
|
|
89
|
+
end
|
|
90
|
+
alias has_next_page? next_page?
|
|
91
|
+
|
|
92
|
+
def next_page
|
|
93
|
+
return nil if @client.nil? || !has_next_page?
|
|
94
|
+
|
|
95
|
+
@client.get(client_relative_path(@links.next))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def prev_page?
|
|
99
|
+
!(@links.nil? || @links.prev.nil?)
|
|
100
|
+
end
|
|
101
|
+
alias has_prev_page? prev_page?
|
|
102
|
+
|
|
103
|
+
def prev_page
|
|
104
|
+
return nil if @client.nil? || !has_prev_page?
|
|
105
|
+
|
|
106
|
+
@client.get(client_relative_path(@links.prev))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def client_relative_path(link)
|
|
110
|
+
client_endpoint_path = URI.parse(@client.endpoint).request_uri
|
|
111
|
+
URI.parse(link).request_uri.sub(client_endpoint_path, '')
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Gitlab
|
|
7
|
+
class Request
|
|
8
|
+
attr_accessor :private_token, :endpoint, :pat_prefix, :body_as_json
|
|
9
|
+
|
|
10
|
+
def self.parse(body)
|
|
11
|
+
body = decode(body)
|
|
12
|
+
|
|
13
|
+
if body.is_a?(Hash)
|
|
14
|
+
ObjectifiedHash.new(body)
|
|
15
|
+
elsif body.is_a?(Array)
|
|
16
|
+
PaginatedResponse.new(body.map { |e| ObjectifiedHash.new(e) })
|
|
17
|
+
elsif body
|
|
18
|
+
true
|
|
19
|
+
elsif !body
|
|
20
|
+
false
|
|
21
|
+
else
|
|
22
|
+
raise Error::Parsing, "Couldn't parse a response body"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.decode(response)
|
|
27
|
+
response ? JSON.parse(response) : {}
|
|
28
|
+
rescue JSON::ParserError
|
|
29
|
+
raise Error::Parsing, 'The response is not a valid JSON'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
%w[get post put patch delete].each do |method|
|
|
33
|
+
define_method(method) do |path, options = {}|
|
|
34
|
+
params = options.dup
|
|
35
|
+
|
|
36
|
+
unless params[:unauthenticated]
|
|
37
|
+
params[:headers] ||= {}
|
|
38
|
+
params[:headers].merge!(authorization_header)
|
|
39
|
+
end
|
|
40
|
+
params.delete(:unauthenticated)
|
|
41
|
+
|
|
42
|
+
jsonify_body_content(params) if body_as_json
|
|
43
|
+
|
|
44
|
+
retries_left = params.delete(:ratelimit_retries) || 3
|
|
45
|
+
begin
|
|
46
|
+
response = make_request(method, path, params)
|
|
47
|
+
validate(response)
|
|
48
|
+
rescue Gitlab::Error::TooManyRequests => e
|
|
49
|
+
retries_left -= 1
|
|
50
|
+
raise e if retries_left.zero?
|
|
51
|
+
|
|
52
|
+
wait_time = response.headers['retry-after'] || 2
|
|
53
|
+
sleep(wait_time.to_i)
|
|
54
|
+
retry
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate(response)
|
|
60
|
+
error_klass = Error.klass(response)
|
|
61
|
+
raise error_klass, response if error_klass
|
|
62
|
+
|
|
63
|
+
parsed = self.class.parse(response.body)
|
|
64
|
+
parsed.client = self if parsed.respond_to?(:client=)
|
|
65
|
+
parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
|
|
66
|
+
parsed
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def request_defaults(sudo = nil)
|
|
70
|
+
raise Error::MissingCredentials, 'Please set an endpoint to API' unless endpoint
|
|
71
|
+
|
|
72
|
+
@sudo = sudo
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def connection
|
|
76
|
+
@connection ||= Faraday.new(url: endpoint) do |conn|
|
|
77
|
+
conn.request :url_encoded
|
|
78
|
+
conn.options.open_timeout = 30
|
|
79
|
+
conn.options.timeout = 60
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def make_request(method, path, params)
|
|
84
|
+
headers = default_headers.merge(params[:headers] || {})
|
|
85
|
+
headers['Sudo'] = @sudo if @sudo
|
|
86
|
+
query = params[:query] || {}
|
|
87
|
+
body = params[:body]
|
|
88
|
+
|
|
89
|
+
connection.send(method) do |req|
|
|
90
|
+
req.url(path)
|
|
91
|
+
req.headers = headers
|
|
92
|
+
|
|
93
|
+
if %w[post put patch].include?(method)
|
|
94
|
+
if body.is_a?(Hash) && headers['Content-Type'] == 'application/json'
|
|
95
|
+
req.body = body.to_json
|
|
96
|
+
elsif body.is_a?(Hash)
|
|
97
|
+
req.body = URI.encode_www_form(body)
|
|
98
|
+
else
|
|
99
|
+
req.body = body
|
|
100
|
+
end
|
|
101
|
+
query.each { |k, v| req.params[k] = v }
|
|
102
|
+
else
|
|
103
|
+
query.each { |k, v| req.params[k] = v }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def default_headers
|
|
109
|
+
{
|
|
110
|
+
'Accept' => 'application/json',
|
|
111
|
+
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
112
|
+
'User-Agent' => user_agent
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def user_agent
|
|
117
|
+
@user_agent || Configuration::DEFAULT_USER_AGENT
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def user_agent=(value)
|
|
121
|
+
@user_agent = value
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def authorization_header
|
|
125
|
+
raise Error::MissingCredentials, 'Please provide a private_token or auth_token for user' unless private_token
|
|
126
|
+
|
|
127
|
+
if private_token.size >= 64
|
|
128
|
+
{ 'Authorization' => "Bearer #{private_token}" }
|
|
129
|
+
elsif private_token.start_with?(pat_prefix.to_s)
|
|
130
|
+
{ 'PRIVATE-TOKEN' => private_token }
|
|
131
|
+
else
|
|
132
|
+
{ 'JOB-TOKEN' => private_token }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def jsonify_body_content(params)
|
|
137
|
+
return unless params[:body] && params[:multipart] != true
|
|
138
|
+
return if params[:headers]&.key?('Content-Type')
|
|
139
|
+
|
|
140
|
+
params[:headers] ||= {}
|
|
141
|
+
params[:headers]['Content-Type'] = 'application/json'
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/lib/gitlab.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'gitlab/version'
|
|
4
|
+
require 'gitlab/objectified_hash'
|
|
5
|
+
require 'gitlab/configuration'
|
|
6
|
+
require 'gitlab/error'
|
|
7
|
+
require 'gitlab/headers/page_links'
|
|
8
|
+
require 'gitlab/headers/total'
|
|
9
|
+
require 'gitlab/paginated_response'
|
|
10
|
+
require 'gitlab/file_response'
|
|
11
|
+
require 'gitlab/request'
|
|
12
|
+
require 'gitlab/api'
|
|
13
|
+
require 'gitlab/client'
|
|
14
|
+
|
|
15
|
+
module Gitlab
|
|
16
|
+
extend Configuration
|
|
17
|
+
|
|
18
|
+
def self.client(options = {})
|
|
19
|
+
Gitlab::Client.new(options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.method_missing(method, *, **keywargs, &)
|
|
23
|
+
return super unless client.respond_to?(method)
|
|
24
|
+
|
|
25
|
+
client.send(method, *, **keywargs, &)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
|
29
|
+
client.respond_to?(method_name) || super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.actions
|
|
33
|
+
hidden = /endpoint|private_token|auth_token|user_agent|sudo|get|post|put|patch|\Adelete\z|validate\z|request_defaults/
|
|
34
|
+
(Gitlab::Client.instance_methods - Object.methods).reject { |e| e[hidden] }
|
|
35
|
+
end
|
|
36
|
+
end
|