flipper-cloud 0.20.0.beta1 → 0.20.2

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: e589ce31508179cafeb1cd8c008d157354c18e5868546d6e76ec543701268fd7
4
- data.tar.gz: 6b3eb3c890ad5afde871ab6594a2b21f8adfd848a45b4a4bc87d325d79020b68
3
+ metadata.gz: ad99886efa608844beb4f7efdd5603ff60280c2ac5ca871a8de2e5fcbb2459f5
4
+ data.tar.gz: f5f162782dd28d9d5e55616030202eb47899438f700d12ec7de218bc9bdba353
5
5
  SHA512:
6
- metadata.gz: 66c7d9c80c1f6be4c8506e25d72f6d88eb2b1b6f0679dec9acdb6e722dcae2b2e2f94026ad25ad260a2e5a2c89cdb826b519b66702ddb338e18b2c7b294429fa
7
- data.tar.gz: aa0de73a845c37c05d1f98cfa33cbc31b24b5d6fb5d8697e81a6079b172e6cecb2bb9c7e06f2cf030d72db1f8142b75c0826fc9ec5f60177a75c1937ab259630
6
+ metadata.gz: 44632f5b5c82b6c72c447716f8946768c03d2388c60fbfcdc07b43ee79389f094d49da3c1a1b5c4f34b4a1172e72b1dd2dbc67128838210f9cb3fb0dace41193
7
+ data.tar.gz: d6968fb80d7cdc47ebb5bb692780e6eee715faf845644302c28cc5f86217c19c939df18456382e5e614d9a72f39d3793298d054aa7d8d076d143ae3393eee8fc
@@ -1,5 +1,5 @@
1
1
  # Usage (from the repo root):
2
- # env TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
3
3
  require 'pathname'
4
4
  require 'logger'
5
5
  root_path = Pathname(__FILE__).dirname.join('..').expand_path
@@ -7,7 +7,7 @@ lib_path = root_path.join('lib')
7
7
  $:.unshift(lib_path)
8
8
 
9
9
  require 'flipper/cloud'
10
- flipper = Flipper::Cloud.new(ENV.fetch('TOKEN'))
10
+ flipper = Flipper::Cloud.new
11
11
 
12
12
  flipper[:stats].enable
13
13
 
@@ -1,3 +1,4 @@
1
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/cached_in_memory.rb
1
2
  require File.expand_path('../../example_setup', __FILE__)
2
3
 
3
4
  require 'flipper/cloud'
@@ -5,12 +6,11 @@ require 'flipper/adapters/active_support_cache_store'
5
6
  require 'active_support/cache'
6
7
  require 'active_support/cache/memory_store'
7
8
 
8
- token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
9
9
  feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
10
10
 
11
11
  Flipper.configure do |config|
12
12
  config.default do
13
- Flipper::Cloud.new(token) do |cloud|
13
+ Flipper::Cloud.new do |cloud|
14
14
  cloud.debug_output = STDOUT
15
15
  cloud.adapter do |adapter|
