gitlab 4.12.0 → 4.20.1

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +24 -16
  4. data/lib/gitlab/api.rb +2 -0
  5. data/lib/gitlab/cli.rb +6 -5
  6. data/lib/gitlab/cli_helpers.rb +10 -16
  7. data/lib/gitlab/client/build_variables.rb +17 -12
  8. data/lib/gitlab/client/commits.rb +42 -5
  9. data/lib/gitlab/client/container_registry.rb +1 -1
  10. data/lib/gitlab/client/epic_issues.rb +23 -0
  11. data/lib/gitlab/client/epics.rb +73 -0
  12. data/lib/gitlab/client/group_badges.rb +88 -0
  13. data/lib/gitlab/client/group_labels.rb +1 -1
  14. data/lib/gitlab/client/groups.rb +247 -2
  15. data/lib/gitlab/client/issue_links.rb +48 -0
  16. data/lib/gitlab/client/issues.rb +12 -1
  17. data/lib/gitlab/client/jobs.rb +91 -8
  18. data/lib/gitlab/client/keys.rb +11 -0
  19. data/lib/gitlab/client/labels.rb +1 -1
  20. data/lib/gitlab/client/merge_request_approvals.rb +155 -2
  21. data/lib/gitlab/client/merge_requests.rb +76 -4
  22. data/lib/gitlab/client/merge_trains.rb +55 -0
  23. data/lib/gitlab/client/notes.rb +27 -0
  24. data/lib/gitlab/client/packages.rb +95 -0
  25. data/lib/gitlab/client/pipeline_schedules.rb +16 -4
  26. data/lib/gitlab/client/pipelines.rb +37 -0
  27. data/lib/gitlab/client/project_exports.rb +54 -0
  28. data/lib/gitlab/client/project_releases.rb +11 -0
  29. data/lib/gitlab/client/projects.rb +119 -7
  30. data/lib/gitlab/client/remote_mirrors.rb +51 -0
  31. data/lib/gitlab/client/repositories.rb +56 -1
  32. data/lib/gitlab/client/repository_files.rb +16 -0
  33. data/lib/gitlab/client/resource_state_events.rb +57 -0
  34. data/lib/gitlab/client/runners.rb +126 -21
  35. data/lib/gitlab/client/search.rb +5 -1
  36. data/lib/gitlab/client/user_snippets.rb +114 -0
  37. data/lib/gitlab/client/users.rb +267 -12
  38. data/lib/gitlab/client.rb +16 -2
  39. data/lib/gitlab/configuration.rb +1 -1
  40. data/lib/gitlab/error.rb +36 -1
  41. data/lib/gitlab/headers/page_links.rb +37 -0
  42. data/lib/gitlab/headers/total.rb +29 -0
  43. data/lib/gitlab/help.rb +8 -8
  44. data/lib/gitlab/objectified_hash.rb +23 -7
  45. data/lib/gitlab/paginated_response.rb +29 -40
  46. data/lib/gitlab/request.rb +35 -21
  47. data/lib/gitlab/shell_history.rb +2 -2
  48. data/lib/gitlab/version.rb +1 -1
  49. data/lib/gitlab.rb +17 -6
  50. metadata +24 -48
  51. data/lib/gitlab/page_links.rb +0 -35
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Gitlab::Client
4
+ # Defines methods related to user snippets.
5
+ # @see https://docs.gitlab.com/ce/api/snippets.html
6
+ module UserSnippets
7
+ # Get a list of the snippets of the current user.
8
+ #
9
+ # @example
10
+ # Gitlab.user_snippets
11
+ #
12
+ # @return [Array<Gitlab::ObjectifiedHash>] List of snippets of current user
13
+ def user_snippets
14
+ get('/snippets')
15
+ end
16
+
17
+ # Get a single snippet.
18
+ #
19
+ # @example
20
+ # Gitlab.user_snippet(1)
21
+ #
22
+ # @param [Integer] id ID of snippet to retrieve.
23
+ # @return [Gitlab::ObjectifiedHash] Information about the user snippet
24
+ def user_snippet(id)
25
+ get("/snippets/#{id}")
26
+ end
27
+
28
+ # Get raw contents of a single snippet.
29
+ #
30
+ # @example
31
+ # Gitlab.user_snippet_raw(1)
32
+ #
33
+ # @param [Integer] id ID of snippet to retrieve.
34
+ # @return [String] User snippet text
35
+ def user_snippet_raw(id)
36
+ get("/snippets/#{id}/raw",
37
+ format: nil,
38
+ headers: { Accept: 'text/plain' },
39
+ parser: ::Gitlab::Request::Parser)
40
+ end
41
+
42
+ # Create a new snippet.
43
+ #
44
+ # @example
45
+ # Gitlab.create_user_snippet({ title: 'REST', file_name: 'api.rb', content: 'some code', description: 'Hello World snippet', visibility: 'public'})
46
+ #
47
+ # @param [Hash] options A customizable set of options.
48
+ # @option options [String] :title (required) Title of a snippet.
49
+ # @option options [String] :file_name (required) Name of a snippet file.
50
+ # @option options [String] :content (required) Content of a snippet.
51
+ # @option options [String] :description (optional) Description of a snippet.
52
+ # @option options [String] :visibility (optional) visibility of a snippet.
53
+ # @return [Gitlab::ObjectifiedHash] Information about created snippet.
54
+ def create_user_snippet(options = {})
55
+ post('/snippets', body: options)
56
+ end
57
+
58
+ # Update an existing snippet.
59
+ #
60
+ # @example
61
+ # Gitlab.edit_user_snippet(34, { file_name: 'README.txt' })
62
+ # Gitlab.edit_user_snippet(34, { file_name: 'README.txt', title: 'New title' })
63
+ #
64
+ # @param [Integer] id ID of snippet to update.
65
+ # @param [Hash] options A customizable set of options.
66
+ # @option options [String] :title (optional) Title of a snippet.
67
+ # @option options [String] :file_name (optional) Name of a snippet file.
68
+ # @option options [String] :content (optional) Content of a snippet.
69
+ # @option options [String] :description (optional) Description of a snippet.
70
+ # @option options [String] :visibility (optional) visibility of a snippet.
71
+ # @return [Gitlab::ObjectifiedHash] Information about updated snippet.
72
+ def edit_user_snippet(id, options = {})
73
+ put("/snippets/#{id}", body: options)
74
+ end
75
+
76
+ # Delete an existing snippet.
77
+ #
78
+ # @example
79
+ # Gitlab.delete_user_snippet(14)
80
+ #
81
+ # @param [Integer] id ID of snippet to delete.
82
+ # @return [void] This API call returns an empty response body.
83
+ def delete_user_snippet(id)
84
+ delete("/snippets/#{id}")
85
+ end
86
+
87
+ # List all public snippets.
88
+ #
89
+ # @example
90
+ # Gitlab.public_snippets
91
+ # Gitlab.public_snippets(per_page: 2, page: 1)
92
+ #
93
+ # @param [Hash] options A customizable set of options.
94
+ # @option options [String] :per_page (optional) Number of snippets to return per page.
95
+ # @option options [String] :page (optional) Page to retrieve.
96
+ #
97
+ # @return [Array<Gitlab::ObjectifiedHash>] List of all public snippets
98
+ def public_snippets(options = {})
99
+ get('/snippets/public', query: options)
100
+ end
101
+
102
+ # Get user agent details for a snippet.
103
+ #
104
+ # @example
105
+ # Gitlab.snippet_user_agent_details(1)
106
+ #
107
+ # @param [Integer] id ID of snippet to delete.
108
+ #
109
+ # @return [Array<Gitlab::ObjectifiedHash>] Details of the user agent
110
+ def snippet_user_agent_details(id)
111
+ get("/snippets/#{id}/user_agent_detail")
112
+ end
113
+ end
114
+ end
@@ -37,11 +37,11 @@ class Gitlab::Client
37
37
  # @example
