flipper-cloud 0.20.0 → 0.21.0.rc1
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/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/app.ru +3 -8
- data/examples/cloud/basic.rb +3 -8
- data/examples/cloud/cached_in_memory.rb +3 -4
- data/examples/cloud/import.rb +9 -17
- data/examples/cloud/local_adapter.rb +3 -4
- data/lib/flipper/cloud/configuration.rb +21 -17
- data/lib/flipper/cloud/middleware.rb +17 -5
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/cloud/configuration_spec.rb +6 -29
- data/spec/flipper/cloud/dsl_spec.rb +0 -4
- data/spec/flipper/cloud/middleware_spec.rb +113 -11
- data/spec/flipper/cloud_spec.rb +85 -0
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ba3c2db9aa7175de2301fb4fea82a25164e935b0f0fe5d7ce38911eee319cf9
|
4
|
+
data.tar.gz: e97f8ec07dcc20057d6934e1cf933be9517d53549317539900630e43a7ffd23e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62d4f0cc6817cfa654e1980156ca7e4d9ba81d1c0bb761f37651455b20f0b4732ceafe1adbf5a30ca09f8c1f8b80b8609c9d61ef969b82be48eedd183e4a5c15
|
7
|
+
data.tar.gz: 4540506c2b1c4eabe0140f3757e0414977ef876454755106bedfffd8159a880ffea37b6bca24bde30e9e6b79259ad86e30afb415219f7a22104c91bcd4edfbe8
|
Binary file
|
data/examples/cloud/app.ru
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
# Usage (from the repo root):
|
2
|
-
# env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret>
|
3
|
-
# env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret>
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> bundle exec rackup examples/cloud/app.ru -p 9999
|
3
|
+
# env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> bundle exec shotgun examples/cloud/app.ru -p 9999
|
4
4
|
# http://localhost:9999/
|
5
|
-
# http://localhost:9999/webhooks
|
6
|
-
|
7
|
-
require 'pathname'
|
8
|
-
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
9
|
-
lib_path = root_path.join('lib')
|
10
|
-
$:.unshift(lib_path)
|
11
5
|
|
6
|
+
require 'bundler/setup'
|
12
7
|
require 'flipper/cloud'
|
13
8
|
Flipper.configure do |config|
|
14
9
|
config.default { Flipper::Cloud.new }
|
data/examples/cloud/basic.rb
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
# Usage (from the repo root):
|
2
|
-
#
|
3
|
-
require '
|
4
|
-
require 'logger'
|
5
|
-
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
6
|
-
lib_path = root_path.join('lib')
|
7
|
-
$:.unshift(lib_path)
|
8
|
-
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
|
3
|
+
require 'bundler/setup'
|
9
4
|
require 'flipper/cloud'
|
10
|
-
flipper = Flipper::Cloud.new
|
5
|
+
flipper = Flipper::Cloud.new
|
11
6
|
|
12
7
|
flipper[:stats].enable
|
13
8
|
|
@@ -1,16 +1,15 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/cached_in_memory.rb
|
2
|
+
require 'bundler/setup'
|
3
3
|
require 'flipper/cloud'
|
4
4
|
require 'flipper/adapters/active_support_cache_store'
|
5
5
|
require 'active_support/cache'
|
6
6
|
require 'active_support/cache/memory_store'
|
7
7
|
|
8
|
-
token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
|
9
8
|
feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
|
10
9
|
|
11
10
|
Flipper.configure do |config|
|
12
11
|
config.default do
|
13
|
-
Flipper::Cloud.new
|
12
|
+
Flipper::Cloud.new do |cloud|
|
14
13
|
cloud.debug_output = STDOUT
|
15
14
|
cloud.adapter do |adapter|
|
16
15
|
Flipper::Adapters::ActiveSupportCacheStore.new(adapter,
|
data/examples/cloud/import.rb
CHANGED
@@ -1,23 +1,15 @@
|
|
1
1
|
# Usage (from the repo root):
|
2
|
-
# env
|
3
|
-
require '
|
4
|
-
require 'logger'
|
5
|
-
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
6
|
-
lib_path = root_path.join('lib')
|
7
|
-
$:.unshift(lib_path)
|
8
|
-
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
|
3
|
+
require 'bundler/setup'
|
9
4
|
require 'flipper'
|
10
5
|
require 'flipper/cloud'
|
11
6
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
memory_flipper.enable(:search)
|
17
|
-
memory_flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
18
|
-
memory_flipper.enable_percentage_of_time(:logging, 5)
|
7
|
+
Flipper.enable(:test)
|
8
|
+
Flipper.enable(:search)
|
9
|
+
Flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
10
|
+
Flipper.enable_percentage_of_time(:logging, 5)
|
19
11
|
|
20
|
-
|
12
|
+
cloud = Flipper::Cloud.new
|
21
13
|
|
22
|
-
#
|
23
|
-
|
14
|
+
# makes cloud identical to memory flipper
|
15
|
+
cloud.import(Flipper)
|
@@ -5,13 +5,12 @@
|
|
5
5
|
# never raise. You could get a slow request every now and then if cloud is
|
6
6
|
# unavailable, but we are hoping to fix that soon by doing the cloud update in a
|
7
7
|
# background thread.
|
8
|
-
|
9
|
-
|
8
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/local_adapter.rb
|
9
|
+
require 'bundler/setup'
|
10
10
|
require 'logger'
|
11
11
|
require 'flipper/cloud'
|
12
12
|
require 'flipper/adapters/redis'
|
13
13
|
|
14
|
-
token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
|
15
14
|
feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
|
16
15
|
|
17
16
|
redis = Redis.new(logger: Logger.new(STDOUT))
|
@@ -19,7 +18,7 @@ redis.flushdb
|
|
19
18
|
|
20
19
|
Flipper.configure do |config|
|
21
20
|
config.default do
|
22
|
-
Flipper::Cloud.new
|
21
|
+
Flipper::Cloud.new do |cloud|
|
23
22
|
cloud.debug_output = STDOUT
|
24
23
|
cloud.local_adapter = Flipper::Adapters::Redis.new(redis)
|
25
24
|
cloud.sync_interval = 10
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "socket"
|
1
2
|
require "flipper/adapters/http"
|
2
3
|
require "flipper/adapters/memory"
|
3
4
|
require "flipper/adapters/dual_write"
|
@@ -12,6 +13,8 @@ module Flipper
|
|
12
13
|
:webhook,
|
13
14
|
].freeze
|
14
15
|
|
16
|
+
DEFAULT_URL = "https://www.flippercloud.io/adapter".freeze
|
17
|
+
|
15
18
|
# Public: The token corresponding to an environment on flippercloud.io.
|
16
19
|
attr_accessor :token
|
17
20
|
|
@@ -56,10 +59,6 @@ module Flipper
|
|
56
59
|
# the local in sync with cloud (default: 10).
|
57
60
|
attr_accessor :sync_interval
|
58
61
|
|
59
|
-
# Public: The method to be used for synchronizing your local flipper
|
60
|
-
# adapter with cloud. (default: :poll, can also be :webhook).
|
61
|
-
attr_reader :sync_method
|
62
|
-
|
63
62
|
# Public: The secret used to verify if syncs in the middleware should
|
64
63
|
# occur or not.
|
65
64
|
attr_accessor :sync_secret
|
@@ -71,6 +70,11 @@ module Flipper
|
|
71
70
|
raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token (e.g. Flipper::Cloud.new('token'))."
|
72
71
|
end
|
73
72
|
|
73
|
+
if ENV["FLIPPER_CLOUD_SYNC_METHOD"]
|
74
|
+
warn "FLIPPER_CLOUD_SYNC_METHOD is deprecated and has no effect."
|
75
|
+
end
|
76
|
+
self.sync_method = options[:sync_method] if options[:sync_method]
|
77
|
+
|
74
78
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
75
79
|
@read_timeout = options.fetch(:read_timeout) { ENV.fetch("FLIPPER_CLOUD_READ_TIMEOUT", 5).to_f }
|
76
80
|
@open_timeout = options.fetch(:open_timeout) { ENV.fetch("FLIPPER_CLOUD_OPEN_TIMEOUT", 5).to_f }
|
@@ -80,8 +84,7 @@ module Flipper
|
|
80
84
|
@local_adapter = options.fetch(:local_adapter) { Adapters::Memory.new }
|
81
85
|
@debug_output = options[:debug_output]
|
82
86
|
@adapter_block = ->(adapter) { adapter }
|
83
|
-
self.
|
84
|
-
self.url = options.fetch(:url) { ENV.fetch("FLIPPER_CLOUD_URL", "https://www.flippercloud.io/adapter".freeze) }
|
87
|
+
self.url = options.fetch(:url) { ENV.fetch("FLIPPER_CLOUD_URL", DEFAULT_URL) }
|
85
88
|
end
|
86
89
|
|
87
90
|
# Public: Read or customize the http adapter. Calling without a block will
|
@@ -113,18 +116,14 @@ module Flipper
|
|
113
116
|
}).call
|
114
117
|
end
|
115
118
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
if new_sync_method == :webhook && sync_secret.nil?
|
124
|
-
raise ArgumentError, "Flipper::Cloud sync_secret is missing. Please set FLIPPER_CLOUD_SYNC_SECRET or provide the sync_secret used to validate webhooks."
|
125
|
-
end
|
119
|
+
# Public: The method that will be used to synchronize local adapter with
|
120
|
+
# cloud. (default: :poll, will be :webhook if sync_secret is set).
|
121
|
+
def sync_method
|
122
|
+
sync_secret ? :webhook : :poll
|
123
|
+
end
|
126
124
|
|
127
|
-
|
125
|
+
def sync_method=(_)
|
126
|
+
warn "Flipper::Cloud: sync_method is deprecated and has no effect."
|
128
127
|
end
|
129
128
|
|
130
129
|
private
|
@@ -153,6 +152,11 @@ module Flipper
|
|
153
152
|
headers: {
|
154
153
|
"Flipper-Cloud-Token" => @token,
|
155
154
|
"Feature-Flipper-Token" => @token,
|
155
|
+
"Client-Lang" => "ruby",
|
156
|
+
"Client-Lang-Version" => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
|
157
|
+
"Client-Platform" => RUBY_PLATFORM,
|
158
|
+
"Client-Engine" => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
|
159
|
+
"Client-Hostname" => Socket.gethostname,
|
156
160
|
},
|
157
161
|
})
|
158
162
|
end
|
@@ -7,6 +7,8 @@ module Flipper
|
|
7
7
|
class Middleware
|
8
8
|
# Internal: The path to match for webhook requests.
|
9
9
|
WEBHOOK_PATH = %r{\A/webhooks\/?\Z}
|
10
|
+
# Internal: The root path to match for requests.
|
11
|
+
ROOT_PATH = %r{\A/\Z}
|
10
12
|
|
11
13
|
def initialize(app, options = {})
|
12
14
|
@app = app
|
@@ -19,7 +21,7 @@ module Flipper
|
|
19
21
|
|
20
22
|
def call!(env)
|
21
23
|
request = Rack::Request.new(env)
|
22
|
-
if request.post? && request.path_info.match(WEBHOOK_PATH)
|
24
|
+
if request.post? && (request.path_info.match(ROOT_PATH) || request.path_info.match(WEBHOOK_PATH))
|
23
25
|
status = 200
|
24
26
|
headers = {
|
25
27
|
"Content-Type" => "application/json",
|
@@ -32,10 +34,20 @@ module Flipper
|
|
32
34
|
begin
|
33
35
|
message_verifier = MessageVerifier.new(secret: flipper.sync_secret)
|
34
36
|
if message_verifier.verify(payload, signature)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
begin
|
38
|
+
flipper.sync
|
39
|
+
body = JSON.generate({
|
40
|
+
groups: Flipper.group_names.map { |name| {name: name}}
|
41
|
+
})
|
42
|
+
rescue Flipper::Adapters::Http::Error => error
|
43
|
+
status = error.response.code.to_i == 402 ? 402 : 500
|
44
|
+
headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
|
45
|
+
headers["Flipper-Cloud-Response-Error-Message"] = error.message
|
46
|
+
rescue => error
|
47
|
+
status = 500
|
48
|
+
headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
|
49
|
+
headers["Flipper-Cloud-Response-Error-Message"] = error.message
|
50
|
+
end
|
39
51
|
end
|
40
52
|
rescue MessageVerifier::InvalidSignature
|
41
53
|
status = 400
|
data/lib/flipper/version.rb
CHANGED
@@ -127,53 +127,30 @@ RSpec.describe Flipper::Cloud::Configuration do
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
-
it "defaults
|
131
|
-
memory_adapter = Flipper::Adapters::Memory.new
|
130
|
+
it "defaults sync_method to :poll" do
|
132
131
|
instance = described_class.new(required_options)
|
133
132
|
|
134
133
|
expect(instance.sync_method).to eq(:poll)
|
135
134
|
end
|
136
135
|
|
137
|
-
it "
|
138
|
-
memory_adapter = Flipper::Adapters::Memory.new
|
136
|
+
it "sets sync_method to :webhook if sync_secret provided" do
|
139
137
|
instance = described_class.new(required_options.merge({
|
140
138
|
sync_secret: "secret",
|
141
|
-
sync_method: :webhook,
|
142
|
-
local_adapter: memory_adapter,
|
143
139
|
}))
|
144
140
|
|
145
141
|
expect(instance.sync_method).to eq(:webhook)
|
146
142
|
expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
|
147
143
|
end
|
148
144
|
|
149
|
-
it "
|
150
|
-
|
151
|
-
described_class.new(required_options
|
152
|
-
}.to raise_error(ArgumentError, "Unsupported sync_method. Valid options are (poll, webhook)")
|
153
|
-
end
|
154
|
-
|
155
|
-
it "can use ENV var for sync_method" do
|
156
|
-
with_modified_env "FLIPPER_CLOUD_SYNC_METHOD" => "webhook" do
|
157
|
-
instance = described_class.new(required_options.merge({
|
158
|
-
sync_secret: "secret",
|
159
|
-
}))
|
145
|
+
it "sets sync_method to :webhook if FLIPPER_CLOUD_SYNC_SECRET set" do
|
146
|
+
with_modified_env "FLIPPER_CLOUD_SYNC_SECRET" => "abc" do
|
147
|
+
instance = described_class.new(required_options)
|
160
148
|
|
161
149
|
expect(instance.sync_method).to eq(:webhook)
|
150
|
+
expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
|
162
151
|
end
|
163
152
|
end
|
164
153
|
|
165
|
-
it "can use string sync_method instead of symbol" do
|
166
|
-
memory_adapter = Flipper::Adapters::Memory.new
|
167
|
-
instance = described_class.new(required_options.merge({
|
168
|
-
sync_secret: "secret",
|
169
|
-
sync_method: "webhook",
|
170
|
-
local_adapter: memory_adapter,
|
171
|
-
}))
|
172
|
-
|
173
|
-
expect(instance.sync_method).to eq(:webhook)
|
174
|
-
expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
|
175
|
-
end
|
176
|
-
|
177
154
|
it "can set sync_secret" do
|
178
155
|
instance = described_class.new(required_options.merge(sync_secret: "from_config"))
|
179
156
|
expect(instance.sync_secret).to eq("from_config")
|
@@ -9,7 +9,6 @@ RSpec.describe Flipper::Cloud::DSL do
|
|
9
9
|
cloud_configuration = Flipper::Cloud::Configuration.new({
|
10
10
|
token: "asdf",
|
11
11
|
sync_secret: "tasty",
|
12
|
-
sync_method: :webhook,
|
13
12
|
})
|
14
13
|
dsl = described_class.new(cloud_configuration)
|
15
14
|
expect(dsl.features).to eq(Set.new)
|
@@ -26,7 +25,6 @@ RSpec.describe Flipper::Cloud::DSL do
|
|
26
25
|
cloud_configuration = Flipper::Cloud::Configuration.new({
|
27
26
|
token: "asdf",
|
28
27
|
sync_secret: "tasty",
|
29
|
-
sync_method: :webhook,
|
30
28
|
})
|
31
29
|
dsl = described_class.new(cloud_configuration)
|
32
30
|
dsl.sync
|
@@ -37,7 +35,6 @@ RSpec.describe Flipper::Cloud::DSL do
|
|
37
35
|
cloud_configuration = Flipper::Cloud::Configuration.new({
|
38
36
|
token: "asdf",
|
39
37
|
sync_secret: "tasty",
|
40
|
-
sync_method: :webhook,
|
41
38
|
})
|
42
39
|
dsl = described_class.new(cloud_configuration)
|
43
40
|
expect(dsl.sync_secret).to eq("tasty")
|
@@ -52,7 +49,6 @@ RSpec.describe Flipper::Cloud::DSL do
|
|
52
49
|
cloud_configuration = Flipper::Cloud::Configuration.new({
|
53
50
|
token: "asdf",
|
54
51
|
sync_secret: "tasty",
|
55
|
-
sync_method: :webhook,
|
56
52
|
local_adapter: local_adapter
|
57
53
|
})
|
58
54
|
end
|
@@ -9,7 +9,6 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
9
9
|
Flipper::Cloud.new("regular") do |config|
|
10
10
|
config.local_adapter = Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
|
11
11
|
config.sync_secret = "regular_tasty"
|
12
|
-
config.sync_method = :webhook
|
13
12
|
end
|
14
13
|
}
|
15
14
|
|
@@ -17,7 +16,6 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
17
16
|
Flipper::Cloud.new("env") do |config|
|
18
17
|
config.local_adapter = Flipper::Adapters::OperationLogger.new(Flipper::Adapters::Memory.new)
|
19
18
|
config.sync_secret = "env_tasty"
|
20
|
-
config.sync_method = :webhook
|
21
19
|
end
|
22
20
|
}
|
23
21
|
|
@@ -53,7 +51,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
53
51
|
env = {
|
54
52
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
55
53
|
}
|
56
|
-
post '/
|
54
|
+
post '/', request_body, env
|
57
55
|
|
58
56
|
expect(last_response.status).to eq(200)
|
59
57
|
expect(JSON.parse(last_response.body)).to eq({
|
@@ -80,13 +78,82 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
80
78
|
env = {
|
81
79
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
82
80
|
}
|
83
|
-
post '/
|
81
|
+
post '/', request_body, env
|
84
82
|
|
85
83
|
expect(last_response.status).to eq(400)
|
86
84
|
expect(stub).not_to have_been_requested
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
88
|
+
context "when flipper cloud responds with 402" do
|
89
|
+
let(:app) { Flipper::Cloud.app(flipper) }
|
90
|
+
|
91
|
+
it "results in 402" do
|
92
|
+
Flipper.register(:admins) { |*args| false }
|
93
|
+
Flipper.register(:staff) { |*args| false }
|
94
|
+
Flipper.register(:basic) { |*args| false }
|
95
|
+
Flipper.register(:plus) { |*args| false }
|
96
|
+
Flipper.register(:premium) { |*args| false }
|
97
|
+
|
98
|
+
stub = stub_request_for_token('regular', status: 402)
|
99
|
+
env = {
|
100
|
+
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
101
|
+
}
|
102
|
+
post '/', request_body, env
|
103
|
+
|
104
|
+
expect(last_response.status).to eq(402)
|
105
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
|
106
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 402")
|
107
|
+
expect(stub).to have_been_requested
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "when flipper cloud responds with non-402 and non-2xx code" do
|
112
|
+
let(:app) { Flipper::Cloud.app(flipper) }
|
113
|
+
|
114
|
+
it "results in 500" do
|
115
|
+
Flipper.register(:admins) { |*args| false }
|
116
|
+
Flipper.register(:staff) { |*args| false }
|
117
|
+
Flipper.register(:basic) { |*args| false }
|
118
|
+
Flipper.register(:plus) { |*args| false }
|
119
|
+
Flipper.register(:premium) { |*args| false }
|
120
|
+
|
121
|
+
stub = stub_request_for_token('regular', status: 503)
|
122
|
+
env = {
|
123
|
+
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
124
|
+
}
|
125
|
+
post '/', request_body, env
|
126
|
+
|
127
|
+
expect(last_response.status).to eq(500)
|
128
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
|
129
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 503")
|
130
|
+
expect(stub).to have_been_requested
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "when flipper cloud responds with timeout" do
|
135
|
+
let(:app) { Flipper::Cloud.app(flipper) }
|
136
|
+
|
137
|
+
it "results in 500" do
|
138
|
+
Flipper.register(:admins) { |*args| false }
|
139
|
+
Flipper.register(:staff) { |*args| false }
|
140
|
+
Flipper.register(:basic) { |*args| false }
|
141
|
+
Flipper.register(:plus) { |*args| false }
|
142
|
+
Flipper.register(:premium) { |*args| false }
|
143
|
+
|
144
|
+
stub = stub_request_for_token('regular', status: :timeout)
|
145
|
+
env = {
|
146
|
+
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
147
|
+
}
|
148
|
+
post '/', request_body, env
|
149
|
+
|
150
|
+
expect(last_response.status).to eq(500)
|
151
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Net::OpenTimeout")
|
152
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("execution expired")
|
153
|
+
expect(stub).to have_been_requested
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
90
157
|
context 'when initialized with flipper instance and flipper instance in env' do
|
91
158
|
let(:app) { Flipper::Cloud.app(flipper) }
|
92
159
|
let(:signature) {
|
@@ -99,7 +166,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
99
166
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
100
167
|
'flipper' => env_flipper,
|
101
168
|
}
|
102
|
-
post '/
|
169
|
+
post '/', request_body, env
|
103
170
|
|
104
171
|
expect(last_response.status).to eq(200)
|
105
172
|
expect(stub).to have_been_requested
|
@@ -118,7 +185,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
118
185
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
119
186
|
'flipper' => env_flipper,
|
120
187
|
}
|
121
|
-
post '/
|
188
|
+
post '/', request_body, env
|
122
189
|
|
123
190
|
expect(last_response.status).to eq(200)
|
124
191
|
expect(stub).to have_been_requested
|
@@ -138,7 +205,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
138
205
|
'flipper' => flipper,
|
139
206
|
'flipper_cloud' => env_flipper,
|
140
207
|
}
|
141
|
-
post '/
|
208
|
+
post '/', request_body, env
|
142
209
|
|
143
210
|
expect(last_response.status).to eq(200)
|
144
211
|
expect(stub).to have_been_requested
|
@@ -149,6 +216,27 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
149
216
|
let(:app) { Flipper::Cloud.app(-> { flipper }) }
|
150
217
|
|
151
218
|
it 'works' do
|
219
|
+
stub = stub_request_for_token('regular')
|
220
|
+
env = {
|
221
|
+
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
222
|
+
}
|
223
|
+
post '/', request_body, env
|
224
|
+
|
225
|
+
expect(last_response.status).to eq(200)
|
226
|
+
expect(stub).to have_been_requested
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'when using older /webhooks path' do
|
231
|
+
let(:app) { Flipper::Cloud.app(flipper) }
|
232
|
+
|
233
|
+
it 'uses instance to sync' do
|
234
|
+
Flipper.register(:admins) { |*args| false }
|
235
|
+
Flipper.register(:staff) { |*args| false }
|
236
|
+
Flipper.register(:basic) { |*args| false }
|
237
|
+
Flipper.register(:plus) { |*args| false }
|
238
|
+
Flipper.register(:premium) { |*args| false }
|
239
|
+
|
152
240
|
stub = stub_request_for_token('regular')
|
153
241
|
env = {
|
154
242
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
@@ -156,13 +244,22 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
156
244
|
post '/webhooks', request_body, env
|
157
245
|
|
158
246
|
expect(last_response.status).to eq(200)
|
247
|
+
expect(JSON.parse(last_response.body)).to eq({
|
248
|
+
"groups" => [
|
249
|
+
{"name" => "admins"},
|
250
|
+
{"name" => "staff"},
|
251
|
+
{"name" => "basic"},
|
252
|
+
{"name" => "plus"},
|
253
|
+
{"name" => "premium"},
|
254
|
+
],
|
255
|
+
})
|
159
256
|
expect(stub).to have_been_requested
|
160
257
|
end
|
161
258
|
end
|
162
259
|
|
163
260
|
describe 'Request method unsupported' do
|
164
261
|
it 'skips middleware' do
|
165
|
-
get '/
|
262
|
+
get '/'
|
166
263
|
expect(last_response.status).to eq(404)
|
167
264
|
expect(last_response.content_type).to eq("application/json")
|
168
265
|
expect(last_response.body).to eq("{}")
|
@@ -177,12 +274,17 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
177
274
|
|
178
275
|
private
|
179
276
|
|
180
|
-
def stub_request_for_token(token)
|
181
|
-
stub_request(:get, "https://www.flippercloud.io/adapter/features").
|
277
|
+
def stub_request_for_token(token, status: 200)
|
278
|
+
stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").
|
182
279
|
with({
|
183
280
|
headers: {
|
184
281
|
'Flipper-Cloud-Token' => token,
|
185
282
|
},
|
186
|
-
})
|
283
|
+
})
|
284
|
+
if status == :timeout
|
285
|
+
stub.to_timeout
|
286
|
+
else
|
287
|
+
stub.to_return(status: status, body: response_body, headers: {})
|
288
|
+
end
|
187
289
|
end
|
188
290
|
end
|
data/spec/flipper/cloud_spec.rb
CHANGED
@@ -114,4 +114,89 @@ RSpec.describe Flipper::Cloud do
|
|
114
114
|
described_class.new('asdf', open_timeout: 1)
|
115
115
|
end
|
116
116
|
end
|
117
|
+
|
118
|
+
it 'can import' do
|
119
|
+
stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
|
120
|
+
with(headers: {
|
121
|
+
'Feature-Flipper-Token'=>'asdf',
|
122
|
+
'Flipper-Cloud-Token'=>'asdf',
|
123
|
+
}).to_return(status: 200, body: "{}", headers: {})
|
124
|
+
|
125
|
+
flipper = Flipper.new(Flipper::Adapters::Memory.new)
|
126
|
+
|
127
|
+
flipper.enable(:test)
|
128
|
+
flipper.enable(:search)
|
129
|
+
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
130
|
+
flipper.enable_percentage_of_time(:logging, 5)
|
131
|
+
|
132
|
+
cloud_flipper = Flipper::Cloud.new("asdf")
|
133
|
+
|
134
|
+
get_all = {
|
135
|
+
"logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
|
136
|
+
"search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
137
|
+
"stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
138
|
+
"test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
139
|
+
}
|
140
|
+
|
141
|
+
expect(flipper.adapter.get_all).to eq(get_all)
|
142
|
+
cloud_flipper.import(flipper)
|
143
|
+
expect(flipper.adapter.get_all).to eq(get_all)
|
144
|
+
expect(cloud_flipper.adapter.get_all).to eq(get_all)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'raises error for failure while importing' do
|
148
|
+
stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
|
149
|
+
with(headers: {
|
150
|
+
'Feature-Flipper-Token'=>'asdf',
|
151
|
+
'Flipper-Cloud-Token'=>'asdf',
|
152
|
+
}).to_return(status: 500, body: "{}")
|
153
|
+
|
154
|
+
flipper = Flipper.new(Flipper::Adapters::Memory.new)
|
155
|
+
|
156
|
+
flipper.enable(:test)
|
157
|
+
flipper.enable(:search)
|
158
|
+
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
159
|
+
flipper.enable_percentage_of_time(:logging, 5)
|
160
|
+
|
161
|
+
cloud_flipper = Flipper::Cloud.new("asdf")
|
162
|
+
|
163
|
+
get_all = {
|
164
|
+
"logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
|
165
|
+
"search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
166
|
+
"stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
167
|
+
"test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
168
|
+
}
|
169
|
+
|
170
|
+
expect(flipper.adapter.get_all).to eq(get_all)
|
171
|
+
expect { cloud_flipper.import(flipper) }.to raise_error(Flipper::Adapters::Http::Error)
|
172
|
+
expect(flipper.adapter.get_all).to eq(get_all)
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'raises error for timeout while importing' do
|
176
|
+
stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
|
177
|
+
with(headers: {
|
178
|
+
'Feature-Flipper-Token'=>'asdf',
|
179
|
+
'Flipper-Cloud-Token'=>'asdf',
|
180
|
+
}).to_timeout
|
181
|
+
|
182
|
+
flipper = Flipper.new(Flipper::Adapters::Memory.new)
|
183
|
+
|
184
|
+
flipper.enable(:test)
|
185
|
+
flipper.enable(:search)
|
186
|
+
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
187
|
+
flipper.enable_percentage_of_time(:logging, 5)
|
188
|
+
|
189
|
+
cloud_flipper = Flipper::Cloud.new("asdf")
|
190
|
+
|
191
|
+
get_all = {
|
192
|
+
"logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
|
193
|
+
"search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
194
|
+
"stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
195
|
+
"test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
|
196
|
+
}
|
197
|
+
|
198
|
+
expect(flipper.adapter.get_all).to eq(get_all)
|
199
|
+
expect { cloud_flipper.import(flipper) }.to raise_error(Net::OpenTimeout)
|
200
|
+
expect(flipper.adapter.get_all).to eq(get_all)
|
201
|
+
end
|
117
202
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper-cloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: flipper
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.21.0.rc1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.21.0.rc1
|
27
27
|
description:
|
28
28
|
email:
|
29
29
|
- nunemaker@gmail.com
|
@@ -31,6 +31,7 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
+
- docs/images/flipper_cloud.png
|
34
35
|
- examples/cloud/app.ru
|
35
36
|
- examples/cloud/basic.rb
|
36
37
|
- examples/cloud/cached_in_memory.rb
|
@@ -65,9 +66,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
66
|
version: '0'
|
66
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
68
|
requirements:
|
68
|
-
- - "
|
69
|
+
- - ">"
|
69
70
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
71
|
+
version: 1.3.1
|
71
72
|
requirements: []
|
72
73
|
rubygems_version: 3.0.3
|
73
74
|
signing_key:
|