16
16
  Flipper::Adapters::ActiveSupportCacheStore.new(adapter,
@@ -1,5 +1,5 @@
1
1
  # Usage (from the repo root):
2
- # env TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
2
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
3
3
  require 'pathname'
4
4
  require 'logger'
5
5
  root_path = Pathname(__FILE__).dirname.join('..').expand_path
@@ -10,14 +10,14 @@ require 'flipper'
10
10
  require 'flipper/cloud'
11
11
 
12
12
  memory_adapter = Flipper::Adapters::Memory.new
13
- memory_flipper = Flipper.new(memory_adapter)
13
+ flipper = Flipper.new(memory_adapter)
14
14
 
15
- memory_flipper.enable(:test)
16
- memory_flipper.enable(:search)
17
- memory_flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
18
- memory_flipper.enable_percentage_of_time(:logging, 5)
15
+ flipper.enable(:test)
16
+ flipper.enable(:search)
17
+ flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
18
+ flipper.enable_percentage_of_time(:logging, 5)
19
19
 
20
- flipper = Flipper::Cloud.new(ENV.fetch('TOKEN'))
20
+ cloud = Flipper::Cloud.new
21
21
 
22
- # wipes cloud clean and makes it identical to memory flipper
23
- flipper.import(memory_flipper)
22
+ # makes cloud identical to memory flipper
23
+ cloud.import(flipper)
@@ -5,13 +5,13 @@
5
5
  # never raise. You could get a slow request every now and then if cloud is
6
6
  # unavailable, but we are hoping to fix that soon by doing the cloud update in a
7
7
  # background thread.
8
+ # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/local_adapter.rb
8
9
  require File.expand_path('../../example_setup', __FILE__)
9
10
 
10
11
  require 'logger'
11
12
  require 'flipper/cloud'
12
13
  require 'flipper/adapters/redis'
13
14
 
14
- token = ENV.fetch("TOKEN") { abort "TOKEN environment variable not set." }
15
15
  feature_name = ENV.fetch("FEATURE") { "testing" }.to_sym
16
16
 
17
17
  redis = Redis.new(logger: Logger.new(STDOUT))
@@ -19,7 +19,7 @@ redis.flushdb
19
19
 
20
20
  Flipper.configure do |config|
21
21
  config.default do
22
- Flipper::Cloud.new(token) do |cloud|
22
+ Flipper::Cloud.new do |cloud|
23
23
  cloud.debug_output = STDOUT
24
24
  cloud.local_adapter = Flipper::Adapters::Redis.new(redis)
25
25
  cloud.sync_interval = 10
@@ -1,5 +1,7 @@
1
+ require "socket"
1
2
  require "flipper/adapters/http"
2
3
  require "flipper/adapters/memory"
4
+ require "flipper/adapters/dual_write"
3
5
  require "flipper/adapters/sync"
4
6
 
5
7
  module Flipper
@@ -67,7 +69,7 @@ module Flipper
67
69
  @token = options.fetch(:token) { ENV["FLIPPER_CLOUD_TOKEN"] }
68
70
 
69
71
  if @token.nil?
70
- raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token used to validate webhooks (e.g. Flipper::Cloud.new('token'))."
72
+ raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token (e.g. Flipper::Cloud.new('token'))."
71
73
  end
72
74
 
73
75
  @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
@@ -129,7 +131,11 @@ module Flipper
129
131
  private
130
132
 
131
133
  def app_adapter
132
- sync_method == :webhook ? local_adapter : sync_adapter
134
+ sync_method == :webhook ? dual_write_adapter : sync_adapter
135
+ end
136
+
137
+ def dual_write_adapter
138
+ Flipper::Adapters::DualWrite.new(local_adapter, http_adapter)
133
139
  end
134
140
 
135
141
  def sync_adapter
@@ -147,6 +153,12 @@ module Flipper
147
153
  debug_output: @debug_output,
148
154
  headers: {
149
155
  "Flipper-Cloud-Token" => @token,
156
+ "Feature-Flipper-Token" => @token,
157
+ "Client-Lang" => "ruby",
158
+ "Client-Lang-Version" => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
159
+ "Client-Platform" => RUBY_PLATFORM,
160
+ "Client-Engine" => defined?(RUBY_ENGINE) ? RUBY_ENGINE : "",
161
+ "Client-Hostname" => Socket.gethostname,
150
162
  },
151
163
  })
152
164
  end
@@ -32,7 +32,20 @@ module Flipper
32
32
  begin
33
33
  message_verifier = MessageVerifier.new(secret: flipper.sync_secret)
34
34
  if message_verifier.verify(payload, signature)
35
- flipper.sync
35
+ begin
36
+ flipper.sync
37
+ body = JSON.generate({
38
+ groups: Flipper.group_names.map { |name| {name: name}}
39
+ })
40
+ rescue Flipper::Adapters::Http::Error => error
41
+ status = error.response.code.to_i == 402 ? 402 : 500
42
+ headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
43
+ headers["Flipper-Cloud-Response-Error-Message"] = error.message
44
+ rescue => error
45
+ status = 500
46
+ headers["Flipper-Cloud-Response-Error-Class"] = error.class.name
47
+ headers["Flipper-Cloud-Response-Error-Message"] = error.message
48
+ end
36
49
  end
37
50
  rescue MessageVerifier::InvalidSignature
38
51
  status = 400
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.20.0.beta1'.freeze
2
+ VERSION = '0.20.2'.freeze
3
3
  end
