gitlab 3.6.1 → 3.7.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.
Files changed (86) hide show
  1. data/.travis.yml +2 -0
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +9 -2
  4. data/gitlab.gemspec +5 -1
  5. data/lib/gitlab.rb +3 -2
  6. data/lib/gitlab/cli_helpers.rb +8 -3
  7. data/lib/gitlab/client.rb +6 -0
  8. data/lib/gitlab/client/build_triggers.rb +51 -0
  9. data/lib/gitlab/client/build_variables.rb +66 -0
  10. data/lib/gitlab/client/builds.rb +106 -0
  11. data/lib/gitlab/client/commits.rb +5 -5
  12. data/lib/gitlab/client/groups.rb +36 -3
  13. data/lib/gitlab/client/issues.rb +16 -3
  14. data/lib/gitlab/client/labels.rb +2 -2
  15. data/lib/gitlab/client/merge_requests.rb +49 -8
  16. data/lib/gitlab/client/milestones.rb +1 -1
  17. data/lib/gitlab/client/notes.rb +28 -1
  18. data/lib/gitlab/client/projects.rb +60 -7
  19. data/lib/gitlab/client/repositories.rb +1 -32
  20. data/lib/gitlab/client/runners.rb +115 -0
  21. data/lib/gitlab/client/services.rb +48 -0
  22. data/lib/gitlab/client/snippets.rb +2 -2
  23. data/lib/gitlab/client/tags.rb +96 -0
  24. data/lib/gitlab/client/users.rb +72 -2
  25. data/lib/gitlab/error.rb +53 -10
  26. data/lib/gitlab/file_response.rb +45 -0
  27. data/lib/gitlab/help.rb +1 -0
  28. data/lib/gitlab/objectified_hash.rb +1 -1
  29. data/lib/gitlab/paginated_response.rb +2 -2
  30. data/lib/gitlab/request.rb +13 -34
  31. data/lib/gitlab/version.rb +1 -1
  32. data/spec/fixtures/build.json +38 -0
  33. data/spec/fixtures/build_artifacts.json +0 -0
  34. data/spec/fixtures/build_cancel.json +24 -0
  35. data/spec/fixtures/build_erase.json +24 -0
  36. data/spec/fixtures/build_retry.json +24 -0
  37. data/spec/fixtures/builds.json +78 -0
  38. data/spec/fixtures/builds_commits.json +64 -0
  39. data/spec/fixtures/error_project_not_found.json +1 -0
  40. data/spec/fixtures/git_hook.json +1 -0
  41. data/spec/fixtures/group_delete.json +1 -0
  42. data/spec/fixtures/group_member_edit.json +1 -0
  43. data/spec/fixtures/group_projects.json +44 -0
  44. data/spec/fixtures/merge_request_commits.json +1 -0
  45. data/spec/fixtures/project_runner_enable.json +7 -0
  46. data/spec/fixtures/project_runners.json +16 -0
  47. data/spec/fixtures/release_create.json +1 -0
  48. data/spec/fixtures/release_update.json +1 -0
  49. data/spec/fixtures/runner.json +26 -0
  50. data/spec/fixtures/runner_delete.json +7 -0
  51. data/spec/fixtures/runner_edit.json +26 -0
  52. data/spec/fixtures/runners.json +16 -0
  53. data/spec/fixtures/runners_all.json +30 -0
  54. data/spec/fixtures/service.json +1 -0
  55. data/spec/fixtures/tag.json +1 -0
  56. data/spec/fixtures/tag_create.json +1 -0
  57. data/spec/fixtures/tag_create_with_description.json +1 -0
  58. data/spec/fixtures/tag_delete.json +1 -0
  59. data/spec/fixtures/tags.json +1 -0
  60. data/spec/fixtures/trigger.json +7 -0
  61. data/spec/fixtures/triggers.json +16 -0
  62. data/spec/fixtures/user_email.json +1 -0
  63. data/spec/fixtures/user_emails.json +1 -0
  64. data/spec/fixtures/user_search.json +1 -0
  65. data/spec/fixtures/variable.json +4 -0
  66. data/spec/fixtures/variables.json +10 -0
  67. data/spec/gitlab/client/build_triggers_spec.rb +67 -0
  68. data/spec/gitlab/client/build_variables_spec.rb +86 -0
  69. data/spec/gitlab/client/builds_spec.rb +148 -0
  70. data/spec/gitlab/client/groups_spec.rb +51 -0
  71. data/spec/gitlab/client/issues_spec.rb +16 -0
  72. data/spec/gitlab/client/merge_requests_spec.rb +27 -4
  73. data/spec/gitlab/client/notes_spec.rb +32 -0
  74. data/spec/gitlab/client/projects_spec.rb +79 -0
  75. data/spec/gitlab/client/runners_spec.rb +185 -0
  76. data/spec/gitlab/client/services_spec.rb +55 -0
  77. data/spec/gitlab/client/tags_spec.rb +109 -0
  78. data/spec/gitlab/client/users_spec.rb +136 -0
  79. data/spec/gitlab/error_spec.rb +45 -0
  80. data/spec/gitlab/file_response_spec.rb +28 -0
  81. data/spec/gitlab/help_spec.rb +6 -1
  82. data/spec/gitlab/request_spec.rb +0 -27
  83. data/spec/gitlab/shell_spec.rb +1 -1
  84. data/spec/spec_helper.rb +2 -2
  85. metadata +131 -23
  86. checksums.yaml +0 -7
