msgr 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +8 -0
- data/.github/workflows/build.yml +52 -0
- data/.github/workflows/lint.yml +20 -0
- data/.rubocop.yml +9 -48
- data/.travis.yml +21 -35
- data/Appraisals +18 -0
- data/CHANGELOG.md +11 -1
- data/Gemfile +8 -15
- data/README.md +8 -20
- data/Rakefile +5 -5
- data/bin/msgr +1 -0
- data/gemfiles/rails_5.2.gemfile +14 -0
- data/gemfiles/rails_6.0.gemfile +14 -0
- data/gemfiles/rails_6.1.gemfile +14 -0
- data/gemfiles/rails_master.gemfile +14 -0
- data/lib/msgr.rb +1 -0
- data/lib/msgr/binding.rb +13 -8
- data/lib/msgr/channel.rb +5 -3
- data/lib/msgr/cli.rb +18 -11
- data/lib/msgr/client.rb +17 -20
- data/lib/msgr/connection.rb +13 -1
- data/lib/msgr/consumer.rb +2 -3
- data/lib/msgr/dispatcher.rb +7 -9
- data/lib/msgr/logging.rb +2 -0
- data/lib/msgr/message.rb +1 -2
- data/lib/msgr/railtie.rb +14 -69
- data/lib/msgr/route.rb +1 -4
- data/lib/msgr/routes.rb +2 -0
- data/lib/msgr/tasks/msgr/drain.rake +11 -0
- data/lib/msgr/test_pool.rb +1 -3
- data/lib/msgr/version.rb +1 -1
- data/msgr.gemspec +2 -6
- data/scripts/simple_test.rb +2 -3
- data/spec/fixtures/{msgr-routes-test-1.rb → msgr_routes_test_1.rb} +0 -0
- data/spec/integration/dummy/Rakefile +1 -1
- data/spec/{msgr/support/.keep → integration/dummy/app/assets/config/manifest.js} +0 -0
- data/spec/integration/dummy/bin/bundle +1 -1
- data/spec/integration/dummy/bin/rails +1 -1
- data/spec/integration/dummy/config/application.rb +1 -1
- data/spec/integration/dummy/config/boot.rb +2 -2
- data/spec/integration/dummy/config/environment.rb +1 -1
- data/spec/integration/dummy/config/rabbitmq.yml +1 -1
- data/spec/integration/msgr/dispatcher_spec.rb +28 -12
- data/spec/integration/msgr/railtie_spec.rb +10 -120
- data/spec/integration/spec_helper.rb +2 -3
- data/spec/integration/{msgr_spec.rb → test_controller_spec.rb} +1 -1
- data/spec/unit/msgr/client_spec.rb +88 -0
- data/spec/{msgr → unit}/msgr/connection_spec.rb +1 -1
- data/spec/{msgr → unit}/msgr/consumer_spec.rb +0 -0
- data/spec/unit/msgr/dispatcher_spec.rb +45 -0
- data/spec/{msgr → unit}/msgr/route_spec.rb +15 -14
- data/spec/{msgr → unit}/msgr/routes_spec.rb +32 -35
- data/spec/{msgr → unit}/msgr_spec.rb +25 -16
- data/spec/{msgr → unit}/spec_helper.rb +1 -1
- data/spec/unit/support/.keep +0 -0
- metadata +37 -33
- data/gemfiles/Gemfile.rails-4-2 +0 -7
- data/gemfiles/Gemfile.rails-5-0 +0 -7
- data/gemfiles/Gemfile.rails-5-1 +0 -7
- data/gemfiles/Gemfile.rails-5-2 +0 -7
- data/gemfiles/Gemfile.rails-master +0 -14
- data/spec/msgr/msgr/client_spec.rb +0 -60
- data/spec/msgr/msgr/dispatcher_spec.rb +0 -44
- data/spec/support/setup.rb +0 -29
data/lib/msgr.rb
CHANGED
data/lib/msgr/binding.rb
CHANGED
@@ -4,7 +4,14 @@ module Msgr
|
|
4
4
|
class Binding
|
5
5
|
include Logging
|
6
6
|
|
7
|
-
attr_reader
|
7
|
+
attr_reader(
|
8
|
+
:channel,
|
9
|
+
:connection,
|
10
|
+
:dispatcher,
|
11
|
+
:queue,
|
12
|
+
:route,
|
13
|
+
:subscription
|
14
|
+
)
|
8
15
|
|
9
16
|
def initialize(connection, route, dispatcher)
|
10
17
|
@connection = connection
|
@@ -43,13 +50,11 @@ module Msgr
|
|
43
50
|
|
44
51
|
def subscribe
|
45
52
|
@subscription = queue.subscribe(manual_ack: true) do |*args|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
"#{err}\n#{err.backtrace.join("\n")}"
|
52
|
-
end
|
53
|
+
dispatcher.call Message.new(channel, *args, route)
|
54
|
+
rescue StandardError => e
|
55
|
+
log(:error) do
|
56
|
+
"Rescued error from subscribe: #{e.class.name}: " \
|
57
|
+
"#{e}\n#{e.backtrace.join("\n")}"
|
53
58
|
end
|
54
59
|
end
|
55
60
|
end
|
data/lib/msgr/channel.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Msgr
|
2
4
|
class Channel
|
3
5
|
include Logging
|
@@ -26,8 +28,8 @@ module Msgr
|
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
|
-
def queue(name)
|
30
|
-
@channel.queue(prefix(name), durable: true).tap do |queue|
|
31
|
+
def queue(name, **opts)
|
32
|
+
@channel.queue(prefix(name), durable: true, **opts).tap do |queue|
|
31
33
|
log(:debug) do
|
32
34
|
"Create queue #{queue.name} (durable: #{queue.durable?}, " \
|
33
35
|
"auto_delete: #{queue.auto_delete?})"
|
@@ -62,4 +64,4 @@ module Msgr
|
|
62
64
|
@channel.close if @channel.open?
|
63
65
|
end
|
64
66
|
end
|
65
|
-
end
|
67
|
+
end
|
data/lib/msgr/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optionparser'
|
2
4
|
|
3
5
|
module Msgr
|
@@ -8,7 +10,8 @@ module Msgr
|
|
8
10
|
@options = options
|
9
11
|
|
10
12
|
if !File.exist?(options[:require]) ||
|
11
|
-
|
13
|
+
(File.directory?(options[:require]) &&
|
14
|
+
!File.exist?("#{options[:require]}/config/application.rb"))
|
12
15
|
raise <<~ERR
|
13
16
|
Rails application or required ruby file not found: #{options[:require]}
|
14
17
|
ERR
|
@@ -22,15 +25,13 @@ module Msgr
|
|
22
25
|
require 'rails'
|
23
26
|
if ::Rails::VERSION::MAJOR == 4
|
24
27
|
require File.expand_path("#{options[:require]}/config/application.rb")
|
25
|
-
::Rails::Application.initializer
|
28
|
+
::Rails::Application.initializer 'msgr.eager_load' do
|
26
29
|
::Rails.application.config.eager_load = true
|
27
30
|
end
|
28
|
-
require 'msgr/railtie'
|
29
|
-
require File.expand_path("#{options[:require]}/config/environment.rb")
|
30
|
-
else
|
31
|
-
require 'msgr/railtie'
|
32
|
-
require File.expand_path("#{options[:require]}/config/environment.rb")
|
33
31
|
end
|
32
|
+
|
33
|
+
require 'msgr/railtie'
|
34
|
+
require File.expand_path("#{options[:require]}/config/environment.rb")
|
34
35
|
else
|
35
36
|
require(options[:require])
|
36
37
|
end
|
@@ -43,7 +44,7 @@ module Msgr
|
|
43
44
|
Msgr.logger = Logger.new(STDOUT)
|
44
45
|
Msgr.client.start
|
45
46
|
|
46
|
-
while readable = IO.select([r])
|
47
|
+
while (readable = IO.select([r]))
|
47
48
|
case readable.first[0].gets.strip
|
48
49
|
when 'INT', 'TERM'
|
49
50
|
Msgr.client.stop
|
@@ -61,18 +62,24 @@ module Msgr
|
|
61
62
|
|
62
63
|
private
|
63
64
|
|
64
|
-
def parse(
|
65
|
+
def parse(_argv)
|
65
66
|
options = {
|
66
67
|
require: Dir.pwd,
|
67
68
|
environment: 'development'
|
68
69
|
}
|
69
70
|
|
70
71
|
OptionParser.new do |o|
|
71
|
-
o.on
|
72
|
+
o.on(
|
73
|
+
'-r', '--require [PATH|DIR]',
|
74
|
+
'Location of Rails application (default to current directory)'
|
75
|
+
) do |arg|
|
72
76
|
options[:require] = arg
|
73
77
|
end
|
74
78
|
|
75
|
-
o.on
|
79
|
+
o.on(
|
80
|
+
'-e', '--environment [env]',
|
81
|
+
'Rails environment (default to development)'
|
82
|
+
) do |arg|
|
76
83
|
options[:environment] = arg
|
77
84
|
end
|
78
85
|
end.parse!
|
data/lib/msgr/client.rb
CHANGED
@@ -2,15 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'uri'
|
4
4
|
require 'cgi'
|
5
|
+
require 'json'
|
5
6
|
|
6
7
|
module Msgr
|
7
|
-
# rubocop:disable Metrics/ClassLength
|
8
8
|
class Client
|
9
9
|
include Logging
|
10
10
|
|
11
11
|
attr_reader :config
|
12
12
|
|
13
|
-
# rubocop:disable MethodLength
|
14
13
|
def initialize(config = {})
|
15
14
|
@config = {
|
16
15
|
host: '127.0.0.1',
|
@@ -18,7 +17,7 @@ module Msgr
|
|
18
17
|
max: 2
|
19
18
|
}
|
20
19
|
|
21
|
-
@config.merge! parse(config.delete(:uri)) if config
|
20
|
+
@config.merge! parse(config.delete(:uri)) if config[:uri]
|
22
21
|
@config.merge! config.symbolize_keys
|
23
22
|
|
24
23
|
@mutex = ::Mutex.new
|
@@ -27,12 +26,7 @@ module Msgr
|
|
27
26
|
|
28
27
|
log(:debug) { "Created new client on process ##{@pid}..." }
|
29
28
|
end
|
30
|
-
# rubocop:enable all
|
31
29
|
|
32
|
-
# rubocop:disable AbcSize
|
33
|
-
# rubocop:disable MethodLength
|
34
|
-
# rubocop:disable PerceivedComplexity
|
35
|
-
# rubocop:disable CyclomaticComplexity
|
36
30
|
def uri
|
37
31
|
@uri = begin
|
38
32
|
uri = ::URI.parse('amqp://localhost')
|
@@ -50,7 +44,6 @@ module Msgr
|
|
50
44
|
uri
|
51
45
|
end
|
52
46
|
end
|
53
|
-
# rubocop:enable all
|
54
47
|
|
55
48
|
def running?
|
56
49
|
mutex.synchronize do
|
@@ -59,7 +52,6 @@ module Msgr
|
|
59
52
|
end
|
60
53
|
end
|
61
54
|
|
62
|
-
# rubocop:disable AbcSize
|
63
55
|
def start
|
64
56
|
mutex.synchronize do
|
65
57
|
check_process!
|
@@ -72,7 +64,6 @@ module Msgr
|
|
72
64
|
connection.bind(@routes)
|
73
65
|
end
|
74
66
|
end
|
75
|
-
# rubocop:enable all
|
76
67
|
|
77
68
|
def connect
|
78
69
|
mutex.synchronize do
|
@@ -85,7 +76,6 @@ module Msgr
|
|
85
76
|
end
|
86
77
|
end
|
87
78
|
|
88
|
-
# rubocop:disable AbcSize
|
89
79
|
def stop(opts = {})
|
90
80
|
mutex.synchronize do
|
91
81
|
check_process!
|
@@ -100,7 +90,6 @@ module Msgr
|
|
100
90
|
reset
|
101
91
|
end
|
102
92
|
end
|
103
|
-
# rubocop:enable all
|
104
93
|
|
105
94
|
def purge(release: false)
|
106
95
|
mutex.synchronize do
|
@@ -112,6 +101,15 @@ module Msgr
|
|
112
101
|
end
|
113
102
|
end
|
114
103
|
|
104
|
+
##
|
105
|
+
# Purge all queues known to Msgr, if they exist.
|
106
|
+
#
|
107
|
+
def drain
|
108
|
+
@routes.each do |route|
|
109
|
+
connection.purge_queue(route.name)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
115
113
|
def publish(payload, opts = {})
|
116
114
|
mutex.synchronize do
|
117
115
|
check_process!
|
@@ -149,6 +147,7 @@ module Msgr
|
|
149
147
|
|
150
148
|
def check_process!
|
151
149
|
return if ::Process.pid == @pid
|
150
|
+
|
152
151
|
log(:warn) do
|
153
152
|
"Fork detected. Reset internal state. (Old PID: #{@pid} / " \
|
154
153
|
"New PID: #{::Process.pid}"
|
@@ -178,22 +177,20 @@ module Msgr
|
|
178
177
|
@dispatcher = nil
|
179
178
|
end
|
180
179
|
|
181
|
-
# rubocop:disable AbcSize
|
182
180
|
def parse(uri)
|
183
181
|
# Legacy parsing of URI configuration; does not follow usual
|
184
182
|
# AMQP vhost encoding but used regular URL path
|
185
183
|
uri = ::URI.parse(uri)
|
186
184
|
|
187
185
|
config = {}
|
188
|
-
config[:user]
|
189
|
-
config[:pass]
|
190
|
-
config[:host]
|
191
|
-
config[:port]
|
192
|
-
config[:vhost] ||= uri.path
|
186
|
+
config[:user] ||= uri.user if uri.user
|
187
|
+
config[:pass] ||= uri.password if uri.password
|
188
|
+
config[:host] ||= uri.host if uri.host
|
189
|
+
config[:port] ||= uri.port if uri.port
|
190
|
+
config[:vhost] ||= uri.path unless uri.path.empty?
|
193
191
|
config[:ssl] ||= uri.scheme.casecmp('amqps').zero?
|
194
192
|
|
195
193
|
config
|
196
194
|
end
|
197
|
-
# rubocop:enable all
|
198
195
|
end
|
199
196
|
end
|
data/lib/msgr/connection.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'bunny'
|
4
4
|
|
5
5
|
module Msgr
|
6
|
-
# rubocop:disable Metrics/ClassLength
|
7
6
|
class Connection
|
8
7
|
include Logging
|
9
8
|
|
@@ -49,6 +48,7 @@ module Msgr
|
|
49
48
|
|
50
49
|
def release
|
51
50
|
return if bindings.empty?
|
51
|
+
|
52
52
|
log(:debug) { "Release bindings (#{bindings.size})..." }
|
53
53
|
|
54
54
|
bindings.each(&:release)
|
@@ -56,6 +56,7 @@ module Msgr
|
|
56
56
|
|
57
57
|
def delete
|
58
58
|
return if bindings.empty?
|
59
|
+
|
59
60
|
log(:debug) { "Delete bindings (#{bindings.size})..." }
|
60
61
|
|
61
62
|
bindings.each(&:delete)
|
@@ -63,11 +64,22 @@ module Msgr
|
|
63
64
|
|
64
65
|
def purge(**kwargs)
|
65
66
|
return if bindings.empty?
|
67
|
+
|
66
68
|
log(:debug) { "Purge bindings (#{bindings.size})..." }
|
67
69
|
|
68
70
|
bindings.each {|b| b.purge(**kwargs) }
|
69
71
|
end
|
70
72
|
|
73
|
+
def purge_queue(name)
|
74
|
+
# Creating the queue in passive mode ensures that queues that do not exist
|
75
|
+
# won't be created just to purge them.
|
76
|
+
# That requires creating a new channel every time, as exceptions (on
|
77
|
+
# missing queues) invalidate the channel.
|
78
|
+
channel.queue(name, passive: true).purge
|
79
|
+
rescue Bunny::NotFound
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
71
83
|
def bindings
|
72
84
|
@bindings ||= []
|
73
85
|
end
|
data/lib/msgr/consumer.rb
CHANGED
@@ -5,6 +5,7 @@ module Msgr
|
|
5
5
|
include Logging
|
6
6
|
|
7
7
|
attr_reader :message
|
8
|
+
|
8
9
|
delegate :payload, to: :@message
|
9
10
|
delegate :action, to: :'@message.route'
|
10
11
|
delegate :consumer, to: :'@message.consumer'
|
@@ -14,9 +15,7 @@ module Msgr
|
|
14
15
|
@auto_ack || @auto_ack.nil?
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
-
@auto_ack = val
|
19
|
-
end
|
18
|
+
attr_writer :auto_ack
|
20
19
|
end
|
21
20
|
|
22
21
|
def dispatch(message)
|
data/lib/msgr/dispatcher.rb
CHANGED
@@ -27,9 +27,6 @@ module Msgr
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
# rubocop:disable Metrics/AbcSize
|
31
|
-
# rubocop:disable Metrics/MethodLength
|
32
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
33
30
|
def dispatch(message)
|
34
31
|
consumer_class = Object.const_get message.route.consumer
|
35
32
|
|
@@ -37,17 +34,18 @@ module Msgr
|
|
37
34
|
|
38
35
|
consumer_class.new.dispatch message
|
39
36
|
|
40
|
-
# Acknowledge message
|
41
|
-
|
42
|
-
|
37
|
+
# Acknowledge message only if it is not already acknowledged and auto
|
38
|
+
# acknowledgment is enabled.
|
39
|
+
message.ack unless message.acked? || !consumer_class.auto_ack?
|
40
|
+
rescue StandardError => e
|
43
41
|
message.nack unless message.acked?
|
44
42
|
|
45
43
|
log(:error) do
|
46
|
-
"Dispatcher error: #{
|
47
|
-
|
44
|
+
"Dispatcher error: #{e.class.name}: #{e}\n" +
|
45
|
+
e.backtrace.join("\n")
|
48
46
|
end
|
49
47
|
|
50
|
-
raise
|
48
|
+
raise e if config[:raise_exceptions]
|
51
49
|
ensure
|
52
50
|
if defined?(ActiveRecord) &&
|
53
51
|
ActiveRecord::Base.connection_pool.active_connection?
|
data/lib/msgr/logging.rb
CHANGED
data/lib/msgr/message.rb
CHANGED
@@ -11,8 +11,7 @@ module Msgr
|
|
11
11
|
@payload = payload
|
12
12
|
@route = route
|
13
13
|
|
14
|
-
# rubocop:disable Style/GuardClause
|
15
|
-
if content_type == 'application/json'
|
14
|
+
if content_type == 'application/json' # rubocop:disable Style/GuardClause
|
16
15
|
@payload = JSON.parse(payload)
|
17
16
|
@payload.symbolize_keys! if @payload.respond_to? :symbolize_keys!
|
18
17
|
end
|
data/lib/msgr/railtie.rb
CHANGED
@@ -4,6 +4,11 @@ module Msgr
|
|
4
4
|
class Railtie < ::Rails::Railtie
|
5
5
|
config.msgr = ActiveSupport::OrderedOptions.new
|
6
6
|
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
checkcredentials: true,
|
9
|
+
routing_file: "#{Rails.root}/config/msgr.rb"
|
10
|
+
}.freeze
|
11
|
+
|
7
12
|
if File.exist?("#{Rails.root}/app/consumers")
|
8
13
|
config.autoload_paths << File.expand_path("#{Rails.root}/app/consumers")
|
9
14
|
end
|
@@ -12,84 +17,24 @@ module Msgr
|
|
12
17
|
app.config.msgr.logger ||= Rails.logger
|
13
18
|
end
|
14
19
|
|
15
|
-
# Start msgr
|
16
20
|
initializer 'msgr.start' do
|
17
21
|
config.after_initialize do |app|
|
18
22
|
Msgr.logger = app.config.msgr.logger
|
19
23
|
|
20
|
-
self.class.load
|
24
|
+
self.class.load(app.config_for(:rabbitmq))
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
return unless cfg # no config given -> does not load Msgr
|
28
|
-
|
29
|
-
Msgr.config = cfg
|
30
|
-
Msgr.client.connect if cfg[:checkcredentials]
|
31
|
-
Msgr.start if cfg[:autostart]
|
32
|
-
end
|
33
|
-
|
34
|
-
def parse_config(cfg)
|
35
|
-
unless cfg.is_a? Hash
|
36
|
-
Rails.logger.warn '[Msgr] Could not load rabbitmq config: Config must be a Hash'
|
37
|
-
return nil
|
38
|
-
end
|
39
|
-
|
40
|
-
unless cfg[Rails.env].is_a?(Hash)
|
41
|
-
Rails.logger.warn "Could not load rabbitmq config for environment \"#{Rails.env}\": is not a Hash"
|
42
|
-
return nil
|
43
|
-
end
|
44
|
-
|
45
|
-
cfg = HashWithIndifferentAccess.new cfg[Rails.env]
|
46
|
-
unless cfg[:uri]
|
47
|
-
raise ArgumentError.new('Could not load rabbitmq environment config: URI missing.')
|
48
|
-
end
|
49
|
-
|
50
|
-
case cfg[:autostart]
|
51
|
-
when true, 'true', 'enabled'
|
52
|
-
cfg[:autostart] = true
|
53
|
-
when false, 'false', 'disabled', nil
|
54
|
-
cfg[:autostart] = false
|
55
|
-
else
|
56
|
-
raise ArgumentError.new("Invalid value for rabbitmq config autostart: \"#{cfg[:autostart]}\"")
|
57
|
-
end
|
58
|
-
|
59
|
-
case cfg[:checkcredentials]
|
60
|
-
when true, 'true', 'enabled', nil
|
61
|
-
cfg[:checkcredentials] = true
|
62
|
-
when false, 'false', 'disabled'
|
63
|
-
cfg[:checkcredentials] = false
|
64
|
-
else
|
65
|
-
raise ArgumentError.new("Invalid value for rabbitmq config checkcredentials: \"#{cfg[:checkcredentials]}\"")
|
66
|
-
end
|
67
|
-
|
68
|
-
case cfg[:raise_exceptions]
|
69
|
-
when true, 'true', 'enabled'
|
70
|
-
cfg[:raise_exceptions] = true
|
71
|
-
when false, 'false', 'disabled', nil
|
72
|
-
cfg[:raise_exceptions] = false
|
73
|
-
else
|
74
|
-
raise ArgumentError.new("Invalid value for rabbitmq config raise_exceptions: \"#{cfg[:raise_exceptions]}\"")
|
75
|
-
end
|
76
|
-
|
77
|
-
cfg[:routing_file] ||= Rails.root.join('config/msgr.rb').to_s
|
78
|
-
cfg
|
79
|
-
end
|
80
|
-
|
81
|
-
def load_config(options)
|
82
|
-
if options.rabbitmq_config || !Rails.application.respond_to?(:config_for)
|
83
|
-
load_file options.rabbitmq_config || Rails.root.join('config', 'rabbitmq.yml')
|
84
|
-
else
|
85
|
-
conf = Rails.application.config_for :rabbitmq
|
28
|
+
rake_tasks do
|
29
|
+
load File.expand_path('tasks/msgr/drain.rake', __dir__)
|
30
|
+
end
|
86
31
|
|
87
|
-
|
88
|
-
|
89
|
-
|
32
|
+
class << self
|
33
|
+
def load(config)
|
34
|
+
config = DEFAULT_OPTIONS.merge(config)
|
90
35
|
|
91
|
-
|
92
|
-
|
36
|
+
Msgr.config = config
|
37
|
+
Msgr.client.connect if config.fetch(:checkcredentials)
|
93
38
|
end
|
94
39
|
end
|
95
40
|
end
|