@@ -143,7 +143,7 @@ RSpec.describe Flipper::Cloud::Configuration do
143
143
  }))
144
144
 
145
145
  expect(instance.sync_method).to eq(:webhook)
146
- expect(instance.adapter).to be_instance_of(Flipper::Adapters::Memory)
146
+ expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
147
147
  end
148
148
 
149
149
  it "raises ArgumentError for invalid sync_method" do
@@ -171,7 +171,7 @@ RSpec.describe Flipper::Cloud::Configuration do
171
171
  }))
172
172
 
173
173
  expect(instance.sync_method).to eq(:webhook)
174
- expect(instance.adapter).to be_instance_of(Flipper::Adapters::Memory)
174
+ expect(instance.adapter).to be_instance_of(Flipper::Adapters::DualWrite)
175
175
  end
176
176
 
177
177
  it "can set sync_secret" do
@@ -1,6 +1,7 @@
1
1
  require 'helper'
2
2
  require 'flipper/cloud/configuration'
3
3
  require 'flipper/cloud/dsl'
4
+ require 'flipper/adapters/operation_logger'
4
5
  require 'flipper/adapters/instrumented'
5
6
 
6
7
  RSpec.describe Flipper::Cloud::DSL do
@@ -41,4 +42,46 @@ RSpec.describe Flipper::Cloud::DSL do
41
42
  dsl = described_class.new(cloud_configuration)
42
43
  expect(dsl.sync_secret).to eq("tasty")
43
44
  end
45
+
46
+ context "when sync_method is webhook" do
47
+ let(:local_adapter) do
48
+ Flipper::Adapters::OperationLogger.new Flipper::Adapters::Memory.new
49
+ end
50
+
51
+ let(:cloud_configuration) do
52
+ cloud_configuration = Flipper::Cloud::Configuration.new({
53
+ token: "asdf",
54
+ sync_secret: "tasty",
55
+ sync_method: :webhook,
56
+ local_adapter: local_adapter
57
+ })
58
+ end
59
+
60
+ subject do
61
+ described_class.new(cloud_configuration)
62
+ end
63
+
64
+ it "sends reads to local adapter" do
65
+ subject.features
66
+ subject.enabled?(:foo)
67
+ expect(local_adapter.count(:features)).to be(1)
68
+ expect(local_adapter.count(:get)).to be(1)
69
+ end
70
+
71
+ it "sends writes to cloud and local" do
72
+ add_stub = stub_request(:post, "https://www.flippercloud.io/adapter/features").
73
+ with({headers: {'Flipper-Cloud-Token'=>'asdf'}}).
74
+ to_return(status: 200, body: '{}', headers: {})
75
+ enable_stub = stub_request(:post, "https://www.flippercloud.io/adapter/features/foo/boolean").
76
+ with(headers: {'Flipper-Cloud-Token'=>'asdf'}).
77
+ to_return(status: 200, body: '{}', headers: {})
78
+
79
+ subject.enable(:foo)
80
+
81
+ expect(local_adapter.count(:add)).to be(1)
82
+ expect(local_adapter.count(:enable)).to be(1)
83
+ expect(add_stub).to have_been_requested
84
+ expect(enable_stub).to have_been_requested
85
+ end
86
+ end
44
87
  end
@@ -43,6 +43,12 @@ RSpec.describe Flipper::Cloud::Middleware do
43
43
  let(:app) { Flipper::Cloud.app(flipper) }
44
44
 
45
45
  it 'uses instance to sync' do
46
+ Flipper.register(:admins) { |*args| false }
47
+ Flipper.register(:staff) { |*args| false }
48
+ Flipper.register(:basic) { |*args| false }
49
+ Flipper.register(:plus) { |*args| false }
50
+ Flipper.register(:premium) { |*args| false }
51
+
46
52
  stub = stub_request_for_token('regular')
