flipper 0.16.0 → 1.4.0
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 +5 -5
- data/.codeclimate.yml +1 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +110 -0
- data/.github/workflows/examples.yml +105 -0
- data/.github/workflows/release.yml +54 -0
- data/.rspec +1 -0
- data/CLAUDE.md +87 -0
- data/Changelog.md +2 -215
- data/Dockerfile +1 -1
- data/Gemfile +28 -20
- data/README.md +72 -62
- data/Rakefile +13 -3
- data/benchmark/enabled_ips.rb +10 -0
- data/benchmark/enabled_multiple_actors_ips.rb +20 -0
- data/benchmark/enabled_profile.rb +20 -0
- data/benchmark/instrumentation_ips.rb +21 -0
- data/benchmark/typecast_ips.rb +27 -0
- data/docker-compose.yml +37 -34
- data/docs/DockerCompose.md +0 -1
- data/docs/README.md +1 -0
- data/docs/images/banner.jpg +0 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/api/basic.ru +18 -0
- data/examples/api/custom_memoized.ru +36 -0
- data/examples/api/memoized.ru +42 -0
- data/examples/basic.rb +1 -12
- 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/poll_interval/README.md +111 -0
- data/examples/cloud/poll_interval/client.rb +108 -0
- data/examples/cloud/poll_interval/server.rb +98 -0
- data/examples/cloud/threaded.rb +33 -0
- data/examples/configuring_default.rb +2 -5
- data/examples/dsl.rb +10 -35
- data/examples/enabled_for_actor.rb +10 -15
- data/examples/expressions.rb +237 -0
- data/examples/group.rb +3 -6
- data/examples/group_dynamic_lookup.rb +5 -19
- data/examples/group_with_members.rb +4 -14
- data/examples/importing.rb +1 -1
- data/examples/individual_actor.rb +2 -5
- data/examples/instrumentation.rb +2 -2
- data/examples/instrumentation_last_accessed_at.rb +38 -0
- data/examples/memoizing.rb +35 -0
- data/examples/mirroring.rb +59 -0
- data/examples/percentage_of_actors.rb +6 -16
- data/examples/percentage_of_actors_enabled_check.rb +7 -10
- data/examples/percentage_of_actors_group.rb +5 -18
- data/examples/percentage_of_time.rb +3 -6
- data/examples/strict.rb +18 -0
- data/exe/flipper +5 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +10 -7
- data/lib/flipper/actor.rb +10 -3
- data/lib/flipper/adapter.rb +50 -8
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/actor_limit.rb +54 -0
- data/lib/flipper/adapters/cache_base.rb +161 -0
- data/lib/flipper/adapters/dual_write.rb +63 -0
- data/lib/flipper/adapters/failover.rb +85 -0
- data/lib/flipper/adapters/failsafe.rb +72 -0
- data/lib/flipper/adapters/http/client.rb +64 -7
- data/lib/flipper/adapters/http/error.rb +19 -1
- data/lib/flipper/adapters/http.rb +97 -43
- data/lib/flipper/adapters/instrumented.rb +47 -26
- data/lib/flipper/adapters/memoizable.rb +44 -40
- data/lib/flipper/adapters/memory.rb +75 -111
- data/lib/flipper/adapters/operation_logger.rb +22 -78
- data/lib/flipper/adapters/poll/poller.rb +2 -0
- data/lib/flipper/adapters/poll.rb +52 -0
- data/lib/flipper/adapters/pstore.rb +27 -17
- 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 +14 -1
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +2 -7
- data/lib/flipper/adapters/sync/synchronizer.rb +13 -6
- data/lib/flipper/adapters/sync.rb +23 -29
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +314 -0
- data/lib/flipper/cloud/configuration.rb +271 -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/migrate.rb +71 -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 +54 -0
- data/lib/flipper/configuration.rb +54 -7
- data/lib/flipper/dsl.rb +58 -47
- data/lib/flipper/engine.rb +102 -0
- data/lib/flipper/errors.rb +3 -21
- data/lib/flipper/export.rb +24 -0
- data/lib/flipper/exporter.rb +17 -0
- data/lib/flipper/exporters/json/export.rb +32 -0
- data/lib/flipper/exporters/json/v1.rb +33 -0
- 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/equal.rb +9 -0
- data/lib/flipper/expressions/feature_enabled.rb +34 -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 +16 -0
- data/lib/flipper/feature.rb +95 -28
- data/lib/flipper/feature_check_context.rb +10 -6
- data/lib/flipper/gate.rb +13 -11
- data/lib/flipper/gate_values.rb +5 -18
- data/lib/flipper/gates/actor.rb +10 -17
- data/lib/flipper/gates/boolean.rb +1 -1
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/gates/group.rb +5 -7
- data/lib/flipper/gates/percentage_of_actors.rb +10 -13
- data/lib/flipper/gates/percentage_of_time.rb +1 -2
- data/lib/flipper/identifier.rb +17 -0
- data/lib/flipper/instrumentation/log_subscriber.rb +35 -8
- data/lib/flipper/instrumentation/statsd.rb +4 -2
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +8 -5
- data/lib/flipper/instrumenters/memory.rb +6 -2
- data/lib/flipper/metadata.rb +8 -1
- data/lib/flipper/middleware/memoizer.rb +46 -27
- data/lib/flipper/middleware/setup_env.rb +13 -3
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +157 -0
- data/lib/flipper/serializers/gzip.rb +22 -0
- data/lib/flipper/serializers/json.rb +17 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +122 -56
- data/lib/flipper/test/shared_adapter_test.rb +120 -52
- data/lib/flipper/test_help.rb +43 -0
- data/lib/flipper/typecast.rb +59 -18
- data/lib/flipper/types/actor.rb +19 -13
- data/lib/flipper/types/group.rb +12 -5
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +11 -1
- data/lib/flipper.rb +71 -12
- 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/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/actor_spec.rb +10 -2
- data/spec/flipper/adapter_builder_spec.rb +72 -0
- data/spec/flipper/adapter_spec.rb +52 -6
- data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
- data/spec/flipper/adapters/dual_write_spec.rb +82 -0
- data/spec/flipper/adapters/failover_spec.rb +141 -0
- data/spec/flipper/adapters/failsafe_spec.rb +58 -0
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +402 -65
- data/spec/flipper/adapters/instrumented_spec.rb +31 -13
- data/spec/flipper/adapters/memoizable_spec.rb +51 -33
- data/spec/flipper/adapters/memory_spec.rb +33 -5
- data/spec/flipper/adapters/operation_logger_spec.rb +38 -12
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- data/spec/flipper/adapters/pstore_spec.rb +0 -2
- data/spec/flipper/adapters/read_only_spec.rb +32 -18
- data/spec/flipper/adapters/strict_spec.rb +64 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -1
- data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -1
- data/spec/flipper/adapters/sync_spec.rb +17 -6
- data/spec/flipper/cli_spec.rb +217 -0
- data/spec/flipper/cloud/configuration_spec.rb +257 -0
- data/spec/flipper/cloud/dsl_spec.rb +90 -0
- data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
- data/spec/flipper/cloud/middleware_spec.rb +307 -0
- data/spec/flipper/cloud/migrate_spec.rb +160 -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 +37 -3
- data/spec/flipper/dsl_spec.rb +67 -80
- data/spec/flipper/engine_spec.rb +374 -0
- data/spec/flipper/export_spec.rb +13 -0
- data/spec/flipper/exporter_spec.rb +16 -0
- data/spec/flipper/exporters/json/export_spec.rb +60 -0
- data/spec/flipper/exporters/json/v1_spec.rb +33 -0
- 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/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 +29 -0
- data/spec/flipper/feature_check_context_spec.rb +18 -20
- data/spec/flipper/feature_spec.rb +461 -48
- data/spec/flipper/gate_spec.rb +0 -2
- data/spec/flipper/gate_values_spec.rb +2 -34
- data/spec/flipper/gates/actor_spec.rb +0 -2
- data/spec/flipper/gates/boolean_spec.rb +1 -3
- data/spec/flipper/gates/expression_spec.rb +190 -0
- data/spec/flipper/gates/group_spec.rb +2 -5
- data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -7
- data/spec/flipper/gates/percentage_of_time_spec.rb +2 -4
- data/spec/flipper/identifier_spec.rb +12 -0
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +24 -7
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +26 -3
- data/spec/flipper/instrumenters/memory_spec.rb +18 -1
- data/spec/flipper/instrumenters/noop_spec.rb +14 -8
- data/spec/flipper/middleware/memoizer_spec.rb +199 -62
- data/spec/flipper/middleware/setup_env_spec.rb +23 -5
- data/spec/flipper/model/active_record_spec.rb +72 -0
- data/spec/flipper/poller_spec.rb +390 -0
- data/spec/flipper/registry_spec.rb +0 -1
- data/spec/flipper/serializers/gzip_spec.rb +13 -0
- data/spec/flipper/serializers/json_spec.rb +13 -0
- data/spec/flipper/typecast_spec.rb +121 -7
- data/spec/flipper/types/actor_spec.rb +63 -47
- data/spec/flipper/types/boolean_spec.rb +0 -1
- data/spec/flipper/types/group_spec.rb +24 -3
- data/spec/flipper/types/percentage_of_actors_spec.rb +0 -1
- data/spec/flipper/types/percentage_of_time_spec.rb +0 -1
- data/spec/flipper/types/percentage_spec.rb +0 -1
- data/spec/{integration_spec.rb → flipper_integration_spec.rb} +301 -59
- data/spec/flipper_spec.rb +123 -29
- data/spec/{helper.rb → spec_helper.rb} +23 -21
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/descriptions.yml +1 -0
- data/spec/support/fail_on_output.rb +8 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +53 -6
- data/test/adapters/actor_limit_test.rb +20 -0
- data/test/test_helper.rb +2 -1
- 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 +31 -0
- data/test_rails/system/test_help_test.rb +52 -0
- metadata +200 -82
- data/.rubocop.yml +0 -54
- data/.rubocop_todo.yml +0 -199
- data/docs/Adapters.md +0 -124
- data/docs/Caveats.md +0 -4
- data/docs/Gates.md +0 -167
- data/docs/Instrumentation.md +0 -27
- data/docs/Optimization.md +0 -114
- data/docs/api/README.md +0 -849
- data/docs/http/README.md +0 -35
- data/docs/read-only/README.md +0 -21
- data/examples/example_setup.rb +0 -8
- data/test/helper.rb +0 -11
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'json'
|
|
1
2
|
require 'pstore'
|
|
2
3
|
require 'set'
|
|
3
4
|
require 'flipper'
|
|
@@ -9,22 +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
|
-
# Public: PStore's thread_safe option.
|
|
21
|
-
attr_reader :thread_safe
|
|
22
|
-
|
|
23
16
|
# Public
|
|
24
|
-
def initialize(path = 'flipper.pstore', thread_safe =
|
|
17
|
+
def initialize(path = 'flipper.pstore', thread_safe = true)
|
|
25
18
|
@path = path
|
|
26
19
|
@store = ::PStore.new(path, thread_safe)
|
|
27
|
-
@
|
|
20
|
+
@features_key = :flipper_features
|
|
28
21
|
end
|
|
29
22
|
|
|
30
23
|
# Public: The set of known features.
|
|
@@ -37,7 +30,7 @@ module Flipper
|
|
|
37
30
|
# Public: Adds a feature to the set of known features.
|
|
38
31
|
def add(feature)
|
|
39
32
|
@store.transaction do
|
|
40
|
-
set_add
|
|
33
|
+
set_add @features_key, feature.key
|
|
41
34
|
end
|
|
42
35
|
true
|
|
43
36
|
end
|
|
@@ -46,7 +39,7 @@ module Flipper
|
|
|
46
39
|
# all the values for the feature.
|
|
47
40
|
def remove(feature)
|
|
48
41
|
@store.transaction do
|
|
49
|
-
set_delete
|
|
42
|
+
set_delete @features_key, feature.key
|
|
50
43
|
clear_gates(feature)
|
|
51
44
|
end
|
|
52
45
|
true
|
|
@@ -73,7 +66,7 @@ module Flipper
|
|
|
73
66
|
end
|
|
74
67
|
end
|
|
75
68
|
|
|
76
|
-
def get_all
|
|
69
|
+
def get_all(**kwargs)
|
|
77
70
|
@store.transaction do
|
|
78
71
|
features = read_feature_keys.map { |key| Flipper::Feature.new(key, self) }
|
|
79
72
|
read_many_features(features)
|
|
@@ -84,10 +77,15 @@ module Flipper
|
|
|
84
77
|
def enable(feature, gate, thing)
|
|
85
78
|
@store.transaction do
|
|
86
79
|
case gate.data_type
|
|
87
|
-
when :boolean
|
|
80
|
+
when :boolean
|
|
81
|
+
clear_gates(feature)
|
|
82
|
+
write key(feature, gate), thing.value.to_s
|
|
83
|
+
when :integer
|
|
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
|
|
@@ -213,3 +219,7 @@ module Flipper
|
|
|
213
219
|
end
|
|
214
220
|
end
|
|
215
221
|
end
|
|
222
|
+
|
|
223
|
+
Flipper.configure do |config|
|
|
224
|
+
config.adapter { Flipper::Adapters::PStore.new }
|
|
225
|
+
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,16 @@ module Flipper
|
|
|
49
52
|
|
|
50
53
|
private
|
|
51
54
|
|
|
55
|
+
def sync_expression
|
|
56
|
+
return if local_expression == remote_expression
|
|
57
|
+
|
|
58
|
+
if remote_expression.nil?
|
|
59
|
+
@feature.disable_expression
|
|
60
|
+
else
|
|
61
|
+
@feature.enable_expression remote_expression
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
52
65
|
def sync_actors
|
|
53
66
|
remote_actors_added = remote_actors - local_actors
|
|
54
67
|
remote_actors_added.each do |flipper_id|
|
|
@@ -7,11 +7,6 @@ module Flipper
|
|
|
7
7
|
# Private: Number of seconds between syncs (default: 10).
|
|
8
8
|
DEFAULT_INTERVAL = 10
|
|
9
9
|
|
|
10
|
-
# Private
|
|
11
|
-
def self.now
|
|
12
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
10
|
# Public: The Float or Integer number of seconds between invocations of
|
|
16
11
|
# the wrapped synchronizer.
|
|
17
12
|
attr_reader :interval
|
|
@@ -19,7 +14,7 @@ module Flipper
|
|
|
19
14
|
# Public: Initializes a new interval synchronizer.
|
|
20
15
|
#
|
|
21
16
|
# synchronizer - The Synchronizer to call when the interval has passed.
|
|
22
|
-
# interval - The Integer number of
|
|
17
|
+
# interval - The Integer number of seconds between invocations of
|
|
23
18
|
# the wrapped synchronizer.
|
|
24
19
|
def initialize(synchronizer, interval: nil)
|
|
25
20
|
@synchronizer = synchronizer
|
|
@@ -46,7 +41,7 @@ module Flipper
|
|
|
46
41
|
end
|
|
47
42
|
|
|
48
43
|
def now
|
|
49
|
-
|
|
44
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
|
50
45
|
end
|
|
51
46
|
end
|
|
52
47
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "flipper/feature"
|
|
2
2
|
require "flipper/gate_values"
|
|
3
|
+
require "flipper/adapters/actor_limit"
|
|
3
4
|
require "flipper/adapters/sync/feature_synchronizer"
|
|
4
5
|
|
|
5
6
|
module Flipper
|
|
@@ -15,28 +16,34 @@ module Flipper
|
|
|
15
16
|
# adapter should be brought in line with.
|
|
16
17
|
# options - The Hash of options.
|
|
17
18
|
# :instrumenter - The instrumenter used to instrument.
|
|
19
|
+
# :raise - Should errors be raised (default: true).
|
|
20
|
+
# :cache_bust - Should cache busting be used for remote get_all (default: false).
|
|
18
21
|
def initialize(local, remote, options = {})
|
|
19
22
|
@local = local
|
|
20
23
|
@remote = remote
|
|
21
24
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
|
22
25
|
@raise = options.fetch(:raise, true)
|
|
26
|
+
@cache_bust = options.fetch(:cache_bust, false)
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
# Public: Forces a sync.
|
|
26
30
|
def call
|
|
27
|
-
@instrumenter.instrument("synchronizer_call.flipper")
|
|
31
|
+
@instrumenter.instrument("synchronizer_call.flipper") do
|
|
32
|
+
Flipper::Adapters::ActorLimit.with_sync_mode { sync }
|
|
33
|
+
end
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
private
|
|
31
37
|
|
|
32
38
|
def sync
|
|
33
39
|
local_get_all = @local.get_all
|
|
34
|
-
remote_get_all = @remote.get_all
|
|
40
|
+
remote_get_all = @remote.get_all(cache_bust: @cache_bust)
|
|
35
41
|
|
|
36
42
|
# Sync all the gate values.
|
|
37
43
|
remote_get_all.each do |feature_key, remote_gates_hash|
|
|
38
|
-
feature = Feature.new(feature_key, @local)
|
|
39
|
-
|
|
44
|
+
feature = Feature.new(feature_key, @local, instrumenter: @instrumenter)
|
|
45
|
+
# Check if feature_key is in hash before accessing to prevent unintended hash modification
|
|
46
|
+
local_gates_hash = local_get_all.key?(feature_key) ? local_get_all[feature_key] : @local.default_config
|
|
40
47
|
local_gate_values = GateValues.new(local_gates_hash)
|
|
41
48
|
remote_gate_values = GateValues.new(remote_gates_hash)
|
|
42
49
|
FeatureSynchronizer.new(feature, local_gate_values, remote_gate_values).call
|
|
@@ -44,11 +51,11 @@ module Flipper
|
|
|
44
51
|
|
|
45
52
|
# Add features that are missing in local and present in remote.
|
|
46
53
|
features_to_add = remote_get_all.keys - local_get_all.keys
|
|
47
|
-
features_to_add.each { |key| Feature.new(key, @local).add }
|
|
54
|
+
features_to_add.each { |key| Feature.new(key, @local, instrumenter: @instrumenter).add }
|
|
48
55
|
|
|
49
56
|
# Remove features that are present in local and missing in remote.
|
|
50
57
|
features_to_remove = local_get_all.keys - remote_get_all.keys
|
|
51
|
-
features_to_remove.each { |key| Feature.new(key, @local).remove }
|
|
58
|
+
features_to_remove.each { |key| Feature.new(key, @local, instrumenter: @instrumenter).remove }
|
|
52
59
|
|
|
53
60
|
nil
|
|
54
61
|
rescue => exception
|
|
@@ -8,21 +8,17 @@ 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
|
-
attr_reader :synchronizer
|
|
12
|
+
attr_reader :synchronizer, :local, :remote
|
|
16
13
|
|
|
17
14
|
# Public: Build a new sync instance.
|
|
18
15
|
#
|
|
19
16
|
# local - The local flipper adapter that should serve reads.
|
|
20
|
-
# remote - The remote flipper
|
|
17
|
+
# remote - The remote flipper adapter that should serve writes and update
|
|
21
18
|
# the local on an interval.
|
|
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
|
|
@@ -34,62 +30,60 @@ module Flipper
|
|
|
34
30
|
synchronizer = Synchronizer.new(@local, @remote, sync_options)
|
|
35
31
|
IntervalSynchronizer.new(synchronizer, interval: options[:interval])
|
|
36
32
|
end
|
|
37
|
-
|
|
33
|
+
synchronize
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def adapter_stack
|
|
37
|
+
"#{name}(local: #{@local.adapter_stack}, remote: #{@remote.adapter_stack})"
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def features
|
|
41
|
-
|
|
41
|
+
synchronize
|
|
42
42
|
@local.features
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def get(feature)
|
|
46
|
-
|
|
46
|
+
synchronize
|
|
47
47
|
@local.get(feature)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def get_multi(features)
|
|
51
|
-
|
|
51
|
+
synchronize
|
|
52
52
|
@local.get_multi(features)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
def get_all
|
|
56
|
-
|
|
57
|
-
@local.get_all
|
|
55
|
+
def get_all(**kwargs)
|
|
56
|
+
synchronize
|
|
57
|
+
@local.get_all(**kwargs)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def add(feature)
|
|
61
|
-
|
|
62
|
-
@local.add(feature)
|
|
63
|
-
result
|
|
61
|
+
@remote.add(feature).tap { @local.add(feature) }
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
def remove(feature)
|
|
67
|
-
|
|
68
|
-
@local.remove(feature)
|
|
69
|
-
result
|
|
65
|
+
@remote.remove(feature).tap { @local.remove(feature) }
|
|
70
66
|
end
|
|
71
67
|
|
|
72
68
|
def clear(feature)
|
|
73
|
-
|
|
74
|
-
@local.clear(feature)
|
|
75
|
-
result
|
|
69
|
+
@remote.clear(feature).tap { @local.clear(feature) }
|
|
76
70
|
end
|
|
77
71
|
|
|
78
72
|
def enable(feature, gate, thing)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
@remote.enable(feature, gate, thing).tap do
|
|
74
|
+
@local.enable(feature, gate, thing)
|
|
75
|
+
end
|
|
82
76
|
end
|
|
83
77
|
|
|
84
78
|
def disable(feature, gate, thing)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
@remote.disable(feature, gate, thing).tap do
|
|
80
|
+
@local.disable(feature, gate, thing)
|
|
81
|
+
end
|
|
88
82
|
end
|
|
89
83
|
|
|
90
84
|
private
|
|
91
85
|
|
|
92
|
-
def
|
|
86
|
+
def synchronize
|
|
93
87
|
@synchronizer.call
|
|
94
88
|
end
|
|
95
89
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
module Adapters
|
|
3
|
+
# A base class for any adapter that wraps another adapter. By default, all methods
|
|
4
|
+
# delegate to the wrapped adapter. Implement `#wrap` to customize the behavior of
|
|
5
|
+
# all delegated methods, or override individual methods as needed.
|
|
6
|
+
class Wrapper
|
|
7
|
+
include Flipper::Adapter
|
|
8
|
+
|
|
9
|
+
METHODS = [
|
|
10
|
+
:import,
|
|
11
|
+
:export,
|
|
12
|
+
:features,
|
|
13
|
+
:add,
|
|
14
|
+
:remove,
|
|
15
|
+
:clear,
|
|
16
|
+
:get,
|
|
17
|
+
:get_multi,
|
|
18
|
+
:get_all,
|
|
19
|
+
:enable,
|
|
20
|
+
:disable,
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
attr_reader :adapter
|
|
24
|
+
|
|
25
|
+
def initialize(adapter)
|
|
26
|
+
@adapter = adapter
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
METHODS.each do |method|
|
|
30
|
+
if RUBY_VERSION >= '3.0'
|
|
31
|
+
define_method(method) do |*args, **kwargs|
|
|
32
|
+
wrap(method, *args, **kwargs) { @adapter.public_send(method, *args, **kwargs) }
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
define_method(method) do |*args|
|
|
36
|
+
wrap(method, *args) { @adapter.public_send(method, *args) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Override this method to customize the behavior of all delegated methods, and just yield to
|
|
42
|
+
# the block to call the wrapped adapter.
|
|
43
|
+
if RUBY_VERSION >= '3.0'
|
|
44
|
+
def wrap(method, *args, **kwargs, &block)
|
|
45
|
+
block.call
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
def wrap(method, *args, &block)
|
|
49
|
+
block.call
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|