Module: Wurk::Preflight::RedisGuard

Defined in:
lib/wurk/preflight/redis_guard.rb

Overview

What keeps app code off Redis while wurk boot-check or wurk preflight --app boots the app. Their initializers run, configure_server blocks included, and some of those write Redis at boot (a dev-only flushdb, a seed). Installed before the app loads, it rides every pool Wurk builds (RedisPool.guard) as a redis-client middleware, so Sidekiq.redis, Wurk.redis and every Wurk data view are covered whichever method or pipeline sends the command. A client the app opens itself (Redis.new, Rails.cache) is not Wurk's and is not covered.

Defined Under Namespace

Modules: Closed, ReadOnly Classes: Refused

Constant Summary collapse

HANDSHAKE =

Sent by redis-client itself while it dials; none of them changes data.

%w[HELLO AUTH SELECT ROLE PING].freeze
CLIENT_HANDSHAKE =
%w[SETINFO SETNAME].freeze
LIB =
::File.expand_path('../..', __dir__)
GEM_FRAMES =
%r{/(?:redis-client|hiredis-client|connection_pool)-[^/]+/}
STDLIB =
::RbConfig::CONFIG['rubylibdir']

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.label ⇒ Object (readonly)

Returns the value of attribute label.



49
50
51
# File 'lib/wurk/preflight/redis_guard.rb', line 49

def label
  @label
end

Class Method Details

.allowed?(command) ⇒ Boolean

Returns:

  • (Boolean)


68
69
70
71
72
73
# File 'lib/wurk/preflight/redis_guard.rb', line 68

def allowed?(command)
  name = command.first.to_s.upcase
  return CLIENT_HANDSHAKE.include?(command[1].to_s.upcase) if name == 'CLIENT'

  Reader::ALLOWED.include?(name) || HANDSHAKE.include?(name)
end

.check!(command) ⇒ Object

Raises:



62
63
64
65
66
# File 'lib/wurk/preflight/redis_guard.rb', line 62

def check!(command)
  return if allowed?(command)

  raise Refused, message("sent #{command.first.to_s.upcase}", 'sends only reads')
end

.culprit(locations = caller_locations) ⇒ Object

The first frame outside Wurk, the Redis client and the pool: the app file (an initializer, a gem's railtie) that asked for Redis.



83
84
85
86
# File 'lib/wurk/preflight/redis_guard.rb', line 83

def culprit(locations = caller_locations)
  frame = locations.find { |loc| app_frame?(loc.absolute_path || loc.path) }
  frame ? "#{frame.path}:#{frame.lineno}" : 'app code'
end

.install(mode, label:) ⇒ Object

mode is :closed or :read_only. Process-wide, as the boot it guards is.



52
53
54
55
# File 'lib/wurk/preflight/redis_guard.rb', line 52

def install(mode, label:)
  @label = label
  RedisPool.guard = mode == :closed ? Closed : ReadOnly
end

.message(what, policy) ⇒ Object



75
76
77
78
79
# File 'lib/wurk/preflight/redis_guard.rb', line 75

def message(what, policy)
  "#{label}: #{culprit} #{what} while the app booted, and #{label} #{policy}. App code that touches Redis " \
    'at boot (a flushdb, a seed) would reach a real server here. Move it into `config.on(:startup)`, which ' \
    "only a running worker fires#{', or pass --redis to allow reads' if policy.start_with?('runs')}."
end

.uninstall ⇒ Object



57
58
59
60
# File 'lib/wurk/preflight/redis_guard.rb', line 57

def uninstall
  RedisPool.guard = nil
  @label = nil
end