47
53
  env = {
48
54
  "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
@@ -50,6 +56,15 @@ RSpec.describe Flipper::Cloud::Middleware do
50
56
  post '/webhooks', request_body, env
51
57
 
52
58
  expect(last_response.status).to eq(200)
59
+ expect(JSON.parse(last_response.body)).to eq({
60
+ "groups" => [
61
+ {"name" => "admins"},
62
+ {"name" => "staff"},
63
+ {"name" => "basic"},
64
+ {"name" => "plus"},
65
+ {"name" => "premium"},
66
+ ],
67
+ })
53
68
  expect(stub).to have_been_requested
54
69
  end
55
70
  end
@@ -72,6 +87,75 @@ RSpec.describe Flipper::Cloud::Middleware do
72
87
  end
73
88
  end
74
89
 
90
+ context "when flipper cloud responds with 402" do
91
+ let(:app) { Flipper::Cloud.app(flipper) }
92
+
93
+ it "results in 402" do
94
+ Flipper.register(:admins) { |*args| false }
95
+ Flipper.register(:staff) { |*args| false }
96
+ Flipper.register(:basic) { |*args| false }
97
+ Flipper.register(:plus) { |*args| false }
98
+ Flipper.register(:premium) { |*args| false }
99
+
100
+ stub = stub_request_for_token('regular', status: 402)
101
+ env = {
102
+ "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
103
+ }
104
+ post '/webhooks', request_body, env
105
+
106
+ expect(last_response.status).to eq(402)
107
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
108
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("Failed with status: 402")
109
+ expect(stub).to have_been_requested
110
+ end
111
+ end
112
+
113
+ context "when flipper cloud responds with non-402 and non-2xx code" do
114
+ let(:app) { Flipper::Cloud.app(flipper) }
115
+
116
+ it "results in 500" do
117
+ Flipper.register(:admins) { |*args| false }
118
+ Flipper.register(:staff) { |*args| false }
119
+ Flipper.register(:basic) { |*args| false }
120
+ Flipper.register(:plus) { |*args| false }
121
+ Flipper.register(:premium) { |*args| false }
122
+
123
+ stub = stub_request_for_token('regular', status: 503)
124
+ env = {
125
+ "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
126
+ }
127
+ post '/webhooks', request_body, env
128
+
129
+ expect(last_response.status).to eq(500)
130
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Flipper::Adapters::Http::Error")
131
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("Failed with status: 503")
132
+ expect(stub).to have_been_requested
133
+ end
134
+ end
135
+
136
+ context "when flipper cloud responds with timeout" do
137
+ let(:app) { Flipper::Cloud.app(flipper) }
138
+
139
+ it "results in 500" do
140
+ Flipper.register(:admins) { |*args| false }
141
+ Flipper.register(:staff) { |*args| false }
142
+ Flipper.register(:basic) { |*args| false }
143
+ Flipper.register(:plus) { |*args| false }
144
+ Flipper.register(:premium) { |*args| false }
145
+
146
+ stub = stub_request_for_token('regular', status: :timeout)
147
+ env = {
148
+ "HTTP_FLIPPER_CLOUD_SIGNATURE" => signature_header_value,
149
+ }
150
+ post '/webhooks', request_body, env
151
+
152
+ expect(last_response.status).to eq(500)
153
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Class"]).to eq("Net::OpenTimeout")
154
+ expect(last_response.headers["Flipper-Cloud-Response-Error-Message"]).to eq("execution expired")
155
+ expect(stub).to have_been_requested
156
+ end
157
+ end
158
+
75
159
  context 'when initialized with flipper instance and flipper instance in env' do
76
160
  let(:app) { Flipper::Cloud.app(flipper) }
77
161
  let(:signature) {
@@ -162,12 +246,17 @@ RSpec.describe Flipper::Cloud::Middleware do
162
246
 
163
247
  private
164
248
 
165
- def stub_request_for_token(token)
166
- stub_request(:get, "https://www.flippercloud.io/adapter/features").
249
+ def stub_request_for_token(token, status: 200)
250
+ stub = stub_request(:get, "https://www.flippercloud.io/adapter/features").
167
251
  with({
168
252
  headers: {
169
253
  'Flipper-Cloud-Token' => token,
170
254
  },
171
- }).to_return(status: 200, body: response_body, headers: {})
255
+ })
256
+ if status == :timeout
257
+ stub.to_timeout
258
+ else
259
+ stub.to_return(status: status, body: response_body, headers: {})
260
+ end
172
261
  end
173
262
  end