@@ -0,0 +1,48 @@
1
+ class Gitlab::Client
2
+ module Services
3
+ # Create/Edit service
4
+ # Full service params documentation: https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/services.md
5
+ #
6
+ # @example
7
+ # Gitlab.change_service(42, :redmine, { new_issue_url: 'https://example.com/projects/test_project/issues/new',
8
+ # project_url: 'https://example.com/projects/test_project/issues',
9
+ # issues_url: 'https://example.com/issues/:id' })
10
+ #
11
+ # @param [Integer] project The ID of a project.
12
+ # @param [String] service A service code name.
13
+ # @param [Hash] params A service parameters.
14
+ # @return [Boolean]
15
+ def change_service(project, service, params)
16
+ put("/projects/#{project}/services/#{correct_service_name(service)}", body: params)
17
+ end
18
+
19
+ # Delete service
20
+ #
21
+ # @example
22
+ # Gitlab.delete_service(42, :redmine)
23
+ #
24
+ # @param [Integer] project The ID of a project.
25
+ # @param [String] service A service code name.
26
+ # @return [Boolean]
27
+ def delete_service(project, service)
28
+ delete("/projects/#{project}/services/#{correct_service_name(service)}")
29
+ end
30
+
31
+ # Get service
32
+ #
33
+ # @example
34
+ # Gitlab.service(42, :redmine)
35
+ #
36
+ # @param [Integer] project The ID of a project.
37
+ # @param [String] service A service code name.
38
+ # @return [Gitlab::ObjectifiedHash]
39
+ def service(project, service)
40
+ get("/projects/#{project}/services/#{correct_service_name(service)}")
41
+ end
42
+
43
+ private
44
+ def correct_service_name(service)
45
+ service.to_s.gsub('_', '-')
46
+ end
47
+ end
48
+ end
@@ -31,7 +31,7 @@ class Gitlab::Client
31
31
  # Creates a new snippet.
32
32
  #
33
33
  # @example
34
- # Gitlab.create_snippet(42, {:title => 'REST', :file_name => 'api.rb', :code => 'some code'})
34
+ # Gitlab.create_snippet(42, { title: 'REST', file_name: 'api.rb', code: 'some code' })
35
35
  #
36
36
  # @param [Integer] project The ID of a project.
37
37
  # @param [Hash] options A customizable set of options.
@@ -47,7 +47,7 @@ class Gitlab::Client
47
47
  # Updates a snippet.
48
48
  #
49
49
  # @example
50
- # Gitlab.edit_snippet(42, 34, :file_name => 'README.txt')
50
+ # Gitlab.edit_snippet(42, 34, { file_name: 'README.txt' })
51
51
  #
52
52
  # @param [Integer] project The ID of a project.
53
53
  # @param [Integer] id The ID of a snippet.
