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
|
@@ -5,39 +5,28 @@ module Flipper
|
|
|
5
5
|
# Internal: Adapter that wraps another adapter with the ability to memoize
|
|
6
6
|
# adapter calls in memory. Used by flipper dsl and the memoizer middleware
|
|
7
7
|
# to make it possible to memoize adapter calls for the duration of a request.
|
|
8
|
-
class Memoizable
|
|
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
|
-
super(adapter)
|
|
31
19
|
@adapter = adapter
|
|
32
|
-
@name = :memoizable
|
|
33
20
|
@cache = cache || {}
|
|
34
21
|
@memoize = false
|
|
22
|
+
@features_key = :flipper_features
|
|
23
|
+
@get_all_key = :all_memoized
|
|
35
24
|
end
|
|
36
25
|
|
|
37
26
|
# Public
|
|
38
27
|
def features
|
|
39
28
|
if memoizing?
|
|
40
|
-
cache.fetch(
|
|
29
|
+
cache.fetch(@features_key) { cache[@features_key] = @adapter.features }
|
|
41
30
|
else
|
|
42
31
|
@adapter.features
|
|
43
32
|
end
|
|
@@ -45,24 +34,20 @@ module Flipper
|
|
|
45
34
|
|
|
46
35
|
# Public
|
|
47
36
|
def add(feature)
|
|
48
|
-
|
|
49
|
-
expire_features_set
|
|
50
|
-
result
|
|
37
|
+
@adapter.add(feature).tap { expire_features_set }
|
|
51
38
|
end
|
|
52
39
|
|
|
53
40
|
# Public
|
|
54
41
|
def remove(feature)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
@adapter.remove(feature).tap do
|
|
43
|
+
expire_features_set
|
|
44
|
+
expire_feature(feature)
|
|
45
|
+
end
|
|
59
46
|
end
|
|
60
47
|
|
|
61
48
|
# Public
|
|
62
49
|
def clear(feature)
|
|
63
|
-
|
|
64
|
-
expire_feature(feature)
|
|
65
|
-
result
|
|
50
|
+
@adapter.clear(feature).tap { expire_feature(feature) }
|
|
66
51
|
end
|
|
67
52
|
|
|
68
53
|
# Public
|
|
@@ -96,21 +81,21 @@ module Flipper
|
|
|
96
81
|
end
|
|
97
82
|
end
|
|
98
83
|
|
|
99
|
-
def get_all
|
|
84
|
+
def get_all(**kwargs)
|
|
100
85
|
if memoizing?
|
|
101
86
|
response = nil
|
|
102
|
-
if cache[
|
|
87
|
+
if cache[@get_all_key]
|
|
103
88
|
response = {}
|
|
104
|
-
cache[
|
|
89
|
+
cache[@features_key].each do |key|
|
|
105
90
|
response[key] = cache[key_for(key)]
|
|
106
91
|
end
|
|
107
92
|
else
|
|
108
|
-
response = @adapter.get_all
|
|
93
|
+
response = @adapter.get_all(**kwargs)
|
|
109
94
|
response.each do |key, value|
|
|
110
95
|
cache[key_for(key)] = value
|
|
111
96
|
end
|
|
112
|
-
cache[
|
|
113
|
-
cache[
|
|
97
|
+
cache[@features_key] = response.keys.to_set
|
|
98
|
+
cache[@get_all_key] = true
|
|
114
99
|
end
|
|
115
100
|
|
|
116
101
|
# Ensures that looking up other features that do not exist doesn't
|
|
@@ -118,22 +103,31 @@ module Flipper
|
|
|
118
103
|
response.default_proc = ->(memo, key) { memo[key] = default_config }
|
|
119
104
|
response
|
|
120
105
|
else
|
|
121
|
-
@adapter.get_all
|
|
106
|
+
@adapter.get_all(**kwargs)
|
|
122
107
|
end
|
|
123
108
|
end
|
|
124
109
|
|
|
125
110
|
# Public
|
|
126
111
|
def enable(feature, gate, thing)
|
|
127
|
-
|
|
128
|
-
expire_feature(feature)
|
|
129
|
-
result
|
|
112
|
+
@adapter.enable(feature, gate, thing).tap { expire_feature(feature) }
|
|
130
113
|
end
|
|
131
114
|
|
|
132
115
|
# Public
|
|
133
116
|
def disable(feature, gate, thing)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
117
|
+
@adapter.disable(feature, gate, thing).tap { expire_feature(feature) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Public
|
|
121
|
+
def read_only?
|
|
122
|
+
@adapter.read_only?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def import(source)
|
|
126
|
+
@adapter.import(source).tap { cache.clear if memoizing? }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def export(format: :json, version: 1)
|
|
130
|
+
@adapter.export(format: format, version: version)
|
|
137
131
|
end
|
|
138
132
|
|
|
139
133
|
# Internal: Turns local caching on/off.
|
|
@@ -149,10 +143,20 @@ module Flipper
|
|
|
149
143
|
!!@memoize
|
|
150
144
|
end
|
|
151
145
|
|
|
146
|
+
if RUBY_VERSION >= '3.0'
|
|
147
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
148
|
+
@adapter.send name, *args, **kwargs, &block
|
|
149
|
+
end
|
|
150
|
+
else
|
|
151
|
+
def method_missing(name, *args, &block)
|
|
152
|
+
@adapter.send name, *args, &block
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
152
156
|
private
|
|
153
157
|
|
|
154
158
|
def key_for(key)
|
|
155
|
-
|
|
159
|
+
"feature/#{key}"
|
|
156
160
|
end
|
|
157
161
|
|
|
158
162
|
def expire_feature(feature)
|
|
@@ -160,7 +164,7 @@ module Flipper
|
|
|
160
164
|
end
|
|
161
165
|
|
|
162
166
|
def expire_features_set
|
|
163
|
-
cache.delete(
|
|
167
|
+
cache.delete(@features_key) if memoizing?
|
|
164
168
|
end
|
|
165
169
|
end
|
|
166
170
|
end
|
|
@@ -1,95 +1,106 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "flipper/adapter"
|
|
2
|
+
require "flipper/typecast"
|
|
2
3
|
|
|
3
4
|
module Flipper
|
|
4
5
|
module Adapters
|
|
5
|
-
# Public: Adapter for storing everything in memory
|
|
6
|
+
# Public: Adapter for storing everything in memory.
|
|
6
7
|
# Useful for tests/specs.
|
|
7
8
|
class Memory
|
|
8
9
|
include ::Flipper::Adapter
|
|
9
10
|
|
|
10
|
-
FeaturesKey = :features
|
|
11
|
-
|
|
12
|
-
# Public: The name of the adapter.
|
|
13
|
-
attr_reader :name
|
|
14
|
-
|
|
15
11
|
# Public
|
|
16
|
-
def initialize(source = nil)
|
|
17
|
-
@source = source
|
|
18
|
-
@
|
|
12
|
+
def initialize(source = nil, threadsafe: true)
|
|
13
|
+
@source = Typecast.features_hash(source)
|
|
14
|
+
@lock = Mutex.new if threadsafe
|
|
15
|
+
reset
|
|
19
16
|
end
|
|
20
17
|
|
|
21
18
|
# Public: The set of known features.
|
|
22
19
|
def features
|
|
23
|
-
|
|
20
|
+
synchronize { @source.keys }.to_set
|
|
24
21
|
end
|
|
25
22
|
|
|
26
23
|
# Public: Adds a feature to the set of known features.
|
|
27
24
|
def add(feature)
|
|
28
|
-
|
|
25
|
+
synchronize { @source[feature.key] ||= default_config }
|
|
29
26
|
true
|
|
30
27
|
end
|
|
31
28
|
|
|
32
29
|
# Public: Removes a feature from the set of known features and clears
|
|
33
30
|
# all the values for the feature.
|
|
34
31
|
def remove(feature)
|
|
35
|
-
|
|
36
|
-
clear(feature)
|
|
32
|
+
synchronize { @source.delete(feature.key) }
|
|
37
33
|
true
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
# Public: Clears all the gate values for a feature.
|
|
41
37
|
def clear(feature)
|
|
42
|
-
feature.
|
|
43
|
-
delete key(feature, gate)
|
|
44
|
-
end
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
synchronize do
|
|
49
|
+
result = {}
|
|
50
|
+
features.each do |feature|
|
|
51
|
+
result[feature.key] = @source[feature.key] || default_config
|
|
52
|
+
end
|
|
53
|
+
result
|
|
60
54
|
end
|
|
55
|
+
end
|
|
61
56
|
|
|
62
|
-
|
|
57
|
+
def get_all(**kwargs)
|
|
58
|
+
synchronize { Typecast.features_hash(@source) }
|
|
63
59
|
end
|
|
64
60
|
|
|
65
61
|
# Public
|
|
66
62
|
def enable(feature, gate, thing)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
synchronize do
|
|
64
|
+
@source[feature.key] ||= default_config
|
|
65
|
+
|
|
66
|
+
case gate.data_type
|
|
67
|
+
when :boolean
|
|
68
|
+
@source[feature.key] = default_config
|
|
69
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
|
70
|
+
when :integer
|
|
71
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
|
72
|
+
when :set
|
|
73
|
+
@source[feature.key][gate.key] << thing.value.to_s
|
|
74
|
+
when :json
|
|
75
|
+
@source[feature.key][gate.key] = thing.value
|
|
76
|
+
else
|
|
77
|
+
raise "#{gate} is not supported by this adapter yet"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
true
|
|
74
81
|
end
|
|
75
|
-
|
|
76
|
-
true
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
# Public
|
|
80
85
|
def disable(feature, gate, thing)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
synchronize do
|
|
87
|
+
@source[feature.key] ||= default_config
|
|
88
|
+
|
|
89
|
+
case gate.data_type
|
|
90
|
+
when :boolean
|
|
91
|
+
@source[feature.key] = default_config
|
|
92
|
+
when :integer
|
|
93
|
+
@source[feature.key][gate.key] = thing.value.to_s
|
|
94
|
+
when :set
|
|
95
|
+
@source[feature.key][gate.key].delete thing.value.to_s
|
|
96
|
+
when :json
|
|
97
|
+
@source[feature.key].delete(gate.key)
|
|
98
|
+
else
|
|
99
|
+
raise "#{gate} is not supported by this adapter yet"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
true
|
|
90
103
|
end
|
|
91
|
-
|
|
92
|
-
true
|
|
93
104
|
end
|
|
94
105
|
|
|
95
106
|
# Public
|
|
@@ -101,79 +112,32 @@ module Flipper
|
|
|
101
112
|
"#<#{self.class.name}:#{object_id} #{attributes.join(', ')}>"
|
|
102
113
|
end
|
|
103
114
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
# Private
|
|
111
|
-
def key(feature, gate)
|
|
112
|
-
"feature/#{feature.key}/#{gate.key}"
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def read_many_features(features)
|
|
116
|
-
result = {}
|
|
117
|
-
features.each do |feature|
|
|
118
|
-
result[feature.key] = read_feature(feature)
|
|
119
|
-
end
|
|
120
|
-
result
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def read_feature(feature)
|
|
124
|
-
result = {}
|
|
125
|
-
|
|
126
|
-
feature.gates.each do |gate|
|
|
127
|
-
result[gate.key] =
|
|
128
|
-
case gate.data_type
|
|
129
|
-
when :boolean, :integer
|
|
130
|
-
read key(feature, gate)
|
|
131
|
-
when :set
|
|
132
|
-
set_members key(feature, gate)
|
|
133
|
-
else
|
|
134
|
-
raise "#{gate} is not supported by this adapter yet"
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
result
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Private
|
|
142
|
-
def read(key)
|
|
143
|
-
@source[key.to_s]
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Private
|
|
147
|
-
def write(key, value)
|
|
148
|
-
@source[key.to_s] = value.to_s
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Private
|
|
152
|
-
def delete(key)
|
|
153
|
-
@source.delete(key.to_s)
|
|
115
|
+
# Public: a more efficient implementation of import for this adapter
|
|
116
|
+
def import(source)
|
|
117
|
+
adapter = self.class.from(source)
|
|
118
|
+
get_all = Typecast.features_hash(adapter.get_all)
|
|
119
|
+
synchronize { @source.replace(get_all) }
|
|
120
|
+
true
|
|
154
121
|
end
|
|
155
122
|
|
|
156
|
-
|
|
157
|
-
def set_add(key, value)
|
|
158
|
-
ensure_set_initialized(key)
|
|
159
|
-
@source[key.to_s].add(value.to_s)
|
|
160
|
-
end
|
|
123
|
+
private
|
|
161
124
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@source[key.to_s].delete(value.to_s)
|
|
125
|
+
def reset
|
|
126
|
+
@pid = Process.pid
|
|
127
|
+
@lock&.unlock if @lock&.locked?
|
|
166
128
|
end
|
|
167
129
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
ensure_set_initialized(key)
|
|
171
|
-
@source[key.to_s]
|
|
130
|
+
def forked?
|
|
131
|
+
@pid != Process.pid
|
|
172
132
|
end
|
|
173
133
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
134
|
+
def synchronize(&block)
|
|
135
|
+
if @lock
|
|
136
|
+
reset if forked?
|
|
137
|
+
@lock.synchronize(&block)
|
|
138
|
+
else
|
|
139
|
+
block.call
|
|
140
|
+
end
|
|
177
141
|
end
|
|
178
142
|
end
|
|
179
143
|
end
|
|
@@ -5,102 +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
|
-
:features,
|
|
22
|
-
:add,
|
|
23
|
-
:remove,
|
|
24
|
-
:clear,
|
|
25
|
-
:get,
|
|
26
|
-
:get_multi,
|
|
27
|
-
:get_all,
|
|
28
|
-
:enable,
|
|
29
|
-
:disable,
|
|
30
|
-
].freeze
|
|
31
|
-
|
|
32
20
|
# Internal: An array of the operations that have happened.
|
|
33
21
|
attr_reader :operations
|
|
34
22
|
|
|
35
|
-
# Internal: The name of the adapter.
|
|
36
|
-
attr_reader :name
|
|
37
|
-
|
|
38
23
|
# Public
|
|
39
24
|
def initialize(adapter, operations = nil)
|
|
40
25
|
super(adapter)
|
|
41
|
-
@adapter = adapter
|
|
42
|
-
@name = :operation_logger
|
|
43
26
|
@operations = operations || []
|
|
44
27
|
end
|
|
45
28
|
|
|
46
|
-
# Public: The set of known features.
|
|
47
|
-
def features
|
|
48
|
-
@operations << Operation.new(:features, [])
|
|
49
|
-
@adapter.features
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Public: Adds a feature to the set of known features.
|
|
53
|
-
def add(feature)
|
|
54
|
-
@operations << Operation.new(:add, [feature])
|
|
55
|
-
@adapter.add(feature)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Public: Removes a feature from the set of known features and clears
|
|
59
|
-
# all the values for the feature.
|
|
60
|
-
def remove(feature)
|
|
61
|
-
@operations << Operation.new(:remove, [feature])
|
|
62
|
-
@adapter.remove(feature)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Public: Clears all the gate values for a feature.
|
|
66
|
-
def clear(feature)
|
|
67
|
-
@operations << Operation.new(:clear, [feature])
|
|
68
|
-
@adapter.clear(feature)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Public
|
|
72
|
-
def get(feature)
|
|
73
|
-
@operations << Operation.new(:get, [feature])
|
|
74
|
-
@adapter.get(feature)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Public
|
|
78
|
-
def get_multi(features)
|
|
79
|
-
@operations << Operation.new(:get_multi, [features])
|
|
80
|
-
@adapter.get_multi(features)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Public
|
|
84
|
-
def get_all
|
|
85
|
-
@operations << Operation.new(:get_all, [])
|
|
86
|
-
@adapter.get_all
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Public
|
|
90
|
-
def enable(feature, gate, thing)
|
|
91
|
-
@operations << Operation.new(:enable, [feature, gate, thing])
|
|
92
|
-
@adapter.enable(feature, gate, thing)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Public
|
|
96
|
-
def disable(feature, gate, thing)
|
|
97
|
-
@operations << Operation.new(:disable, [feature, gate, thing])
|
|
98
|
-
@adapter.disable(feature, gate, thing)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
29
|
# Public: Count the number of times a certain operation happened.
|
|
102
|
-
def count(type)
|
|
103
|
-
type
|
|
30
|
+
def count(type = nil)
|
|
31
|
+
if type
|
|
32
|
+
type(type).size
|
|
33
|
+
else
|
|
34
|
+
@operations.size
|
|
35
|
+
end
|
|
104
36
|
end
|
|
105
37
|
|
|
106
38
|
# Public: Get all operations of a certain type.
|
|
@@ -117,6 +49,18 @@ module Flipper
|
|
|
117
49
|
def reset
|
|
118
50
|
@operations.clear
|
|
119
51
|
end
|
|
52
|
+
|
|
53
|
+
def inspect
|
|
54
|
+
inspect_id = ::Kernel::format "%x", (object_id * 2)
|
|
55
|
+
%(#<#{self.class}:0x#{inspect_id} @name=#{name.inspect}, @operations=#{@operations.inspect}, @adapter=#{@adapter.inspect}>)
|
|
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
|
|
120
64
|
end
|
|
121
65
|
end
|
|
122
66
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'flipper/adapters/sync/synchronizer'
|
|
2
|
+
require 'flipper/poller'
|
|
3
|
+
|
|
4
|
+
module Flipper
|
|
5
|
+
module Adapters
|
|
6
|
+
class Poll
|
|
7
|
+
extend Forwardable
|
|
8
|
+
include ::Flipper::Adapter
|
|
9
|
+
|
|
10
|
+
# Deprecated
|
|
11
|
+
Poller = ::Flipper::Poller
|
|
12
|
+
|
|
13
|
+
attr_reader :adapter, :poller
|
|
14
|
+
|
|
15
|
+
def_delegators :synced_adapter, :features, :get, :get_multi, :get_all, :add, :remove, :clear, :enable, :disable
|
|
16
|
+
|
|
17
|
+
def initialize(poller, adapter)
|
|
18
|
+
@adapter = adapter
|
|
19
|
+
@poller = poller
|
|
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
|
+
|
|
36
|
+
@poller.start
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def synced_adapter
|
|
42
|
+
@poller.start
|
|
43
|
+
poller_last_synced_at = @poller.last_synced_at.value
|
|
44
|
+
if poller_last_synced_at > @last_synced_at
|
|
45
|
+
Flipper::Adapters::Sync::Synchronizer.new(@adapter, @poller.adapter).call
|
|
46
|
+
@last_synced_at = poller_last_synced_at
|
|
47
|
+
end
|
|
48
|
+
@adapter
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|