@@ -114,4 +114,89 @@ RSpec.describe Flipper::Cloud do
114
114
  described_class.new('asdf', open_timeout: 1)
115
115
  end
116
116
  end
117
+
118
+ it 'can import' do
119
+ stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
120
+ with(headers: {
121
+ 'Feature-Flipper-Token'=>'asdf',
122
+ 'Flipper-Cloud-Token'=>'asdf',
123
+ }).to_return(status: 200, body: "{}", headers: {})
124
+
125
+ flipper = Flipper.new(Flipper::Adapters::Memory.new)
126
+
127
+ flipper.enable(:test)
128
+ flipper.enable(:search)
129
+ flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
130
+ flipper.enable_percentage_of_time(:logging, 5)
131
+
132
+ cloud_flipper = Flipper::Cloud.new("asdf")
133
+
134
+ get_all = {
135
+ "logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
136
+ "search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
137
+ "stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
138
+ "test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
139
+ }
140
+
141
+ expect(flipper.adapter.get_all).to eq(get_all)
142
+ cloud_flipper.import(flipper)
143
+ expect(flipper.adapter.get_all).to eq(get_all)
144
+ expect(cloud_flipper.adapter.get_all).to eq(get_all)
145
+ end
146
+
147
+ it 'raises error for failure while importing' do
148
+ stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
149
+ with(headers: {
150
+ 'Feature-Flipper-Token'=>'asdf',
151
+ 'Flipper-Cloud-Token'=>'asdf',
152
+ }).to_return(status: 500, body: "{}")
153
+
154
+ flipper = Flipper.new(Flipper::Adapters::Memory.new)
155
+
156
+ flipper.enable(:test)
157
+ flipper.enable(:search)
158
+ flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
159
+ flipper.enable_percentage_of_time(:logging, 5)
160
+
161
+ cloud_flipper = Flipper::Cloud.new("asdf")
162
+
163
+ get_all = {
164
+ "logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
165
+ "search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
166
+ "stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
167
+ "test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
168
+ }
169
+
170
+ expect(flipper.adapter.get_all).to eq(get_all)
171
+ expect { cloud_flipper.import(flipper) }.to raise_error(Flipper::Adapters::Http::Error)
172
+ expect(flipper.adapter.get_all).to eq(get_all)
173
+ end
174
+
175
+ it 'raises error for timeout while importing' do
176
+ stub_request(:post, /www\.flippercloud\.io\/adapter\/features.*/).
177
+ with(headers: {
178
+ 'Feature-Flipper-Token'=>'asdf',
179
+ 'Flipper-Cloud-Token'=>'asdf',
180
+ }).to_timeout
181
+
182
+ flipper = Flipper.new(Flipper::Adapters::Memory.new)
183
+
184
+ flipper.enable(:test)
185
+ flipper.enable(:search)
186
+ flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
187
+ flipper.enable_percentage_of_time(:logging, 5)
188
+
189
+ cloud_flipper = Flipper::Cloud.new("asdf")
190
+
191
+ get_all = {
192
+ "logging" => {actors: Set.new, boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: "5"},
193
+ "search" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
194
+ "stats" => {actors: Set["jnunemaker"], boolean: nil, groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
195
+ "test" => {actors: Set.new, boolean: "true", groups: Set.new, percentage_of_actors: nil, percentage_of_time: nil},
196
+ }
197
+
198
+ expect(flipper.adapter.get_all).to eq(get_all)
199
+ expect { cloud_flipper.import(flipper) }.to raise_error(Net::OpenTimeout)
200
+ expect(flipper.adapter.get_all).to eq(get_all)
201
+ end
117
202
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0.beta1
4
+ version: 0.20.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-11 00:00:00.000000000 Z
11
+ date: 2021-01-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.0.beta1
19
+ version: 0.20.2
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.0.beta1
26
+ version: 0.20.2
27
27
  description:
28
28
  email:
29
29
  - nunemaker@gmail.com
@@ -65,9 +65,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
65
  version: '0'
66
66
  required_rubygems_version: !ruby/object:Gem::Requirement
67
67
  requirements:
68
- - - ">"
68
+ - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: 1.3.1
70
+ version: '0'
71
71
  requirements: []
72
72
  rubygems_version: 3.0.3
73
73
  signing_key: