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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ac2ac6d779943f69836c9d8c6e4bcd58d45b31947114b09c154f2045285c006
4
- data.tar.gz: f80e71c83eea9aa839dc80141fb56b2e8701f0ba431b3baa62b62ac77bbfff4f
3
+ metadata.gz: 69bb21ad2d5ae7944825472e2d16fead9a99e3136331f2b9ad1b3a58a84d0340
4
+ data.tar.gz: 7b049a433a94cb80aa8077507837b449bca2fef71d05153a6ea45a6959cbaded
5
5
  SHA512:
6
- metadata.gz: 50bd6b0bf3aaa5b1a825b297a1cac4ebf6d6e849bc25dd0d1bd6d8fea38b47677da4095f3ba9c5237b622aebd5c49cb905176efb8e40d7b9a0cc2e88ba0f5a55
7
- data.tar.gz: 25ab8c3a7e347383660023830a8ff46af6e14176f6875c6812b92825c7abbcd8133b56c9fb971139417f3ce2843c0925c408c7968b456864cc9af3680cf5cbb0
6
+ metadata.gz: 050d427a9fcbe50168de8b46c822249bb71a843e55e24720ccbb7a13e075c0ebff0f8592a9cfa59e6fbbdcb2877afd21336c4f863f297e4ec41b0d7b71753027
7
+ data.tar.gz: 3d671ce575cd6f50fe456cd372faa81de5cbe6ca39a397403d1801e8adb7e4b2fa1b035df69028144c8e22a77b04b0d232e34530e8068225f779a918b1516b51
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)
@@ -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 = 'FeatureFlipper.com adapter for Flipper'
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(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.3'.freeze
2
+ VERSION = '0.22.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.3
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-01-10 00:00:00.000000000 Z
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.20.3
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.20.3
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: FeatureFlipper.com adapter for Flipper
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