38
38
  # Gitlab.create_user('joe@foo.org', 'secret', 'joe', { name: 'Joe Smith' })
39
39
  # or
40
- # Gitlab.create_user('joe@foo.org', 'secret')
40
+ # Gitlab.create_user('joe@foo.org', 'secret', 'joe')
41
41
  #
42
- # @param [String] email The email of a user.
43
- # @param [String] password The password of a user.
44
- # @param [String] username The username of a user.
42
+ # @param [String] email(required) The email of a user.
43
+ # @param [String] password(required) The password of a user.
44
+ # @param [String] username(required) The username of a user.
45
45
  # @param [Hash] options A customizable set of options.
46
46
  # @option options [String] :name The name of a user. Defaults to email.
47
47
  # @option options [String] :skype The skype of a user.
@@ -51,15 +51,29 @@ class Gitlab::Client
51
51
  # @return [Gitlab::ObjectifiedHash] Information about created user.
52
52
  def create_user(*args)
53
53
  options = args.last.is_a?(Hash) ? args.pop : {}
54
- body = if args[2]
55
- { email: args[0], password: args[1], username: args[2] }
56
- else
57
- { email: args[0], password: args[1], name: args[0] }
58
- end
54
+ raise ArgumentError, 'Missing required parameters' unless args[2]
55
+
56
+ body = { email: args[0], password: args[1], username: args[2], name: args[0] }
59
57
  body.merge!(options)