@@ -0,0 +1,96 @@
1
+ class Gitlab::Client
2
+ # Defines methods related to tags.
3
+ # @see https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/tags.md
4
+ module Tags
5
+ # Gets a list of project repository tags.
6
+ #
7
+ # @example
8
+ # Gitlab.tags(42)
9
+ #
10
+ # @param [Integer] project The ID of a project.
11
+ # @param [Hash] options A customizable set of options.
12
+ # @option options [Integer] :page The page number.
13
+ # @option options [Integer] :per_page The number of results per page.
14
+ # @return [Array<Gitlab::ObjectifiedHash>]
15
+ def tags(project, options={})
16
+ get("/projects/#{project}/repository/tags", query: options)
17
+ end
18
+ alias_method :repo_tags, :tags
19
+
20
+ # Creates a new project repository tag.
21
+ #
22
+ # @example
23
+ # Gitlab.create_tag(42, 'new_tag', 'master')
24
+ # Gitlab.create_tag(42, 'v1.0', 'master', 'Release 1.0')
25
+ #
26
+ # @param [Integer] project The ID of a project.
27
+ # @param [String] tag_name The name of the new tag.
28
+ # @param [String] ref The ref (commit sha, branch name, or another tag) the tag will point to.
29
+ # @param [String] message Optional message for tag, creates annotated tag if specified.
30
+ # @param [String] description Optional release notes for tag.
31
+ # @return [Gitlab::ObjectifiedHash]
32
+ def create_tag(project, tag_name, ref, message='', description=nil)
33
+ post("/projects/#{project}/repository/tags", body: { tag_name: tag_name, ref: ref, message: message, description: description })
34
+ end
35
+ alias_method :repo_create_tag, :create_tag
36
+
37
+ # Gets information about a repository tag.
38
+ #
39
+ # @example
40
+ # Gitlab.tag(3, 'api')
41
+ # Gitlab.repo_tag(5, 'master')
42
+ #
43
+ # @param [Integer] project The ID of a project.
44
+ # @param [String] tag The name of the tag.
45
+ # @return [Gitlab::ObjectifiedHash]
46
+ def tag(project, tag)
47
+ get("/projects/#{project}/repository/tags/#{tag}")
48
+ end
49
+ alias_method :repo_tag, :tag
50
+
51
+ # Deletes a repository tag. Requires Gitlab >= 6.8.x
52
+ #
53
+ # @example
54
+ # Gitlab.delete_tag(3, 'api')
55
+ # Gitlab.repo_delete_tag(5, 'master')
56
+ #
57
+ # @param [Integer] project The ID of a project.
58
+ # @param [String] tag The name of the tag to delete
59
+ # @return [Gitlab::ObjectifiedHash]
60
+ def delete_tag(project, tag)
61
+ delete("/projects/#{project}/repository/tags/#{tag}")
62
+ end
63
+ alias_method :repo_delete_tag, :delete_tag
64
+
65
+ # Adds release notes to an existing repository tag. Requires Gitlab >= 8.2.0
66
+ #
67
+ # @example
68
+ # Gitlab.create_release(3, '1.0.0', 'This is ready for production')
69
+ # Gitlab.repo_create_release(5, '1.0.0', 'This is ready for production')
70
+ #
71
+ # @param [Integer] project The ID of a project.
72
+ # @param [String] tag The name of the new tag.
73
+ # @param [String] description Release notes with markdown support
74
+ # @return [Gitlab::ObjectifiedHash]
75
+ def create_release(project, tag, description)
76
+ post("/projects/#{project}/repository/tags/#{tag}/release", body: { description: description })
77
+ end
78
+ alias_method :repo_create_release, :create_release
79
+
80
+ # Updates the release notes of a given release. Requires Gitlab >= 8.2.0
81
+ #
82
+ # @example
83
+ # Gitlab.update_release(3, '1.0.0', 'This is even more ready for production')
84
+ # Gitlab.repo_update_release(5, '1.0.0', 'This is even more ready for production')
85
+ #
86
+ # @param [Integer] project The ID of a project.
87
+ # @param [String] tag The name of the new tag.
88
+ # @param [String] description Release notes with markdown support
89
+ # @return [Gitlab::ObjectifiedHash]
90
+ def update_release(project, tag, description)
91
+ put("/projects/#{project}/repository/tags/#{tag}/release", body: { description: description })
92
+ end
93
+ alias_method :repo_update_release, :update_release
94
+
95
+ end
96
+ end
@@ -33,7 +33,7 @@ class Gitlab::Client
33
33
  # Requires authentication from an admin account.
34
34
  #
35
35
  # @example
36
- # Gitlab.create_user('joe@foo.org', 'secret', 'joe', :name => 'Joe Smith')
36
+ # Gitlab.create_user('joe@foo.org', 'secret', 'joe', { name: 'Joe Smith' })
37
37
  # or
38
38
  # Gitlab.create_user('joe@foo.org', 'secret')
39
39
  #
@@ -61,7 +61,7 @@ class Gitlab::Client
61
61
  # Updates a user.
62
62
  #
63
63
  # @example
64
- # Gitlab.edit_user(15, :email => 'joe.smith@foo.org', :projects_limit => 20)
64
+ # Gitlab.edit_user(15, { email: 'joe.smith@foo.org', projects_limit: 20 })
65
65
  #
66
66
  # @param [Integer] id The ID of a user.
67
67
  # @param [Hash] options A customizable set of options.
@@ -169,5 +169,75 @@ class Gitlab::Client
169
169
  def delete_ssh_key(id)
170
170
  delete("/user/keys/#{id}")
171
171
  end
172
+
173
+ # Gets user emails.
174
+ # Will return emails an authorized user if no user ID passed.
175
+ #
176
+ # @example
177
+ # Gitlab.emails
178
+ # Gitlab.emails(2)
179
+ #
180
+ # @param [Integer] user_id The ID of a user.
181
+ # @return [Gitlab::ObjectifiedHash]
182
+ def emails(user_id=nil)
183
+ url = user_id.to_i.zero? ? "/user/emails" : "/users/#{user_id}/emails"
184
+ get(url)
185
+ end
186
+
187
+ # Get a single email.
188
+ #
189
+ # @example
190
+ # Gitlab.email(3)
191
+ #
192
+ # @param [Integer] id The ID of a email.
193
+ # @return [Gitlab::ObjectifiedHash]
194
+ def email(id)
195
+ get("/user/emails/#{id}")
196
+ end
197
+
198
+ # Creates a new email
199
+ # Will create a new email an authorized user if no user ID passed.
200
+ #
201
+ # @example
202
+ # Gitlab.add_email('email@example.com')
203
+ # Gitlab.add_email('email@example.com', 2)
204
+ #
205
+ # @param [String] email Email address
206
+ # @param [Integer] user_id The ID of a user.
207
+ # @return [Gitlab::ObjectifiedHash]
208
+ def add_email(email, user_id=nil)
209
+ url = user_id.to_i.zero? ? "/user/emails" : "/users/#{user_id}/emails"
210
+ post(url, body: {email: email})
211
+ end
212
+
213
+ # Delete email
214
+ # Will delete a email an authorized user if no user ID passed.
215
+ #
216
+ # @example
217
+ # Gitlab.delete_email(2)
218
+ # Gitlab.delete_email(3, 2)
219
+ #
220
+ # @param [Integer] id Email address ID
221
+ # @param [Integer] user_id The ID of a user.
222
+ # @return [Boolean]
223
+ def delete_email(id, user_id=nil)
224
+ url = user_id.to_i.zero? ? "/user/emails/#{id}" : "/users/#{user_id}/emails/#{id}"
225
+ delete(url)
226
+ end
227
+
228
+ # Search for groups by name
229
+ #
230
+ # @example
231
+ # Gitlab.user_search('gitlab')
232
+ #
233
+ # @param [String] search A string to search for in user names and paths.
234
+ # @param [Hash] options A customizable set of options.
235
+ # @option options [String] :per_page Number of user to return per page
236
+ # @option options [String] :page The page to retrieve
237
+ # @return [Array<Gitlab::ObjectifiedHash>]
238
+ def user_search(search, options={})
239
+ options[:search] = search
240
+ get("/users", query: options)
241
+ end
172
242
  end
173
243
  end
@@ -9,34 +9,77 @@ module Gitlab
9
9
  # Raised when impossible to parse response body.
10
10
  class Parsing < Error; end
11
11
 
