bug_bunny 1.0.0 → 2.0.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.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bug_bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - gabix
@@ -15,28 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '2.20'
18
+ version: '2.24'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '2.20'
26
- - !ruby/object:Gem::Dependency
27
- name: rubocop
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
25
+ version: '2.24'
40
26
  description: Gem for sync and async comunication via rabbit bunny.
41
27
  email:
42
28
  - gab.edera@gmail.com
@@ -45,25 +31,20 @@ extensions: []
45
31
  extra_rdoc_files: []
46
32
  files:
47
33
  - CHANGELOG.md
48
- - Gemfile
49
34
  - README.md
50
35
  - Rakefile
51
36
  - lib/bug_bunny.rb
52
- - lib/bug_bunny/adapter.rb
53
37
  - lib/bug_bunny/config.rb
54
38
  - lib/bug_bunny/controller.rb
55
39
  - lib/bug_bunny/exception.rb
56
- - lib/bug_bunny/helpers.rb
57
- - lib/bug_bunny/message.rb
58
- - lib/bug_bunny/queue.rb
40
+ - lib/bug_bunny/publisher.rb
59
41
  - lib/bug_bunny/rabbit.rb
60
- - lib/bug_bunny/railtie.rb
61
- - lib/bug_bunny/response.rb
62
- - lib/bug_bunny/security.rb
42
+ - lib/bug_bunny/resource.rb
63
43
  - lib/bug_bunny/version.rb
64
44
  - sig/bug_bunny.rbs
65
45
  homepage: https://github.com/gedera/bug_bunny
66
- licenses: []
46
+ licenses:
47
+ - MIT
67
48
  metadata:
68
49
  homepage_uri: https://github.com/gedera/bug_bunny
69
50
  source_code_uri: https://github.com/gedera/bug_bunny