60
58
  post('/users', body: body)
61
59
  end
62
60
 
61
+ # Creates a service account.
62
+ # Requires authentication from an admin account.
63
+ #
64
+ # @example
65
+ # Gitlab.create_service_account('service_account_6018816a18e515214e0c34c2b33523fc', 'Service account user')
66
+ #
67
+ # @param [String] name (required) The email of the service account.
68
+ # @param [String] username (required) The username of the service account.
69
+ # @return [Gitlab::ObjectifiedHash] Information about created service account.
70
+ def create_service_account(*args)
71
+ raise ArgumentError, 'Missing required parameters' unless args[1]
72
+
73
+ body = { name: args[0], username: args[1] }
74
+ post('/service_accounts', body: body)
75
+ end
76
+
63
77
  # Updates a user.
64
78
  #
65
79
  # @example
@@ -112,6 +126,39 @@ class Gitlab::Client
112
126
  post("/users/#{user_id}/unblock")
113
127
  end
114
128
 
129
+ # Deactivates the specified user. Available only for admin.
130
+ #
131
+ # @example
132
+ # Gitlab.deactivate_user(15)
133
+ #
134
+ # @param [Integer] user_id The Id of user
135
+ # @return [Boolean] success or not
136
+ def deactivate_user(user_id)
137
+ post("/users/#{user_id}/deactivate")
138
+ end
139
+
140
+ # Activate the specified user. Available only for admin.
141
+ #
142
+ # @example
143
+ # Gitlab.activate_user(15)
144
+ #
145
+ # @param [Integer] user_id The Id of user
146
+ # @return [Boolean] success or not
147
+ def activate_user(user_id)
148
+ post("/users/#{user_id}/activate")
149
+ end
150
+
151
+ # Approves the specified user. Available only for admin.
152
+ #
153
+ # @example
154
+ # Gitlab.approve_user(15)
155
+ #
156
+ # @param [Integer] user_id The Id of user
157
+ # @return [Boolean] success or not
158
+ def approve_user(user_id)
159
+ post("/users/#{user_id}/approve")
160
+ end
161
+
115
162
  # Creates a new user session.
116
163
  #
117
164
  # @example
@@ -125,6 +172,20 @@ class Gitlab::Client
125
172
  post('/session', body: { email: email, password: password }, unauthenticated: true)
126
173
  end
127
174
 
175
+ # Gets a list of user activities (for admin access only).
176
+ #
177
+ # @example
178
+ # Gitlab.activities
179
+ #
180
+ # @param [Hash] options A customizable set of options.
181
+ # @option options [Integer] :page The page number.
182
+ # @option options [Integer] :per_page The number of results per page.
183
+ # @option options [String] :from The start date for paginated results.
184
+ # @return [Array<Gitlab::ObjectifiedHash>]
185
+ def activities(options = {})
186
+ get('/user/activities', query: options)
187
+ end
188
+
128
189
  # Gets a list of user's SSH keys.
129
190
  #
130
191
  # @example
@@ -227,10 +288,15 @@ class Gitlab::Client
227
288
  #
228
289
  # @param [String] email Email address
229
290
  # @param [Integer] user_id The ID of a user.
291
+ # @param [Boolean] skip_confirmation Skip confirmation and assume e-mail is verified
230
292
  # @return [Gitlab::ObjectifiedHash]
231
- def add_email(email, user_id = nil)
293
+ def add_email(email, user_id = nil, skip_confirmation = nil)
232
294
  url = user_id.to_i.zero? ? '/user/emails' : "/users/#{user_id}/emails"
233
- post(url, body: { email: email })
295
+ if skip_confirmation.nil?
296
+ post(url, body: { email: email })
297
+ else
298
+ post(url, body: { email: email, skip_confirmation: skip_confirmation })
299
+ end
234
300
  end
235
301
 