12
+ # Custom error class for rescuing from HTTP response errors.
13
+ class ResponseError < Error
14
+ def initialize(response)
15
+ @response = response
16
+ super(build_error_message)
17
+ end
18
+
19
+ # Status code returned in the http response.
20
+ #
21
+ # @return [Integer]
22
+ def response_status
23
+ @response.code
24
+ end
25
+
26
+ private
27
+
28
+ # Human friendly message.
29
+ #
30
+ # @return [String]
31
+ def build_error_message
32
+ parsed_response = @response.parsed_response
33
+ message = parsed_response.message || parsed_response.error
34
+
35
+ "Server responded with code #{@response.code}, message: " \
36
+ "#{handle_message(message)}. " \
37
+ "Request URI: #{@response.request.base_uri}#{@response.request.path}"
38
+ end
39
+
40
+ # Handle error response message in case of nested hashes
41
+ def handle_message(message)
42
+ case message
43
+ when Gitlab::ObjectifiedHash
44
+ message.to_h.sort.map do |key, val|
45
+ "'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : val).join(' ')}"
46
+ end.join(', ')
47
+ when Array
48
+ message.join(' ')
49
+ else
50
+ message
51
+ end
52
+ end
53
+ end
54
+
12
55
  # Raised when API endpoint returns the HTTP status code 400.
13
- class BadRequest < Error; end
56
+ class BadRequest < ResponseError; end
14
57
 
15
58
  # Raised when API endpoint returns the HTTP status code 401.
16
- class Unauthorized < Error; end
59
+ class Unauthorized < ResponseError; end
17
60
 
18
61
  # Raised when API endpoint returns the HTTP status code 403.
19
- class Forbidden < Error; end
62
+ class Forbidden < ResponseError; end
20
63
 
21
64
  # Raised when API endpoint returns the HTTP status code 404.
22
- class NotFound < Error; end
65
+ class NotFound < ResponseError; end
23
66
 
24
67
  # Raised when API endpoint returns the HTTP status code 405.
25
- class MethodNotAllowed < Error; end
68
+ class MethodNotAllowed < ResponseError; end
26
69
 
27
70
  # Raised when API endpoint returns the HTTP status code 409.
28
- class Conflict < Error; end
71
+ class Conflict < ResponseError; end
29
72
 
30
73
  # Raised when API endpoint returns the HTTP status code 422.
31
- class Unprocessable < Error; end
74
+ class Unprocessable < ResponseError; end
32
75
 
33
76
  # Raised when API endpoint returns the HTTP status code 500.
34
- class InternalServerError < Error; end
77
+ class InternalServerError < ResponseError; end
35
78
 
36
79
  # Raised when API endpoint returns the HTTP status code 502.
37
- class BadGateway < Error; end
80
+ class BadGateway < ResponseError; end
38
81
 
39
82
  # Raised when API endpoint returns the HTTP status code 503.
40
- class ServiceUnavailable < Error; end
83
+ class ServiceUnavailable < ResponseError; end
41
84
  end
42
85
  end
@@ -0,0 +1,45 @@
1
+ module Gitlab
2
+ # Wrapper class of file response.
3
+ class FileResponse
4
+ HEADER_CONTENT_DISPOSITION = 'Content-Disposition'.freeze
5
+
6
+ attr_reader :filename
7
+
8
+ def initialize(file)
9
+ @file = file
10
+ end
11
+
12
+ # @return [bool] Always false
13
+ def empty?
14
+ false
15
+ end
16
+
17
+ # @return [Hash] A hash consisting of filename and io object
18
+ def to_hash
19
+ { filename: @filename, data: @file }
20
+ end
21
+ alias_method :to_h, :to_hash
22
+
23
+ # @return [String] Formatted string with the class name, object id and filename.
24
+ def inspect
25
+ "#<#{self.class}:#{object_id} {filename: #{filename.inspect}}>"
26
+ end
27
+
28
+ def method_missing(name, *args, &block)
29
+ if @file.respond_to?(name)
30
+ @file.send(name, *args, &block)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def respond_to_missing?(method_name, include_private = false)
37
+ super || @file.respond_to?(method_name, include_private)
38
+ end
39
+
40
+ # Parse filename from the 'Content Disposition' header.
41
+ def parse_headers!(headers)
42
+ @filename = headers[HEADER_CONTENT_DISPOSITION].split("filename=")[1]
43
+ end
44
+ end
45
+ end
@@ -88,6 +88,7 @@ module Gitlab::Help
88
88
  output_str.gsub!(/\s(@)/, "\n@")
