flipper 0.28.0 → 1.3.6
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.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +50 -7
- data/.github/workflows/examples.yml +52 -10
- data/CLAUDE.md +74 -0
- data/Changelog.md +1 -526
- data/Gemfile +17 -8
- data/README.md +31 -27
- data/Rakefile +2 -2
- data/benchmark/typecast_ips.rb +8 -0
- data/docs/images/banner.jpg +0 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/app.ru +12 -0
- data/examples/cloud/backoff_policy.rb +13 -0
- data/examples/cloud/basic.rb +22 -0
- data/examples/cloud/cloud_setup.rb +20 -0
- data/examples/cloud/forked.rb +36 -0
- data/examples/cloud/import.rb +17 -0
- data/examples/cloud/threaded.rb +33 -0
- data/examples/dsl.rb +0 -14
- data/examples/expressions.rb +213 -0
- data/examples/mirroring.rb +59 -0
- data/examples/strict.rb +18 -0
- data/exe/flipper +5 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +8 -4
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +10 -0
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/actor_limit.rb +28 -0
- data/lib/flipper/adapters/cache_base.rb +143 -0
- data/lib/flipper/adapters/dual_write.rb +1 -3
- data/lib/flipper/adapters/failover.rb +0 -4
- data/lib/flipper/adapters/failsafe.rb +0 -4
- data/lib/flipper/adapters/http/client.rb +40 -12
- data/lib/flipper/adapters/http/error.rb +2 -2
- data/lib/flipper/adapters/http.rb +19 -14
- data/lib/flipper/adapters/instrumented.rb +0 -4
- data/lib/flipper/adapters/memoizable.rb +14 -19
- data/lib/flipper/adapters/memory.rb +37 -19
- data/lib/flipper/adapters/operation_logger.rb +18 -92
- data/lib/flipper/adapters/poll.rb +16 -3
- data/lib/flipper/adapters/pstore.rb +17 -11
- data/lib/flipper/adapters/read_only.rb +8 -41
- data/lib/flipper/adapters/strict.rb +45 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +10 -1
- data/lib/flipper/adapters/sync.rb +0 -4
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +263 -0
- data/lib/flipper/cloud/configuration.rb +266 -0
- data/lib/flipper/cloud/dsl.rb +27 -0
- data/lib/flipper/cloud/message_verifier.rb +95 -0
- data/lib/flipper/cloud/middleware.rb +63 -0
- data/lib/flipper/cloud/routes.rb +14 -0
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
- data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
- data/lib/flipper/cloud/telemetry/metric.rb +39 -0
- data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
- data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
- data/lib/flipper/cloud/telemetry.rb +191 -0
- data/lib/flipper/cloud.rb +53 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +47 -42
- data/lib/flipper/engine.rb +102 -0
- data/lib/flipper/export.rb +0 -2
- data/lib/flipper/exporters/json/export.rb +1 -1
- data/lib/flipper/exporters/json/v1.rb +1 -1
- data/lib/flipper/expression/builder.rb +73 -0
- data/lib/flipper/expression/constant.rb +25 -0
- data/lib/flipper/expression.rb +71 -0
- data/lib/flipper/expressions/all.rb +9 -0
- data/lib/flipper/expressions/any.rb +9 -0
- data/lib/flipper/expressions/boolean.rb +9 -0
- data/lib/flipper/expressions/comparable.rb +13 -0
- data/lib/flipper/expressions/duration.rb +28 -0
- data/lib/flipper/expressions/equal.rb +9 -0
- data/lib/flipper/expressions/greater_than.rb +9 -0
- data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/less_than.rb +9 -0
- data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/not_equal.rb +9 -0
- data/lib/flipper/expressions/now.rb +9 -0
- data/lib/flipper/expressions/number.rb +9 -0
- data/lib/flipper/expressions/percentage.rb +9 -0
- data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
- data/lib/flipper/expressions/property.rb +9 -0
- data/lib/flipper/expressions/random.rb +9 -0
- data/lib/flipper/expressions/string.rb +9 -0
- data/lib/flipper/expressions/time.rb +9 -0
- data/lib/flipper/feature.rb +63 -1
- data/lib/flipper/gate.rb +2 -1
- data/lib/flipper/gate_values.rb +5 -2
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/instrumentation/log_subscriber.rb +28 -5
- data/lib/flipper/instrumentation/statsd.rb +4 -2
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +0 -4
- data/lib/flipper/metadata.rb +8 -1
- data/lib/flipper/middleware/memoizer.rb +30 -14
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +10 -9
- data/lib/flipper/serializers/gzip.rb +22 -0
- data/lib/flipper/serializers/json.rb +17 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
- data/lib/flipper/test/shared_adapter_test.rb +77 -58
- data/lib/flipper/test_help.rb +43 -0
- data/lib/flipper/typecast.rb +37 -9
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +11 -1
- data/lib/flipper.rb +44 -7
- data/lib/generators/flipper/setup_generator.rb +68 -0
- data/lib/generators/flipper/templates/initializer.rb +45 -0
- data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
- data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
- data/lib/generators/flipper/update_generator.rb +35 -0
- data/package-lock.json +41 -0
- data/package.json +10 -0
- data/spec/fixtures/environment.rb +1 -0
- data/spec/flipper/adapter_builder_spec.rb +72 -0
- data/spec/flipper/adapter_spec.rb +1 -0
- data/spec/flipper/adapters/actor_limit_spec.rb +20 -0
- data/spec/flipper/adapters/dual_write_spec.rb +2 -2
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +135 -74
- data/spec/flipper/adapters/instrumented_spec.rb +1 -1
- data/spec/flipper/adapters/memoizable_spec.rb +21 -21
- data/spec/flipper/adapters/memory_spec.rb +11 -2
- data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- data/spec/flipper/adapters/read_only_spec.rb +32 -17
- data/spec/flipper/adapters/strict_spec.rb +64 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +27 -0
- data/spec/flipper/cli_spec.rb +166 -0
- data/spec/flipper/cloud/configuration_spec.rb +251 -0
- data/spec/flipper/cloud/dsl_spec.rb +82 -0
- data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
- data/spec/flipper/cloud/middleware_spec.rb +289 -0
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
- data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
- data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
- data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
- data/spec/flipper/cloud/telemetry_spec.rb +208 -0
- data/spec/flipper/cloud_spec.rb +186 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +34 -73
- data/spec/flipper/engine_spec.rb +374 -0
- data/spec/flipper/exporters/json/v1_spec.rb +3 -3
- data/spec/flipper/expression/builder_spec.rb +248 -0
- data/spec/flipper/expression_spec.rb +188 -0
- data/spec/flipper/expressions/all_spec.rb +15 -0
- data/spec/flipper/expressions/any_spec.rb +15 -0
- data/spec/flipper/expressions/boolean_spec.rb +15 -0
- data/spec/flipper/expressions/duration_spec.rb +43 -0
- data/spec/flipper/expressions/equal_spec.rb +24 -0
- data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/greater_than_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_spec.rb +32 -0
- data/spec/flipper/expressions/not_equal_spec.rb +15 -0
- data/spec/flipper/expressions/now_spec.rb +11 -0
- data/spec/flipper/expressions/number_spec.rb +21 -0
- data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
- data/spec/flipper/expressions/percentage_spec.rb +15 -0
- data/spec/flipper/expressions/property_spec.rb +13 -0
- data/spec/flipper/expressions/random_spec.rb +9 -0
- data/spec/flipper/expressions/string_spec.rb +11 -0
- data/spec/flipper/expressions/time_spec.rb +13 -0
- data/spec/flipper/feature_spec.rb +380 -10
- data/spec/flipper/gate_values_spec.rb +2 -2
- data/spec/flipper/gates/expression_spec.rb +108 -0
- data/spec/flipper/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
- data/spec/flipper/middleware/memoizer_spec.rb +79 -10
- data/spec/flipper/model/active_record_spec.rb +72 -0
- data/spec/flipper/serializers/gzip_spec.rb +13 -0
- data/spec/flipper/serializers/json_spec.rb +13 -0
- data/spec/flipper/typecast_spec.rb +43 -7
- data/spec/flipper/types/actor_spec.rb +18 -1
- data/spec/flipper_integration_spec.rb +114 -16
- data/spec/flipper_spec.rb +87 -29
- data/spec/spec_helper.rb +17 -17
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/fail_on_output.rb +8 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/spec_helpers.rb +34 -8
- data/test/adapters/actor_limit_test.rb +20 -0
- data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
- data/test_rails/generators/flipper/update_generator_test.rb +96 -0
- data/test_rails/helper.rb +22 -2
- data/test_rails/system/test_help_test.rb +52 -0
- metadata +179 -19
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
|
@@ -8,35 +8,25 @@ module Flipper
|
|
|
8
8
|
class Memoizable
|
|
9
9
|
include ::Flipper::Adapter
|
|
10
10
|
|
|
11
|
-
FeaturesKey = :flipper_features
|
|
12
|
-
GetAllKey = :all_memoized
|
|
13
|
-
|
|
14
11
|
# Internal
|
|
15
12
|
attr_reader :cache
|
|
16
13
|
|
|
17
|
-
# Public: The name of the adapter.
|
|
18
|
-
attr_reader :name
|
|
19
|
-
|
|
20
14
|
# Internal: The adapter this adapter is wrapping.
|
|
21
15
|
attr_reader :adapter
|
|
22
16
|
|
|
23
|
-
# Private
|
|
24
|
-
def self.key_for(key)
|
|
25
|
-
"feature/#{key}"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
17
|
# Public
|
|
29
18
|
def initialize(adapter, cache = nil)
|
|
30
19
|
@adapter = adapter
|
|
31
|
-
@name = :memoizable
|
|
32
20
|
@cache = cache || {}
|
|
33
21
|
@memoize = false
|
|
22
|
+
@features_key = :flipper_features
|
|
23
|
+
@get_all_key = :all_memoized
|
|
34
24
|
end
|
|
35
25
|
|
|
36
26
|
# Public
|
|
37
27
|
def features
|
|
38
28
|
if memoizing?
|
|
39
|
-
cache.fetch(
|
|
29
|
+
cache.fetch(@features_key) { cache[@features_key] = @adapter.features }
|
|
40
30
|
else
|
|
41
31
|
@adapter.features
|
|
42
32
|
end
|
|
@@ -94,9 +84,9 @@ module Flipper
|
|
|
94
84
|
def get_all
|
|
95
85
|
if memoizing?
|
|
96
86
|
response = nil
|
|
97
|
-
if cache[
|
|
87
|
+
if cache[@get_all_key]
|
|
98
88
|
response = {}
|
|
99
|
-
cache[
|
|
89
|
+
cache[@features_key].each do |key|
|
|
100
90
|
response[key] = cache[key_for(key)]
|
|
101
91
|
end
|
|
102
92
|
else
|
|
@@ -104,8 +94,8 @@ module Flipper
|
|
|
104
94
|
response.each do |key, value|
|
|
105
95
|
cache[key_for(key)] = value
|
|
106
96
|
end
|
|
107
|
-
cache[
|
|
108
|
-
cache[
|
|
97
|
+
cache[@features_key] = response.keys.to_set
|
|
98
|
+
cache[@get_all_key] = true
|
|
109
99
|
end
|
|
110
100
|
|
|
111
101
|
# Ensures that looking up other features that do not exist doesn't
|
|
@@ -127,6 +117,11 @@ module Flipper
|
|
|
127
117
|
@adapter.disable(feature, gate, thing).tap { expire_feature(feature) }
|
|
128
118
|
end
|
|
129
119
|
|
|
120
|
+
# Public
|
|
121
|
+
def read_only?
|
|
122
|
+
@adapter.read_only?
|
|
123
|
+
end
|
|
124
|
+
|
|
130
125
|
def import(source)
|
|
131
126
|
@adapter.import(source).tap { cache.clear if memoizing? }
|
|
132
127
|
end
|
|
@@ -161,7 +156,7 @@ module Flipper
|
|
|
161
156
|
private
|
|
162
157
|
|
|
163
158
|
def key_for(key)
|
|
164
|
-
|
|
159
|
+
"feature/#{key}"
|
|
165
160
|
end
|
|
166
161
|
|
|
167
162
|
def expire_feature(feature)
|
|
@@ -169,7 +164,7 @@ module Flipper
|
|
|
169
164
|
end
|
|
170
165
|
|
|
171
166
|
def expire_features_set
|
|
172
|
-
cache.delete(
|
|
167
|
+
cache.delete(@features_key) if memoizing?
|
|
173
168
|
end
|
|
174
169
|
end
|
|
175
170
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require "flipper/adapter"
|
|
2
2
|
require "flipper/typecast"
|
|
3
|
-
require 'concurrent/atomic/read_write_lock'
|
|
4
3
|
|
|
5
4
|
module Flipper
|
|
6
5
|
module Adapters
|
|
@@ -9,49 +8,44 @@ module Flipper
|
|
|
9
8
|
class Memory
|
|
10
9
|
include ::Flipper::Adapter
|
|
11
10
|
|
|
12
|
-
FeaturesKey = :features
|
|
13
|
-
|
|
14
|
-
# Public: The name of the adapter.
|
|
15
|
-
attr_reader :name
|
|
16
|
-
|
|
17
11
|
# Public
|
|
18
|
-
def initialize(source = nil)
|
|
12
|
+
def initialize(source = nil, threadsafe: true)
|
|
19
13
|
@source = Typecast.features_hash(source)
|
|
20
|
-
@
|
|
21
|
-
|
|
14
|
+
@lock = Mutex.new if threadsafe
|
|
15
|
+
reset
|
|
22
16
|
end
|
|
23
17
|
|
|
24
18
|
# Public: The set of known features.
|
|
25
19
|
def features
|
|
26
|
-
|
|
20
|
+
synchronize { @source.keys }.to_set
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
# Public: Adds a feature to the set of known features.
|
|
30
24
|
def add(feature)
|
|
31
|
-
|
|
25
|
+
synchronize { @source[feature.key] ||= default_config }
|
|
32
26
|
true
|
|
33
27
|
end
|
|
34
28
|
|
|
35
29
|
# Public: Removes a feature from the set of known features and clears
|
|
36
30
|
# all the values for the feature.
|
|
37
31
|
def remove(feature)
|
|
38
|
-
|
|
32
|
+
synchronize { @source.delete(feature.key) }
|
|
39
33
|
true
|
|
40
34
|
end
|
|
41
35
|
|
|
42
36
|
# Public: Clears all the gate values for a feature.
|
|
43
37
|
def clear(feature)
|
|
44
|
-
|
|
38
|
+
synchronize { @source[feature.key] = default_config }
|
|
45
39
|
true
|
|
46
40
|
end
|
|
47
41
|
|
|
48
42
|
# Public
|
|
49
43
|
def get(feature)
|
|
50
|
-
|
|
44
|
+
synchronize { @source[feature.key] } || default_config
|
|
51
45
|
end
|
|
52
46
|
|
|
53
47
|
def get_multi(features)
|
|
54
|
-
|
|
48
|
+
synchronize do
|
|
55
49
|
result = {}
|
|
56
50
|
features.each do |feature|
|
|
57
51
|
result[feature.key] = @source[feature.key] || default_config
|
|
@@ -61,12 +55,12 @@ module Flipper
|
|
|
61
55
|
end
|
|
62
56
|
|
|
63
57
|
def get_all
|
|
64
|
-
|
|
58
|
+
synchronize { Typecast.features_hash(@source) }
|
|
65
59
|
end
|
|
66
60
|
|
|
67
61
|
# Public
|
|
68
62
|
def enable(feature, gate, thing)
|
|
69
|
-
|
|
63
|
+
synchronize do
|
|
70
64
|
@source[feature.key] ||= default_config
|
|
71
65
|
|
|
72
66
|
case gate.data_type
|
|
@@ -77,6 +71,8 @@ module Flipper
|
|
|
77
71
|
@source[feature.key][gate.key] = thing.value.to_s
|
|
78
72
|
when :set
|
|
79
73
|
@source[feature.key][gate.key] << thing.value.to_s
|
|
74
|
+
when :json
|
|
75
|
+
@source[feature.key][gate.key] = thing.value
|
|
80
76
|
else
|
|
81
77
|
raise "#{gate} is not supported by this adapter yet"
|
|
82
78
|
end
|
|
@@ -87,7 +83,7 @@ module Flipper
|
|
|
87
83
|
|
|
88
84
|
# Public
|
|
89
85
|
def disable(feature, gate, thing)
|
|
90
|
-
|
|
86
|
+
synchronize do
|
|
91
87
|
@source[feature.key] ||= default_config
|
|
92
88
|
|
|
93
89
|
case gate.data_type
|
|
@@ -97,6 +93,8 @@ module Flipper
|
|
|
97
93
|
@source[feature.key][gate.key] = thing.value.to_s
|
|
98
94
|
when :set
|
|
99
95
|
@source[feature.key][gate.key].delete thing.value.to_s
|
|
96
|
+
when :json
|
|
97
|
+
@source[feature.key].delete(gate.key)
|
|
100
98
|
else
|
|
101
99
|
raise "#{gate} is not supported by this adapter yet"
|
|
102
100
|
end
|
|
@@ -118,9 +116,29 @@ module Flipper
|
|
|
118
116
|
def import(source)
|
|
119
117
|
adapter = self.class.from(source)
|
|
120
118
|
get_all = Typecast.features_hash(adapter.get_all)
|
|
121
|
-
|
|
119
|
+
synchronize { @source.replace(get_all) }
|
|
122
120
|
true
|
|
123
121
|
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def reset
|
|
126
|
+
@pid = Process.pid
|
|
127
|
+
@lock&.unlock if @lock&.locked?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def forked?
|
|
131
|
+
@pid != Process.pid
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def synchronize(&block)
|
|
135
|
+
if @lock
|
|
136
|
+
reset if forked?
|
|
137
|
+
@lock.synchronize(&block)
|
|
138
|
+
else
|
|
139
|
+
block.call
|
|
140
|
+
end
|
|
141
|
+
end
|
|
124
142
|
end
|
|
125
143
|
end
|
|
126
144
|
end
|
|
@@ -5,115 +5,34 @@ module Flipper
|
|
|
5
5
|
# Public: Adapter that wraps another adapter and stores the operations.
|
|
6
6
|
#
|
|
7
7
|
# Useful in tests to verify calls and such. Never use outside of testing.
|
|
8
|
-
class OperationLogger
|
|
9
|
-
include Flipper::Adapter
|
|
8
|
+
class OperationLogger < Wrapper
|
|
10
9
|
|
|
11
10
|
class Operation
|
|
12
|
-
attr_reader :type, :args
|
|
11
|
+
attr_reader :type, :args, :kwargs
|
|
13
12
|
|
|
14
|
-
def initialize(type, args)
|
|
13
|
+
def initialize(type, args, kwargs = {})
|
|
15
14
|
@type = type
|
|
16
15
|
@args = args
|
|
16
|
+
@kwargs = kwargs
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
OperationTypes = [
|
|
21
|
-
:import,
|
|
22
|
-
:export,
|
|
23
|
-
:features,
|
|
24
|
-
:add,
|
|
25
|
-
:remove,
|
|
26
|
-
:clear,
|
|
27
|
-
:get,
|
|
28
|
-
:get_multi,
|
|
29
|
-
:get_all,
|
|
30
|
-
:enable,
|
|
31
|
-
:disable,
|
|
32
|
-
].freeze
|
|
33
|
-
|
|
34
20
|
# Internal: An array of the operations that have happened.
|
|
35
21
|
attr_reader :operations
|
|
36
22
|
|
|
37
|
-
# Internal: The name of the adapter.
|
|
38
|
-
attr_reader :name
|
|
39
|
-
|
|
40
23
|
# Public
|
|
41
24
|
def initialize(adapter, operations = nil)
|
|
42
|
-
|
|
43
|
-
@name = :operation_logger
|
|
25
|
+
super(adapter)
|
|
44
26
|
@operations = operations || []
|
|
45
27
|
end
|
|
46
28
|
|
|
47
|
-
# Public: The set of known features.
|
|
48
|
-
def features
|
|
49
|
-
@operations << Operation.new(:features, [])
|
|
50
|
-
@adapter.features
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Public: Adds a feature to the set of known features.
|
|
54
|
-
def add(feature)
|
|
55
|
-
@operations << Operation.new(:add, [feature])
|
|
56
|
-
@adapter.add(feature)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Public: Removes a feature from the set of known features and clears
|
|
60
|
-
# all the values for the feature.
|
|
61
|
-
def remove(feature)
|
|
62
|
-
@operations << Operation.new(:remove, [feature])
|
|
63
|
-
@adapter.remove(feature)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Public: Clears all the gate values for a feature.
|
|
67
|
-
def clear(feature)
|
|
68
|
-
@operations << Operation.new(:clear, [feature])
|
|
69
|
-
@adapter.clear(feature)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Public
|
|
73
|
-
def get(feature)
|
|
74
|
-
@operations << Operation.new(:get, [feature])
|
|
75
|
-
@adapter.get(feature)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Public
|
|
79
|
-
def get_multi(features)
|
|
80
|
-
@operations << Operation.new(:get_multi, [features])
|
|
81
|
-
@adapter.get_multi(features)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Public
|
|
85
|
-
def get_all
|
|
86
|
-
@operations << Operation.new(:get_all, [])
|
|
87
|
-
@adapter.get_all
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Public
|
|
91
|
-
def enable(feature, gate, thing)
|
|
92
|
-
@operations << Operation.new(:enable, [feature, gate, thing])
|
|
93
|
-
@adapter.enable(feature, gate, thing)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Public
|
|
97
|
-
def disable(feature, gate, thing)
|
|
98
|
-
@operations << Operation.new(:disable, [feature, gate, thing])
|
|
99
|
-
@adapter.disable(feature, gate, thing)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Public
|
|
103
|
-
def import(source)
|
|
104
|
-
@operations << Operation.new(:import, [source])
|
|
105
|
-
@adapter.import(source)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Public
|
|
109
|
-
def export(format: :json, version: 1)
|
|
110
|
-
@operations << Operation.new(:export, [format, version])
|
|
111
|
-
@adapter.export(format: format, version: version)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
29
|
# Public: Count the number of times a certain operation happened.
|
|
115
|
-
def count(type)
|
|
116
|
-
type
|
|
30
|
+
def count(type = nil)
|
|
31
|
+
if type
|
|
32
|
+
type(type).size
|
|
33
|
+
else
|
|
34
|
+
@operations.size
|
|
35
|
+
end
|
|
117
36
|
end
|
|
118
37
|
|
|
119
38
|
# Public: Get all operations of a certain type.
|
|
@@ -135,6 +54,13 @@ module Flipper
|
|
|
135
54
|
inspect_id = ::Kernel::format "%x", (object_id * 2)
|
|
136
55
|
%(#<#{self.class}:0x#{inspect_id} @name=#{name.inspect}, @operations=#{@operations.inspect}, @adapter=#{@adapter.inspect}>)
|
|
137
56
|
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def wrap(method, *args, **kwargs, &block)
|
|
61
|
+
@operations << Operation.new(method, args, kwargs)
|
|
62
|
+
block.call
|
|
63
|
+
end
|
|
138
64
|
end
|
|
139
65
|
end
|
|
140
66
|
end
|
|
@@ -10,16 +10,29 @@ module Flipper
|
|
|
10
10
|
# Deprecated
|
|
11
11
|
Poller = ::Flipper::Poller
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
attr_reader :name, :adapter, :poller
|
|
13
|
+
attr_reader :adapter, :poller
|
|
15
14
|
|
|
16
15
|
def_delegators :synced_adapter, :features, :get, :get_multi, :get_all, :add, :remove, :clear, :enable, :disable
|
|
17
16
|
|
|
18
17
|
def initialize(poller, adapter)
|
|
19
|
-
@name = :poll
|
|
20
18
|
@adapter = adapter
|
|
21
19
|
@poller = poller
|
|
22
20
|
@last_synced_at = 0
|
|
21
|
+
|
|
22
|
+
# If the adapter is empty, we need to sync before starting the poller.
|
|
23
|
+
# Yes, this will block the main thread, but that's better than thinking
|
|
24
|
+
# nothing is enabled.
|
|
25
|
+
if adapter.features.empty?
|
|
26
|
+
begin
|
|
27
|
+
@poller.sync
|
|
28
|
+
rescue
|
|
29
|
+
# TODO: Warn here that it's possible that no data has been synced
|
|
30
|
+
# and flags are being evaluated without flag data being present
|
|
31
|
+
# until a sync completes. We rescue to avoid flipper being down
|
|
32
|
+
# causing your processes to crash.
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
23
36
|
@poller.start
|
|
24
37
|
end
|
|
25
38
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'json'
|
|
1
2
|
require 'pstore'
|
|
2
3
|
require 'set'
|
|
3
4
|
require 'flipper'
|
|
@@ -9,19 +10,14 @@ module Flipper
|
|
|
9
10
|
class PStore
|
|
10
11
|
include ::Flipper::Adapter
|
|
11
12
|
|
|
12
|
-
FeaturesKey = :flipper_features
|
|
13
|
-
|
|
14
|
-
# Public: The name of the adapter.
|
|
15
|
-
attr_reader :name
|
|
16
|
-
|
|
17
13
|
# Public: The path to where the file is stored.
|
|
18
14
|
attr_reader :path
|
|
19
15
|
|
|
20
16
|
# Public
|
|
21
17
|
def initialize(path = 'flipper.pstore', thread_safe = true)
|
|
22
|
-
@name = :pstore
|
|
23
18
|
@path = path
|
|
24
19
|
@store = ::PStore.new(path, thread_safe)
|
|
20
|
+
@features_key = :flipper_features
|
|
25
21
|
end
|
|
26
22
|
|
|
27
23
|
# Public: The set of known features.
|
|
@@ -34,7 +30,7 @@ module Flipper
|
|
|
34
30
|
# Public: Adds a feature to the set of known features.
|
|
35
31
|
def add(feature)
|
|
36
32
|
@store.transaction do
|
|
37
|
-
set_add
|
|
33
|
+
set_add @features_key, feature.key
|
|
38
34
|
end
|
|
39
35
|
true
|
|
40
36
|
end
|
|
@@ -43,7 +39,7 @@ module Flipper
|
|
|
43
39
|
# all the values for the feature.
|
|
44
40
|
def remove(feature)
|
|
45
41
|
@store.transaction do
|
|
46
|
-
set_delete
|
|
42
|
+
set_delete @features_key, feature.key
|
|
47
43
|
clear_gates(feature)
|
|
48
44
|
end
|
|
49
45
|
true
|
|
@@ -88,6 +84,8 @@ module Flipper
|
|
|
88
84
|
write key(feature, gate), thing.value.to_s
|
|
89
85
|
when :set
|
|
90
86
|
set_add key(feature, gate), thing.value.to_s
|
|
87
|
+
when :json
|
|
88
|
+
write key(feature, gate), Typecast.to_json(thing.value)
|
|
91
89
|
else
|
|
92
90
|
raise "#{gate} is not supported by this adapter yet"
|
|
93
91
|
end
|
|
@@ -109,6 +107,10 @@ module Flipper
|
|
|
109
107
|
@store.transaction do
|
|
110
108
|
set_delete key(feature, gate), thing.value.to_s
|
|
111
109
|
end
|
|
110
|
+
when :json
|
|
111
|
+
@store.transaction do
|
|
112
|
+
delete key(feature, gate)
|
|
113
|
+
end
|
|
112
114
|
else
|
|
113
115
|
raise "#{gate} is not supported by this adapter yet"
|
|
114
116
|
end
|
|
@@ -135,7 +137,7 @@ module Flipper
|
|
|
135
137
|
end
|
|
136
138
|
|
|
137
139
|
def read_feature_keys
|
|
138
|
-
set_members
|
|
140
|
+
set_members @features_key
|
|
139
141
|
end
|
|
140
142
|
|
|
141
143
|
def read_many_features(features)
|
|
@@ -150,12 +152,16 @@ module Flipper
|
|
|
150
152
|
result = {}
|
|
151
153
|
|
|
152
154
|
feature.gates.each do |gate|
|
|
155
|
+
key = key(feature, gate)
|
|
153
156
|
result[gate.key] =
|
|
154
157
|
case gate.data_type
|
|
155
158
|
when :boolean, :integer
|
|
156
|
-
read key
|
|
159
|
+
read key
|
|
157
160
|
when :set
|
|
158
|
-
set_members key
|
|
161
|
+
set_members key
|
|
162
|
+
when :json
|
|
163
|
+
value = read(key)
|
|
164
|
+
Typecast.from_json(value)
|
|
159
165
|
else
|
|
160
166
|
raise "#{gate} is not supported by this adapter yet"
|
|
161
167
|
end
|
|
@@ -3,8 +3,8 @@ require 'flipper'
|
|
|
3
3
|
module Flipper
|
|
4
4
|
module Adapters
|
|
5
5
|
# Public: Adapter that wraps another adapter and raises for any writes.
|
|
6
|
-
class ReadOnly
|
|
7
|
-
|
|
6
|
+
class ReadOnly < Wrapper
|
|
7
|
+
WRITE_METHODS = %i[add remove clear enable disable]
|
|
8
8
|
|
|
9
9
|
class WriteAttempted < Error
|
|
10
10
|
def initialize(message = nil)
|
|
@@ -12,49 +12,16 @@ module Flipper
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# Public
|
|
19
|
-
def initialize(adapter)
|
|
20
|
-
@adapter = adapter
|
|
21
|
-
@name = :read_only
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def features
|
|
25
|
-
@adapter.features
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def get(feature)
|
|
29
|
-
@adapter.get(feature)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def get_multi(features)
|
|
33
|
-
@adapter.get_multi(features)
|
|
15
|
+
def read_only?
|
|
16
|
+
true
|
|
34
17
|
end
|
|
35
18
|
|
|
36
|
-
|
|
37
|
-
@adapter.get_all
|
|
38
|
-
end
|
|
19
|
+
private
|
|
39
20
|
|
|
40
|
-
def
|
|
41
|
-
raise WriteAttempted
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def remove(_feature)
|
|
45
|
-
raise WriteAttempted
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def clear(_feature)
|
|
49
|
-
raise WriteAttempted
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def enable(_feature, _gate, _thing)
|
|
53
|
-
raise WriteAttempted
|
|
54
|
-
end
|
|
21
|
+
def wrap(method, *args, **kwargs)
|
|
22
|
+
raise WriteAttempted if WRITE_METHODS.include?(method)
|
|
55
23
|
|
|
56
|
-
|
|
57
|
-
raise WriteAttempted
|
|
24
|
+
yield
|
|
58
25
|
end
|
|
59
26
|
end
|
|
60
27
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
module Adapters
|
|
3
|
+
# An adapter that ensures a feature exists before checking it.
|
|
4
|
+
class Strict < Wrapper
|
|
5
|
+
attr_reader :handler
|
|
6
|
+
|
|
7
|
+
class NotFound < ::Flipper::Error
|
|
8
|
+
def initialize(name)
|
|
9
|
+
super "Could not find feature #{name.inspect}. Call `Flipper.add(#{name.inspect})` to create it."
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(adapter, handler = nil, &block)
|
|
14
|
+
super(adapter)
|
|
15
|
+
@handler = block || handler
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(feature)
|
|
19
|
+
assert_feature_exists(feature)
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_multi(features)
|
|
24
|
+
features.each { |feature| assert_feature_exists(feature) }
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def assert_feature_exists(feature)
|
|
31
|
+
return if @adapter.features.include?(feature.key)
|
|
32
|
+
|
|
33
|
+
case handler
|
|
34
|
+
when Proc then handler.call(feature)
|
|
35
|
+
when :warn then warn NotFound.new(feature.key).message
|
|
36
|
+
when :noop, false, nil
|
|
37
|
+
# noop
|
|
38
|
+
else # truthy or :raise
|
|
39
|
+
raise NotFound.new(feature.key)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -9,6 +9,7 @@ module Flipper
|
|
|
9
9
|
class FeatureSynchronizer
|
|
10
10
|
extend Forwardable
|
|
11
11
|
|
|
12
|
+
def_delegator :@local_gate_values, :expression, :local_expression
|
|
12
13
|
def_delegator :@local_gate_values, :boolean, :local_boolean
|
|
13
14
|
def_delegator :@local_gate_values, :actors, :local_actors
|
|
14
15
|
def_delegator :@local_gate_values, :groups, :local_groups
|
|
@@ -17,6 +18,7 @@ module Flipper
|
|
|
17
18
|
def_delegator :@local_gate_values, :percentage_of_time,
|
|
18
19
|
:local_percentage_of_time
|
|
19
20
|
|
|
21
|
+
def_delegator :@remote_gate_values, :expression, :remote_expression
|
|
20
22
|
def_delegator :@remote_gate_values, :boolean, :remote_boolean
|
|
21
23
|
def_delegator :@remote_gate_values, :actors, :remote_actors
|
|
22
24
|
def_delegator :@remote_gate_values, :groups, :remote_groups
|
|
@@ -40,8 +42,9 @@ module Flipper
|
|
|
40
42
|
@feature.enable
|
|
41
43
|
else
|
|
42
44
|
@feature.disable if local_boolean_enabled?
|
|
43
|
-
sync_actors
|
|
44
45
|
sync_groups
|
|
46
|
+
sync_actors
|
|
47
|
+
sync_expression
|
|
45
48
|
sync_percentage_of_actors
|
|
46
49
|
sync_percentage_of_time
|
|
47
50
|
end
|
|
@@ -49,6 +52,12 @@ module Flipper
|
|
|
49
52
|
|
|
50
53
|
private
|
|
51
54
|
|
|
55
|
+
def sync_expression
|
|
56
|
+
return if local_expression == remote_expression
|
|
57
|
+
|
|
58
|
+
@feature.enable_expression remote_expression
|
|
59
|
+
end
|
|
60
|
+
|
|
52
61
|
def sync_actors
|
|
53
62
|
remote_actors_added = remote_actors - local_actors
|
|
54
63
|
remote_actors_added.each do |flipper_id|
|
|
@@ -8,9 +8,6 @@ module Flipper
|
|
|
8
8
|
class Sync
|
|
9
9
|
include ::Flipper::Adapter
|
|
10
10
|
|
|
11
|
-
# Public: The name of the adapter.
|
|
12
|
-
attr_reader :name
|
|
13
|
-
|
|
14
11
|
# Public: The synchronizer that will keep the local and remote in sync.
|
|
15
12
|
attr_reader :synchronizer
|
|
16
13
|
|
|
@@ -22,7 +19,6 @@ module Flipper
|
|
|
22
19
|
# interval - The Float or Integer number of seconds between syncs from
|
|
23
20
|
# remote to local. Default value is set in IntervalSynchronizer.
|
|
24
21
|
def initialize(local, remote, options = {})
|
|
25
|
-
@name = :sync
|
|
26
22
|
@local = local
|
|
27
23
|
@remote = remote
|
|
28
24
|
@synchronizer = options.fetch(:synchronizer) do
|