236
302
  # Delete email
@@ -248,7 +314,7 @@ class Gitlab::Client
248
314
  delete(url)
249
315
  end
250
316
 
251
- # Search for groups by name
317
+ # Search for users by name
252
318
  #
253
319
  # @example
254
320
  # Gitlab.user_search('gitlab')
@@ -262,5 +328,194 @@ class Gitlab::Client
262
328
  options[:search] = search
263
329
  get('/users', query: options)
264
330
  end
331
+
332
+ # Get user by username
333
+ #
334
+ # @example
335
+ # Gitlab.user_by_username('gitlab')
336
+ #
337
+ # @param [String] username A username to get.
338
+ # @param [Hash] options A customizable set of options.
339
+ # @return [Array<Gitlab::ObjectifiedHash>]
340
+ def user_by_username(username, options = {})
341
+ options[:username] = username
342
+ get('/users', query: options)
343
+ end
344
+
345
+ # Gets user custom_attributes.
346
+ #
347
+ # @example
348
+ # Gitlab.user_custom_attributes(2)
349
+ #
350
+ # @param [Integer] user_id The ID of a user.
351
+ # @return [Gitlab::ObjectifiedHash]
352
+ def user_custom_attributes(user_id)
353
+ get("/users/#{user_id}/custom_attributes")
354
+ end
355
+
356
+ # Gets single user custom_attribute.
357
+ #
358
+ # @example
359
+ # Gitlab.user_custom_attribute(key, 2)
360
+ #
361
+ # @param [String] key The custom_attributes key
362
+ # @param [Integer] user_id The ID of a user.
363
+ # @return [Gitlab::ObjectifiedHash]
364
+ def user_custom_attribute(key, user_id)
365
+ get("/users/#{user_id}/custom_attributes/#{key}")
366
+ end
367
+
368
+ # Creates a new custom_attribute
369
+ #
370
+ # @example
371
+ # Gitlab.add_custom_attribute('some_new_key', 'some_new_value', 2)
372
+ #
373
+ # @param [String] key The custom_attributes key
374
+ # @param [String] value The custom_attributes value
375
+ # @param [Integer] user_id The ID of a user.
376
+ # @return [Gitlab::ObjectifiedHash]
377
+ def add_user_custom_attribute(key, value, user_id)
378
+ url = "/users/#{user_id}/custom_attributes/#{key}"
379
+ put(url, body: { value: value })
380
+ end
381
+
382
+ # Delete custom_attribute
383
+ # Will delete a custom_attribute
384
+ #
385
+ # @example
386
+ # Gitlab.delete_user_custom_attribute('somekey', 2)
387
+ #
388
+ # @param [String] key The custom_attribute key to delete
389
+ # @param [Integer] user_id The ID of a user.
390
+ # @return [Boolean]
391
+ def delete_user_custom_attribute(key, user_id)
392
+ delete("/users/#{user_id}/custom_attributes/#{key}")
393
+ end
394
+
395
+ # Get all impersonation tokens for a user
396
+ #
397
+ # @example
398
+ # Gitlab.user_impersonation_tokens(1)
399
+ #
400
+ # @param [Integer] user_id The ID of the user.
401
+ # @param [String] state Filter impersonation tokens by state {}
402
+ # @return [Array<Gitlab::ObjectifiedHash>]
403
+ def user_impersonation_tokens(user_id)
404
+ get("/users/#{user_id}/impersonation_tokens")
405
+ end
406
+
407
+ # Get impersonation token information
408
+ #
409
+ # @example
410
+ # Gitlab.user_impersonation_token(1, 1)
411
+ #
412
+ # @param [Integer] user_id The ID of the user.
413
+ # @param [Integer] impersonation_token_id ID of the impersonation token.
414
+ # @return [Gitlab::ObjectifiedHash]
415
+ def user_impersonation_token(user_id, impersonation_token_id)
416
+ get("/users/#{user_id}/impersonation_tokens/#{impersonation_token_id}")
417
+ end
418
+
419
+ # Create impersonation token
420
+ #
421
+ # @example
422
+ # Gitlab.create_user_impersonation_token(2, "token", ["api", "read_user"])
423
+ # Gitlab.create_user_impersonation_token(2, "token", ["api", "read_user"], "1970-01-01")
424
+ #
425
+ # @param [Integer] user_id The ID of the user.
426
+ # @param [String] name Name for impersonation token.
427
+ # @param [Array<String>] scopes Array of scopes for the impersonation token
428
+ # @param [String] expires_at Date for impersonation token expiration in ISO format.
429
+ # @return [Gitlab::ObjectifiedHash]
430
+ def create_user_impersonation_token(user_id, name, scopes, expires_at = nil)
431
+ body = { name: name, scopes: scopes }
432
+ body[:expires_at] = expires_at if expires_at
433
+ post("/users/#{user_id}/impersonation_tokens", body: body)
434
+ end
435
+
436
+ # Get all personal access tokens for a user
437
+ #
438
+ # @example
439
+ # Gitlab.user_personal_access_tokens(1)
440
+ #
441
+ # @param [Integer] user_id The ID of the user.
442
+ # @return [Array<Gitlab::ObjectifiedHash>]
443
+ def user_personal_access_tokens(user_id)
444
+ get("/personal_access_tokens?user_id=#{user_id}")
445
+ end
446
+
447
+ # Create personal access token
448
+ #
449
+ # @example
450
+ # Gitlab.create_personal_access_token(2, "token", ["api", "read_user"])
451
+ # Gitlab.create_personal_access_token(2, "token", ["api", "read_user"], "1970-01-01")
452
+ #
453
+ # @param [Integer] user_id The ID of the user.
454
+ # @param [String] name Name of the personal access token.
455
+ # @param [Array<String>] scopes Array of scopes for the impersonation token
456
+ # @param [String] expires_at Date for impersonation token expiration in ISO format.
457
+ # @return [Gitlab::ObjectifiedHash]
458
+ def create_personal_access_token(user_id, name, scopes, expires_at = nil)
459
+ body = { name: name, scopes: scopes }
460
+ body[:expires_at] = expires_at if expires_at
461
+ post("/users/#{user_id}/personal_access_tokens", body: body)
462
+ end
463
+
464
+ # Rotate a personal access token
465
+ #
466
+ # @example
467
+ # Gitlab.rotate_personal_access_token(1)
468
+ #
469
+ # @param [Integer] personal_access_token_id ID of the personal access token.
470
+ # @return [Gitlab::ObjectifiedHash]
471
+ def rotate_personal_access_token(personal_access_token_id, expires_at = nil)
472
+ body = {}
473
+ body[:expires_at] = expires_at if expires_at
474
+ post("/personal_access_tokens/#{personal_access_token_id}/rotate", body: body)
475
+ end
476
+
477
+ # Revoke an impersonation token
478
+ #
479
+ # @example
480
+ # Gitlab.revoke_user_impersonation_token(1, 1)
481
+ #
482
+ # @param [Integer] user_id The ID of the user.
483
+ # @param [Integer] impersonation_token_id ID of the impersonation token.
484
+ # @return [Gitlab::ObjectifiedHash]
485
+ def revoke_user_impersonation_token(user_id, impersonation_token_id)
486
+ delete("/users/#{user_id}/impersonation_tokens/#{impersonation_token_id}")
487
+ end
488
+
489
+ # Lists all projects and groups a user is a member of
490
+ #
491
+ # @example
492
+ # Gitlab.memberships(2)
493
+ #
494
+ # @param [Integer] user_id The ID of the user.
495
+ def memberships(user_id)
496
+ get("/users/#{user_id}/memberships")
497
+ end
498
+
499
+ # Revoke a personal access token
500
+ #
501
+ # @example
502
+ # Gitlab.revoke_personal_access_token(1)
503
+ #
504
+ # @param [Integer] personal_access_token_id ID of the personal access token.
505
+ # @return [Gitlab::ObjectifiedHash]
506
+ def revoke_personal_access_token(personal_access_token_id)
507
+ delete("/personal_access_tokens/#{personal_access_token_id}")
508
+ end
509
+
510
+ # Disables two factor authentication (2FA) for the specified user.
511
+ #
512
+ # @example
513
+ # Gitlab.disable_two_factor(1)
514
+ #
515
+ # @param [Integer] id The ID of a user.
516
+ # @return [Gitlab::ObjectifiedHash]
517
+ def disable_two_factor(user_id)
518
+ patch("/users/#{user_id}/disable_two_factor")
519
+ end
265
520
  end