89
89
  output_str.gsub!(/(\])\n(\:)/, '\1 \2')
90
90
  output_str.gsub!(/(\:.*)(\n)(.*\.)/, '\1 \3')
91
+ output_str.gsub!(/\{(.+)\}/, '"{\1}"')
91
92
 
92
93
  end
93
94
  end # class << self
@@ -27,7 +27,7 @@ module Gitlab
27
27
  @data.key?(key.to_s) ? @data[key.to_s] : nil
28
28
  end
29
29
 
30
- def respond_to?(method_name, include_private=false)
30
+ def respond_to_missing?(method_name, include_private = false)
31
31
  @hash.keys.map(&:to_sym).include?(method_name.to_sym) || super
32
32
  end
33
33
  end
@@ -23,8 +23,8 @@ module Gitlab
23
23
  end
24
24
  end
25
25
 
26
- def respond_to?(method, include_all=false)
27
- super || @array.respond_to?(method, include_all)
26
+ def respond_to_missing?(method_name, include_private = false)
27
+ super || @array.respond_to?(method_name, include_private)
28
28
  end
29
29
 
30
30
  def parse_headers!(headers)
@@ -64,19 +64,21 @@ module Gitlab
64
64
  # Checks the response code for common errors.
65
65
  # Returns parsed response for successful requests.
66
66
  def validate(response)
67
- case response.code
68
- when 400 then fail Error::BadRequest.new error_message(response)
69
- when 401 then fail Error::Unauthorized.new error_message(response)
70
- when 403 then fail Error::Forbidden.new error_message(response)
71
- when 404 then fail Error::NotFound.new error_message(response)
72
- when 405 then fail Error::MethodNotAllowed.new error_message(response)
73
- when 409 then fail Error::Conflict.new error_message(response)
74
- when 422 then fail Error::Unprocessable.new error_message(response)
75
- when 500 then fail Error::InternalServerError.new error_message(response)
76
- when 502 then fail Error::BadGateway.new error_message(response)
77
- when 503 then fail Error::ServiceUnavailable.new error_message(response)
67
+ error_klass = case response.code
68
+ when 400 then Error::BadRequest
69
+ when 401 then Error::Unauthorized
70
+ when 403 then Error::Forbidden
71
+ when 404 then Error::NotFound
72
+ when 405 then Error::MethodNotAllowed
73
+ when 409 then Error::Conflict
74
+ when 422 then Error::Unprocessable
75
+ when 500 then Error::InternalServerError
76
+ when 502 then Error::BadGateway
77
+ when 503 then Error::ServiceUnavailable
78
78
  end
79
79
 
80
+ fail error_klass.new(response) if error_klass
81
+
80
82
  parsed = response.parsed_response
81
83
  parsed.client = self if parsed.respond_to?(:client=)
82
84
  parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
@@ -111,28 +113,5 @@ module Gitlab
111
113
  def set_httparty_config(options)
112
114
  options.merge!(httparty) if httparty
113
115
  end
114
-
115
- def error_message(response)
116
- parsed_response = response.parsed_response
117
- message = parsed_response.message || parsed_response.error
118
-
119
- "Server responded with code #{response.code}, message: " \
120
- "#{handle_error(message)}. " \
121
- "Request URI: #{response.request.base_uri}#{response.request.path}"
122
- end
123
-
124
- # Handle error response message in case of nested hashes
125
- def handle_error(message)
126
- case message
127
- when Gitlab::ObjectifiedHash
128
- message.to_h.sort.map do |key, val|
129
- "'#{key}' #{(val.is_a?(Hash) ? val.sort.map { |k, v| "(#{k}: #{v.join(' ')})" } : val).join(' ')}"
130
- end.join(', ')
131
- when Array
132
- message.join(' ')
133
- else
134
- message
135
- end
136
- end
137
116
  end
138
117
  end