flipper-cloud 0.20.2 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad99886efa608844beb4f7efdd5603ff60280c2ac5ca871a8de2e5fcbb2459f5
4
- data.tar.gz: f5f162782dd28d9d5e55616030202eb47899438f700d12ec7de218bc9bdba353
3
+ metadata.gz: 8f8ef3d6224837583368a9c10fd382244f493e57fe324a3c5e9bbeb2d8ed16c7
4
+ data.tar.gz: 0a21cf517ac2e92b791f9eb6436ee123af93202ade8f0d815af0103bd9c07606
5
5
  SHA512:
6
- metadata.gz: 44632f5b5c82b6c72c447716f8946768c03d2388c60fbfcdc07b43ee79389f094d49da3c1a1b5c4f34b4a1172e72b1dd2dbc67128838210f9cb3fb0dace41193
7
- data.tar.gz: d6968fb80d7cdc47ebb5bb692780e6eee715faf845644302c28cc5f86217c19c939df18456382e5e614d9a72f39d3793298d054aa7d8d076d143ae3393eee8fc
6
+ metadata.gz: b2e5e4ae41036280248bc5edf3a569752a35ce4a053464c2d0955e45d7c39172ad4af089a7242924547f073f7b15ac0cb8ebcfb33e8f175db374623663ebf3a1
7
+ data.tar.gz: 8b4666ff117ed3ff2ac70fc726f19ccae58af60479a7d596cf01e672ec1e1b8e1efa370ba1e08debbfc3378e3a12565499be509aa3ddc682da304c9ff5fa7bed
Binary file
@@ -1,14 +1,9 @@
1
1
  # Usage (from the repo root):
2
- # env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> FLIPPER_CLOUD_SYNC_METHOD=webhook bundle exec rackup examples/ui/basic.ru -p 9999
3
- # env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> FLIPPER_CLOUD_SYNC_METHOD=webhook bundle exec shotgun examples/ui/basic.ru -p 9999
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 }
@@ -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 'pathname'
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
- flipper[:stats].enable
6
+ Flipper[:stats].enable
13
7
 
14
- if flipper[:stats].enabled?
8
+ if Flipper[:stats].enabled?
15
9
  puts 'Enabled!'
16
10
  else
17
11
  puts 'Disabled!'
18
12
  end
19
13
 
20
- flipper[:stats].disable
14
+ Flipper[:stats].disable
21
15
 
22
- if flipper[:stats].enabled?
16
+ if Flipper[:stats].enabled?
23
17
  puts 'Enabled!'
24
18
  else
25
19
  puts 'Disabled!'
@@ -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 'pathname'
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
- memory_adapter = Flipper::Adapters::Memory.new
13
- flipper = Flipper.new(memory_adapter)
14
-
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)
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(flipper)
15
+ cloud.import(Flipper)
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(token = nil, options = {})
18
- options = options.merge(token: token) if token
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.sync_method = options.fetch(:sync_method) { ENV.fetch("FLIPPER_CLOUD_SYNC_METHOD", :poll).to_sym }
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
- def sync_method=(new_sync_method)
118
- new_sync_method = new_sync_method.to_sym
119
-
120
- unless VALID_SYNC_METHODS.include?(new_sync_method)
121
- raise ArgumentError, "Unsupported sync_method. Valid options are (#{VALID_SYNC_METHODS.to_a.join(', ')})"
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
- @sync_method = new_sync_method
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
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.20.2'.freeze
2
+ VERSION = '0.21.0'.freeze
3
3
  end
@@ -127,53 +127,30 @@ RSpec.describe Flipper::Cloud::Configuration do
127
127
  end
128
128
  end
129
129
 
130
- it "defaults to sync_method to poll" do
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 "can use webhook for sync_method" do
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 "raises ArgumentError for invalid sync_method" do
150
- expect {
151
- described_class.new(required_options.merge(sync_method: :foo))
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 '/webhooks', request_body, env
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 '/webhooks', request_body, env
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 '/webhooks', request_body, env
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 eq("Failed with status: 402")
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 '/webhooks', request_body, env
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 eq("Failed with status: 503")
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 '/webhooks', request_body, env
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 '/webhooks', request_body, env
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 '/webhooks', request_body, env
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 '/webhooks', request_body, env
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 '/webhooks'
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("{}")
@@ -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.20.2
4
+ version: 0.21.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-01-08 00:00:00.000000000 Z
11
+ date: 2021-05-09 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.2
19
+ version: 0.21.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.20.2
26
+ version: 0.21.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
@@ -76,6 +78,7 @@ summary: FeatureFlipper.com 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