bug_bunny 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d3bbeb691836f8f7b516454ed095e36fe7e719dc99f8a435fb58e2ed995e2d80
4
+ data.tar.gz: 8c1b3ed5758f0a19d0ee73233a4b92e328aaa114a18c851433f2bedbb2edeabe
5
+ SHA512:
6
+ metadata.gz: 0e7cbbe33d45b497526a1a35b6d841bfd141edda37baf91975d64d218617f2cb8d8a9ec858efd4d75097d0b564cb0f96ae7326d7cb9be51c9ec926e621882b6c
7
+ data.tar.gz: c59537749ecd8c3dab9cff99c4f41ad5a62af569ddbbad13223ec3b3ad2ed9f2c3acaf96d6a736718afbfe5bd40e28dcfe147c61e5e5d3861fc71007db7580b4
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # Changelog
2
+ ## Version 0.1.0
3
+ Migration bunny logic from utils
data/Gemfile ADDED
@@ -0,0 +1,8 @@
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"
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # BugBunny
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/bug_bunny`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bug_bunny.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/bug_bunny.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/bug_bunny/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "bug_bunny"
7
+ spec.version = BugBunny::VERSION
8
+ spec.authors = ["gabix"]
9
+ spec.email = ["gab.edera@gmail.com"]
10
+
11
+ spec.summary = "Gem for sync and async comunication via rabbit bunny."
12
+ spec.description = "Gem for sync and async comunication via rabbit bunny."
13
+ spec.homepage = "https://github.com/gedera/bug_bunny"
14
+ spec.required_ruby_version = ">= 2.6.0"
15
+
16
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/gedera/bug_bunny"
20
+ spec.metadata["changelog_uri"] = "https://github.com/gedera/bug_bunny/blob/main/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
27
+ end
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ # Uncomment to register a new dependency of your gem
34
+ # spec.add_dependency "example-gem", "~> 1.0"
35
+
36
+ spec.add_dependency "bunny", "~> 2.20"
37
+ spec.add_development_dependency "rubocop"
38
+
39
+ # For more information and examples about making a new gem, check out our
40
+ # guide at: https://bundler.io/guides/creating_gem.html
41
+ end
@@ -0,0 +1,384 @@
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
+ :consume_response
22
+
23
+ def initialize
24
+ @logger = Logger.new('./log/bug_bunny.log', 'monthly')
25
+ @communication_response = ::BugBunny::Response.new status: false
26
+ @time_to_wait = 2
27
+ create_adapter_with_rabbit
28
+ end
29
+
30
+ def publish!(message, publish_queue, opts = {})
31
+ Timeout::timeout(TIMEOUT) do
32
+ if opts[:check_consumers_count] && publish_queue.check_consumers.zero?
33
+ self.communication_response = ::BugBunny::Response.new(status: false, response: CONSUMER_COUNT_ZERO)
34
+ return
35
+ end
36
+
37
+ publish_opts = { routing_key: publish_queue.name,
38
+ persistent: opts[:persistent],
39
+ correlation_id: message.correlation_id }
40
+
41
+ publish_opts[:reply_to] = opts[:reply_to] if opts[:reply_to]
42
+
43
+ # Esta es la idea en el caso que nos pongamos mas mañosos y queramos cambiar las exchange a la hora de publicar.
44
+ # _exchange = if opts.has_key?(:exchange_type)
45
+ # channel.exchange(opts[:exchange_type].to_s, { type: opts[:exchange_type] })
46
+ # else
47
+ # exchange
48
+ # end
49
+ # _exchange.publish(message.to_json, publish_opts)
50
+ logger.debug("#{publish_queue.name}-Send Request: (#{message})")
51
+
52
+ rabbit.exchange.publish(message.to_json, publish_opts)
53
+ rabbit.channel.wait_for_confirms if rabbit.confirm_select
54
+
55
+ self.communication_response = ::BugBunny::Response.new(status: true)
56
+ end
57
+ rescue Timeout::Error => e
58
+ logger.error(e)
59
+ close_connection!
60
+ self.communication_response = ::BugBunny::Response.new(status: false, response: PUBLISH_TIMEOUT, exception: e)
61
+ rescue StandardError => e
62
+ logger.error(e)
63
+ close_connection!
64
+ self.communication_response = ::BugBunny::Response.new(status: false, response: BOMBA, exception: e)
65
+ end
66
+
67
+ def consume!(queue, thread: false, manual_ack: true, exclusive: false, block: true, opts: {})
68
+ Signal.trap('INT') { exit }
69
+
70
+ logger.debug("Suscribe consumer to: #{queue.name}")
71
+ logger.debug("ENTRO AL CONSUMER #{rabbit.try(:identifier)}")
72
+
73
+ self.consumer = queue.rabbit_queue.subscribe(manual_ack: manual_ack, exclusive: exclusive, block: block) do |delivery_info, metadata, json_payload|
74
+ # Session depends on thread info, subscribe block cleans thread info
75
+ # ::Session.init unless Session.tags_context
76
+
77
+ begin
78
+ payload = ActiveSupport::JSON.decode(json_payload).deep_symbolize_keys # Timezones pulenteado
79
+ rescue StandardError
80
+ payload = JSON.parse(json_payload).deep_symbolize_keys
81
+ end
82
+
83
+ # Session for Sentry logger
84
+ # locale, version, service_name
85
+ # payload.except(:body, :service_name).each do |k, v|
86
+ # Session.assign(k, v)
87
+ # end
88
+ # Session.from_service = payload[:service_name]
89
+ # Session.correlation_id = metadata.correlation_id
90
+ # Session.queue_name = queue.name
91
+
92
+ unless defined?(ActiveRecord) && ActiveRecord::Base.connection_pool.with_connection(&:active?)
93
+ logger.error('[PG] PG connection down')
94
+ exit 7
95
+ end
96
+
97
+ begin
98
+ message = ::BugBunny::Message.new(correlation_id: metadata.correlation_id, reply_to: metadata.reply_to, **payload)
99
+
100
+ # Default sentry info
101
+ # ::Session.request_id = message.correlation_id rescue nil
102
+ # ::Session.tags_context.merge!(
103
+ # server_version: message.version,
104
+ # service_action: message.service_action,
105
+ # service_name: message.service_name,
106
+ # isp_id: (message.body&.fetch(:isp_id, nil) rescue nil)
107
+ # )
108
+ # ::Session.extra_context[:message] = message.body
109
+
110
+ logger.info("#{queue.name}-Received Request: (#{message.service_action})")
111
+ logger.debug("#{queue.name}-Received Request: (#{message})")
112
+ logger.debug("Message will be yield")
113
+ logger.debug("Block given? #{block_given?}")
114
+ yield(message) if block_given?
115
+ logger.debug('Message processed')
116
+
117
+ begin
118
+ Timeout.timeout(5) do
119
+ rabbit.channel.ack delivery_info.delivery_tag if delivery_info[:consumer].manual_acknowledgement?
120
+ end
121
+ rescue Timeout::Error => e
122
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)} can not check manual_ack #{e.to_s}")
123
+ rescue ::StandardError => e
124
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)} can not check manual_ack #{e.to_s}")
125
+ end
126
+
127
+ self.service_message = message
128
+ self.communication_response = ::BugBunny::Response.new(status: true)
129
+ rescue ::SystemExit => e # Ensure exit code
130
+ raise e
131
+ rescue => e
132
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
133
+ logger.error(e)
134
+
135
+ close_connection!
136
+
137
+ # Session.clean!
138
+ self.communication_response = ::BugBunny::Response.new(status: false, response: BOMBA, exception: e)
139
+ end
140
+
141
+ if thread # sync consumer flag :D
142
+ begin
143
+ Timeout::timeout(1) do
144
+ delivery_info[:consumer].cancel
145
+ end
146
+ rescue Timeout::Error => e
147
+ close_connection!
148
+ thread.exit
149
+ end
150
+ close_connection!
151
+ thread.exit
152
+ end
153
+ end
154
+
155
+ if thread
156
+ close_connection!
157
+ thread.exit
158
+ else
159
+ while true
160
+ begin
161
+ logger.debug("SALIO DEL CONSUMER #{rabbit.try(:identifier)}")
162
+ logger.debug(rabbit.status)
163
+ exit # consumer.cancel
164
+ rescue Bunny::NotAllowedError => e
165
+ logger.debug("NOT ALLOWED #{e.to_s}")
166
+ break
167
+ rescue Timeout::Error => e
168
+ if queue.rabbit_queue.channel.status == :closed || queue.rabbit_queue.channel.connection.status == :closed
169
+ logger.debug("Channel or connection closed")
170
+ break
171
+ end
172
+
173
+ sleep time_to_wait
174
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
175
+ logger.error(e)
176
+ retry
177
+ rescue StandardError => e
178
+ if queue.rabbit_queue.channel.status == :closed || queue.rabbit_queue.channel.connection.status == :closed
179
+ logger.debug("Channel or connection closed")
180
+ break
181
+ end
182
+
183
+ sleep time_to_wait
184
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
185
+ logger.error(e)
186
+ retry
187
+ end
188
+ end
189
+ end
190
+ rescue Timeout::Error => e
191
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
192
+ logger.error(e)
193
+ close_connection!
194
+ ::BugBunny::Response.new(status: false, response: CONSUMER_TIMEOUT, exception: e)
195
+ rescue StandardError => e
196
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
197
+ logger.error(e)
198
+ close_connection!
199
+ ::BugBunny::Response.new(status: false, response: BOMBA, exception: e)
200
+ end
201
+
202
+ def publish_and_consume!(publish_message, sync_queue, opts={})
203
+ reply_queue = build_queue('', initialize: true, exclusive: true, durable: false, auto_delete: true)
204
+
205
+ retries = 0
206
+ begin
207
+ publish!(publish_message, sync_queue, opts.merge(reply_to: reply_queue.name))
208
+ rescue
209
+ if (retries += 1) <= 3
210
+ sleep 0.5
211
+ retry
212
+ end
213
+
214
+ close_connection!
215
+ raise
216
+ end
217
+
218
+ return communication_response unless communication_response.success?
219
+
220
+ t = Thread.new do
221
+ retries = 0
222
+ begin
223
+ consume!(reply_queue, thread: Thread.current, exclusive: true) do |msg|
224
+ yield(msg) if block_given?
225
+ end
226
+ rescue
227
+ if (retries += 1) <= 3
228
+ sleep 0.5
229
+ retry
230
+ end
231
+ raise
232
+ end
233
+ end
234
+ t.join
235
+ communication_response
236
+ end
237
+
238
+ def build_queue(name, opts = {})
239
+ init = opts.key?(:initialize) ? opts[:initialize] : true
240
+ new_queue = ::BugBunny::Queue.new(opts.merge(name: name))
241
+
242
+ if init
243
+ logger.debug("Building rabbit new_queue: #{name} status: #{rabbit.status} queue_options: #{new_queue.options}")
244
+
245
+ retries = 0
246
+ begin
247
+ built_queue = rabbit.channel.queue(new_queue.name.to_s, new_queue.options)
248
+ rescue StandardError
249
+ if (retries += 1) <= 3
250
+ sleep 0.5
251
+ retry
252
+ end
253
+ raise
254
+ end
255
+
256
+ new_queue.rabbit_queue = built_queue
257
+ new_queue.name = new_queue.rabbit_queue.name
258
+ end
259
+
260
+ new_queue
261
+ rescue Timeout::Error, StandardError => e
262
+ logger.debug("Rabbit Identifier: #{rabbit.try(:identifier)}")
263
+ logger.debug("Status adapter created: #{rabbit.status}")
264
+ logger.error(e)
265
+
266
+ close_connection!
267
+ raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
268
+ end
269
+
270
+ def self.make_response(comunication_result, consume_result = nil)
271
+ if comunication_result.success?
272
+ consume_result || comunication_result
273
+ else
274
+ comunication_result.response = comunication_result.response.to_s
275
+ comunication_result
276
+ end
277
+ end
278
+
279
+ def make_response
280
+ if communication_response.success?
281
+ consume_response || communication_response
282
+ else
283
+ communication_response.response = communication_response.response.to_s
284
+ communication_response
285
+ end
286
+ end
287
+
288
+ def status
289
+ rabbit.try(:status)
290
+ end
291
+
292
+ def close_connection!
293
+ rabbit.try(:close)
294
+ end
295
+
296
+ def check_pg_exception!(exception)
297
+ # el consumidor no reconecta (rails tasks) asi que salimos a la goma
298
+ if PG_EXCEPTIONS_TO_EXIT.any? { |msg| exception.try(:message)&.starts_with?(msg) }
299
+ exit 7 # salimos con un int especial para identificarlo
300
+ end
301
+ end
302
+
303
+ private
304
+
305
+ # AMQ::Protocol::EmptyResponseError: Este error lo note cuando el rabbit
306
+ # acepta la connection pero aun no ha terminado de inicializar el servicio,
307
+ # por lo que salta esta exception.
308
+ # Errno::ECONNRESET: Este error se presenta cuando justo esta arrancando
309
+ # el rabbit y se quiere conectar al mismo. El rabbit resetea la connection,
310
+ # haciendo saltar esta exception.
311
+ def create_adapter_with_rabbit
312
+ self.rabbit = ::BugBunny::Rabbit.new(confirm_select: true, logger: logger)
313
+ rescue Bunny::NetworkFailure, Bunny::TCPConnectionFailed,
314
+ Bunny::ConnectionForced, AMQ::Protocol::EmptyResponseError,
315
+ Errno::ECONNRESET => e
316
+
317
+ logger.debug(e)
318
+ close_connection!
319
+ raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
320
+ rescue OpenSSL::SSL::SSLError, OpenSSL::X509::CertificateError => e
321
+ # el `e.to_s` devuelve alguno de los sgtes errores. Por ej:
322
+ # SSL_connect returned=1 errno=0 state=unknown state: sslv3
323
+ # alert bad certificate
324
+ # SSL_CTX_use_PrivateKey: key values mismatch
325
+ # OpenSSL::X509::CertificateError: not enough data // headers too short
326
+ if respond_to?(:handle_ssl_issues)
327
+ handle_ssl_issues # esto pide los certificados de nuevo
328
+ @retries ||= 0
329
+ @retries += 1
330
+ sleep 1
331
+ retry if @retries < 4
332
+ @retries = 0 # reset the counter
333
+ end
334
+
335
+ # Si sigue fallando desp de 3 veces o no tiene definido el handle
336
+ # bombita rodriguez
337
+ logger.error(e)
338
+
339
+ close_connection!
340
+ raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
341
+ rescue Timeout::Error, StandardError => e
342
+ logger.error(e)
343
+
344
+ close_connection!
345
+ raise Exception::ComunicationRabbitError.new(COMUNICATION_ERROR, e.backtrace)
346
+ end
347
+
348
+ def self.health_check(request: nil)
349
+ message_to_publish = ::BugBunny::Message.new(service_action: self::SERVICE_HEALTH_CHECK, body: {})
350
+
351
+ request ||= self::ROUTING_KEY_SYNC_REQUEST
352
+
353
+ begin
354
+ service_adapter = new
355
+ sync_queue = service_adapter.build_queue(
356
+ request,
357
+ self::QUEUES_PROPS[self::ROUTING_KEY_SYNC_REQUEST]
358
+ )
359
+ rescue ::BugBunny::Exception::ComunicationRabbitError => e
360
+ (service_adapter ||= nil).try(:close_connection!)
361
+ return ::BugBunny::Response.new(status: false, response: e.message)
362
+ end
363
+
364
+ service_adapter.publish_and_consume!(message_to_publish, sync_queue, check_consumers_count: true)
365
+
366
+ service_adapter.close_connection! # ensure the adapter is close
367
+
368
+ if service_adapter.communication_response.success?
369
+ msg = service_adapter.service_message
370
+
371
+ result = case msg.status
372
+ when 'success'
373
+ { status: true }
374
+ when 'error', 'critical'
375
+ { status: false }
376
+ end
377
+
378
+ service_adapter.consume_response = ::BugBunny::Response.new(result)
379
+ end
380
+
381
+ service_adapter.make_response
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,16 @@
1
+ module BugBunny
2
+ class Controller
3
+ def self.health_check(_message)
4
+ { status: :success, body: {} }
5
+ end
6
+
7
+ def self.exec_action(message)
8
+ send(message.service_action, message)
9
+ end
10
+
11
+ def self.method_missing(name, message, *args, &block)
12
+ Session.message = message
13
+ message.build_message(reply_to: message.reply_to).server_no_action!
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,61 @@
1
+ module BugBunny
2
+ class Exception
3
+
4
+ class ServiceError < StandardError
5
+ def to_s
6
+ :service_error
7
+ end
8
+ end
9
+
10
+ class NeedSignature < StandardError
11
+ def to_s
12
+ :need_signature
13
+ end
14
+ end
15
+
16
+ class InvalidSignature < StandardError
17
+ def to_s
18
+ :invalid_signature
19
+ end
20
+ end
21
+
22
+ class GatewayError < StandardError
23
+ def to_s
24
+ :gateway_error
25
+ end
26
+ end
27
+
28
+ class UltraCriticError < StandardError
29
+ end
30
+
31
+ class ComunicationRabbitError < StandardError
32
+ attr_accessor :backtrace
33
+
34
+ def initialize(msg, backtrace)
35
+ @backtrace = backtrace
36
+ super(msg)
37
+ end
38
+ end
39
+
40
+ class RetryWithoutError < StandardError
41
+ def to_s
42
+ "retry_sidekiq_without_error"
43
+ end
44
+
45
+ def backtrace
46
+ []
47
+ end
48
+ end
49
+
50
+ ServiceClasses = [
51
+ Exception::NeedSignature,
52
+ Exception::InvalidSignature,
53
+ Exception::ServiceError,
54
+ Exception::GatewayError,
55
+ Exception::RetryWithoutError
56
+ ]
57
+
58
+ # Exceptions from ActiveRecord::StatementInvalid
59
+ PG_EXCEPTIONS_TO_EXIT = %w[PG::ConnectionBad PG::UnableToSend].freeze
60
+ end
61
+ end
@@ -0,0 +1,36 @@
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
@@ -0,0 +1,161 @@
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
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,110 @@
1
+ module BugBunny
2
+ class Rabbit
3
+ require 'bunny'
4
+
5
+ attr_accessor :exchange,
6
+ :rabbit_channel,
7
+ :confirm_select,
8
+ :no_ack,
9
+ :persistent,
10
+ :block,
11
+ :logger,
12
+ :identifier,
13
+ :connection
14
+
15
+ def initialize(attrs = {})
16
+ @block = attrs[:block] || true
17
+ @no_ack = attrs[:no_ack] || true
18
+ @persistent = attrs[:persistent] || true
19
+ @confirm_select = attrs[:confirm_select] || true
20
+ @logger = attrs[:logger] || Logger.new('./log/bug_rabbit.log', 'monthly')
21
+ @identifier = SecureRandom.uuid
22
+
23
+ create_connection
24
+ set_channel
25
+ end
26
+
27
+ def set_channel
28
+ logger.debug("Set Channel: #{connection.status}") if logger
29
+ try(:close_channel)
30
+ @rabbit_channel = connection.create_channel
31
+ @exchange = channel.default_exchange
32
+ channel.confirm_select if confirm_select
33
+ @rabbit_channel
34
+ end
35
+
36
+ def channel
37
+ open? ? @rabbit_channel : set_channel
38
+ end
39
+
40
+ def close
41
+ @rabbit_channel.close if defined?(@rabbit_channel)
42
+ connection.close if connection.present?
43
+ rescue Bunny::ChannelAlreadyClosed
44
+ nil
45
+ end
46
+
47
+ def close_channel
48
+ @rabbit_channel.close if defined?(@rabbit_channel)
49
+ end
50
+
51
+ def status
52
+ {
53
+ connection: connection.status,
54
+ channel: @rabbit_channel.status,
55
+ identifier: identifier
56
+ }
57
+ end
58
+
59
+ def open?
60
+ (connection.status == :open) &&
61
+ (@rabbit_channel.status == :open)
62
+ end
63
+
64
+ def connection_openned?
65
+ [:open, :connecting, :connected].include?(connection.status)
66
+ end
67
+
68
+ # status = :open, :connected, :connecting,
69
+ # :closing, :disconnected, :not_connected, :closed
70
+ def create_connection
71
+ options = {}
72
+
73
+ # if WisproUtils::Config.defaults.use_tls
74
+ # path = (Rails.root.join('private', 'certs') rescue './private/certs')
75
+ # options.merge!(tls: true,
76
+ # port: ENV['RABBIT_SSL_PORT'] || 5671,
77
+ # log_level: ENV['LOG_LEVEL'] || :debug,
78
+ # verify_peer: true,
79
+ # tls_cert: "#{path}/cert.pem",
80
+ # tls_key: "#{path}/key.pem",
81
+ # tls_ca_certificates: ["#{path}/ca.pem"])
82
+ # end
83
+
84
+ logger&.debug('Stablish new connection to rabbit')
85
+ logger&.debug("amqp://#{ENV['RABBIT_USER']}:" \
86
+ "#{ENV['RABBIT_PASS']}@#{ENV['RABBIT_HOST']}" \
87
+ "/#{ENV['RABBIT_VIRTUAL_HOST']}")
88
+
89
+
90
+ bunny_logger = ::Logger.new('./log/bunny.log', 7, 10485760)
91
+ bunny_logger.level = ::Logger::DEBUG
92
+ options.merge!(
93
+ heartbeat_interval: 20, # 20.seconds per connection
94
+ logger: bunny_logger,
95
+ # Override bunny client_propierties
96
+ client_properties: { product: identifier, platform: ''}
97
+ )
98
+
99
+ rabbit_conn = Bunny.new("amqp://#{ENV['RABBIT_USER']}" \
100
+ ":#{ENV['RABBIT_PASS']}@"\
101
+ "#{ENV['RABBIT_HOST']}/"\
102
+ "#{ENV['RABBIT_VIRTUAL_HOST']}",
103
+ options)
104
+ rabbit_conn.start
105
+ logger&.debug("New status connection: #{rabbit_conn.status}")
106
+
107
+ self.connection = rabbit_conn
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,15 @@
1
+ module BugBunny
2
+ class Response
3
+ attr_accessor :status, :response, :exception
4
+
5
+ def initialize(attrs={})
6
+ @status = attrs[:status]
7
+ @response = attrs[:response]
8
+ @exception = attrs[:exception]
9
+ end
10
+
11
+ def success?
12
+ @status
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module BugBunny
2
+ module Security
3
+ def self.sign_message(secret, message)
4
+ digest = OpenSSL::Digest.new('SHA512')
5
+ private_key = OpenSSL::PKey::RSA.new(secret)
6
+ Base64.encode64(private_key.sign(digest, message))
7
+ end
8
+
9
+ def self.check_sign(key, signature, message)
10
+ pub_key = OpenSSL::PKey::RSA.new(key)
11
+ digest = OpenSSL::Digest.new('SHA512')
12
+ if pub_key.verify(digest, Base64.decode64(signature), message)
13
+ true
14
+ else
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BugBunny
4
+ VERSION = "0.1.0"
5
+ end
data/lib/bug_bunny.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bunny'
4
+ require_relative "bug_bunny/version"
5
+ require_relative "bug_bunny/adapter"
6
+ require_relative "bug_bunny/controller"
7
+ require_relative "bug_bunny/exception"
8
+ require_relative "bug_bunny/message"
9
+ require_relative "bug_bunny/queue"
10
+ require_relative "bug_bunny/rabbit"
11
+ require_relative "bug_bunny/response"
12
+ require_relative "bug_bunny/security"
13
+ require_relative "bug_bunny/helpers"
14
+
15
+ module BugBunny
16
+ end
data/sig/bug_bunny.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module BugBunny
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bug_bunny
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - gabix
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-02-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bunny
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.20'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.20'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Gem for sync and async comunication via rabbit bunny.
42
+ email:
43
+ - gab.edera@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - Gemfile
50
+ - README.md
51
+ - Rakefile
52
+ - bug_bunny.gemspec
53
+ - lib/bug_bunny.rb
54
+ - lib/bug_bunny/adapter.rb
55
+ - lib/bug_bunny/controller.rb
56
+ - lib/bug_bunny/exception.rb
57
+ - lib/bug_bunny/helpers.rb
58
+ - lib/bug_bunny/message.rb
59
+ - lib/bug_bunny/queue.rb
60
+ - lib/bug_bunny/rabbit.rb
61
+ - lib/bug_bunny/response.rb
62
+ - lib/bug_bunny/security.rb
63
+ - lib/bug_bunny/version.rb
64
+ - sig/bug_bunny.rbs
65
+ homepage: https://github.com/gedera/bug_bunny
66
+ licenses: []
67
+ metadata:
68
+ homepage_uri: https://github.com/gedera/bug_bunny
69
+ source_code_uri: https://github.com/gedera/bug_bunny
70
+ changelog_uri: https://github.com/gedera/bug_bunny/blob/main/CHANGELOG.md
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 2.6.0
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubygems_version: 3.4.6
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Gem for sync and async comunication via rabbit bunny.
90
+ test_files: []