266
521
  end
data/lib/gitlab/client.rb CHANGED
@@ -19,12 +19,16 @@ module Gitlab
19
19
  include ContainerRegistry
20
20
  include Deployments
21
21
  include Environments
22
+ include EpicIssues
23
+ include Epics
22
24
  include Events
23
25
  include Features
26
+ include GroupBadges
24
27
  include GroupBoards
25
28
  include GroupLabels
26
29
  include GroupMilestones
27
30
  include Groups
31
+ include IssueLinks
28
32
  include Issues
29
33
  include Jobs
30
34
  include Keys
@@ -33,22 +37,27 @@ module Gitlab
33
37
  include Markdown
34
38
  include MergeRequestApprovals
35
39
  include MergeRequests
40
+ include MergeTrains
36
41
  include Milestones
37
42
  include Namespaces
38
43
  include Notes
44
+ include Packages
39
45
  include PipelineSchedules
40
46
  include PipelineTriggers
41
47
  include Pipelines
42
48
  include ProjectBadges
43
49
  include ProjectClusters
50
+ include ProjectExports
44
51
  include ProjectReleaseLinks
45
52
  include ProjectReleases
46
53
  include Projects
47
54
  include ProtectedTags
55
+ include RemoteMirrors
48
56
  include Repositories
49
57
  include RepositoryFiles
