flipper 0.10.2 → 0.11.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +42 -0
- data/.rubocop_todo.yml +188 -0
- data/Changelog.md +10 -0
- data/Gemfile +6 -3
- data/README.md +4 -3
- data/Rakefile +13 -13
- data/docs/Adapters.md +2 -1
- data/docs/DockerCompose.md +6 -3
- data/docs/Gates.md +25 -3
- data/docs/Optimization.md +27 -5
- data/docs/api/README.md +73 -32
- data/docs/http/README.md +34 -0
- data/docs/read-only/README.md +22 -0
- data/examples/percentage_of_actors_group.rb +49 -0
- data/flipper.gemspec +15 -15
- data/lib/flipper.rb +2 -5
- data/lib/flipper/adapter.rb +10 -0
- data/lib/flipper/adapters/http.rb +147 -0
- data/lib/flipper/adapters/http/client.rb +83 -0
- data/lib/flipper/adapters/http/error.rb +14 -0
- data/lib/flipper/adapters/instrumented.rb +36 -36
- data/lib/flipper/adapters/memoizable.rb +2 -6
- data/lib/flipper/adapters/memory.rb +10 -9
- data/lib/flipper/adapters/operation_logger.rb +1 -1
- data/lib/flipper/adapters/pstore.rb +12 -11
- data/lib/flipper/adapters/read_only.rb +6 -6
- data/lib/flipper/dsl.rb +1 -3
- data/lib/flipper/feature.rb +11 -16
- data/lib/flipper/gate.rb +3 -3
- data/lib/flipper/gate_values.rb +6 -6
- data/lib/flipper/gates/group.rb +2 -2
- data/lib/flipper/gates/percentage_of_actors.rb +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/metriks.rb +1 -1
- data/lib/flipper/instrumentation/statsd.rb +1 -1
- data/lib/flipper/instrumentation/statsd_subscriber.rb +1 -3
- data/lib/flipper/instrumentation/subscriber.rb +11 -10
- data/lib/flipper/instrumenters/memory.rb +1 -5
- data/lib/flipper/instrumenters/noop.rb +1 -1
- data/lib/flipper/middleware/memoizer.rb +11 -27
- data/lib/flipper/middleware/setup_env.rb +44 -0
- data/lib/flipper/registry.rb +8 -10
- data/lib/flipper/spec/shared_adapter_specs.rb +45 -67
- data/lib/flipper/test/shared_adapter_test.rb +25 -31
- data/lib/flipper/typecast.rb +2 -2
- data/lib/flipper/types/actor.rb +2 -4
- data/lib/flipper/types/group.rb +1 -1
- data/lib/flipper/types/percentage.rb +2 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/fixtures/feature.json +31 -0
- data/spec/flipper/adapters/http_spec.rb +148 -0
- data/spec/flipper/adapters/instrumented_spec.rb +20 -20
- data/spec/flipper/adapters/memoizable_spec.rb +59 -59
- data/spec/flipper/adapters/operation_logger_spec.rb +16 -16
- data/spec/flipper/adapters/pstore_spec.rb +6 -6
- data/spec/flipper/adapters/read_only_spec.rb +28 -34
- data/spec/flipper/dsl_spec.rb +73 -84
- data/spec/flipper/feature_check_context_spec.rb +27 -27
- data/spec/flipper/feature_spec.rb +186 -196
- data/spec/flipper/gate_spec.rb +11 -11
- data/spec/flipper/gate_values_spec.rb +46 -45
- data/spec/flipper/gates/actor_spec.rb +2 -2
- data/spec/flipper/gates/boolean_spec.rb +24 -23
- data/spec/flipper/gates/group_spec.rb +19 -19
- data/spec/flipper/gates/percentage_of_actors_spec.rb +10 -10
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/metriks_subscriber_spec.rb +20 -20
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +11 -11
- data/spec/flipper/instrumenters/memory_spec.rb +5 -5
- data/spec/flipper/instrumenters/noop_spec.rb +6 -6
- data/spec/flipper/middleware/memoizer_spec.rb +83 -100
- data/spec/flipper/middleware/setup_env_spec.rb +76 -0
- data/spec/flipper/registry_spec.rb +35 -39
- data/spec/flipper/typecast_spec.rb +18 -18
- data/spec/flipper/types/actor_spec.rb +30 -29
- data/spec/flipper/types/boolean_spec.rb +8 -8
- data/spec/flipper/types/group_spec.rb +28 -28
- data/spec/flipper/types/percentage_spec.rb +14 -14
- data/spec/flipper_spec.rb +61 -54
- data/spec/helper.rb +26 -21
- data/spec/integration_spec.rb +121 -113
- data/spec/support/fake_udp_socket.rb +1 -1
- data/spec/support/spec_helpers.rb +32 -4
- data/test/adapters/pstore_test.rb +3 -3
- data/test/test_helper.rb +1 -1
- metadata +20 -5
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'pstore'
|
2
|
+
require 'set'
|
3
3
|
|
4
4
|
module Flipper
|
5
5
|
module Adapters
|
@@ -17,7 +17,7 @@ module Flipper
|
|
17
17
|
attr_reader :path
|
18
18
|
|
19
19
|
# Public
|
20
|
-
def initialize(path =
|
20
|
+
def initialize(path = 'flipper.pstore')
|
21
21
|
@path = path
|
22
22
|
@store = ::PStore.new(path)
|
23
23
|
@name = :pstore
|
@@ -55,14 +55,15 @@ module Flipper
|
|
55
55
|
result = {}
|
56
56
|
|
57
57
|
feature.gates.each do |gate|
|
58
|
-
result[gate.key] =
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
result[gate.key] =
|
59
|
+
case gate.data_type
|
60
|
+
when :boolean, :integer
|
61
|
+
read key(feature, gate)
|
62
|
+
when :set
|
63
|
+
set_members key(feature, gate)
|
64
|
+
else
|
65
|
+
raise "#{gate} is not supported by this adapter yet"
|
66
|
+
end
|
66
67
|
end
|
67
68
|
|
68
69
|
result
|
@@ -6,7 +6,7 @@ module Flipper
|
|
6
6
|
|
7
7
|
class WriteAttempted < Error
|
8
8
|
def initialize(message = nil)
|
9
|
-
super(message ||
|
9
|
+
super(message || 'write attempted while in read only mode')
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -27,23 +27,23 @@ module Flipper
|
|
27
27
|
@adapter.get(feature)
|
28
28
|
end
|
29
29
|
|
30
|
-
def add(
|
30
|
+
def add(_feature)
|
31
31
|
raise WriteAttempted
|
32
32
|
end
|
33
33
|
|
34
|
-
def remove(
|
34
|
+
def remove(_feature)
|
35
35
|
raise WriteAttempted
|
36
36
|
end
|
37
37
|
|
38
|
-
def clear(
|
38
|
+
def clear(_feature)
|
39
39
|
raise WriteAttempted
|
40
40
|
end
|
41
41
|
|
42
|
-
def enable(
|
42
|
+
def enable(_feature, _gate, _thing)
|
43
43
|
raise WriteAttempted
|
44
44
|
end
|
45
45
|
|
46
|
-
def disable(
|
46
|
+
def disable(_feature, _gate, _thing)
|
47
47
|
raise WriteAttempted
|
48
48
|
end
|
49
49
|
end
|
data/lib/flipper/dsl.rb
CHANGED
@@ -158,9 +158,7 @@ module Flipper
|
|
158
158
|
raise ArgumentError, "#{name} must be a String or Symbol"
|
159
159
|
end
|
160
160
|
|
161
|
-
@memoized_features[name.to_sym] ||= Feature.new(name, @adapter,
|
162
|
-
:instrumenter => instrumenter,
|
163
|
-
})
|
161
|
+
@memoized_features[name.to_sym] ||= Feature.new(name, @adapter, instrumenter: instrumenter)
|
164
162
|
end
|
165
163
|
|
166
164
|
# Public: Preload the features with the given names.
|
data/lib/flipper/feature.rb
CHANGED
@@ -41,7 +41,7 @@ module Flipper
|
|
41
41
|
#
|
42
42
|
# Returns the result of Adapter#enable.
|
43
43
|
def enable(thing = true)
|
44
|
-
instrument(:enable)
|
44
|
+
instrument(:enable) do |payload|
|
45
45
|
adapter.add self
|
46
46
|
|
47
47
|
gate = gate_for(thing)
|
@@ -50,14 +50,14 @@ module Flipper
|
|
50
50
|
payload[:thing] = wrapped_thing
|
51
51
|
|
52
52
|
adapter.enable self, gate, wrapped_thing
|
53
|
-
|
53
|
+
end
|
54
54
|
end
|
55
55
|
|
56
56
|
# Public: Disable this feature for something.
|
57
57
|
#
|
58
58
|
# Returns the result of Adapter#disable.
|
59
59
|
def disable(thing = false)
|
60
|
-
instrument(:disable)
|
60
|
+
instrument(:disable) do |payload|
|
61
61
|
adapter.add self
|
62
62
|
|
63
63
|
gate = gate_for(thing)
|
@@ -65,12 +65,8 @@ module Flipper
|
|
65
65
|
payload[:gate_name] = gate.name
|
66
66
|
payload[:thing] = wrapped_thing
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
else
|
71
|
-
adapter.disable self, gate, wrapped_thing
|
72
|
-
end
|
73
|
-
}
|
68
|
+
adapter.disable self, gate, wrapped_thing
|
69
|
+
end
|
74
70
|
end
|
75
71
|
|
76
72
|
# Public: Removes this feature.
|
@@ -84,14 +80,14 @@ module Flipper
|
|
84
80
|
#
|
85
81
|
# Returns true if enabled, false if not.
|
86
82
|
def enabled?(thing = nil)
|
87
|
-
instrument(:enabled?)
|
83
|
+
instrument(:enabled?) do |payload|
|
88
84
|
values = gate_values
|
89
85
|
thing = gate(:actor).wrap(thing) unless thing.nil?
|
90
86
|
payload[:thing] = thing
|
91
87
|
context = FeatureCheckContext.new(
|
92
88
|
feature_name: @name,
|
93
89
|
values: values,
|
94
|
-
thing: thing
|
90
|
+
thing: thing
|
95
91
|
)
|
96
92
|
|
97
93
|
if open_gate = gates.detect { |gate| gate.open?(context) }
|
@@ -100,7 +96,7 @@ module Flipper
|
|
100
96
|
else
|
101
97
|
false
|
102
98
|
end
|
103
|
-
|
99
|
+
end
|
104
100
|
end
|
105
101
|
|
106
102
|
# Public: Enables a feature for an actor.
|
@@ -346,19 +342,18 @@ module Flipper
|
|
346
342
|
# Returns a Flipper::Gate.
|
347
343
|
# Raises Flipper::GateNotFound if no gate found for thing
|
348
344
|
def gate_for(thing)
|
349
|
-
gates.detect { |gate| gate.protects?(thing) } ||
|
350
|
-
raise(GateNotFound.new(thing))
|
345
|
+
gates.detect { |gate| gate.protects?(thing) } || raise(GateNotFound, thing)
|
351
346
|
end
|
352
347
|
|
353
348
|
private
|
354
349
|
|
355
350
|
# Private: Instrument a feature operation.
|
356
351
|
def instrument(operation)
|
357
|
-
@instrumenter.instrument(InstrumentationName)
|
352
|
+
@instrumenter.instrument(InstrumentationName) do |payload|
|
358
353
|
payload[:feature_name] = name
|
359
354
|
payload[:operation] = operation
|
360
355
|
payload[:result] = yield(payload) if block_given?
|
361
|
-
|
356
|
+
end
|
362
357
|
end
|
363
358
|
end
|
364
359
|
end
|
data/lib/flipper/gate.rb
CHANGED
@@ -18,21 +18,21 @@ module Flipper
|
|
18
18
|
raise 'Not implemented'
|
19
19
|
end
|
20
20
|
|
21
|
-
def enabled?(
|
21
|
+
def enabled?(_value)
|
22
22
|
raise 'Not implemented'
|
23
23
|
end
|
24
24
|
|
25
25
|
# Internal: Check if a gate is open for a thing. Implemented in subclass.
|
26
26
|
#
|
27
27
|
# Returns true if gate open for thing, false if not.
|
28
|
-
def open?(
|
28
|
+
def open?(_thing, _value, _options = {})
|
29
29
|
false
|
30
30
|
end
|
31
31
|
|
32
32
|
# Internal: Check if a gate is protects a thing. Implemented in subclass.
|
33
33
|
#
|
34
34
|
# Returns true if gate protects thing, false if not.
|
35
|
-
def protects?(
|
35
|
+
def protects?(_thing)
|
36
36
|
false
|
37
37
|
end
|
38
38
|
|
data/lib/flipper/gate_values.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require
|
1
|
+
require 'set'
|
2
2
|
|
3
3
|
module Flipper
|
4
4
|
class GateValues
|
5
5
|
# Private: Array of instance variables that are readable through the []
|
6
6
|
# instance method.
|
7
7
|
LegitIvars = {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
'boolean' => '@boolean',
|
9
|
+
'actors' => '@actors',
|
10
|
+
'groups' => '@groups',
|
11
|
+
'percentage_of_time' => '@percentage_of_time',
|
12
|
+
'percentage_of_actors' => '@percentage_of_actors',
|
13
13
|
}.freeze
|
14
14
|
|
15
15
|
attr_reader :boolean
|
data/lib/flipper/gates/group.rb
CHANGED
@@ -27,14 +27,14 @@ module Flipper
|
|
27
27
|
if context.thing.nil?
|
28
28
|
false
|
29
29
|
else
|
30
|
-
value.any?
|
30
|
+
value.any? do |name|
|
31
31
|
begin
|
32
32
|
group = Flipper.group(name)
|
33
33
|
group.match?(context.thing, context)
|
34
34
|
rescue GroupNotRegistered
|
35
35
|
false
|
36
36
|
end
|
37
|
-
|
37
|
+
end
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -29,8 +29,8 @@ module Flipper
|
|
29
29
|
|
30
30
|
if Types::Actor.wrappable?(context.thing)
|
31
31
|
actor = Types::Actor.wrap(context.thing)
|
32
|
-
|
33
|
-
Zlib.crc32(
|
32
|
+
id = "#{context.feature_name}#{actor.value}"
|
33
|
+
Zlib.crc32(id) % 100 < percentage
|
34
34
|
else
|
35
35
|
false
|
36
36
|
end
|
@@ -25,9 +25,7 @@ module Flipper
|
|
25
25
|
description = "Flipper feature(#{feature_name}) #{operation} #{result.inspect}"
|
26
26
|
details = "thing=#{thing.inspect}"
|
27
27
|
|
28
|
-
unless gate_name.nil?
|
29
|
-
details += " gate_name=#{gate_name}"
|
30
|
-
end
|
28
|
+
details += " gate_name=#{gate_name}" unless gate_name.nil?
|
31
29
|
|
32
30
|
name = '%s (%.1fms)' % [description, event.duration]
|
33
31
|
debug " #{color(name, CYAN, true)} [ #{details} ]"
|
@@ -54,7 +52,7 @@ module Flipper
|
|
54
52
|
operation = event.payload[:operation]
|
55
53
|
result = event.payload[:result]
|
56
54
|
|
57
|
-
description =
|
55
|
+
description = 'Flipper '
|
58
56
|
description << "feature(#{feature_name}) " unless feature_name.nil?
|
59
57
|
description << "adapter(#{adapter_name}) "
|
60
58
|
description << "#{operation} "
|
@@ -17,12 +17,12 @@ module Flipper
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Internal: Override in subclass.
|
20
|
-
def update_timer(
|
20
|
+
def update_timer(_metric)
|
21
21
|
raise 'not implemented'
|
22
22
|
end
|
23
23
|
|
24
24
|
# Internal: Override in subclass.
|
25
|
-
def update_counter(
|
25
|
+
def update_counter(_metric)
|
26
26
|
raise 'not implemented'
|
27
27
|
end
|
28
28
|
|
@@ -34,7 +34,8 @@ module Flipper
|
|
34
34
|
if respond_to?(method_name)
|
35
35
|
send(method_name)
|
36
36
|
else
|
37
|
-
puts "Could not update #{operation_type} metrics as #{self.class}
|
37
|
+
puts "Could not update #{operation_type} metrics as #{self.class} " \
|
38
|
+
"did not respond to `#{method_name}`"
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
@@ -49,11 +50,12 @@ module Flipper
|
|
49
50
|
update_timer "flipper.feature_operation.#{operation}"
|
50
51
|
|
51
52
|
if @payload[:operation] == :enabled?
|
52
|
-
metric_name =
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
metric_name =
|
54
|
+
if result
|
55
|
+
"flipper.feature.#{feature_name}.enabled"
|
56
|
+
else
|
57
|
+
"flipper.feature.#{feature_name}.disabled"
|
58
|
+
end
|
57
59
|
|
58
60
|
update_counter metric_name
|
59
61
|
end
|
@@ -67,11 +69,10 @@ module Flipper
|
|
67
69
|
value = @payload[:value]
|
68
70
|
key = @payload[:key]
|
69
71
|
|
70
|
-
|
71
72
|
update_timer "flipper.adapter.#{adapter_name}.#{operation}"
|
72
73
|
end
|
73
74
|
|
74
|
-
QUESTION_MARK =
|
75
|
+
QUESTION_MARK = '?'.freeze
|
75
76
|
|
76
77
|
# Private
|
77
78
|
def strip_trailing_question_mark(operation)
|
@@ -17,11 +17,7 @@ module Flipper
|
|
17
17
|
# block rather than the one passed to #instrument.
|
18
18
|
payload = payload.dup
|
19
19
|
|
20
|
-
result = if block_given?
|
21
|
-
yield payload
|
22
|
-
else
|
23
|
-
nil
|
24
|
-
end
|
20
|
+
result = (yield payload if block_given?)
|
25
21
|
@events << Event.new(name, payload, result)
|
26
22
|
result
|
27
23
|
end
|
@@ -3,43 +3,29 @@ require 'rack/body_proxy'
|
|
3
3
|
module Flipper
|
4
4
|
module Middleware
|
5
5
|
class Memoizer
|
6
|
-
# Public: Initializes an instance of the Memoizer middleware.
|
6
|
+
# Public: Initializes an instance of the Memoizer middleware. The flipper
|
7
|
+
# instance must be setup in the env of the request. You can do this by
|
8
|
+
# using the Flipper::Middleware::SetupEnv middleware.
|
7
9
|
#
|
8
10
|
# app - The app this middleware is included in.
|
9
|
-
# flipper_or_block - The Flipper::DSL instance or a block that yields a
|
10
|
-
# Flipper::DSL instance to use for all operations.
|
11
11
|
#
|
12
12
|
# Examples
|
13
13
|
#
|
14
|
-
#
|
15
|
-
# flipper = Flipper.new(...)
|
16
|
-
# use Flipper::Middleware::Memoizer, flipper
|
17
|
-
#
|
18
|
-
# # using with a block that yields a flipper instance
|
19
|
-
# use Flipper::Middleware::Memoizer, lambda { Flipper.new(...) }
|
14
|
+
# use Flipper::Middleware::Memoizer
|
20
15
|
#
|
21
16
|
# # using with preload_all features
|
22
|
-
# use Flipper::Middleware::Memoizer,
|
17
|
+
# use Flipper::Middleware::Memoizer, preload_all: true
|
23
18
|
#
|
24
19
|
# # using with preload specific features
|
25
|
-
# use Flipper::Middleware::Memoizer,
|
20
|
+
# use Flipper::Middleware::Memoizer, preload: [:stats, :search, :some_feature]
|
26
21
|
#
|
27
|
-
def initialize(app,
|
22
|
+
def initialize(app, opts = {})
|
28
23
|
@app = app
|
29
24
|
@opts = opts
|
30
|
-
|
31
|
-
if flipper_or_block.respond_to?(:call)
|
32
|
-
@flipper_block = flipper_or_block
|
33
|
-
else
|
34
|
-
@flipper = flipper_or_block
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def flipper
|
39
|
-
@flipper ||= @flipper_block.call
|
40
25
|
end
|
41
26
|
|
42
27
|
def call(env)
|
28
|
+
flipper = env.fetch('flipper')
|
43
29
|
original = flipper.adapter.memoizing?
|
44
30
|
flipper.adapter.memoize = true
|
45
31
|
|
@@ -48,14 +34,12 @@ module Flipper
|
|
48
34
|
flipper.preload(names)
|
49
35
|
end
|
50
36
|
|
51
|
-
if @opts[:preload]
|
52
|
-
flipper.preload(@opts[:preload])
|
53
|
-
end
|
37
|
+
flipper.preload(@opts[:preload]) if @opts[:preload]
|
54
38
|
|
55
39
|
response = @app.call(env)
|
56
|
-
response[2] = Rack::BodyProxy.new(response[2])
|
40
|
+
response[2] = Rack::BodyProxy.new(response[2]) do
|
57
41
|
flipper.adapter.memoize = original
|
58
|
-
|
42
|
+
end
|
59
43
|
response
|
60
44
|
rescue
|
61
45
|
flipper.adapter.memoize = original
|