data/Gemfile DELETED
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in bug_bunny.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
@@ -1,347 +0,0 @@
1
- module BugBunny
2
- class Adapter
3
- PERSIST_MESSAGE = true
4
-
5
- SERVICE_HEALTH_CHECK = :health_check
6
- TIMEOUT = 3
7
- BOMBA = :bomba
8
- PUBLISH_TIMEOUT = :publish_timeout
9
- CONSUMER_TIMEOUT = :consumer_timeout
10
- COMUNICATION_ERROR = :comunication_error
11
- CONSUMER_COUNT_ZERO = :consumer_count_zero
12
-
13
- PG_EXCEPTIONS_TO_EXIT = %w[PG::ConnectionBad PG::UnableToSend].freeze
14
-
15
- attr_accessor :consumer,
16
- :rabbit,
17
- :logger,
18
- :time_to_wait,
19
- :communication_response,
20
- :service_message
21
-
22
- def initialize(attrs = {})
23
- @logger = Logger.new('./log/bug_bunny.log', 'monthly')
24
- @communication_response = ::BugBunny::Response.new status: false
25
- @time_to_wait = 2
26
- create_adapter_with_rabbit
27
- end
28
-
29
- def publish!(message, publish_queue, opts = {})
30
- Timeout::timeout(TIMEOUT) do
31
- if opts[:check_consumers_count] && publish_queue.check_consumers.zero?
32
- self.communication_response = ::BugBunny::Response.new(status: false, response: CONSUMER_COUNT_ZERO)
33
- return
34
- end
35
-
36
- publish_opts = { routing_key: publish_queue.name,
37
- persistent: opts[:persistent],
38
- correlation_id: message.correlation_id }
39
-
40
- publish_opts[:reply_to] = opts[:reply_to] if opts[:reply_to]
41
-
42
- # Esta es la idea en el caso que nos pongamos mas mañosos y queramos cambiar las exchange a la hora de publicar.
43
- # _exchange = if opts.has_key?(:exchange_type)
44
- # channel.exchange(opts[:exchange_type].to_s, { type: opts[:exchange_type] })
45
- # else
46
- # exchange
47
- # end
48
- # _exchange.publish(message.to_json, publish_opts)
49
- logger.debug("#{publish_queue.name}-Send Request: (#{message})")
50
-
51
- rabbit.exchange.publish(message.to_json, publish_opts)
52
- rabbit.channel.wait_for_confirms if rabbit.confirm_select
53
-
54
- self.communication_response = ::BugBunny::Response.new(status: true)
55
- end
56
- rescue Timeout::Error => e
57
- logger.error(e)
58
- close_connection!
59
- self.communication_response = ::BugBunny::Response.new(status: false, response: PUBLISH_TIMEOUT, exception: e)
60
- rescue StandardError => e
61
- logger.error(e)
62
- close_connection!
63
- self.communication_response = ::BugBunny::Response.new(status: false, response: BOMBA, exception: e)
64
- end
65
-
66
- def consume!(queue, thread: false, manual_ack: true, exclusive: false, block: true, opts: {})
67
- Signal.trap('INT') { exit }
68
-
69
- logger.debug("Suscribe consumer to: #{queue.name}")
70
- logger.debug("ENTRO AL CONSUMER #{rabbit.try(:identifier)}")
71
-
72
- self.consumer = queue.rabbit_queue.subscribe(manual_ack: manual_ack, exclusive: exclusive, block: block) do |delivery_info, metadata, json_payload|
73
- # Session depends on thread info, subscribe block cleans thread info
74
- # ::Session.init unless Session.tags_context
75
-
76
- begin
77
- payload = ActiveSupport::JSON.decode(json_payload).deep_symbolize_keys # Timezones pulenteado
78
- rescue StandardError
79
- payload = JSON.parse(json_payload).deep_symbolize_keys
80
- end
81
-
82
- # Session for Sentry logger
83
- # locale, version, service_name
84
- # payload.except(:body, :service_name).each do |k, v|
85
- # Session.assign(k, v)
86
- # end
87
- # Session.from_service = payload[:service_name]
88
- # Session.correlation_id = metadata.correlation_id
89
- # Session.queue_name = queue.name
90
-
91
- # unless defined?(ActiveRecord) && ActiveRecord::Base.connection_pool.with_connection(&:active?)
92
- # logger.error('[PG] PG connection down')
93
- # exit 7
94
- # end
95
-
96
- begin
97
- message = ::BugBunny::Message.new(correlation_id: metadata.correlation_id, reply_to: metadata.reply_to, **payload)
98
-
99
- # Default sentry info
100
- # ::Session.request_id = message.correlation_id rescue nil
101
- # ::Session.tags_context.merge!(
102
- # server_version: message.version,
103
- # service_action: message.service_action,
104
- # service_name: message.service_name,
105
- # isp_id: (message.body&.fetch(:isp_id, nil) rescue nil)
106
- # )
107
- # ::Session.extra_context[:message] = message.body
108
-
109
- logger.info("#{queue.name}-Received Request: (#{message.service_action})")
110
- logger.debug("#{queue.name}-Received Request: (#{message})")
111
- logger.debug("Message will be yield")
112
- logger.debug("Block given? #{block_given?}")
113
- yield(message) if block_given?
114
- logger.debug('Message processed')
115
-
116
- begin
117
- Timeout.timeout(5) do
118
- rabbit.channel.ack delivery_info.delivery_tag if delivery_info[:consumer].manual_acknowledgement?
119
- end
120
- rescue Timeout::Error => e
121
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)} can not check manual_ack #{e.to_s}")
122
- rescue ::StandardError => e
123
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)} can not check manual_ack #{e.to_s}")
124
- end
125
-
126
- self.service_message = message
127
- self.communication_response = ::BugBunny::Response.new(status: true)
128
- rescue ::SystemExit => e # Ensure exit code
129
- raise e
130
- rescue => e
131
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
132
- logger.error(e)
133
-
134
- close_connection!
135
-
136
- # Session.clean!
137
- self.communication_response = ::BugBunny::Response.new(status: false, response: BOMBA, exception: e)
138
- end
139
-
140
- if thread # sync consumer flag :D
141
- begin
142
- Timeout::timeout(1) do
143
- delivery_info[:consumer].cancel
144
- end
145
- rescue Timeout::Error => e
146
- close_connection!
147
- thread.exit
148
- end
149
- close_connection!
150
- thread.exit
151
- end
152
- end
153
-
154
- if thread
155
- close_connection!
156
- thread.exit
157
- else
158
- while true
159
- begin
160
- logger.debug("SALIO DEL CONSUMER #{rabbit.try(:identifier)}")
161
- logger.debug(rabbit.status)
162
- exit # consumer.cancel
163
- rescue Bunny::NotAllowedError => e
164
- logger.debug("NOT ALLOWED #{e.to_s}")
165
- break
166
- rescue Timeout::Error => e
167
- if queue.rabbit_queue.channel.status == :closed || queue.rabbit_queue.channel.connection.status == :closed
168
- logger.debug("Channel or connection closed")
169
- break
170
- end
171
-
172
- sleep time_to_wait
173
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
174
- logger.error(e)
175
- retry
176
- rescue StandardError => e
177
- if queue.rabbit_queue.channel.status == :closed || queue.rabbit_queue.channel.connection.status == :closed
178
- logger.debug("Channel or connection closed")
179
- break
180
- end
181
-
182
- sleep time_to_wait
183
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
184
- logger.error(e)
185
- retry
186
- end
187
- end
188
- end
189
- rescue Timeout::Error => e
190
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
191
- logger.error(e)
192
- close_connection!
193
- ::BugBunny::Response.new(status: false, response: CONSUMER_TIMEOUT, exception: e)
194
- rescue StandardError => e
195
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
196
- logger.error(e)
197
- close_connection!
198
- ::BugBunny::Response.new(status: false, response: BOMBA, exception: e)
199
- end
200
-
201
- def publish_and_consume!(publish_message, sync_queue, opts={})
202
- reply_queue = build_queue('', initialize: true, exclusive: true, durable: false, auto_delete: true)
203
-
204
- retries = 0
205
- begin
206
- publish!(publish_message, sync_queue, opts.merge(reply_to: reply_queue.name))
207
- rescue
208
- if (retries += 1) <= 3
209
- sleep 0.5
210
- retry
211
- end
212
-
213
- close_connection!
214
- raise
215
- end
216
-
217
- return communication_response unless communication_response.success?
218
-
219
- t = Thread.new do
220
- retries = 0
221
- begin
222
- consume!(reply_queue, thread: Thread.current, exclusive: true) do |msg|
223
- yield(msg) if block_given?
224
- end
225
- rescue
226
- if (retries += 1) <= 3
227
- sleep 0.5
228
- retry
229
- end
230
- raise
231
- end
232
- end
233
- t.join
234
- communication_response
235
- end
236
-
237
- def build_queue(name, opts = {})
238
- init = opts.key?(:initialize) ? opts[:initialize] : true
239
- new_queue = ::BugBunny::Queue.new(opts.merge(name: name))
240
-
241
- if init
242
- logger.debug("Building rabbit new_queue: #{name} status: #{rabbit.status} queue_options: #{new_queue.options}")
243
-
244
- retries = 0
245
- begin
246
- built_queue = rabbit.channel.queue(new_queue.name.to_s, new_queue.options)
247
- rescue StandardError
248
- if (retries += 1) <= 3
249
- sleep 0.5
250
- retry
251
- end
252
- raise
253
- end
254
-
255
- new_queue.rabbit_queue = built_queue
256
- new_queue.name = new_queue.rabbit_queue.name
257
- end
258
-
259
- new_queue
260
- rescue Timeout::Error, StandardError => e
261
- logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
262
- logger.debug("Status adapter created: #{rabbit.status}")
263
- logger.error(e)
264
-
265
- close_connection!
266
- raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
267
- end
268
-
269
- def self.make_response(comunication_result, consume_result = nil)
270
- if comunication_result.success?
271
- consume_result || comunication_result
272
- else
273
- comunication_result.response = comunication_result.response.to_s
274
- comunication_result
275
- end
276
- end
277
-
278
- def make_response
279
- if communication_response.success?
280
- service_message || communication_response
281
- else
282
- communication_response.response = communication_response.response.to_s
283
- communication_response
284
- end
285
- end
286
-
287
- def status
288
- rabbit.try(:status)
289
- end
290
-
291
- def close_connection!
292
- rabbit.try(:close)
293
- end
294
-
295
- def check_pg_exception!(exception)
296
- # el consumidor no reconecta (rails tasks) asi que salimos a la goma
297
- if PG_EXCEPTIONS_TO_EXIT.any? { |msg| exception.try(:message)&.starts_with?(msg) }
298
- exit 7 # salimos con un int especial para identificarlo
299
- end
300
- end
301
-
302
- private
303
-
304
- # AMQ::Protocol::EmptyResponseError: Este error lo note cuando el rabbit
305
- # acepta la connection pero aun no ha terminado de inicializar el servicio,
306
- # por lo que salta esta exception.
307
- # Errno::ECONNRESET: Este error se presenta cuando justo esta arrancando
308
- # el rabbit y se quiere conectar al mismo. El rabbit resetea la connection,
309
- # haciendo saltar esta exception.
310
- def create_adapter_with_rabbit
311
- self.rabbit = ::BugBunny::Rabbit.new(confirm_select: true, logger: logger)
312
- rescue Bunny::NetworkFailure, Bunny::TCPConnectionFailed,
313
- Bunny::ConnectionForced, AMQ::Protocol::EmptyResponseError,
314
- Errno::ECONNRESET => e
315
-
316
- logger.debug(e)
317
- close_connection!
318
- raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
319
- rescue OpenSSL::SSL::SSLError, OpenSSL::X509::CertificateError => e
320
- # el `e.to_s` devuelve alguno de los sgtes errores. Por ej:
321
- # SSL_connect returned=1 errno=0 state=unknown state: sslv3
322
- # alert bad certificate
323
- # SSL_CTX_use_PrivateKey: key values mismatch
324
- # OpenSSL::X509::CertificateError: not enough data // headers too short
325
- if respond_to?(:handle_ssl_issues)
326
- handle_ssl_issues # esto pide los certificados de nuevo
327
- @retries ||= 0
328
- @retries += 1
329
- sleep 1
330
- retry if @retries < 4
331
- @retries = 0 # reset the counter
332
- end
333
-
334
- # Si sigue fallando desp de 3 veces o no tiene definido el handle
335
- # bombita rodriguez
336
- logger.error(e)
337
-
338
- close_connection!
339
- raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
340
- rescue Timeout::Error, StandardError => e
341
- logger.error(e)
342
-
343
- close_connection!
344
- raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
345
- end
346
- end
347
- end
@@ -1,36 +0,0 @@
1
- module BugBunny
2
- module Helpers
3
- extend self
4
-
5
- def datetime_values_to_utc(data)
6
- case data
7
- when Hash
8
- data.inject({}) {|memo, (k, v)| memo.merge!({k => datetime_values_to_utc(v)}) }
9
- when Array
10
- data.map {|e| datetime_values_to_utc(e) }
11
- when DateTime, Time
12
- data.to_time.utc.iso8601
13
- else
14
- data
15
- end
16
- end
17
-
18
- def utc_values_to_local(data)
19
- case data
20
- when Hash
21
- data.inject({}) {|memo, (k, v)| memo.merge!({k => utc_values_to_local(v)}) }
22
- when Array
23
- data.map {|e| utc_values_to_local(e) }
24
- when DateTime, Time
25
- data.to_time.localtime # ensure we always use Time instances
26
- when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:.*Z$/
27
- t = Time.respond_to?(:zone) ? Time.zone.parse(data) : Time.parse(data)
28
- t.to_time.localtime
29
- when /^\d{4}-\d{2}-\d{2}$/
30
- Date.parse(data)
31
- else
32
- data
33
- end
34
- end
35
- end
36
- end
@@ -1,161 +0,0 @@
1
- module BugBunny
2
- class Message
3
- # API ERROR RESPONSE KEY
4
- FIELD_ERROR = :field
5
- SERVER_ERROR = :server
6
-
7
- # API ERROR RESPONSE CODES
8
- MISSING_FIELD = :missing
9
- UNKNOWN_FIELD = :unknown
10
- NOT_FOUND = :not_found
11
- NO_ACTION = :no_action
12
- TIMEOUT = :timeout
13
- BOMBA = :bomba
14
-
15
- attr_accessor :correlation_id,
16
- :body,
17
- :signature,
18
- :errors,
19
- :status,
20
- :service_name,
21
- :service_action,
22
- :version,
23
- :reply_to,
24
- :exception
25
-
26
- def initialize(opts = {})
27
- @correlation_id = opts[:correlation_id] || SecureRandom.uuid
28
- @body = deserialize_body(opts[:body] || opts[:response] || {})
29
- @errors = opts[:errors]
30
- @status = opts[:status] || :success
31
- @service_name = opts[:service_name]
32
- @service_action = opts[:service_action] # Deberiamos raisear si esto no viene...
33
- @version = opts[:version]
34
- @signature = opts[:signature]
35
- @reply_to = opts[:reply_to]
36
- @exception = opts[:exception]
37
- end
38
-
39
- def server_not_found!
40
- server_error! [NOT_FOUND]
41
- end
42
-
43
- def server_timeout!
44
- server_error! [TIMEOUT]
45
- end
46
-
47
- def server_no_action!
48
- server_error! [NO_ACTION]
49
- end
50
-
51
- def server_error!(errors=nil)
52
- self.status = :error
53
- self.body = {}
54
- if errors
55
- self.errors ||= {}
56
- self.errors[SERVER_ERROR] ||= []
57
- self.errors[SERVER_ERROR] += [errors].flatten # just in case
58
- else
59
- self.exception = Exception::ServiceError.new
60
- end
61
- self
62
- end
63
-
64
- def signed?
65
- signature.present?
66
- end
67
-
68
- def sign!(key)
69
- self.signature = ::BugBunny::Security.sign_message(key, body.to_json)
70
- end
71
-
72
- def invalid_signature?(key)
73
- !valid_signature?(key)
74
- end
75
-
76
- def valid_signature?(key)
77
- return if signature.blank?
78
-
79
- ::BugBunny::Security.check_sign(key, signature, body.to_json)
80
- end
81
-
82
- def formatted
83
- resp = {
84
- correlation_id: correlation_id,
85
- version: version,
86
- status: status,
87
- service_name: service_name,
88
- service_action: service_action,
89
- signature: signature,
90
- errors: errors,
91
- body: serialize_body
92
- }
93
-
94
- if exception
95
- # resp[:exception] = exception.backtrace.join("\n") rescue nil
96
- resp[:exception] = [
97
- exception.to_s,
98
- exception.try(:backtrace) || []
99
- ].flatten.join("\n") rescue exception.to_s
100
-
101
- unless ::BugBunny::Exception::ServiceClasses.include?(exception.class)
102
- self.exception = Exception::ServiceError.new
103
- end
104
- resp[:errors] ||= {}
105
- unless resp[:errors][SERVER_ERROR]&.any?
106
- resp[:errors][SERVER_ERROR] ||= []
107
- resp[:errors][SERVER_ERROR] << exception.to_s
108
- end
109
- resp[:status] = self.status = :critical
110
- end
111
-
112
- resp
113
- end
114
-
115
- def to_json
116
- formatted.to_json
117
- end
118
-
119
- def to_s
120
- to_json # Asegurarse de que siempre se llame al "formatted"
121
- end
122
-
123
- def to_h
124
- formatted
125
- rescue StandardError
126
- original_to_h
127
- end
128
-
129
- alias :original_to_h :to_h
130
-
131
- def success?
132
- status.to_sym == :success
133
- end
134
-
135
- def error?
136
- status.to_sym == :error
137
- end
138
-
139
- def critical?
140
- status.to_sym == :critical
141
- end
142
-
143
- def build_message(params = {})
144
- Message.new({ version: version, correlation_id: correlation_id, service_action: service_action }.merge(params))
145
- end
146
-
147
- def serialize_body
148
- Helpers.datetime_values_to_utc(body)
149
- end
150
-
151
- def deserialize_body(body)
152
- Helpers.utc_values_to_local(body)
153
- end
154
-
155
- # def critical_response
156
- # ::BugBunny::ParserMessage.humanize_error(errors, :adapter)
157
- # rescue StandarError
158
- # [:general_error]
159
- # end
160
- end
161
- end
@@ -1,23 +0,0 @@
1
- module BugBunny
2
- class Queue
3
- attr_accessor :name, :auto_delete, :durable, :exclusive, :rabbit_queue
4
-
5
- def initialize(attrs={})
6
- # "Real" queue opts
7
- @name = attrs.fetch(:name, 'undefined')
8
- @auto_delete = attrs.fetch(:auto_delete, true)
9
- @durable = attrs.fetch(:durable, false)
10
- @exclusive = attrs.fetch(:exclusive, false)
11
- end
12
-
13
- def options
14
- { durable: durable, exclusive: exclusive, auto_delete: auto_delete }
15
- end
16
-
17
- def check_consumers
18
- rabbit_queue.consumer_count
19
- rescue StandardError
20
- 0
21
- end
22
- end
23
- end