flipper-cloud 0.20.0.beta2 → 0.20.3
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/examples/cloud/basic.rb +2 -2
- data/examples/cloud/cached_in_memory.rb +2 -2
- data/examples/cloud/import.rb +9 -9
- data/examples/cloud/local_adapter.rb +2 -2
- data/lib/flipper/cloud/configuration.rb +8 -1
- data/lib/flipper/cloud/middleware.rb +14 -1
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/cloud/middleware_spec.rb +92 -3
- data/spec/flipper/cloud_spec.rb +85 -0
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ac2ac6d779943f69836c9d8c6e4bcd58d45b31947114b09c154f2045285c006
|
4
|
+
data.tar.gz: f80e71c83eea9aa839dc80141fb56b2e8701f0ba431b3baa62b62ac77bbfff4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50bd6b0bf3aaa5b1a825b297a1cac4ebf6d6e849bc25dd0d1bd6d8fea38b47677da4095f3ba9c5237b622aebd5c49cb905176efb8e40d7b9a0cc2e88ba0f5a55
|
7
|
+
data.tar.gz: 25ab8c3a7e347383660023830a8ff46af6e14176f6875c6812b92825c7abbcd8133b56c9fb971139417f3ce2843c0925c408c7968b456864cc9af3680cf5cbb0
|
data/examples/cloud/basic.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Usage (from the repo root):
|
2
|
-
#
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
|
3
3
|
require 'pathname'
|
4
4
|
require 'logger'
|
5
5
|
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
@@ -7,7 +7,7 @@ lib_path = root_path.join('lib')
|
|
7
7
|
$:.unshift(lib_path)
|
8
8
|
|
9
9
|
require 'flipper/cloud'
|
10
|
-
flipper = Flipper::Cloud.new
|
10
|
+
flipper = Flipper::Cloud.new
|
11
11
|
|
12
12
|
flipper[:stats].enable
|
13
13
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/cached_in_memory.rb
|
1
2
|
require File.expand_path('../../example_setup', __FILE__)
|
2
3
|
|
3
4
|
require 'flipper/cloud'
|
@@ -5,12 +6,11 @@ require 'flipper/adapters/active_support_cache_store'
|
|
5
6
|
require 'active_support/cache'
|
6
7
|
require 'active_support/cache/memory_store'
|
7
8
|
|
8
|
-
token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
|
9
9
|
feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
|
10
10
|
|
11
11
|
Flipper.configure do |config|
|
12
12
|
config.default do
|
13
|
-
Flipper::Cloud.new
|
13
|
+
Flipper::Cloud.new do |cloud|
|
14
14
|
cloud.debug_output = STDOUT
|
15
15
|
cloud.adapter do |adapter|
|
16
16
|
Flipper::Adapters::ActiveSupportCacheStore.new(adapter,
|
data/examples/cloud/import.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Usage (from the repo root):
|
2
|
-
# env
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
|
3
3
|
require 'pathname'
|
4
4
|
require 'logger'
|
5
5
|
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
@@ -10,14 +10,14 @@ require 'flipper'
|
|
10
10
|
require 'flipper/cloud'
|
11
11
|
|
12
12
|
memory_adapter = Flipper::Adapters::Memory.new
|
13
|
-
|
13
|
+
flipper = Flipper.new(memory_adapter)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
flipper.enable(:test)
|
16
|
+
flipper.enable(:search)
|
17
|
+
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
18
|
+
flipper.enable_percentage_of_time(:logging, 5)
|
19
19
|
|
20
|
-
|
20
|
+
cloud = Flipper::Cloud.new
|
21
21
|
|
22
|
-
#
|
23
|
-
|
22
|
+
# makes cloud identical to memory flipper
|
23
|
+
cloud.import(flipper)
|
@@ -5,13 +5,13 @@
|
|
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
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/local_adapter.rb
|
8
9
|
require File.expand_path('../../example_setup', __FILE__)
|
9
10
|
|
10
11
|
require 'logger'
|
11
12
|
require 'flipper/cloud'
|
12
13
|
require 'flipper/adapters/redis'
|
13
14
|
|
14
|
-
token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
|
15
15
|
feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
|
16
16
|
|
17
17
|
redis = Redis.new(logger: Logger.new(STDOUT))
|
@@ -19,7 +19,7 @@ redis.flushdb
|
|
19
19
|
|
20
20
|
Flipper.configure do |config|
|
21
21
|
config.default do
|
22
|
-
Flipper::Cloud.new
|
22
|
+
Flipper::Cloud.new do |cloud|
|
23
23
|
cloud.debug_output = STDOUT
|
24
24
|
cloud.local_adapter = Flipper::Adapters::Redis.new(redis)
|
25
25
|
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"
|
@@ -68,7 +69,7 @@ module Flipper
|
|
68
69
|
@token = options.fetch(:token) { ENV["FLIPPER_CLOUD_TOKEN"] }
|
69
70
|
|
70
71
|
if @token.nil?
|
71
|
-
raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token
|
72
|
+
raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token (e.g. Flipper::Cloud.new('token'))."
|
72
73
|
end
|
73
74
|
|
74
75
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
@@ -152,6 +153,12 @@ module Flipper
|
|
152
153
|
debug_output: @debug_output,
|
153
154
|
headers: {
|
154
155
|
"Flipper-Cloud-Token" => @token,
|
156
|
+
"Feature-Flipper-Token" => @token,
|
157
|
+
"Client-Lang" => "ruby",
|
158
|
+
"Client-Lang-Version" => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
|
159
|
+
"Client-Platform" => RUBY_PLATFORM,
|
160
|
+
"Client-Engine" => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
|
161
|
+
"Client-Hostname" => Socket.gethostname,
|
155
162
|
},
|
156
163
|
})
|
157
164
|
end
|
@@ -32,7 +32,20 @@ module Flipper
|
|
32
32
|
begin
|
33
33
|
message_verifier = MessageVerifier.new(secret: flipper.sync_secret)
|
34
34
|
if message_verifier.verify(payload, signature)
|
35
|
-
|
35
|
+
begin
|
36
|
+
flipper.sync
|
37
|
+
body = JSON.generate({
|
38
|
+
groups: Flipper.group_names.map { |name| {name: name}}
|
39
|
+
})
|
40
|
+
rescue Flipper::Adapters::Http::Error => error
|
41
|
+
status = error.response.code.to_i == 402 ? 402 : 500
|
42
|
+
headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
|
43
|
+
headers["Flipper-Cloud-Response-Error-Message"] = error.message
|
44
|
+
rescue => error
|
45
|
+
status = 500
|
46
|
+
headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
|
47
|
+
headers["Flipper-Cloud-Response-Error-Message"] = error.message
|
48
|
+
end
|
36
49
|
end
|
37
50
|
rescue MessageVerifier::InvalidSignature
|
38
51
|
status = 400
|
data/lib/flipper/version.rb
CHANGED
@@ -43,6 +43,12 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
43
43
|
let(:app) { Flipper::Cloud.app(flipper) }
|
44
44
|
|
45
45
|
it 'uses instance to sync' do
|
46
|
+
Flipper.register(:admins) { |*args| false }
|
47
|
+
Flipper.register(:staff) { |*args| false }
|
48
|
+
Flipper.register(:basic) { |*args| false }
|
49
|
+
Flipper.register(:plus) { |*args| false }
|
50
|
+
Flipper.register(:premium) { |*args| false }
|
51
|
+
|
46
52
|
stub = stub_request_for_token('regular')
|
47
53
|
env = {
|
48
54
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
@@ -50,6 +56,15 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
50
56
|
post '/webhooks', request_body, env
|
51
57
|
|
52
58
|
expect(last_response.status).to eq(200)
|
59
|
+
expect(JSON.parse(last_response.body)).to eq({
|
60
|
+
"groups" => [
|
61
|
+
{"name" => "admins"},
|
62
|
+
{"name" => "staff"},
|
63
|
+
{"name" => "basic"},
|
64
|
+
{"name" => "plus"},
|
65
|
+
{"name" => "premium"},
|
66
|
+
],
|
67
|
+
})
|
53
68
|
expect(stub).to have_been_requested
|
54
69
|
end
|
55
70
|
end
|
@@ -72,6 +87,75 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
72
87
|
end
|
73
88
|
end
|
74
89
|
|
90
|
+
context "when flipper cloud responds with 402" do
|
91
|
+
let(:app) { Flipper::Cloud.app(flipper) }
|
92
|
+
|
93
|
+
it "results in 402" do
|
94
|
+
Flipper.register(:admins) { |*args| false }
|
95
|
+
Flipper.register(:staff) { |*args| false }
|
96
|
+
Flipper.register(:basic) { |*args| false }
|
97
|
+
Flipper.register(:plus) { |*args| false }
|
98
|
+
Flipper.register(:premium) { |*args| false }
|
99
|
+
|
100
|
+
stub = stub_request_for_token('regular', status: 402)
|
101
|
+
env = {
|
102
|
+
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
103
|
+
}
|
104
|
+
post '/webhooks', request_body, env
|
105
|
+
|
106
|
+
expect(last_response.status).to eq(402)
|
107
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
|
108
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("Failed with status: 402")
|
109
|
+
expect(stub).to have_been_requested
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when flipper cloud responds with non-402 and non-2xx code" do
|
114
|
+
let(:app) { Flipper::Cloud.app(flipper) }
|
115
|
+
|
116
|
+
it "results in 500" do
|
117
|
+
Flipper.register(:admins) { |*args| false }
|
118
|
+
Flipper.register(:staff) { |*args| false }
|
119
|
+
Flipper.register(:basic) { |*args| false }
|
120
|
+
Flipper.register(:plus) { |*args| false }
|
121
|
+
Flipper.register(:premium) { |*args| false }
|
122
|
+
|
123
|
+
stub = stub_request_for_token('regular', status: 503)
|
124
|
+
env = {
|
125
|
+
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
126
|
+
}
|
127
|
+
post '/webhooks', request_body, env
|
128
|
+
|
129
|
+
expect(last_response.status).to eq(500)
|
130
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
|
131
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("Failed with status: 503")
|
132
|
+
expect(stub).to have_been_requested
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "when flipper cloud responds with timeout" do
|
137
|
+
let(:app) { Flipper::Cloud.app(flipper) }
|
138
|
+
|
139
|
+
it "results in 500" do
|
140
|
+
Flipper.register(:admins) { |*args| false }
|
141
|
+
Flipper.register(:staff) { |*args| false }
|
142
|
+
Flipper.register(:basic) { |*args| false }
|
143
|
+
Flipper.register(:plus) { |*args| false }
|
144
|
+
Flipper.register(:premium) { |*args| false }
|
145
|
+
|
146
|
+
stub = stub_request_for_token('regular', status: :timeout)
|
147
|
+
env = {
|
148
|
+
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
149
|
+
}
|
150
|
+
post '/webhooks', request_body, env
|
151
|
+
|
152
|
+
expect(last_response.status).to eq(500)
|
153
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Net::OpenTimeout")
|
154
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("execution expired")
|
155
|
+
expect(stub).to have_been_requested
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
75
159
|
context 'when initialized with flipper instance and flipper instance in env' do
|
76
160
|
let(:app) { Flipper::Cloud.app(flipper) }
|
77
161
|
let(:signature) {
|
@@ -162,12 +246,17 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
162
246
|
|
163
247
|
private
|
164
248
|
|
165
|
-
def stub_request_for_token(token)
|
166
|
-
stub_request(:get, "https://www.flippercloud.io/adapter/features").
|
249
|
+
def stub_request_for_token(token, status: 200)
|
250
|
+
stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").
|
167
251
|
with({
|
168
252
|
headers: {
|
169
253
|
'Flipper-Cloud-Token' => token,
|
170
254
|
},
|
171
|
-
})
|
255
|
+
})
|
256
|
+
if status == :timeout
|
257
|
+
stub.to_timeout
|
258
|
+
else
|
259
|
+
stub.to_return(status: status, body: response_body, headers: {})
|
260
|
+
end
|
172
261
|
end
|
173
262
|
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.20.
|
4
|
+
version: 0.20.3
|
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-01-10 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.20.
|
19
|
+
version: 0.20.3
|
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.20.
|
26
|
+
version: 0.20.3
|
27
27
|
description:
|
28
28
|
email:
|
29
29
|
- nunemaker@gmail.com
|
@@ -65,9 +65,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
65
|
version: '0'
|
66
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
|
-
- - "
|
68
|
+
- - ">="
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version:
|
70
|
+
version: '0'
|
71
71
|
requirements: []
|
72
72
|
rubygems_version: 3.0.3
|
73
73
|
signing_key:
|