flipper-cloud 0.20.3 → 0.22.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/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/app.ru +3 -8
- data/examples/cloud/basic.rb +5 -11
- data/examples/cloud/import.rb +6 -14
- data/flipper-cloud.gemspec +1 -1
- data/lib/flipper/cloud.rb +25 -2
- data/lib/flipper/cloud/configuration.rb +16 -18
- data/lib/flipper/cloud/engine.rb +29 -0
- data/lib/flipper/cloud/middleware.rb +3 -1
- data/lib/flipper/cloud/routes.rb +13 -0
- 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/engine_spec.rb +98 -0
- data/spec/flipper/cloud/middleware_spec.rb +43 -15
- data/spec/flipper/cloud_spec.rb +11 -11
- metadata +10 -7
- data/examples/cloud/cached_in_memory.rb +0 -29
- data/examples/cloud/local_adapter.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69bb21ad2d5ae7944825472e2d16fead9a99e3136331f2b9ad1b3a58a84d0340
|
4
|
+
data.tar.gz: 7b049a433a94cb80aa8077507837b449bca2fef71d05153a6ea45a6959cbaded
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 050d427a9fcbe50168de8b46c822249bb71a843e55e24720ccbb7a13e075c0ebff0f8592a9cfa59e6fbbdcb2877afd21336c4f863f297e4ec41b0d7b71753027
|
7
|
+
data.tar.gz: 3d671ce575cd6f50fe456cd372faa81de5cbe6ca39a397403d1801e8adb7e4b2fa1b035df69028144c8e22a77b04b0d232e34530e8068225f779a918b1516b51
|
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,25 +1,19 @@
|
|
1
1
|
# Usage (from the repo root):
|
2
2
|
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
|
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
|
-
|
3
|
+
require 'bundler/setup'
|
9
4
|
require 'flipper/cloud'
|
10
|
-
flipper = Flipper::Cloud.new
|
11
5
|
|
12
|
-
|
6
|
+
Flipper[:stats].enable
|
13
7
|
|
14
|
-
if
|
8
|
+
if Flipper[:stats].enabled?
|
15
9
|
puts 'Enabled!'
|
16
10
|
else
|
17
11
|
puts 'Disabled!'
|
18
12
|
end
|
19
13
|
|
20
|
-
|
14
|
+
Flipper[:stats].disable
|
21
15
|
|
22
|
-
if
|
16
|
+
if Flipper[:stats].enabled?
|
23
17
|
puts 'Enabled!'
|
24
18
|
else
|
25
19
|
puts 'Disabled!'
|
data/examples/cloud/import.rb
CHANGED
@@ -1,23 +1,15 @@
|
|
1
1
|
# Usage (from the repo root):
|
2
2
|
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
|
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
|
-
|
3
|
+
require 'bundler/setup'
|
9
4
|
require 'flipper'
|
10
5
|
require 'flipper/cloud'
|
11
6
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
flipper.enable(:search)
|
17
|
-
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
18
|
-
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
14
|
# makes cloud identical to memory flipper
|
23
|
-
cloud.import(
|
15
|
+
cloud.import(Flipper)
|
data/flipper-cloud.gemspec
CHANGED
@@ -9,7 +9,7 @@ end
|
|
9
9
|
Gem::Specification.new do |gem|
|
10
10
|
gem.authors = ['John Nunemaker']
|
11
11
|
gem.email = ['nunemaker@gmail.com']
|
12
|
-
gem.summary = '
|
12
|
+
gem.summary = 'FlipperCloud.io adapter for Flipper'
|
13
13
|
gem.license = 'MIT'
|
14
14
|
gem.homepage = 'https://github.com/jnunemaker/flipper'
|
15
15
|
|
data/lib/flipper/cloud.rb
CHANGED
@@ -4,6 +4,7 @@ require "flipper/middleware/memoizer"
|
|
4
4
|
require "flipper/cloud/configuration"
|
5
5
|
require "flipper/cloud/dsl"
|
6
6
|
require "flipper/cloud/middleware"
|
7
|
+
require "flipper/cloud/engine" if defined?(Rails::Engine)
|
7
8
|
|
8
9
|
module Flipper
|
9
10
|
module Cloud
|
@@ -14,8 +15,14 @@ module Flipper
|
|
14
15
|
# options - The Hash of options. See Flipper::Cloud::Configuration.
|
15
16
|
# block - The block that configuration will be yielded to allowing you to
|
16
17
|
# customize this cloud instance and its adapter.
|
17
|
-
def self.new(
|
18
|
-
|
18
|
+
def self.new(options = {}, deprecated_options = {})
|
19
|
+
if options.is_a?(String)
|
20
|
+
warn "`Flipper::Cloud.new(token)` is deprecated. Use `Flipper::Cloud.new(token: token)` " +
|
21
|
+
"or set the `FLIPPER_CLOUD_TOKEN` environment variable.\n" +
|
22
|
+
caller[0]
|
23
|
+
options = deprecated_options.merge(token: options)
|
24
|
+
end
|
25
|
+
|
19
26
|
configuration = Configuration.new(options)
|
20
27
|
yield configuration if block_given?
|
21
28
|
DSL.new(configuration)
|
@@ -36,5 +43,21 @@ module Flipper
|
|
36
43
|
builder.define_singleton_method(:inspect) { klass.inspect } # pretty rake routes output
|
37
44
|
builder
|
38
45
|
end
|
46
|
+
|
47
|
+
# Private: Configure Flipper to use Cloud by default
|
48
|
+
def self.set_default
|
49
|
+
Flipper.configure do |config|
|
50
|
+
config.default do
|
51
|
+
if ENV["FLIPPER_CLOUD_TOKEN"]
|
52
|
+
self.new(local_adapter: config.adapter)
|
53
|
+
else
|
54
|
+
warn "Missing FLIPPER_CLOUD_TOKEN environment variable. Disabling Flipper::Cloud."
|
55
|
+
Flipper.new(config.adapter)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
39
60
|
end
|
40
61
|
end
|
62
|
+
|
63
|
+
Flipper::Cloud.set_default
|
@@ -13,6 +13,8 @@ module Flipper
|
|
13
13
|
:webhook,
|
14
14
|
].freeze
|
15
15
|
|
16
|
+
DEFAULT_URL = "https://www.flippercloud.io/adapter".freeze
|
17
|
+
|
16
18
|
# Public: The token corresponding to an environment on flippercloud.io.
|
17
19
|
attr_accessor :token
|
18
20
|
|
@@ -57,10 +59,6 @@ module Flipper
|
|
57
59
|
# the local in sync with cloud (default: 10).
|
58
60
|
attr_accessor :sync_interval
|
59
61
|
|
60
|
-
# Public: The method to be used for synchronizing your local flipper
|
61
|
-
# adapter with cloud. (default: :poll, can also be :webhook).
|
62
|
-
attr_reader :sync_method
|
63
|
-
|
64
62
|
# Public: The secret used to verify if syncs in the middleware should
|
65
63
|
# occur or not.
|
66
64
|
attr_accessor :sync_secret
|
@@ -69,9 +67,14 @@ module Flipper
|
|
69
67
|
@token = options.fetch(:token) { ENV["FLIPPER_CLOUD_TOKEN"] }
|
70
68
|
|
71
69
|
if @token.nil?
|
72
|
-
raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token (e.g. Flipper::Cloud.new('token'))."
|
70
|
+
raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token (e.g. Flipper::Cloud.new(token: 'token'))."
|
73
71
|
end
|
74
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
|
+
|
75
78
|
@instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
|
76
79
|
@read_timeout = options.fetch(:read_timeout) { ENV.fetch("FLIPPER_CLOUD_READ_TIMEOUT", 5).to_f }
|
77
80
|
@open_timeout = options.fetch(:open_timeout) { ENV.fetch("FLIPPER_CLOUD_OPEN_TIMEOUT", 5).to_f }
|
@@ -81,8 +84,7 @@ module Flipper
|
|
81
84
|
@local_adapter = options.fetch(:local_adapter) { Adapters::Memory.new }
|
82
85
|
@debug_output = options[:debug_output]
|
83
86
|
@adapter_block = ->(adapter) { adapter }
|
84
|
-
self.
|
85
|
-
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) }
|
86
88
|
end
|
87
89
|
|
88
90
|
# Public: Read or customize the http adapter. Calling without a block will
|
@@ -114,18 +116,14 @@ module Flipper
|
|
114
116
|
}).call
|
115
117
|
end
|
116
118
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
end
|
123
|
-
|
124
|
-
if new_sync_method == :webhook && sync_secret.nil?
|
125
|
-
raise ArgumentError, "Flipper::Cloud sync_secret is missing. Please set FLIPPER_CLOUD_SYNC_SECRET or provide the sync_secret used to validate webhooks."
|
126
|
-
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
|
127
124
|
|
128
|
-
|
125
|
+
def sync_method=(_)
|
126
|
+
warn "Flipper::Cloud: sync_method is deprecated and has no effect."
|
129
127
|
end
|
130
128
|
|
131
129
|
private
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "flipper/railtie"
|
2
|
+
|
3
|
+
module Flipper
|
4
|
+
module Cloud
|
5
|
+
class Engine < Rails::Engine
|
6
|
+
paths["config/routes.rb"] = ["lib/flipper/cloud/routes.rb"]
|
7
|
+
|
8
|
+
config.before_configuration do
|
9
|
+
config.flipper.cloud_path = "_flipper"
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "flipper.cloud.default", before: :load_config_initializers do |app|
|
13
|
+
Flipper.configure do |config|
|
14
|
+
config.default do
|
15
|
+
if ENV["FLIPPER_CLOUD_TOKEN"]
|
16
|
+
Flipper::Cloud.new(
|
17
|
+
local_adapter: config.adapter,
|
18
|
+
instrumenter: app.config.flipper.instrumenter
|
19
|
+
)
|
20
|
+
else
|
21
|
+
warn "Missing FLIPPER_CLOUD_TOKEN environment variable. Disabling Flipper::Cloud."
|
22
|
+
Flipper.new(config.adapter)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
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",
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Default routes loaded by Flipper::Cloud::Engine
|
2
|
+
Rails.application.routes.draw do
|
3
|
+
if ENV["FLIPPER_CLOUD_TOKEN"] && ENV["FLIPPER_CLOUD_SYNC_SECRET"]
|
4
|
+
config = Rails.application.config.flipper
|
5
|
+
|
6
|
+
cloud_app = Flipper::Cloud.app(nil,
|
7
|
+
env_key: config.env_key,
|
8
|
+
memoizer_options: { preload: config.preload }
|
9
|
+
)
|
10
|
+
|
11
|
+
mount cloud_app, at: config.cloud_path
|
12
|
+
end
|
13
|
+
end
|
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
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'rails'
|
3
|
+
require 'flipper/cloud'
|
4
|
+
|
5
|
+
RSpec.describe Flipper::Cloud::Engine do
|
6
|
+
let(:env) do
|
7
|
+
{ "FLIPPER_CLOUD_TOKEN" => "test-token" }
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:application) do
|
11
|
+
Class.new(Rails::Application) do
|
12
|
+
config.eager_load = false
|
13
|
+
config.logger = ActiveSupport::Logger.new($stdout)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# App for Rack::Test
|
18
|
+
let(:app) { application.routes }
|
19
|
+
|
20
|
+
before do
|
21
|
+
Rails.application = nil
|
22
|
+
|
23
|
+
# Force loading of flipper to configure itself
|
24
|
+
load 'flipper/cloud.rb'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "initializes cloud configuration" do
|
28
|
+
stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
|
29
|
+
|
30
|
+
with_modified_env env do
|
31
|
+
application.initialize!
|
32
|
+
|
33
|
+
expect(Flipper.instance).to be_a(Flipper::Cloud::DSL)
|
34
|
+
expect(Flipper.instance.instrumenter).to be(ActiveSupport::Notifications)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with CLOUD_SYNC_SECRET" do
|
39
|
+
before do
|
40
|
+
env.update "FLIPPER_CLOUD_SYNC_SECRET" => "test-secret"
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:request_body) do
|
44
|
+
JSON.generate({
|
45
|
+
"environment_id" => 1,
|
46
|
+
"webhook_id" => 1,
|
47
|
+
"delivery_id" => SecureRandom.uuid,
|
48
|
+
"action" => "sync",
|
49
|
+
})
|
50
|
+
end
|
51
|
+
let(:timestamp) { Time.now }
|
52
|
+
let(:signature) {
|
53
|
+
Flipper::Cloud::MessageVerifier.new(secret: env["FLIPPER_CLOUD_SYNC_SECRET"]).generate(request_body, timestamp)
|
54
|
+
}
|
55
|
+
let(:signature_header_value) {
|
56
|
+
Flipper::Cloud::MessageVerifier.new(secret: "").header(signature, timestamp)
|
57
|
+
}
|
58
|
+
|
59
|
+
it "configures webhook app" do
|
60
|
+
with_modified_env env do
|
61
|
+
application.initialize!
|
62
|
+
|
63
|
+
stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").with({
|
64
|
+
headers: { "Flipper-Cloud-Token" => ENV["FLIPPER_CLOUD_TOKEN"] },
|
65
|
+
}).to_return(status: 200, body: JSON.generate({ features: {} }), headers: {})
|
66
|
+
|
67
|
+
post "/_flipper", request_body, { "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value }
|
68
|
+
|
69
|
+
expect(last_response.status).to eq(200)
|
70
|
+
expect(stub).to have_been_requested
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "without CLOUD_SYNC_SECRET" do
|
76
|
+
it "does not configure webhook app" do
|
77
|
+
with_modified_env env do
|
78
|
+
application.initialize!
|
79
|
+
|
80
|
+
post "/_flipper"
|
81
|
+
expect(last_response.status).to eq(404)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "without FLIPPER_CLOUD_TOKEN" do
|
87
|
+
it "gracefully skips configuring webhook app" do
|
88
|
+
with_modified_env "FLIPPER_CLOUD_TOKEN" => nil do
|
89
|
+
application.initialize!
|
90
|
+
expect(silence { Flipper.instance }).to match(/Missing FLIPPER_CLOUD_TOKEN/)
|
91
|
+
expect(Flipper.instance).to be_a(Flipper::DSL)
|
92
|
+
|
93
|
+
post "/_flipper"
|
94
|
+
expect(last_response.status).to eq(404)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -6,18 +6,16 @@ require 'flipper/adapters/operation_logger'
|
|
6
6
|
|
7
7
|
RSpec.describe Flipper::Cloud::Middleware do
|
8
8
|
let(:flipper) {
|
9
|
-
Flipper::Cloud.new("regular") do |config|
|
9
|
+
Flipper::Cloud.new(token: "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
|
|
16
15
|
let(:env_flipper) {
|
17
|
-
Flipper::Cloud.new("env") do |config|
|
16
|
+
Flipper::Cloud.new(token: "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,7 +78,7 @@ 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
|
@@ -101,11 +99,11 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
101
99
|
env = {
|
102
100
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
103
101
|
}
|
104
|
-
post '/
|
102
|
+
post '/', request_body, env
|
105
103
|
|
106
104
|
expect(last_response.status).to eq(402)
|
107
105
|
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
|
106
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 402")
|
109
107
|
expect(stub).to have_been_requested
|
110
108
|
end
|
111
109
|
end
|
@@ -124,11 +122,11 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
124
122
|
env = {
|
125
123
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
126
124
|
}
|
127
|
-
post '/
|
125
|
+
post '/', request_body, env
|
128
126
|
|
129
127
|
expect(last_response.status).to eq(500)
|
130
128
|
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
|
129
|
+
expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to include("Failed with status: 503")
|
132
130
|
expect(stub).to have_been_requested
|
133
131
|
end
|
134
132
|
end
|
@@ -147,7 +145,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
147
145
|
env = {
|
148
146
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
149
147
|
}
|
150
|
-
post '/
|
148
|
+
post '/', request_body, env
|
151
149
|
|
152
150
|
expect(last_response.status).to eq(500)
|
153
151
|
expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Net::OpenTimeout")
|
@@ -168,7 +166,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
168
166
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
169
167
|
'flipper' => env_flipper,
|
170
168
|
}
|
171
|
-
post '/
|
169
|
+
post '/', request_body, env
|
172
170
|
|
173
171
|
expect(last_response.status).to eq(200)
|
174
172
|
expect(stub).to have_been_requested
|
@@ -187,7 +185,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
187
185
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
188
186
|
'flipper' => env_flipper,
|
189
187
|
}
|
190
|
-
post '/
|
188
|
+
post '/', request_body, env
|
191
189
|
|
192
190
|
expect(last_response.status).to eq(200)
|
193
191
|
expect(stub).to have_been_requested
|
@@ -207,7 +205,7 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
207
205
|
'flipper' => flipper,
|
208
206
|
'flipper_cloud' => env_flipper,
|
209
207
|
}
|
210
|
-
post '/
|
208
|
+
post '/', request_body, env
|
211
209
|
|
212
210
|
expect(last_response.status).to eq(200)
|
213
211
|
expect(stub).to have_been_requested
|
@@ -218,6 +216,27 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
218
216
|
let(:app) { Flipper::Cloud.app(-> { flipper }) }
|
219
217
|
|
220
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
|
+
|
221
240
|
stub = stub_request_for_token('regular')
|
222
241
|
env = {
|
223
242
|
"HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
|
@@ -225,13 +244,22 @@ RSpec.describe Flipper::Cloud::Middleware do
|
|
225
244
|
post '/webhooks', request_body, env
|
226
245
|
|
227
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
|
+
})
|
228
256
|
expect(stub).to have_been_requested
|
229
257
|
end
|
230
258
|
end
|
231
259
|
|
232
260
|
describe 'Request method unsupported' do
|
233
261
|
it 'skips middleware' do
|
234
|
-
get '/
|
262
|
+
get '/'
|
235
263
|
expect(last_response.status).to eq(404)
|
236
264
|
expect(last_response.content_type).to eq("application/json")
|
237
265
|
expect(last_response.body).to eq("{}")
|
data/spec/flipper/cloud_spec.rb
CHANGED
@@ -12,7 +12,7 @@ RSpec.describe Flipper::Cloud do
|
|
12
12
|
let(:token) { 'asdf' }
|
13
13
|
|
14
14
|
before do
|
15
|
-
@instance = described_class.new(token)
|
15
|
+
@instance = described_class.new(token: token)
|
16
16
|
memoized_adapter = @instance.adapter
|
17
17
|
sync_adapter = memoized_adapter.adapter
|
18
18
|
@http_adapter = sync_adapter.instance_variable_get('@remote')
|
@@ -52,7 +52,7 @@ RSpec.describe Flipper::Cloud do
|
|
52
52
|
before do
|
53
53
|
stub_request(:get, /fakeflipper\.com/).to_return(status: 200, body: "{}")
|
54
54
|
|
55
|
-
@instance = described_class.new('asdf', url: 'https://www.fakeflipper.com/sadpanda')
|
55
|
+
@instance = described_class.new(token: 'asdf', url: 'https://www.fakeflipper.com/sadpanda')
|
56
56
|
memoized_adapter = @instance.adapter
|
57
57
|
sync_adapter = memoized_adapter.adapter
|
58
58
|
@http_adapter = sync_adapter.instance_variable_get('@remote')
|
@@ -75,12 +75,12 @@ RSpec.describe Flipper::Cloud do
|
|
75
75
|
|
76
76
|
it 'can set instrumenter' do
|
77
77
|
instrumenter = Flipper::Instrumenters::Memory.new
|
78
|
-
instance = described_class.new('asdf', instrumenter: instrumenter)
|
78
|
+
instance = described_class.new(token: 'asdf', instrumenter: instrumenter)
|
79
79
|
expect(instance.instrumenter).to be(instrumenter)
|
80
80
|
end
|
81
81
|
|
82
82
|
it 'allows wrapping adapter with another adapter like the instrumenter' do
|
83
|
-
instance = described_class.new('asdf') do |config|
|
83
|
+
instance = described_class.new(token: 'asdf') do |config|
|
84
84
|
config.adapter do |adapter|
|
85
85
|
Flipper::Adapters::Instrumented.new(adapter)
|
86
86
|
end
|
@@ -92,26 +92,26 @@ RSpec.describe Flipper::Cloud do
|
|
92
92
|
it 'can set debug_output' do
|
93
93
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
94
94
|
.with(hash_including(debug_output: STDOUT))
|
95
|
-
described_class.new('asdf', debug_output: STDOUT)
|
95
|
+
described_class.new(token: 'asdf', debug_output: STDOUT)
|
96
96
|
end
|
97
97
|
|
98
98
|
it 'can set read_timeout' do
|
99
99
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
100
100
|
.with(hash_including(read_timeout: 1))
|
101
|
-
described_class.new('asdf', read_timeout: 1)
|
101
|
+
described_class.new(token: 'asdf', read_timeout: 1)
|
102
102
|
end
|
103
103
|
|
104
104
|
it 'can set open_timeout' do
|
105
105
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
106
106
|
.with(hash_including(open_timeout: 1))
|
107
|
-
described_class.new('asdf', open_timeout: 1)
|
107
|
+
described_class.new(token: 'asdf', open_timeout: 1)
|
108
108
|
end
|
109
109
|
|
110
110
|
if RUBY_VERSION >= '2.6.0'
|
111
111
|
it 'can set write_timeout' do
|
112
112
|
expect(Flipper::Adapters::Http::Client).to receive(:new)
|
113
113
|
.with(hash_including(open_timeout: 1))
|
114
|
-
described_class.new('asdf', open_timeout: 1)
|
114
|
+
described_class.new(token: 'asdf', open_timeout: 1)
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
@@ -129,7 +129,7 @@ RSpec.describe Flipper::Cloud do
|
|
129
129
|
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
130
130
|
flipper.enable_percentage_of_time(:logging, 5)
|
131
131
|
|
132
|
-
cloud_flipper = Flipper::Cloud.new("asdf")
|
132
|
+
cloud_flipper = Flipper::Cloud.new(token: "asdf")
|
133
133
|
|
134
134
|
get_all = {
|
135
135
|
"logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
|
@@ -158,7 +158,7 @@ RSpec.describe Flipper::Cloud do
|
|
158
158
|
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
159
159
|
flipper.enable_percentage_of_time(:logging, 5)
|
160
160
|
|
161
|
-
cloud_flipper = Flipper::Cloud.new("asdf")
|
161
|
+
cloud_flipper = Flipper::Cloud.new(token: "asdf")
|
162
162
|
|
163
163
|
get_all = {
|
164
164
|
"logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
|
@@ -186,7 +186,7 @@ RSpec.describe Flipper::Cloud do
|
|
186
186
|
flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
187
187
|
flipper.enable_percentage_of_time(:logging, 5)
|
188
188
|
|
189
|
-
cloud_flipper = Flipper::Cloud.new("asdf")
|
189
|
+
cloud_flipper = Flipper::Cloud.new(token: "asdf")
|
190
190
|
|
191
191
|
get_all = {
|
192
192
|
"logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
|
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.22.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-08 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.22.0
|
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.22.0
|
27
27
|
description:
|
28
28
|
email:
|
29
29
|
- nunemaker@gmail.com
|
@@ -31,21 +31,23 @@ 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
|
-
- examples/cloud/cached_in_memory.rb
|
37
37
|
- examples/cloud/import.rb
|
38
|
-
- examples/cloud/local_adapter.rb
|
39
38
|
- flipper-cloud.gemspec
|
40
39
|
- lib/flipper-cloud.rb
|
41
40
|
- lib/flipper/cloud.rb
|
42
41
|
- lib/flipper/cloud/configuration.rb
|
43
42
|
- lib/flipper/cloud/dsl.rb
|
43
|
+
- lib/flipper/cloud/engine.rb
|
44
44
|
- lib/flipper/cloud/message_verifier.rb
|
45
45
|
- lib/flipper/cloud/middleware.rb
|
46
|
+
- lib/flipper/cloud/routes.rb
|
46
47
|
- lib/flipper/version.rb
|
47
48
|
- spec/flipper/cloud/configuration_spec.rb
|
48
49
|
- spec/flipper/cloud/dsl_spec.rb
|
50
|
+
- spec/flipper/cloud/engine_spec.rb
|
49
51
|
- spec/flipper/cloud/message_verifier_spec.rb
|
50
52
|
- spec/flipper/cloud/middleware_spec.rb
|
51
53
|
- spec/flipper/cloud_spec.rb
|
@@ -72,10 +74,11 @@ requirements: []
|
|
72
74
|
rubygems_version: 3.0.3
|
73
75
|
signing_key:
|
74
76
|
specification_version: 4
|
75
|
-
summary:
|
77
|
+
summary: FlipperCloud.io adapter for Flipper
|
76
78
|
test_files:
|
77
79
|
- spec/flipper/cloud/configuration_spec.rb
|
78
80
|
- spec/flipper/cloud/dsl_spec.rb
|
81
|
+
- spec/flipper/cloud/engine_spec.rb
|
79
82
|
- spec/flipper/cloud/message_verifier_spec.rb
|
80
83
|
- spec/flipper/cloud/middleware_spec.rb
|
81
84
|
- spec/flipper/cloud_spec.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/cached_in_memory.rb
|
2
|
-
require File.expand_path('../../example_setup', __FILE__)
|
3
|
-
|
4
|
-
require 'flipper/cloud'
|
5
|
-
require 'flipper/adapters/active_support_cache_store'
|
6
|
-
require 'active_support/cache'
|
7
|
-
require 'active_support/cache/memory_store'
|
8
|
-
|
9
|
-
feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
|
10
|
-
|
11
|
-
Flipper.configure do |config|
|
12
|
-
config.default do
|
13
|
-
Flipper::Cloud.new do |cloud|
|
14
|
-
cloud.debug_output = STDOUT
|
15
|
-
cloud.adapter do |adapter|
|
16
|
-
Flipper::Adapters::ActiveSupportCacheStore.new(adapter,
|
17
|
-
ActiveSupport::Cache::MemoryStore.new, {expires_in: 5.seconds})
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
loop do
|
24
|
-
# Should only print out http call every 5 seconds
|
25
|
-
p Flipper.enabled?(feature_name)
|
26
|
-
puts "\n\n"
|
27
|
-
|
28
|
-
sleep 1
|
29
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# This is an example of using cloud with a local adapter. All cloud feature
|
2
|
-
# changes are synced to the local adapter on an interval. All feature reads are
|
3
|
-
# directed to the local adapter, which means reads are fast and not dependent on
|
4
|
-
# cloud being available. You can turn internet on/off and more and this should
|
5
|
-
# never raise. You could get a slow request every now and then if cloud is
|
6
|
-
# unavailable, but we are hoping to fix that soon by doing the cloud update in a
|
7
|
-
# background thread.
|
8
|
-
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/local_adapter.rb
|
9
|
-
require File.expand_path('../../example_setup', __FILE__)
|
10
|
-
|
11
|
-
require 'logger'
|
12
|
-
require 'flipper/cloud'
|
13
|
-
require 'flipper/adapters/redis'
|
14
|
-
|
15
|
-
feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
|
16
|
-
|
17
|
-
redis = Redis.new(logger: Logger.new(STDOUT))
|
18
|
-
redis.flushdb
|
19
|
-
|
20
|
-
Flipper.configure do |config|
|
21
|
-
config.default do
|
22
|
-
Flipper::Cloud.new do |cloud|
|
23
|
-
cloud.debug_output = STDOUT
|
24
|
-
cloud.local_adapter = Flipper::Adapters::Redis.new(redis)
|
25
|
-
cloud.sync_interval = 10
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
loop do
|
31
|
-
# Should only print out http call every 10 seconds
|
32
|
-
p Flipper.enabled?(feature_name)
|
33
|
-
puts "\n\n"
|
34
|
-
|
35
|
-
sleep 1
|
36
|
-
end
|