flipper 0.22.0 → 0.28.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 +4 -4
- data/.codeclimate.yml +1 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +26 -20
- data/.github/workflows/examples.yml +62 -0
- data/.rspec +1 -0
- data/.tool-versions +1 -0
- data/Changelog.md +152 -3
- data/Dockerfile +1 -1
- data/Gemfile +9 -6
- data/README.md +15 -67
- data/Rakefile +4 -2
- 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 +19 -0
- data/docker-compose.yml +37 -34
- data/docs/README.md +1 -0
- data/docs/images/banner.jpg +0 -0
- data/examples/api/basic.ru +3 -4
- data/examples/api/custom_memoized.ru +3 -4
- data/examples/api/memoized.ru +3 -4
- data/examples/dsl.rb +3 -3
- data/examples/enabled_for_actor.rb +4 -2
- data/examples/instrumentation.rb +1 -0
- data/examples/instrumentation_last_accessed_at.rb +38 -0
- data/flipper.gemspec +2 -2
- data/lib/flipper/actor.rb +4 -0
- data/lib/flipper/adapter.rb +23 -7
- data/lib/flipper/adapters/dual_write.rb +10 -16
- data/lib/flipper/adapters/failover.rb +83 -0
- data/lib/flipper/adapters/failsafe.rb +76 -0
- data/lib/flipper/adapters/http/client.rb +18 -12
- data/lib/flipper/adapters/http/error.rb +19 -1
- data/lib/flipper/adapters/http.rb +14 -4
- data/lib/flipper/adapters/instrumented.rb +25 -2
- data/lib/flipper/adapters/memoizable.rb +27 -18
- data/lib/flipper/adapters/memory.rb +56 -39
- data/lib/flipper/adapters/operation_logger.rb +16 -3
- data/lib/flipper/adapters/poll/poller.rb +2 -0
- data/lib/flipper/adapters/poll.rb +39 -0
- data/lib/flipper/adapters/pstore.rb +2 -5
- data/lib/flipper/adapters/sync/interval_synchronizer.rb +1 -6
- data/lib/flipper/adapters/sync/synchronizer.rb +2 -1
- data/lib/flipper/adapters/sync.rb +9 -15
- data/lib/flipper/dsl.rb +9 -11
- data/lib/flipper/errors.rb +3 -20
- data/lib/flipper/export.rb +26 -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/feature.rb +32 -26
- data/lib/flipper/feature_check_context.rb +10 -6
- data/lib/flipper/gate.rb +12 -11
- data/lib/flipper/gate_values.rb +0 -16
- data/lib/flipper/gates/actor.rb +10 -17
- data/lib/flipper/gates/boolean.rb +1 -1
- 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 +2 -2
- data/lib/flipper/instrumentation/log_subscriber.rb +7 -3
- data/lib/flipper/instrumentation/subscriber.rb +8 -1
- data/lib/flipper/instrumenters/memory.rb +6 -2
- data/lib/flipper/metadata.rb +1 -1
- data/lib/flipper/middleware/memoizer.rb +2 -12
- data/lib/flipper/poller.rb +117 -0
- data/lib/flipper/railtie.rb +23 -22
- data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
- data/lib/flipper/test/shared_adapter_test.rb +24 -0
- data/lib/flipper/typecast.rb +28 -15
- data/lib/flipper/types/actor.rb +19 -13
- data/lib/flipper/types/group.rb +12 -5
- data/lib/flipper/version.rb +1 -1
- data/lib/flipper.rb +6 -4
- data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
- data/spec/flipper/actor_spec.rb +10 -2
- data/spec/flipper/adapter_spec.rb +29 -4
- data/spec/flipper/adapters/dual_write_spec.rb +0 -2
- data/spec/flipper/adapters/failover_spec.rb +129 -0
- data/spec/flipper/adapters/failsafe_spec.rb +58 -0
- data/spec/flipper/adapters/http_spec.rb +64 -6
- data/spec/flipper/adapters/instrumented_spec.rb +28 -12
- data/spec/flipper/adapters/memoizable_spec.rb +30 -12
- data/spec/flipper/adapters/memory_spec.rb +3 -4
- data/spec/flipper/adapters/operation_logger_spec.rb +29 -12
- data/spec/flipper/adapters/pstore_spec.rb +0 -2
- data/spec/flipper/adapters/read_only_spec.rb +0 -1
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync/interval_synchronizer_spec.rb +4 -5
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +0 -1
- data/spec/flipper/adapters/sync_spec.rb +0 -2
- data/spec/flipper/configuration_spec.rb +0 -1
- data/spec/flipper/dsl_spec.rb +38 -12
- 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/feature_check_context_spec.rb +17 -19
- data/spec/flipper/feature_spec.rb +76 -33
- 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/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 +0 -1
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -6
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -1
- 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 +0 -23
- data/spec/flipper/middleware/setup_env_spec.rb +0 -2
- data/spec/flipper/poller_spec.rb +47 -0
- data/spec/flipper/railtie_spec.rb +73 -33
- data/spec/flipper/registry_spec.rb +0 -1
- data/spec/flipper/typecast_spec.rb +82 -4
- data/spec/flipper/types/actor_spec.rb +45 -46
- data/spec/flipper/types/boolean_spec.rb +0 -1
- data/spec/flipper/types/group_spec.rb +2 -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/flipper_integration_spec.rb +62 -51
- data/spec/flipper_spec.rb +7 -2
- data/spec/{helper.rb → spec_helper.rb} +4 -2
- data/spec/support/skippable.rb +18 -0
- data/spec/support/spec_helpers.rb +2 -6
- metadata +63 -19
- 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 -137
- data/docs/api/README.md +0 -884
- data/docs/http/README.md +0 -36
- data/docs/read-only/README.md +0 -24
data/docker-compose.yml
CHANGED
|
@@ -1,34 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
version: "2.4"
|
|
2
|
+
services:
|
|
3
|
+
# postgres:
|
|
4
|
+
# container_name: flipper_postgres
|
|
5
|
+
# image: postgres:9.4
|
|
6
|
+
redis:
|
|
7
|
+
container_name: flipper_redis
|
|
8
|
+
image: redis:6.2.5
|
|
9
|
+
mongo:
|
|
10
|
+
container_name: flipper_mongo
|
|
11
|
+
image: mongo:4.4.8
|
|
12
|
+
memcached:
|
|
13
|
+
container_name: flipper_memcached
|
|
14
|
+
image: memcached:1.4.33
|
|
15
|
+
app:
|
|
16
|
+
container_name: flipper_app
|
|
17
|
+
build:
|
|
18
|
+
context: .
|
|
19
|
+
dockerfile: Dockerfile
|
|
20
|
+
volumes:
|
|
21
|
+
- .:/srv/app
|
|
22
|
+
volumes_from:
|
|
23
|
+
- bundle_cache
|
|
24
|
+
links:
|
|
25
|
+
# - postgres
|
|
26
|
+
- redis
|
|
27
|
+
- mongo
|
|
28
|
+
- memcached
|
|
29
|
+
environment:
|
|
30
|
+
- REDIS_URL=redis://redis:6379
|
|
31
|
+
- MONGODB_HOST=mongo
|
|
32
|
+
- MEMCACHED_URL=memcached:11211
|
|
33
|
+
bundle_cache:
|
|
34
|
+
container_name: flipper_bundle_cache
|
|
35
|
+
image: busybox
|
|
36
|
+
volumes:
|
|
37
|
+
- /bundle_cache
|
data/docs/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
See [flippercloud.io](https://flippercloud.io/docs) for extensive docs.
|
data/docs/images/banner.jpg
CHANGED
|
Binary file
|
data/examples/api/basic.ru
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Usage:
|
|
3
|
-
# # if you want it to not reload and be really fast
|
|
4
3
|
# bin/rackup examples/api/basic.ru -p 9999
|
|
5
4
|
#
|
|
6
|
-
# # if you want reloading
|
|
7
|
-
# bin/shotgun examples/api/basic.ru -p 9999
|
|
8
|
-
#
|
|
9
5
|
# http://localhost:9999/
|
|
10
6
|
#
|
|
11
7
|
|
|
12
8
|
require 'bundler/setup'
|
|
9
|
+
require 'rack/reloader'
|
|
13
10
|
require "flipper/api"
|
|
14
11
|
require "flipper/adapters/pstore"
|
|
15
12
|
|
|
16
13
|
# You can uncomment this to get some default data:
|
|
17
14
|
# Flipper.enable :logging
|
|
18
15
|
|
|
16
|
+
use Rack::Reloader
|
|
17
|
+
|
|
19
18
|
run Flipper::Api.app
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Usage:
|
|
3
|
-
# # if you want it to not reload and be really fast
|
|
4
3
|
# bin/rackup examples/api/custom_memoized.ru -p 9999
|
|
5
4
|
#
|
|
6
|
-
# # if you want reloading
|
|
7
|
-
# bin/shotgun examples/api/custom_memoized.ru -p 9999
|
|
8
|
-
#
|
|
9
5
|
# http://localhost:9999/
|
|
10
6
|
#
|
|
11
7
|
|
|
12
8
|
require 'bundler/setup'
|
|
9
|
+
require 'rack/reloader'
|
|
13
10
|
require "active_support/notifications"
|
|
14
11
|
require "flipper/api"
|
|
15
12
|
require "flipper/adapters/pstore"
|
|
@@ -31,6 +28,8 @@ ActiveSupport::Notifications.subscribe(/.*/, ->(*args) {
|
|
|
31
28
|
# You can uncomment this to get some default data:
|
|
32
29
|
# flipper[:logging].enable_percentage_of_time 5
|
|
33
30
|
|
|
31
|
+
use Rack::Reloader
|
|
32
|
+
|
|
34
33
|
run Flipper::Api.app(flipper) { |builder|
|
|
35
34
|
builder.use Flipper::Middleware::SetupEnv, flipper
|
|
36
35
|
builder.use Flipper::Middleware::Memoizer, preload: true
|
data/examples/api/memoized.ru
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Usage:
|
|
3
|
-
# # if you want it to not reload and be really fast
|
|
4
3
|
# bin/rackup examples/api/memoized.ru -p 9999
|
|
5
4
|
#
|
|
6
|
-
# # if you want reloading
|
|
7
|
-
# bin/shotgun examples/api/memoized.ru -p 9999
|
|
8
|
-
#
|
|
9
5
|
# http://localhost:9999/
|
|
10
6
|
#
|
|
11
7
|
|
|
12
8
|
require 'bundler/setup'
|
|
9
|
+
require 'rack/reloader'
|
|
13
10
|
require "active_support/notifications"
|
|
14
11
|
require "flipper/api"
|
|
15
12
|
require "flipper/adapters/pstore"
|
|
@@ -38,6 +35,8 @@ Flipper.register(:admins) { |actor|
|
|
|
38
35
|
# You can uncomment this to get some default data:
|
|
39
36
|
# Flipper.enable :logging
|
|
40
37
|
|
|
38
|
+
use Rack::Reloader
|
|
39
|
+
|
|
41
40
|
run Flipper::Api.app { |builder|
|
|
42
41
|
builder.use Flipper::Middleware::Memoizer, preload: true
|
|
43
42
|
}
|
data/examples/dsl.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'bundler/setup'
|
|
2
2
|
require 'flipper'
|
|
3
3
|
|
|
4
|
-
# create
|
|
4
|
+
# create an actor with an identifier
|
|
5
5
|
class Person < Struct.new(:id)
|
|
6
6
|
include Flipper::Identifier
|
|
7
7
|
end
|
|
@@ -58,8 +58,8 @@ responds_to_flipper_id = Struct.new(:flipper_id).new(10)
|
|
|
58
58
|
puts Flipper.actor(responds_to_flipper_id).inspect
|
|
59
59
|
|
|
60
60
|
# get an instance of an actor using an object
|
|
61
|
-
|
|
62
|
-
puts Flipper.actor(
|
|
61
|
+
actor = Struct.new(:flipper_id).new(22)
|
|
62
|
+
puts Flipper.actor(actor).inspect
|
|
63
63
|
|
|
64
64
|
# register a top level group
|
|
65
65
|
admins = Flipper.register(:admins) { |actor|
|
|
@@ -31,5 +31,7 @@ Flipper.enable_percentage_of_actors :pro_stats, 50
|
|
|
31
31
|
Flipper.enable_group :tweets, :admins
|
|
32
32
|
Flipper.enable_actor :posts, user2
|
|
33
33
|
|
|
34
|
-
pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name)
|
|
35
|
-
pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name)
|
|
34
|
+
pp Flipper.features.select { |feature| feature.enabled?(user1) }.map(&:name).sort
|
|
35
|
+
pp Flipper.features.select { |feature| feature.enabled?(user2) }.map(&:name).sort
|
|
36
|
+
pp Flipper.features.select { |feature| feature.enabled?(user1, user2) }.map(&:name).sort
|
|
37
|
+
pp Flipper.features.select { |feature| feature.enabled?([user2, user1]) }.map(&:name).sort
|
data/examples/instrumentation.rb
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Quick example of how to keep track of when a feature was last checked.
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'active_support/isolated_execution_state'
|
|
5
|
+
require 'active_support/notifications'
|
|
6
|
+
require 'flipper'
|
|
7
|
+
|
|
8
|
+
class FlipperSubscriber
|
|
9
|
+
def self.stats
|
|
10
|
+
@stats ||= {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(name, start, finish, id, payload)
|
|
14
|
+
if payload[:operation] == :enabled?
|
|
15
|
+
feature_name = payload.fetch(:feature_name)
|
|
16
|
+
self.class.stats[feature_name] = Time.now
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
ActiveSupport::Notifications.subscribe(/feature_operation.flipper/, new)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Flipper.configure do |config|
|
|
24
|
+
config.default {
|
|
25
|
+
Flipper.new(config.adapter, instrumenter: ActiveSupport::Notifications)
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Flipper.enabled?(:search)
|
|
30
|
+
Flipper.enabled?(:booyeah)
|
|
31
|
+
Flipper.enabled?(:hooray)
|
|
32
|
+
sleep 1
|
|
33
|
+
Flipper.enabled?(:booyeah)
|
|
34
|
+
Flipper.enabled?(:hooray)
|
|
35
|
+
sleep 1
|
|
36
|
+
Flipper.enabled?(:hooray)
|
|
37
|
+
|
|
38
|
+
pp FlipperSubscriber.stats
|
data/flipper.gemspec
CHANGED
|
@@ -13,7 +13,6 @@ end
|
|
|
13
13
|
|
|
14
14
|
ignored_files = plugin_files
|
|
15
15
|
ignored_files << Dir['script/*']
|
|
16
|
-
ignored_files << '.travis.yml'
|
|
17
16
|
ignored_files << '.gitignore'
|
|
18
17
|
ignored_files << 'Guardfile'
|
|
19
18
|
ignored_files.flatten!.uniq!
|
|
@@ -28,11 +27,12 @@ Gem::Specification.new do |gem|
|
|
|
28
27
|
gem.homepage = 'https://github.com/jnunemaker/flipper'
|
|
29
28
|
gem.license = 'MIT'
|
|
30
29
|
|
|
31
|
-
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
|
32
30
|
gem.files = `git ls-files`.split("\n") - ignored_files + ['lib/flipper/version.rb']
|
|
33
31
|
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - ignored_test_files
|
|
34
32
|
gem.name = 'flipper'
|
|
35
33
|
gem.require_paths = ['lib']
|
|
36
34
|
gem.version = Flipper::VERSION
|
|
37
35
|
gem.metadata = Flipper::METADATA
|
|
36
|
+
|
|
37
|
+
gem.add_dependency 'concurrent-ruby', '< 2'
|
|
38
38
|
end
|
data/lib/flipper/actor.rb
CHANGED
data/lib/flipper/adapter.rb
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
require "set"
|
|
2
|
-
require "flipper/feature"
|
|
3
|
-
require "flipper/adapters/sync/synchronizer"
|
|
4
|
-
|
|
5
1
|
module Flipper
|
|
6
2
|
# Adding a module include so we have some hooks for stuff down the road
|
|
7
3
|
module Adapter
|
|
@@ -20,6 +16,11 @@ module Flipper
|
|
|
20
16
|
percentage_of_time: nil,
|
|
21
17
|
}
|
|
22
18
|
end
|
|
19
|
+
|
|
20
|
+
def from(source)
|
|
21
|
+
return source if source.is_a?(Flipper::Adapter)
|
|
22
|
+
source.adapter
|
|
23
|
+
end
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# Public: Get all features and gate values in one call. Defaults to one call
|
|
@@ -43,9 +44,19 @@ module Flipper
|
|
|
43
44
|
|
|
44
45
|
# Public: Ensure that adapter is in sync with source adapter provided.
|
|
45
46
|
#
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
# source - The source dsl, adapter or export to import.
|
|
48
|
+
#
|
|
49
|
+
# Returns true if successful.
|
|
50
|
+
def import(source)
|
|
51
|
+
Adapters::Sync::Synchronizer.new(self, self.class.from(source), raise: true).call
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Public: Exports the adapter in a given format for a given format version.
|
|
56
|
+
#
|
|
57
|
+
# Returns a Flipper::Export instance.
|
|
58
|
+
def export(format: :json, version: 1)
|
|
59
|
+
Flipper::Exporter.build(format: format, version: version).call(self)
|
|
49
60
|
end
|
|
50
61
|
|
|
51
62
|
# Public: Default config for a feature's gate values.
|
|
@@ -54,3 +65,8 @@ module Flipper
|
|
|
54
65
|
end
|
|
55
66
|
end
|
|
56
67
|
end
|
|
68
|
+
|
|
69
|
+
require "set"
|
|
70
|
+
require "flipper/exporter"
|
|
71
|
+
require "flipper/feature"
|
|
72
|
+
require "flipper/adapters/sync/synchronizer"
|
|
@@ -4,7 +4,7 @@ module Flipper
|
|
|
4
4
|
include ::Flipper::Adapter
|
|
5
5
|
|
|
6
6
|
# Public: The name of the adapter.
|
|
7
|
-
attr_reader :name
|
|
7
|
+
attr_reader :name, :local, :remote
|
|
8
8
|
|
|
9
9
|
# Public: Build a new sync instance.
|
|
10
10
|
#
|
|
@@ -34,33 +34,27 @@ module Flipper
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def add(feature)
|
|
37
|
-
|
|
38
|
-
@local.add(feature)
|
|
39
|
-
result
|
|
37
|
+
@remote.add(feature).tap { @local.add(feature) }
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
def remove(feature)
|
|
43
|
-
|
|
44
|
-
@local.remove(feature)
|
|
45
|
-
result
|
|
41
|
+
@remote.remove(feature).tap { @local.remove(feature) }
|
|
46
42
|
end
|
|
47
43
|
|
|
48
44
|
def clear(feature)
|
|
49
|
-
|
|
50
|
-
@local.clear(feature)
|
|
51
|
-
result
|
|
45
|
+
@remote.clear(feature).tap { @local.clear(feature) }
|
|
52
46
|
end
|
|
53
47
|
|
|
54
48
|
def enable(feature, gate, thing)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
@remote.enable(feature, gate, thing).tap do
|
|
50
|
+
@local.enable(feature, gate, thing)
|
|
51
|
+
end
|
|
58
52
|
end
|
|
59
53
|
|
|
60
54
|
def disable(feature, gate, thing)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
@remote.disable(feature, gate, thing).tap do
|
|
56
|
+
@local.disable(feature, gate, thing)
|
|
57
|
+
end
|
|
64
58
|
end
|
|
65
59
|
end
|
|
66
60
|
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
module Adapters
|
|
3
|
+
class Failover
|
|
4
|
+
include ::Flipper::Adapter
|
|
5
|
+
|
|
6
|
+
# Public: The name of the adapter.
|
|
7
|
+
attr_reader :name
|
|
8
|
+
|
|
9
|
+
# Public: Build a new failover instance.
|
|
10
|
+
#
|
|
11
|
+
# primary - The primary flipper adapter.
|
|
12
|
+
# secondary - The secondary flipper adapter which services reads when
|
|
13
|
+
# the primary adapter is unavailable.
|
|
14
|
+
# options - Hash of options:
|
|
15
|
+
# :dual_write - Boolean, whether to update secondary when
|
|
16
|
+
# primary is updated
|
|
17
|
+
# :errors - Array of exception types for which to failover
|
|
18
|
+
|
|
19
|
+
def initialize(primary, secondary, options = {})
|
|
20
|
+
@name = :failover
|
|
21
|
+
@primary = primary
|
|
22
|
+
@secondary = secondary
|
|
23
|
+
|
|
24
|
+
@dual_write = options.fetch(:dual_write, false)
|
|
25
|
+
@errors = options.fetch(:errors, [ StandardError ])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def features
|
|
29
|
+
@primary.features
|
|
30
|
+
rescue *@errors
|
|
31
|
+
@secondary.features
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def get(feature)
|
|
35
|
+
@primary.get(feature)
|
|
36
|
+
rescue *@errors
|
|
37
|
+
@secondary.get(feature)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_multi(features)
|
|
41
|
+
@primary.get_multi(features)
|
|
42
|
+
rescue *@errors
|
|
43
|
+
@secondary.get_multi(features)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def get_all
|
|
47
|
+
@primary.get_all
|
|
48
|
+
rescue *@errors
|
|
49
|
+
@secondary.get_all
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def add(feature)
|
|
53
|
+
@primary.add(feature).tap do
|
|
54
|
+
@secondary.add(feature) if @dual_write
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def remove(feature)
|
|
59
|
+
@primary.remove(feature).tap do
|
|
60
|
+
@secondary.remove(feature) if @dual_write
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def clear(feature)
|
|
65
|
+
@primary.clear(feature).tap do
|
|
66
|
+
@secondary.clear(feature) if @dual_write
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def enable(feature, gate, thing)
|
|
71
|
+
@primary.enable(feature, gate, thing).tap do
|
|
72
|
+
@secondary.enable(feature, gate, thing) if @dual_write
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def disable(feature, gate, thing)
|
|
77
|
+
@primary.disable(feature, gate, thing).tap do
|
|
78
|
+
@secondary.disable(feature, gate, thing) if @dual_write
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
module Adapters
|
|
3
|
+
class Failsafe
|
|
4
|
+
include ::Flipper::Adapter
|
|
5
|
+
|
|
6
|
+
# Public: The name of the adapter.
|
|
7
|
+
attr_reader :name
|
|
8
|
+
|
|
9
|
+
# Public: Build a new Failsafe instance.
|
|
10
|
+
#
|
|
11
|
+
# adapter - Flipper adapter to guard.
|
|
12
|
+
# options - Hash of options:
|
|
13
|
+
# :errors - Array of exception types for which to fail safe
|
|
14
|
+
|
|
15
|
+
def initialize(adapter, options = {})
|
|
16
|
+
@adapter = adapter
|
|
17
|
+
@errors = options.fetch(:errors, [StandardError])
|
|
18
|
+
@name = :failsafe
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def features
|
|
22
|
+
@adapter.features
|
|
23
|
+
rescue *@errors
|
|
24
|
+
Set.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add(feature)
|
|
28
|
+
@adapter.add(feature)
|
|
29
|
+
rescue *@errors
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def remove(feature)
|
|
34
|
+
@adapter.remove(feature)
|
|
35
|
+
rescue *@errors
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def clear(feature)
|
|
40
|
+
@adapter.clear(feature)
|
|
41
|
+
rescue *@errors
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def get(feature)
|
|
46
|
+
@adapter.get(feature)
|
|
47
|
+
rescue *@errors
|
|
48
|
+
{}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def get_multi(features)
|
|
52
|
+
@adapter.get_multi(features)
|
|
53
|
+
rescue *@errors
|
|
54
|
+
{}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def get_all
|
|
58
|
+
@adapter.get_all
|
|
59
|
+
rescue *@errors
|
|
60
|
+
{}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def enable(feature, gate, thing)
|
|
64
|
+
@adapter.enable(feature, gate, thing)
|
|
65
|
+
rescue *@errors
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def disable(feature, gate, thing)
|
|
70
|
+
@adapter.disable(feature, gate, thing)
|
|
71
|
+
rescue *@errors
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -14,6 +14,10 @@ module Flipper
|
|
|
14
14
|
|
|
15
15
|
HTTPS_SCHEME = "https".freeze
|
|
16
16
|
|
|
17
|
+
attr_reader :uri, :headers
|
|
18
|
+
attr_reader :basic_auth_username, :basic_auth_password
|
|
19
|
+
attr_reader :read_timeout, :open_timeout, :write_timeout, :max_retries, :debug_output
|
|
20
|
+
|
|
17
21
|
def initialize(options = {})
|
|
18
22
|
@uri = URI(options.fetch(:url))
|
|
19
23
|
@headers = DEFAULT_HEADERS.merge(options[:headers] || {})
|
|
@@ -22,6 +26,7 @@ module Flipper
|
|
|
22
26
|
@read_timeout = options[:read_timeout]
|
|
23
27
|
@open_timeout = options[:open_timeout]
|
|
24
28
|
@write_timeout = options[:write_timeout]
|
|
29
|
+
@max_retries = options.key?(:max_retries) ? options[:max_retries] : 0
|
|
25
30
|
@debug_output = options[:debug_output]
|
|
26
31
|
end
|
|
27
32
|
|
|
@@ -58,7 +63,8 @@ module Flipper
|
|
|
58
63
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
59
64
|
http.read_timeout = @read_timeout if @read_timeout
|
|
60
65
|
http.open_timeout = @open_timeout if @open_timeout
|
|
61
|
-
|
|
66
|
+
http.max_retries = @max_retries if @max_retries
|
|
67
|
+
http.write_timeout = @write_timeout if @write_timeout
|
|
62
68
|
http.set_debug_output(@debug_output) if @debug_output
|
|
63
69
|
|
|
64
70
|
if uri.scheme == HTTPS_SCHEME
|
|
@@ -70,9 +76,19 @@ module Flipper
|
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
def build_request(http_method, uri, headers, options)
|
|
79
|
+
request_headers = {
|
|
80
|
+
"Client-Language" => "ruby",
|
|
81
|
+
"Client-Language-Version" => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
|
|
82
|
+
"Client-Platform" => RUBY_PLATFORM,
|
|
83
|
+
"Client-Engine" => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
|
|
84
|
+
"Client-Pid" => Process.pid.to_s,
|
|
85
|
+
"Client-Thread" => Thread.current.object_id.to_s,
|
|
86
|
+
"Client-Hostname" => Socket.gethostname,
|
|
87
|
+
}.merge(headers)
|
|
88
|
+
|
|
73
89
|
body = options[:body]
|
|
74
90
|
request = http_method.new(uri.request_uri)
|
|
75
|
-
request.initialize_http_header(
|
|
91
|
+
request.initialize_http_header(request_headers)
|
|
76
92
|
request.body = body if body
|
|
77
93
|
|
|
78
94
|
if @basic_auth_username && @basic_auth_password
|
|
@@ -81,16 +97,6 @@ module Flipper
|
|
|
81
97
|
|
|
82
98
|
request
|
|
83
99
|
end
|
|
84
|
-
|
|
85
|
-
def apply_write_timeout(http)
|
|
86
|
-
if @write_timeout
|
|
87
|
-
if RUBY_VERSION >= '2.6.0'
|
|
88
|
-
http.write_timeout = @write_timeout
|
|
89
|
-
else
|
|
90
|
-
Kernel.warn("Warning: option :write_timeout requires Ruby version 2.6.0 or later")
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
100
|
end
|
|
95
101
|
end
|
|
96
102
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
1
3
|
module Flipper
|
|
2
4
|
module Adapters
|
|
3
5
|
class Http
|
|
@@ -6,7 +8,23 @@ module Flipper
|
|
|
6
8
|
|
|
7
9
|
def initialize(response)
|
|
8
10
|
@response = response
|
|
9
|
-
|
|
11
|
+
message = "Failed with status: #{response.code}"
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
data = JSON.parse(response.body)
|
|
15
|
+
|
|
16
|
+
if error_message = data["message"]
|
|
17
|
+
message << "\n\n#{data["message"]}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if more_info = data["more_info"]
|
|
21
|
+
message << "\n#{data["more_info"]}"
|
|
22
|
+
end
|
|
23
|
+
rescue => exception
|
|
24
|
+
# welp we tried
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
super(message)
|
|
10
28
|
end
|
|
11
29
|
end
|
|
12
30
|
end
|
|
@@ -10,7 +10,7 @@ module Flipper
|
|
|
10
10
|
class Http
|
|
11
11
|
include Flipper::Adapter
|
|
12
12
|
|
|
13
|
-
attr_reader :name
|
|
13
|
+
attr_reader :name, :client
|
|
14
14
|
|
|
15
15
|
def initialize(options = {})
|
|
16
16
|
@client = Client.new(url: options.fetch(:url),
|
|
@@ -19,6 +19,8 @@ module Flipper
|
|
|
19
19
|
basic_auth_password: options[:basic_auth_password],
|
|
20
20
|
read_timeout: options[:read_timeout],
|
|
21
21
|
open_timeout: options[:open_timeout],
|
|
22
|
+
write_timeout: options[:write_timeout],
|
|
23
|
+
max_retries: options[:max_retries],
|
|
22
24
|
debug_output: options[:debug_output])
|
|
23
25
|
@name = :http
|
|
24
26
|
end
|
|
@@ -37,7 +39,7 @@ module Flipper
|
|
|
37
39
|
|
|
38
40
|
def get_multi(features)
|
|
39
41
|
csv_keys = features.map(&:key).join(',')
|
|
40
|
-
response = @client.get("/features?keys=#{csv_keys}")
|
|
42
|
+
response = @client.get("/features?keys=#{csv_keys}&exclude_gate_names=true")
|
|
41
43
|
raise Error, response unless response.is_a?(Net::HTTPOK)
|
|
42
44
|
|
|
43
45
|
parsed_response = JSON.parse(response.body)
|
|
@@ -55,7 +57,7 @@ module Flipper
|
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def get_all
|
|
58
|
-
response = @client.get("/features")
|
|
60
|
+
response = @client.get("/features?exclude_gate_names=true")
|
|
59
61
|
raise Error, response unless response.is_a?(Net::HTTPOK)
|
|
60
62
|
|
|
61
63
|
parsed_response = JSON.parse(response.body)
|
|
@@ -74,7 +76,7 @@ module Flipper
|
|
|
74
76
|
end
|
|
75
77
|
|
|
76
78
|
def features
|
|
77
|
-
response = @client.get('/features')
|
|
79
|
+
response = @client.get('/features?exclude_gate_names=true')
|
|
78
80
|
raise Error, response unless response.is_a?(Net::HTTPOK)
|
|
79
81
|
|
|
80
82
|
parsed_response = JSON.parse(response.body)
|
|
@@ -121,6 +123,14 @@ module Flipper
|
|
|
121
123
|
true
|
|
122
124
|
end
|
|
123
125
|
|
|
126
|
+
def import(source)
|
|
127
|
+
adapter = self.class.from(source)
|
|
128
|
+
export = adapter.export(format: :json, version: 1)
|
|
129
|
+
response = @client.post("/import", export.contents)
|
|
130
|
+
raise Error, response unless response.is_a?(Net::HTTPNoContent)
|
|
131
|
+
true
|
|
132
|
+
end
|
|
133
|
+
|
|
124
134
|
private
|
|
125
135
|
|
|
126
136
|
def request_body_for_gate(gate, value)
|