50
58
  include RepositorySubmodules
51
59
  include ResourceLabelEvents
60
+ include ResourceStateEvents
52
61
  include Runners
53
62
  include Search
54
63
  include Services
@@ -59,6 +68,7 @@ module Gitlab
59
68
  include Templates
60
69
  include Todos
61
70
  include Users
71
+ include UserSnippets
62
72
  include Versions
63
73
  include Wikis
64
74
 
@@ -71,14 +81,18 @@ module Gitlab
71
81
  inspected
72
82
  end
73
83
 
84
+ # Utility method for URL encoding of a string.
85
+ # Copied from https://ruby-doc.org/stdlib-2.7.0/libdoc/erb/rdoc/ERB/Util.html
86
+ #
87
+ # @return [String]
74
88
  def url_encode(url)
75
- URI.encode(url.to_s, /\W/)
89
+ url.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| sprintf('%%%02X', m.unpack1('C')) } # rubocop:disable Style/FormatString
76
90
  end
77
91
 
78
92
  private
79
93
 
80
94
  def only_show_last_four_chars(token)
81
- "#{'*' * (token.size - 4)}#{token[-4..-1]}"
95
+ "#{'*' * (token.size - 4)}#{token[-4..]}"
82
96
  end
83
97
  end
84
98
  end
@@ -35,7 +35,7 @@ module Gitlab
35
35
 
36
36
  # Resets all configuration options to the defaults.
37
37
  def reset
38
- self.endpoint = ENV['GITLAB_API_ENDPOINT']
38
+ self.endpoint = ENV['GITLAB_API_ENDPOINT'] || ENV['CI_API_V4_URL']
39
39
  self.private_token = ENV['GITLAB_API_PRIVATE_TOKEN'] || ENV['GITLAB_API_AUTH_TOKEN']
40
40
  self.httparty = get_httparty_config(ENV['GITLAB_API_HTTPARTY_OPTIONS'])
41
41
  self.sudo = nil
data/lib/gitlab/error.rb CHANGED
@@ -34,6 +34,17 @@ module Gitlab
34
34
  @response.parsed_response.message
35
35
  end
36
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
+
37
48
  private
38
49
 
39
50
  # Human friendly message.
@@ -63,6 +74,14 @@ module Gitlab
63
74
  else
64
75
  @response.parsed_response
65
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
66
85
  end
67
86
 
68
87
  # Handle error response message in case of nested hashes
@@ -116,6 +135,9 @@ module Gitlab
116
135
  # Raised when API endpoint returns the HTTP status code 503.
117
136
  class ServiceUnavailable < ResponseError; end
118
137
 
138
+ # Raised when API endpoint returns the HTTP status code 522.
139
+ class ConnectionTimedOut < ResponseError; end
140
+
119
141
  # HTTP status codes mapped to error classes.
120
142
  STATUS_MAPPINGS = {
121
143
  400 => BadRequest,
@@ -129,7 +151,20 @@ module Gitlab
129
151
  429 => TooManyRequests,
130
152
  500 => InternalServerError,
131
153
  502 => BadGateway,
132
- 503 => ServiceUnavailable
154
+ 503 => ServiceUnavailable,
155
+ 522 => ConnectionTimedOut
133
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
134
169
  end
135
170
  end
@@ -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="([^"]+)"/.freeze
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+$/.freeze
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