flipper 0.28.3 → 1.4.2
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 +56 -10
- data/.github/workflows/examples.yml +54 -11
- data/.github/workflows/release.yml +54 -0
- data/.superset/config.json +4 -0
- data/CLAUDE.md +93 -0
- data/Changelog.md +1 -559
- data/Gemfile +17 -8
- data/README.md +34 -29
- 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/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/dsl.rb +0 -14
- data/examples/expressions.rb +237 -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 +27 -1
- 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 +7 -5
- data/lib/flipper/adapters/failover.rb +9 -7
- data/lib/flipper/adapters/failsafe.rb +2 -6
- data/lib/flipper/adapters/http/client.rb +53 -14
- data/lib/flipper/adapters/http/error.rb +2 -2
- data/lib/flipper/adapters/http.rb +56 -16
- data/lib/flipper/adapters/instrumented.rb +2 -6
- data/lib/flipper/adapters/memoizable.rb +17 -22
- data/lib/flipper/adapters/memory.rb +5 -7
- data/lib/flipper/adapters/operation_logger.rb +18 -92
- data/lib/flipper/adapters/poll.rb +16 -3
- data/lib/flipper/adapters/pstore.rb +18 -12
- data/lib/flipper/adapters/read_only.rb +8 -41
- data/lib/flipper/adapters/strict.rb +75 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
- data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
- data/lib/flipper/adapters/sync.rb +7 -7
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +314 -0
- data/lib/flipper/cloud/configuration.rb +267 -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 +25 -4
- data/lib/flipper/dsl.rb +48 -43
- 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/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 +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 +13 -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 +54 -12
- 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 +61 -8
- 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 +21 -0
- data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
- data/spec/flipper/adapters/dual_write_spec.rb +15 -2
- data/spec/flipper/adapters/failover_spec.rb +12 -0
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +375 -74
- data/spec/flipper/adapters/instrumented_spec.rb +1 -1
- data/spec/flipper/adapters/memoizable_spec.rb +21 -21
- 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 +122 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -0
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
- data/spec/flipper/adapters/sync_spec.rb +13 -0
- data/spec/flipper/cli_spec.rb +217 -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/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 +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/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_spec.rb +380 -10
- data/spec/flipper/gate_values_spec.rb +2 -2
- data/spec/flipper/gates/expression_spec.rb +190 -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 +107 -7
- data/spec/flipper/model/active_record_spec.rb +72 -0
- data/spec/flipper/poller_spec.rb +347 -4
- 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 +247 -16
- data/spec/flipper_spec.rb +92 -29
- data/spec/spec_helper.rb +24 -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 +139 -75
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
data/lib/flipper/typecast.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
require 'set'
|
|
2
|
+
require "flipper/serializers/json"
|
|
3
|
+
require "flipper/serializers/gzip"
|
|
2
4
|
|
|
3
5
|
module Flipper
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
class Typecast
|
|
7
|
+
TRUTH_MAP = {
|
|
6
8
|
true => true,
|
|
7
9
|
1 => true,
|
|
8
10
|
'true' => true,
|
|
@@ -13,7 +15,7 @@ module Flipper
|
|
|
13
15
|
#
|
|
14
16
|
# Returns true or false.
|
|
15
17
|
def self.to_boolean(value)
|
|
16
|
-
!!
|
|
18
|
+
!!TRUTH_MAP[value]
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
# Internal: Convert value to an integer.
|
|
@@ -36,17 +38,25 @@ module Flipper
|
|
|
36
38
|
raise ArgumentError, "#{value.inspect} cannot be converted to a float"
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
# Internal: Convert value to a
|
|
41
|
+
# Internal: Convert value to a number.
|
|
40
42
|
#
|
|
41
43
|
# Returns a Integer or Float representation of the value.
|
|
42
44
|
# Raises ArgumentError if conversion is not possible.
|
|
43
|
-
def self.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
def self.to_number(value)
|
|
46
|
+
case value
|
|
47
|
+
when Numeric
|
|
48
|
+
value
|
|
49
|
+
when String
|
|
50
|
+
value.include?('.') ? to_float(value) : to_integer(value)
|
|
51
|
+
when NilClass
|
|
52
|
+
0
|
|
53
|
+
else
|
|
54
|
+
value.to_f
|
|
55
|
+
end
|
|
47
56
|
rescue NoMethodError
|
|
48
|
-
raise ArgumentError, "#{value.inspect} cannot be converted to a
|
|
57
|
+
raise ArgumentError, "#{value.inspect} cannot be converted to a number"
|
|
49
58
|
end
|
|
59
|
+
singleton_class.send(:alias_method, :to_percentage, :to_number)
|
|
50
60
|
|
|
51
61
|
# Internal: Convert value to a set.
|
|
52
62
|
#
|
|
@@ -71,6 +81,8 @@ module Flipper
|
|
|
71
81
|
normalized_value = case value
|
|
72
82
|
when Array, Set
|
|
73
83
|
value.to_set
|
|
84
|
+
when Hash
|
|
85
|
+
value
|
|
74
86
|
else
|
|
75
87
|
value ? value.to_s : value
|
|
76
88
|
end
|
|
@@ -79,5 +91,21 @@ module Flipper
|
|
|
79
91
|
end
|
|
80
92
|
normalized_source
|
|
81
93
|
end
|
|
94
|
+
|
|
95
|
+
def self.to_json(source)
|
|
96
|
+
Serializers::Json.serialize(source)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.from_json(source)
|
|
100
|
+
Serializers::Json.deserialize(source)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.to_gzip(source)
|
|
104
|
+
Serializers::Gzip.serialize(source)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.from_gzip(source)
|
|
108
|
+
Serializers::Gzip.deserialize(source)
|
|
109
|
+
end
|
|
82
110
|
end
|
|
83
111
|
end
|
data/lib/flipper/version.rb
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
module Flipper
|
|
2
|
-
VERSION = '
|
|
2
|
+
VERSION = '1.4.2'.freeze
|
|
3
|
+
|
|
4
|
+
REQUIRED_RUBY_VERSION = '2.6'.freeze
|
|
5
|
+
NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
|
|
6
|
+
|
|
7
|
+
REQUIRED_RAILS_VERSION = '5.2'.freeze
|
|
8
|
+
NEXT_REQUIRED_RAILS_VERSION = '6.1.0'.freeze
|
|
9
|
+
|
|
10
|
+
def self.deprecated_ruby_version?
|
|
11
|
+
Gem::Version.new(RUBY_VERSION) < Gem::Version.new(NEXT_REQUIRED_RUBY_VERSION)
|
|
12
|
+
end
|
|
3
13
|
end
|
data/lib/flipper.rb
CHANGED
|
@@ -56,18 +56,66 @@ module Flipper
|
|
|
56
56
|
# Public: All the methods delegated to instance. These should match the
|
|
57
57
|
# interface of Flipper::DSL.
|
|
58
58
|
def_delegators :instance,
|
|
59
|
-
:enabled?, :enable, :disable,
|
|
60
|
-
:
|
|
59
|
+
:enabled?, :enable, :disable,
|
|
60
|
+
:enable_expression, :disable_expression,
|
|
61
|
+
:expression, :add_expression, :remove_expression,
|
|
62
|
+
:enable_actor, :disable_actor,
|
|
61
63
|
:enable_group, :disable_group,
|
|
62
64
|
:enable_percentage_of_actors, :disable_percentage_of_actors,
|
|
63
|
-
:actors, :percentage_of_actors,
|
|
64
65
|
:enable_percentage_of_time, :disable_percentage_of_time,
|
|
65
|
-
:time, :percentage_of_time,
|
|
66
66
|
:features, :feature, :[], :preload, :preload_all,
|
|
67
|
-
:adapter, :add, :exist?, :remove, :import, :export,
|
|
68
|
-
:memoize=, :memoizing?,
|
|
67
|
+
:adapter, :adapter_stack, :add, :exist?, :remove, :import, :export,
|
|
68
|
+
:memoize=, :memoizing?, :read_only?,
|
|
69
69
|
:sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper.
|
|
70
70
|
|
|
71
|
+
def any(*args)
|
|
72
|
+
Expression.build({ Any: args.flatten })
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def all(*args)
|
|
76
|
+
Expression.build({ All: args.flatten })
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def constant(value)
|
|
80
|
+
Expression.build(value)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def property(name)
|
|
84
|
+
Expression.build({ Property: name })
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def string(value)
|
|
88
|
+
Expression.build({ String: value })
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def number(value)
|
|
92
|
+
Expression.build({ Number: value })
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def boolean(value)
|
|
96
|
+
Expression.build({ Boolean: value })
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def random(max)
|
|
100
|
+
Expression.build({ Random: max })
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def now
|
|
104
|
+
Expression.build({ Now: [] })
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def time(value)
|
|
108
|
+
Expression.build({ Time: value })
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def feature_enabled(name)
|
|
112
|
+
Expression.build({ FeatureEnabled: name })
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def feature_disabled(name)
|
|
116
|
+
feature_enabled(name).eq(false)
|
|
117
|
+
end
|
|
118
|
+
|
|
71
119
|
# Public: Use this to register a group by name.
|
|
72
120
|
#
|
|
73
121
|
# name - The Symbol name of the group.
|
|
@@ -142,9 +190,13 @@ end
|
|
|
142
190
|
|
|
143
191
|
require 'flipper/actor'
|
|
144
192
|
require 'flipper/adapter'
|
|
193
|
+
require 'flipper/adapters/wrapper'
|
|
194
|
+
require 'flipper/adapters/actor_limit'
|
|
195
|
+
require 'flipper/adapters/instrumented'
|
|
145
196
|
require 'flipper/adapters/memoizable'
|
|
146
197
|
require 'flipper/adapters/memory'
|
|
147
|
-
require 'flipper/adapters/
|
|
198
|
+
require 'flipper/adapters/strict'
|
|
199
|
+
require 'flipper/adapter_builder'
|
|
148
200
|
require 'flipper/configuration'
|
|
149
201
|
require 'flipper/dsl'
|
|
150
202
|
require 'flipper/errors'
|
|
@@ -157,6 +209,7 @@ require 'flipper/middleware/memoizer'
|
|
|
157
209
|
require 'flipper/middleware/setup_env'
|
|
158
210
|
require 'flipper/poller'
|
|
159
211
|
require 'flipper/registry'
|
|
212
|
+
require 'flipper/expression'
|
|
160
213
|
require 'flipper/type'
|
|
161
214
|
require 'flipper/types/actor'
|
|
162
215
|
require 'flipper/types/boolean'
|
|
@@ -167,4 +220,4 @@ require 'flipper/types/percentage_of_time'
|
|
|
167
220
|
require 'flipper/typecast'
|
|
168
221
|
require 'flipper/version'
|
|
169
222
|
|
|
170
|
-
require "flipper/
|
|
223
|
+
require "flipper/engine" if defined?(Rails)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'rails/generators/active_record'
|
|
2
|
+
|
|
3
|
+
module Flipper
|
|
4
|
+
module Generators
|
|
5
|
+
class SetupGenerator < ::Rails::Generators::Base
|
|
6
|
+
desc 'Peform any necessary steps to install Flipper'
|
|
7
|
+
source_paths << File.expand_path('templates', __dir__)
|
|
8
|
+
|
|
9
|
+
class_option :token, type: :string, default: nil, aliases: '-t',
|
|
10
|
+
desc: "Your personal environment token for Flipper Cloud"
|
|
11
|
+
|
|
12
|
+
def generate_initializer
|
|
13
|
+
template 'initializer.rb', 'config/initializers/flipper.rb'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate_active_record
|
|
17
|
+
invoke 'flipper:active_record' if defined?(Flipper::Adapters::ActiveRecord)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure_cloud_token
|
|
21
|
+
return unless options[:token]
|
|
22
|
+
|
|
23
|
+
configure_with_dotenv || configure_with_credentials
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def configure_with_dotenv
|
|
29
|
+
['.env.development', '.env.local', '.env'].detect do |file|
|
|
30
|
+
next unless exists?(file)
|
|
31
|
+
append_to_file file, "\nFLIPPER_CLOUD_TOKEN=#{options[:token]}\n"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def configure_with_credentials
|
|
36
|
+
return unless exists?("config/credentials.yml.enc") && (ENV["RAILS_MASTER_KEY"] || exists?("config/master.key"))
|
|
37
|
+
|
|
38
|
+
content = "flipper:\n cloud_token: #{options[:token]}\n"
|
|
39
|
+
action InjectIntoEncryptedFile.new(self, Rails.application.credentials, content, after: /\z/)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check if a file exists in the destination root
|
|
43
|
+
def exists?(path)
|
|
44
|
+
File.exist?(File.expand_path(path, destination_root))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Action to inject content into ActiveSupport::EncryptedFile
|
|
48
|
+
class InjectIntoEncryptedFile < Thor::Actions::InjectIntoFile
|
|
49
|
+
def initialize(base, encrypted_file, data, config)
|
|
50
|
+
@encrypted_file = encrypted_file
|
|
51
|
+
super(base, encrypted_file.content_path, data, config)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def content
|
|
55
|
+
@content ||= @encrypted_file.read
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def replace!(regexp, string, force)
|
|
59
|
+
if force || !replacement_present?
|
|
60
|
+
success = content.gsub!(regexp, string)
|
|
61
|
+
@encrypted_file.write content unless pretend?
|
|
62
|
+
success
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Rails.application.configure do
|
|
2
|
+
## Memoization ensures that only one adapter call is made per feature per request.
|
|
3
|
+
## For more info, see https://www.flippercloud.io/docs/optimization#memoization
|
|
4
|
+
# config.flipper.memoize = true
|
|
5
|
+
|
|
6
|
+
## Flipper preloads all features before each request, which is recommended if:
|
|
7
|
+
## * you have a limited number of features (< 100?)
|
|
8
|
+
## * most of your requests depend on most of your features
|
|
9
|
+
## * you have limited gate data combined across all features (< 1k enabled gates, like individual actors, across all features)
|
|
10
|
+
##
|
|
11
|
+
## For more info, see https://www.flippercloud.io/docs/optimization#preloading
|
|
12
|
+
# config.flipper.preload = true
|
|
13
|
+
|
|
14
|
+
## Warn or raise an error if an unknown feature is checked
|
|
15
|
+
## Can be set to `:warn`, `:raise`, or `false`
|
|
16
|
+
# config.flipper.strict = Rails.env.development? && :warn
|
|
17
|
+
|
|
18
|
+
## Show Flipper checks in logs
|
|
19
|
+
# config.flipper.log = true
|
|
20
|
+
|
|
21
|
+
## Reconfigure Flipper to use the Memory adapter and disable Cloud in tests
|
|
22
|
+
# config.flipper.test_help = true
|
|
23
|
+
|
|
24
|
+
## The path that Flipper Cloud will use to sync features
|
|
25
|
+
# config.flipper.cloud_path = "_flipper"
|
|
26
|
+
|
|
27
|
+
## The instrumenter that Flipper will use. Defaults to ActiveSupport::Notifications.
|
|
28
|
+
# config.flipper.instrumenter = ActiveSupport::Notifications
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Flipper.configure do |config|
|
|
32
|
+
## Configure other adapters that you want to use here:
|
|
33
|
+
## See http://flippercloud.io/docs/adapters
|
|
34
|
+
# config.use Flipper::Adapters::ActiveSupportCacheStore, Rails.cache, expires_in: 5.minutes
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
## Register a group that can be used for enabling features.
|
|
38
|
+
##
|
|
39
|
+
## Flipper.enable_group :my_feature, :admins
|
|
40
|
+
##
|
|
41
|
+
## See https://www.flippercloud.io/docs/features#enablement-group
|
|
42
|
+
#
|
|
43
|
+
# Flipper.register(:admins) do |actor|
|
|
44
|
+
# actor.respond_to?(:admin?) && actor.admin?
|
|
45
|
+
# end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class CreateFlipperTables < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
+
def up
|
|
3
|
+
create_table :flipper_features do |t|
|
|
4
|
+
t.string :key, null: false
|
|
5
|
+
t.timestamps null: false
|
|
6
|
+
end
|
|
7
|
+
add_index :flipper_features, :key, unique: true
|
|
8
|
+
|
|
9
|
+
create_table :flipper_gates do |t|
|
|
10
|
+
t.string :feature_key, null: false
|
|
11
|
+
t.string :key, null: false
|
|
12
|
+
t.string :value
|
|
13
|
+
t.timestamps null: false
|
|
14
|
+
end
|
|
15
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def down
|
|
19
|
+
drop_table :flipper_gates
|
|
20
|
+
drop_table :flipper_features
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ChangeFlipperGatesValueToText < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def up
|
|
5
|
+
# Ensure this incremental update migration is idempotent
|
|
6
|
+
return unless connection.column_exists? :flipper_gates, :value, :string
|
|
7
|
+
|
|
8
|
+
if index_exists? :flipper_gates, [:feature_key, :key, :value]
|
|
9
|
+
remove_index :flipper_gates, [:feature_key, :key, :value]
|
|
10
|
+
end
|
|
11
|
+
change_column :flipper_gates, :value, :text
|
|
12
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: { value: 255 }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def down
|
|
16
|
+
change_column :flipper_gates, :value, :string
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
|
|
6
|
+
module Flipper
|
|
7
|
+
module Generators
|
|
8
|
+
#
|
|
9
|
+
# Rails generator used for updating Flipper in a Rails application.
|
|
10
|
+
# Run it with +bin/rails g flipper:update+ in your console.
|
|
11
|
+
#
|
|
12
|
+
class UpdateGenerator < Rails::Generators::Base
|
|
13
|
+
include ActiveRecord::Generators::Migration
|
|
14
|
+
|
|
15
|
+
TEMPLATES = File.join(File.dirname(__FILE__), 'templates/update')
|
|
16
|
+
source_paths << TEMPLATES
|
|
17
|
+
|
|
18
|
+
# Generates incremental migration files unless they already exist.
|
|
19
|
+
# All migrations should be idempotent e.g. +add_index+ is guarded with +if_index_exists?+
|
|
20
|
+
def update_migration_files
|
|
21
|
+
migration_templates = Dir.children(File.join(TEMPLATES, 'migrations')).sort
|
|
22
|
+
migration_templates.each do |template_file|
|
|
23
|
+
destination_file = template_file.match(/^\d*_(.*\.rb)/)[1] # 01_create_flipper_tables.rb.erb => create_flipper_tables.rb
|
|
24
|
+
migration_template "migrations/#{template_file}", File.join(db_migrate_path, destination_file), skip: true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def migration_version
|
|
31
|
+
"[#{ActiveRecord::VERSION::STRING.to_f}]"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/package-lock.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flipper",
|
|
3
|
+
"lockfileVersion": 3,
|
|
4
|
+
"requires": true,
|
|
5
|
+
"packages": {
|
|
6
|
+
"": {
|
|
7
|
+
"hasInstallScript": true,
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@popperjs/core": "^2.11.8",
|
|
10
|
+
"bootstrap": "^5.3.3"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"node_modules/@popperjs/core": {
|
|
14
|
+
"version": "2.11.8",
|
|
15
|
+
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
|
16
|
+
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
|
17
|
+
"funding": {
|
|
18
|
+
"type": "opencollective",
|
|
19
|
+
"url": "https://opencollective.com/popperjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"node_modules/bootstrap": {
|
|
23
|
+
"version": "5.3.3",
|
|
24
|
+
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
|
25
|
+
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
|
26
|
+
"funding": [
|
|
27
|
+
{
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://github.com/sponsors/twbs"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "opencollective",
|
|
33
|
+
"url": "https://opencollective.com/bootstrap"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@popperjs/core": "^2.11.8"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
data/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Placeholder for config/environment.rb
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
RSpec.describe Flipper::AdapterBuilder do
|
|
2
|
+
describe "#initialize" do
|
|
3
|
+
it "instance_eval's block with no arg" do
|
|
4
|
+
called = false
|
|
5
|
+
self_in_block = nil
|
|
6
|
+
|
|
7
|
+
described_class.new do
|
|
8
|
+
called = true
|
|
9
|
+
self_in_block = self
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
expect(self_in_block).to be_instance_of(described_class)
|
|
13
|
+
expect(called).to be(true)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "evals block with arg" do
|
|
17
|
+
called = false
|
|
18
|
+
self_outside_block = self
|
|
19
|
+
self_in_block = nil
|
|
20
|
+
|
|
21
|
+
described_class.new do |arg|
|
|
22
|
+
called = true
|
|
23
|
+
self_in_block = self
|
|
24
|
+
expect(arg).to be_instance_of(described_class)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
expect(self_in_block).to be(self_outside_block)
|
|
28
|
+
expect(called).to be(true)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#use" do
|
|
33
|
+
it "wraps the store adapter with the given adapter" do
|
|
34
|
+
subject.use(Flipper::Adapters::Memoizable)
|
|
35
|
+
subject.use(Flipper::Adapters::Strict, :warn)
|
|
36
|
+
|
|
37
|
+
memoizable_adapter = subject.to_adapter
|
|
38
|
+
strict_adapter = memoizable_adapter.adapter
|
|
39
|
+
memory_adapter = strict_adapter.adapter
|
|
40
|
+
|
|
41
|
+
expect(memoizable_adapter).to be_instance_of(Flipper::Adapters::Memoizable)
|
|
42
|
+
expect(strict_adapter).to be_instance_of(Flipper::Adapters::Strict)
|
|
43
|
+
expect(strict_adapter.handler).to be(:warn)
|
|
44
|
+
expect(memory_adapter).to be_instance_of(Flipper::Adapters::Memory)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "passes block to adapter initializer" do
|
|
48
|
+
expected_block = lambda {}
|
|
49
|
+
adapter_class = double('adapter class')
|
|
50
|
+
|
|
51
|
+
subject.use(adapter_class, &expected_block)
|
|
52
|
+
|
|
53
|
+
expect(adapter_class).to receive(:new) { |&block| expect(block).to be(expected_block) }.and_return(:adapter)
|
|
54
|
+
expect(subject.to_adapter).to be(:adapter)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "#store" do
|
|
59
|
+
it "defaults to memory adapter" do
|
|
60
|
+
expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::Memory)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "only saves one store" do
|
|
64
|
+
require "flipper/adapters/pstore"
|
|
65
|
+
subject.store(Flipper::Adapters::PStore)
|
|
66
|
+
expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::PStore)
|
|
67
|
+
|
|
68
|
+
subject.store(Flipper::Adapters::Memory)
|
|
69
|
+
expect(subject.to_adapter).to be_instance_of(Flipper::Adapters::Memory)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -6,6 +6,7 @@ RSpec.describe Flipper::Adapter do
|
|
|
6
6
|
boolean: nil,
|
|
7
7
|
groups: Set.new,
|
|
8
8
|
actors: Set.new,
|
|
9
|
+
expression: nil,
|
|
9
10
|
percentage_of_actors: nil,
|
|
10
11
|
percentage_of_time: nil,
|
|
11
12
|
}
|
|
@@ -142,4 +143,24 @@ RSpec.describe Flipper::Adapter do
|
|
|
142
143
|
expect(export.features.dig("search", :boolean)).to eq("true")
|
|
143
144
|
end
|
|
144
145
|
end
|
|
146
|
+
|
|
147
|
+
describe "#adapter_stack" do
|
|
148
|
+
it "returns the adapter name for a simple adapter" do
|
|
149
|
+
adapter = Flipper::Adapters::Memory.new
|
|
150
|
+
expect(adapter.adapter_stack).to eq("memory")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "returns the chain for wrapped adapters" do
|
|
154
|
+
memory = Flipper::Adapters::Memory.new
|
|
155
|
+
memoizable = Flipper::Adapters::Memoizable.new(memory)
|
|
156
|
+
expect(memoizable.adapter_stack).to eq("memoizable -> memory")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "returns the chain for deeply nested adapters" do
|
|
160
|
+
memory = Flipper::Adapters::Memory.new
|
|
161
|
+
strict = Flipper::Adapters::Strict.new(memory)
|
|
162
|
+
memoizable = Flipper::Adapters::Memoizable.new(strict)
|
|
163
|
+
expect(memoizable.adapter_stack).to eq("memoizable -> strict -> memory")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
145
166
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "flipper/adapters/actor_limit"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Flipper::Adapters::ActorLimit do
|
|
4
|
+
it_should_behave_like 'a flipper adapter' do
|
|
5
|
+
let(:limit) { 5 }
|
|
6
|
+
let(:adapter) { Flipper::Adapters::ActorLimit.new(Flipper::Adapters::Memory.new, limit) }
|
|
7
|
+
|
|
8
|
+
subject { adapter }
|
|
9
|
+
|
|
10
|
+
describe '#enable' do
|
|
11
|
+
it "fails when limit exceeded" do
|
|
12
|
+
5.times { |i| feature.enable Flipper::Actor.new("User;#{i}") }
|
|
13
|
+
|
|
14
|
+
expect {
|
|
15
|
+
feature.enable Flipper::Actor.new("User;6")
|
|
16
|
+
}.to raise_error(Flipper::Adapters::ActorLimit::LimitExceeded)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "allows exceeding limit when in sync mode" do
|
|
20
|
+
5.times { |i| feature.enable Flipper::Actor.new("User;#{i}") }
|
|
21
|
+
|
|
22
|
+
described_class.with_sync_mode do
|
|
23
|
+
expect {
|
|
24
|
+
feature.enable Flipper::Actor.new("User;6")
|
|
25
|
+
}.not_to raise_error
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '.sync_mode' do
|
|
32
|
+
after do
|
|
33
|
+
described_class.sync_mode = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'defaults to nil/falsy' do
|
|
37
|
+
expect(described_class.sync_mode).to be_falsy
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'can be set and read' do
|
|
41
|
+
described_class.sync_mode = true
|
|
42
|
+
expect(described_class.sync_mode).to be true
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '.with_sync_mode' do
|
|
47
|
+
after do
|
|
48
|
+
described_class.sync_mode = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'sets sync_mode to true within block' do
|
|
52
|
+
described_class.with_sync_mode do
|
|
53
|
+
expect(described_class.sync_mode).to be true
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'restores previous value after block' do
|
|
58
|
+
expect(described_class.sync_mode).to be_falsy
|
|
59
|
+
described_class.with_sync_mode { }
|
|
60
|
+
expect(described_class.sync_mode).to be_falsy
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'restores previous value even on exception' do
|
|
64
|
+
expect {
|
|
65
|
+
described_class.with_sync_mode { raise "boom" }
|
|
66
|
+
}.to raise_error("boom")
|
|
67
|
+
expect(described_class.sync_mode).to be_falsy
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'returns the block result' do
|
|
71
|
+
result = described_class.with_sync_mode { 42 }
|
|
72
|
+
expect(result).to eq(42)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -55,15 +55,28 @@ RSpec.describe Flipper::Adapters::DualWrite do
|
|
|
55
55
|
|
|
56
56
|
it 'updates remote and local for #enable' do
|
|
57
57
|
feature = sync[:search]
|
|
58
|
-
subject.enable feature, feature.gate(:boolean),
|
|
58
|
+
subject.enable feature, feature.gate(:boolean), Flipper::Types::Boolean.new(true)
|
|
59
59
|
expect(remote_adapter.count(:enable)).to be(1)
|
|
60
60
|
expect(local_adapter.count(:enable)).to be(1)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
it 'updates remote and local for #disable' do
|
|
64
64
|
feature = sync[:search]
|
|
65
|
-
subject.disable feature, feature.gate(:boolean),
|
|
65
|
+
subject.disable feature, feature.gate(:boolean), Flipper::Types::Boolean.new(false)
|
|
66
66
|
expect(remote_adapter.count(:disable)).to be(1)
|
|
67
67
|
expect(local_adapter.count(:disable)).to be(1)
|
|
68
68
|
end
|
|
69
|
+
|
|
70
|
+
describe '#adapter_stack' do
|
|
71
|
+
it 'returns the tree representation' do
|
|
72
|
+
expect(subject.adapter_stack).to eq("dual_write(local: operation_logger -> memory, remote: operation_logger -> memory)")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'shows nested adapters in the tree' do
|
|
76
|
+
memory = Flipper::Adapters::Memory.new
|
|
77
|
+
strict = Flipper::Adapters::Strict.new(Flipper::Adapters::Memory.new)
|
|
78
|
+
adapter = described_class.new(memory, strict)
|
|
79
|
+
expect(adapter.adapter_stack).to eq("dual_write(local: memory, remote: strict -> memory)")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
69
82
|
end
|