unleash 4.0.0 → 4.1.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/.github/workflows/pull_request.yml +3 -1
- data/README.md +69 -7
- data/examples/bootstrap.rb +51 -0
- data/examples/default-toggles.json +42 -0
- data/lib/unleash/bootstrap/configuration.rb +25 -0
- data/lib/unleash/bootstrap/handler.rb +22 -0
- data/lib/unleash/bootstrap/provider/base.rb +14 -0
- data/lib/unleash/bootstrap/provider/from_file.rb +14 -0
- data/lib/unleash/bootstrap/provider/from_url.rb +19 -0
- data/lib/unleash/client.rb +8 -8
- data/lib/unleash/configuration.rb +10 -3
- data/lib/unleash/scheduled_executor.rb +5 -2
- data/lib/unleash/strategy/remote_address.rb +17 -1
- data/lib/unleash/toggle_fetcher.rb +29 -10
- data/lib/unleash/util/http.rb +6 -3
- data/lib/unleash/version.rb +1 -1
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14f988b84b07e45401ad61a5814c1ca44185fc9358cef33f050b1d3bae45dd4e
|
4
|
+
data.tar.gz: afda9cf356088ef976047fe58d38d3b457fb1d8797cb7f519de6bdb953f43267
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1ac85ea8b774a230212583b33b9fdab3110fadab2fa1652810ea0913312c1fd20abe4471761b5ccd399d66a52995c0ddb6b803ee42aa4c4432add8a5524e51d
|
7
|
+
data.tar.gz: b03da5caf2bf25683b2d61599a37f15582d26664553ef72096dfd3073aceb96224eaa46fd19c948f9f4fc143e3226ee176a4e66d762016aedf736fb903edae85
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Unleash::Client
|
2
2
|
|
3
|
-
|
4
|
-
[
|
4
|
+
[](https://coveralls.io/github/Unleash/unleash-client-ruby?branch=main)
|
5
5
|
[](https://badge.fury.io/rb/unleash)
|
6
6
|
|
7
7
|
Unleash client so you can roll out your features with confidence.
|
@@ -10,11 +10,13 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f
|
|
10
10
|
|
11
11
|
## Supported Ruby Interpreters
|
12
12
|
|
13
|
+
* MRI 3.1
|
13
14
|
* MRI 3.0
|
14
15
|
* MRI 2.7
|
15
16
|
* MRI 2.6
|
16
17
|
* MRI 2.5
|
17
|
-
* jruby
|
18
|
+
* jruby 9.3
|
19
|
+
* jruby 9.2
|
18
20
|
|
19
21
|
## Installation
|
20
22
|
|
@@ -69,7 +71,7 @@ Argument | Description | Required? | Type | Default Value|
|
|
69
71
|
`environment` | Environment the program is running on. Could be for example `prod` or `dev`. Not yet in use. | N | String | `default` |
|
70
72
|
`project_name` | Name of the project to retrieve features from. If not set, all feature flags will be retrieved. | N | String | nil |
|
71
73
|
`refresh_interval` | How often the unleash client should check with the server for configuration changes. | N | Integer | 15 |
|
72
|
-
`metrics_interval` | How often the unleash client should send metrics to server. | N | Integer |
|
74
|
+
`metrics_interval` | How often the unleash client should send metrics to server. | N | Integer | 60 |
|
73
75
|
`disable_client` | Disables all communication with the Unleash server, effectively taking it *offline*. If set, `is_enabled?` will always answer with the `default_value` and configuration validation is skipped. Defeats the entire purpose of using unleash, but can be useful in when running tests. | N | Boolean | `false` |
|
74
76
|
`disable_metrics` | Disables sending metrics to Unleash server. | N | Boolean | `false` |
|
75
77
|
`custom_http_headers` | Custom headers to send to Unleash. As of Unleash v4.0.0, the `Authorization` header is required. For example: `{'Authorization': '<API token>'}` | N | Hash | {} |
|
@@ -78,9 +80,14 @@ Argument | Description | Required? | Type | Default Value|
|
|
78
80
|
`backup_file` | Filename to store the last known state from the Unleash server. Best to not change this from the default. | N | String | `Dir.tmpdir + "/unleash-#{app_name}-repo.json` |
|
79
81
|
`logger` | Specify a custom `Logger` class to handle logs for the Unleash client. | N | Class | `Logger.new(STDOUT)` |
|
80
82
|
`log_level` | Change the log level for the `Logger` class. Constant from `Logger::Severity`. | N | Constant | `Logger::WARN` |
|
83
|
+
`bootstrap_config` | Bootstrap config on how to loaded data on start-up. This is useful for loading large states on startup without (or before) hitting the network. | N | Unleash::Bootstrap::Configuration | `nil` |
|
81
84
|
|
82
|
-
For
|
85
|
+
For a more in-depth look, please see `lib/unleash/configuration.rb`.
|
83
86
|
|
87
|
+
Environment Variable | Description
|
88
|
+
---------|---------
|
89
|
+
`UNLEASH_BOOTSTRAP_FILE` | File to read bootstrap data from
|
90
|
+
`UNLEASH_BOOTSTRAP_URL` | URL to read bootstrap data from
|
84
91
|
|
85
92
|
## Usage in a plain Ruby Application
|
86
93
|
|
@@ -88,7 +95,7 @@ For in a more in depth look, please see `lib/unleash/configuration.rb`.
|
|
88
95
|
require 'unleash'
|
89
96
|
require 'unleash/context'
|
90
97
|
|
91
|
-
@unleash = Unleash::Client.new(app_name: 'my_ruby_app', url: 'http://unleash.herokuapp.com/api', custom_http_headers: {'Authorization': '<API token>'})
|
98
|
+
@unleash = Unleash::Client.new(app_name: 'my_ruby_app', url: 'http://unleash.herokuapp.com/api', custom_http_headers: { 'Authorization': '<API token>' })
|
92
99
|
|
93
100
|
feature_name = "AwesomeFeature"
|
94
101
|
unleash_context = Unleash::Context.new
|
@@ -266,6 +273,62 @@ variant = UNLEASH.get_variant "ColorVariants", @unleash_context, fallback_varian
|
|
266
273
|
puts "variant color is: #{variant.payload.fetch('color')}"
|
267
274
|
```
|
268
275
|
|
276
|
+
## Bootstrapping
|
277
|
+
|
278
|
+
Bootstrap configuration allows the client to be initialized with a predefined set of toggle states. Bootstrapping can be configured by providing a bootstrap configuration when initializing the client.
|
279
|
+
```ruby
|
280
|
+
@unleash = Unleash::Client.new(
|
281
|
+
url: 'http://unleash.herokuapp.com/api',
|
282
|
+
app_name: 'my_ruby_app',
|
283
|
+
custom_http_headers: { 'Authorization': '<API token>' },
|
284
|
+
bootstrap_config: Unleash::Bootstrap::Configuration.new({
|
285
|
+
url: "http://unleash.herokuapp.com/api/client/features",
|
286
|
+
url_headers: {'Authorization': '<API token>'}
|
287
|
+
})
|
288
|
+
)
|
289
|
+
```
|
290
|
+
The `Bootstrap::Configuration` initializer takes a hash with one of the following options specified:
|
291
|
+
|
292
|
+
* `file_path` - An absolute or relative path to a file containing a JSON string of the response body from the Unleash server. This can also be set though the `UNLEASH_BOOTSTRAP_FILE` environment variable.
|
293
|
+
* `url` - A url pointing to an Unleash server's features endpoint, the code sample above is illustrative. This can also be set though the `UNLEASH_BOOTSTRAP_URL` environment variable.
|
294
|
+
* `url_headers` - Headers for the GET http request to the `url` above. Only used if the `url` parameter is also set. If this option isn't set then the bootstrapper will use the same url headers as the Unleash client.
|
295
|
+
* `data` - A raw JSON string as returned by the Unleash server.
|
296
|
+
* `block` - A lambda containing custom logic if you need it, an example is provided below.
|
297
|
+
|
298
|
+
You should only specify one type of bootstrapping since only one will be invoked and the others will be ignored. The order of preference is as follows:
|
299
|
+
|
300
|
+
- Select a data bootstrapper if it exists.
|
301
|
+
- If no data bootstrapper exists, select the block bootstrapper.
|
302
|
+
- If no block bootstrapper exists, select the file bootstrapper from either parameters or the specified environment variable.
|
303
|
+
- If no file bootstrapper exists, then check for a URL bootstrapper from either the parameters or the specified environment variable.
|
304
|
+
|
305
|
+
|
306
|
+
Example usage:
|
307
|
+
|
308
|
+
First saving the toggles locally:
|
309
|
+
```shell
|
310
|
+
curl -H 'Authorization: <API token>' -XGET 'http://unleash.herokuapp.com/api' > ./default-toggles.json
|
311
|
+
```
|
312
|
+
|
313
|
+
Now using them on start up:
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
|
317
|
+
custom_boostrapper = lambda {
|
318
|
+
File.read('./default-toggles.json')
|
319
|
+
}
|
320
|
+
|
321
|
+
@unleash = Unleash::Client.new(
|
322
|
+
app_name: 'my_ruby_app',
|
323
|
+
url: 'http://unleash.herokuapp.com/api',
|
324
|
+
custom_http_headers: { 'Authorization': '<API token>' },
|
325
|
+
bootstrap_config: Unleash::Bootstrap::Configuration.new({
|
326
|
+
block: custom_boostrapper
|
327
|
+
}
|
328
|
+
)
|
329
|
+
```
|
330
|
+
|
331
|
+
This example could be easily achieved with a file bootstrapper, this is just to illustrate the usage of custom bootstrapping. Be aware that the client initializer will block until bootstrapping is complete.
|
269
332
|
|
270
333
|
#### Client methods
|
271
334
|
|
@@ -304,7 +367,6 @@ This client comes with the all the required strategies out of the box:
|
|
304
367
|
* UnknownStrategy
|
305
368
|
* UserWithIdStrategy
|
306
369
|
|
307
|
-
|
308
370
|
## Development
|
309
371
|
|
310
372
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'unleash'
|
4
|
+
require 'unleash/context'
|
5
|
+
require 'unleash/bootstrap/configuration'
|
6
|
+
|
7
|
+
puts ">> START bootstrap.rb"
|
8
|
+
|
9
|
+
@unleash = Unleash::Client.new(
|
10
|
+
url: 'http://unleash.herokuapp.com/api',
|
11
|
+
custom_http_headers: { 'Authorization': '943ca9171e2c884c545c5d82417a655fb77cec970cc3b78a8ff87f4406b495d0' },
|
12
|
+
app_name: 'bootstrap-test',
|
13
|
+
instance_id: 'local-test-cli',
|
14
|
+
refresh_interval: 2,
|
15
|
+
disable_client: true,
|
16
|
+
disable_metrics: true,
|
17
|
+
metrics_interval: 2,
|
18
|
+
retry_limit: 2,
|
19
|
+
bootstrap_config: Unleash::Bootstrap::Configuration.new(file_path: "examples/default-toggles.json")
|
20
|
+
)
|
21
|
+
|
22
|
+
feature_name = "featureX"
|
23
|
+
unleash_context = Unleash::Context.new
|
24
|
+
unleash_context.user_id = 123
|
25
|
+
|
26
|
+
sleep 1
|
27
|
+
3.times do
|
28
|
+
if @unleash.is_enabled?(feature_name, unleash_context)
|
29
|
+
puts "> #{feature_name} is enabled"
|
30
|
+
else
|
31
|
+
puts "> #{feature_name} is not enabled"
|
32
|
+
end
|
33
|
+
sleep 1
|
34
|
+
puts "---"
|
35
|
+
puts ""
|
36
|
+
puts ""
|
37
|
+
end
|
38
|
+
|
39
|
+
sleep 3
|
40
|
+
feature_name = "foobar"
|
41
|
+
if @unleash.is_enabled?(feature_name, unleash_context, true)
|
42
|
+
puts "> #{feature_name} is enabled"
|
43
|
+
else
|
44
|
+
puts "> #{feature_name} is not enabled"
|
45
|
+
end
|
46
|
+
|
47
|
+
puts "> shutting down client..."
|
48
|
+
|
49
|
+
@unleash.shutdown
|
50
|
+
|
51
|
+
puts ">> END bootstrap.rb"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
{
|
2
|
+
"version": 1,
|
3
|
+
"features": [
|
4
|
+
{
|
5
|
+
"name": "featureX",
|
6
|
+
"enabled": true,
|
7
|
+
"strategies": [
|
8
|
+
{
|
9
|
+
"name": "default"
|
10
|
+
}
|
11
|
+
]
|
12
|
+
},
|
13
|
+
{
|
14
|
+
"name": "featureY",
|
15
|
+
"enabled": false,
|
16
|
+
"strategies": [
|
17
|
+
{
|
18
|
+
"name": "baz",
|
19
|
+
"parameters": {
|
20
|
+
"foo": "bar"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
]
|
24
|
+
},
|
25
|
+
{
|
26
|
+
"name": "featureZ",
|
27
|
+
"enabled": true,
|
28
|
+
"strategies": [
|
29
|
+
{
|
30
|
+
"name": "default"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"name": "hola",
|
34
|
+
"parameters": {
|
35
|
+
"name": "val"
|
36
|
+
}
|
37
|
+
}
|
38
|
+
]
|
39
|
+
}
|
40
|
+
]
|
41
|
+
}
|
42
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Unleash
|
2
|
+
module Bootstrap
|
3
|
+
class Configuration
|
4
|
+
attr_accessor :data, :file_path, :url, :url_headers, :block
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
self.file_path = resolve_value_indifferently(opts, 'file_path') || ENV['UNLEASH_BOOTSTRAP_FILE'] || nil
|
8
|
+
self.url = resolve_value_indifferently(opts, 'url') || ENV['UNLEASH_BOOTSTRAP_URL'] || nil
|
9
|
+
self.url_headers = resolve_value_indifferently(opts, 'url_headers')
|
10
|
+
self.data = resolve_value_indifferently(opts, 'data')
|
11
|
+
self.block = resolve_value_indifferently(opts, 'block')
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
![self.data, self.file_path, self.url, self.block].all?(&:nil?)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def resolve_value_indifferently(opts, key)
|
21
|
+
opts[key] || opts[key.to_sym]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'unleash/bootstrap/provider/from_url'
|
2
|
+
require 'unleash/bootstrap/provider/from_file'
|
3
|
+
|
4
|
+
module Unleash
|
5
|
+
module Bootstrap
|
6
|
+
class Handler
|
7
|
+
attr_accessor :configuration
|
8
|
+
|
9
|
+
def initialize(configuration)
|
10
|
+
self.configuration = configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String] JSON string representing data returned from an Unleash server
|
14
|
+
def retrieve_toggles
|
15
|
+
return configuration.data unless self.configuration.data.nil?
|
16
|
+
return configuration.block.call if self.configuration.block.is_a?(Proc)
|
17
|
+
return Provider::FromFile.read(configuration.file_path) unless self.configuration.file_path.nil?
|
18
|
+
return Provider::FromUrl.read(configuration.url, configuration.url_headers) unless self.configuration.url.nil?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'unleash/bootstrap/provider/base'
|
2
|
+
|
3
|
+
module Unleash
|
4
|
+
module Bootstrap
|
5
|
+
module Provider
|
6
|
+
class FromUrl < Base
|
7
|
+
# @param url [String]
|
8
|
+
# @param headers [Hash, nil] HTTP headers to use. If not set, the unleash client SDK ones will be used.
|
9
|
+
def self.read(url, headers = nil)
|
10
|
+
response = Unleash::Util::Http.get(URI.parse(url), nil, headers)
|
11
|
+
|
12
|
+
return nil if response.code != '200'
|
13
|
+
|
14
|
+
response.body
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/unleash/client.rb
CHANGED
@@ -18,8 +18,9 @@ module Unleash
|
|
18
18
|
Unleash.logger = Unleash.configuration.logger.clone
|
19
19
|
Unleash.logger.level = Unleash.configuration.log_level
|
20
20
|
|
21
|
+
Unleash.toggle_fetcher = Unleash::ToggleFetcher.new
|
21
22
|
if Unleash.configuration.disable_client
|
22
|
-
Unleash.logger.warn "Unleash::Client is disabled! Will only return default results!"
|
23
|
+
Unleash.logger.warn "Unleash::Client is disabled! Will only return default (or bootstrapped if available) results!"
|
23
24
|
return
|
24
25
|
end
|
25
26
|
|
@@ -37,11 +38,6 @@ module Unleash
|
|
37
38
|
default_value_param
|
38
39
|
end
|
39
40
|
|
40
|
-
if Unleash.configuration.disable_client
|
41
|
-
Unleash.logger.warn "unleash_client is disabled! Always returning #{default_value} for feature #{feature}!"
|
42
|
-
return default_value
|
43
|
-
end
|
44
|
-
|
45
41
|
toggle_as_hash = Unleash&.toggles&.select{ |toggle| toggle['name'] == feature }&.first
|
46
42
|
|
47
43
|
if toggle_as_hash.nil?
|
@@ -121,11 +117,11 @@ module Unleash
|
|
121
117
|
end
|
122
118
|
|
123
119
|
def start_toggle_fetcher
|
124
|
-
Unleash.toggle_fetcher = Unleash::ToggleFetcher.new
|
125
120
|
self.fetcher_scheduled_executor = Unleash::ScheduledExecutor.new(
|
126
121
|
'ToggleFetcher',
|
127
122
|
Unleash.configuration.refresh_interval,
|
128
|
-
Unleash.configuration.retry_limit
|
123
|
+
Unleash.configuration.retry_limit,
|
124
|
+
first_fetch_is_eager
|
129
125
|
)
|
130
126
|
self.fetcher_scheduled_executor.run do
|
131
127
|
Unleash.toggle_fetcher.fetch
|
@@ -161,5 +157,9 @@ module Unleash
|
|
161
157
|
def disabled_variant
|
162
158
|
@disabled_variant ||= Unleash::FeatureToggle.disabled_variant
|
163
159
|
end
|
160
|
+
|
161
|
+
def first_fetch_is_eager
|
162
|
+
Unleash.configuration.use_bootstrap?
|
163
|
+
end
|
164
164
|
end
|
165
165
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
require 'tmpdir'
|
3
|
+
require 'unleash/bootstrap/configuration'
|
3
4
|
|
4
5
|
module Unleash
|
5
6
|
class Configuration
|
@@ -18,7 +19,8 @@ module Unleash
|
|
18
19
|
:metrics_interval,
|
19
20
|
:backup_file,
|
20
21
|
:logger,
|
21
|
-
:log_level
|
22
|
+
:log_level,
|
23
|
+
:bootstrap_config
|
22
24
|
|
23
25
|
def initialize(opts = {})
|
24
26
|
ensure_valid_opts(opts)
|
@@ -70,6 +72,10 @@ module Unleash
|
|
70
72
|
self.url.delete_suffix '/'
|
71
73
|
end
|
72
74
|
|
75
|
+
def use_bootstrap?
|
76
|
+
self.bootstrap_config&.valid?
|
77
|
+
end
|
78
|
+
|
73
79
|
private
|
74
80
|
|
75
81
|
def ensure_valid_opts(opts)
|
@@ -87,11 +93,12 @@ module Unleash
|
|
87
93
|
self.disable_client = false
|
88
94
|
self.disable_metrics = false
|
89
95
|
self.refresh_interval = 10
|
90
|
-
self.metrics_interval =
|
96
|
+
self.metrics_interval = 60
|
91
97
|
self.timeout = 30
|
92
|
-
self.retry_limit =
|
98
|
+
self.retry_limit = 5
|
93
99
|
self.backup_file = nil
|
94
100
|
self.log_level = Logger::WARN
|
101
|
+
self.bootstrap_config = nil
|
95
102
|
|
96
103
|
self.custom_http_headers = {}
|
97
104
|
end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
module Unleash
|
2
2
|
class ScheduledExecutor
|
3
|
-
attr_accessor :name, :interval, :max_exceptions, :retry_count, :thread
|
3
|
+
attr_accessor :name, :interval, :max_exceptions, :retry_count, :thread, :immediate_execution
|
4
4
|
|
5
|
-
def initialize(name, interval, max_exceptions = 5)
|
5
|
+
def initialize(name, interval, max_exceptions = 5, immediate_execution = false)
|
6
6
|
self.name = name || ''
|
7
7
|
self.interval = interval
|
8
8
|
self.max_exceptions = max_exceptions
|
9
9
|
self.retry_count = 0
|
10
10
|
self.thread = nil
|
11
|
+
self.immediate_execution = immediate_execution
|
11
12
|
end
|
12
13
|
|
13
14
|
def run(&blk)
|
14
15
|
self.thread = Thread.new do
|
15
16
|
Thread.current[:name] = self.name
|
16
17
|
|
18
|
+
run_blk{ blk.call } if self.immediate_execution
|
19
|
+
|
17
20
|
Unleash.logger.debug "thread #{name} loop starting"
|
18
21
|
loop do
|
19
22
|
Unleash.logger.debug "thread #{name} sleeping for #{interval} seconds"
|
@@ -13,7 +13,23 @@ module Unleash
|
|
13
13
|
return false unless params.fetch(PARAM, nil).is_a? String
|
14
14
|
return false unless context.class.name == 'Unleash::Context'
|
15
15
|
|
16
|
-
|
16
|
+
remote_address = ipaddr_or_nil_from_str(context.remote_address)
|
17
|
+
|
18
|
+
params[PARAM]
|
19
|
+
.split(',')
|
20
|
+
.map(&:strip)
|
21
|
+
.map{ |ipblock| ipaddr_or_nil_from_str(ipblock) }
|
22
|
+
.compact
|
23
|
+
.map{ |ipb| ipb.include? remote_address }
|
24
|
+
.any?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def ipaddr_or_nil_from_str(ip)
|
30
|
+
IPAddr.new(ip)
|
31
|
+
rescue StandardError
|
32
|
+
nil
|
17
33
|
end
|
18
34
|
end
|
19
35
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'unleash/configuration'
|
2
|
+
require 'unleash/bootstrap/handler'
|
2
3
|
require 'net/http'
|
3
4
|
require 'json'
|
4
5
|
|
@@ -13,10 +14,15 @@ module Unleash
|
|
13
14
|
self.toggle_resource = ConditionVariable.new
|
14
15
|
self.retry_count = 0
|
15
16
|
|
16
|
-
# start by fetching synchronously, and failing back to reading the backup file.
|
17
17
|
begin
|
18
|
-
|
18
|
+
# if bootstrap configuration is available, initialize. An immediate API read is also triggered
|
19
|
+
if Unleash.configuration.use_bootstrap?
|
20
|
+
bootstrap
|
21
|
+
else
|
22
|
+
fetch
|
23
|
+
end
|
19
24
|
rescue StandardError => e
|
25
|
+
# fail back to reading the backup file
|
20
26
|
Unleash.logger.warn "ToggleFetcher was unable to fetch from the network, attempting to read from backup file."
|
21
27
|
Unleash.logger.debug "Exception Caught: #{e}"
|
22
28
|
read!
|
@@ -36,6 +42,8 @@ module Unleash
|
|
36
42
|
# rename to refresh_from_server! ??
|
37
43
|
def fetch
|
38
44
|
Unleash.logger.debug "fetch()"
|
45
|
+
return if Unleash.configuration.disable_client
|
46
|
+
|
39
47
|
response = Unleash::Util::Http.get(Unleash.configuration.fetch_toggles_uri, etag)
|
40
48
|
|
41
49
|
if response.code == '304'
|
@@ -46,14 +54,7 @@ module Unleash
|
|
46
54
|
end
|
47
55
|
|
48
56
|
self.etag = response['ETag']
|
49
|
-
|
50
|
-
|
51
|
-
if response_hash['version'] >= 1
|
52
|
-
features = response_hash['features']
|
53
|
-
else
|
54
|
-
raise NotImplemented, "Version of features provided by unleash server" \
|
55
|
-
" is unsupported by this client."
|
56
|
-
end
|
57
|
+
features = get_features(response.body)
|
57
58
|
|
58
59
|
# always synchronize with the local cache when fetching:
|
59
60
|
synchronize_with_local_cache!(features)
|
@@ -126,5 +127,23 @@ module Unleash
|
|
126
127
|
file&.close
|
127
128
|
end
|
128
129
|
end
|
130
|
+
|
131
|
+
def bootstrap
|
132
|
+
bootstrap_payload = Unleash::Bootstrap::Handler.new(Unleash.configuration.bootstrap_config).retrieve_toggles
|
133
|
+
synchronize_with_local_cache! get_features bootstrap_payload
|
134
|
+
update_running_client!
|
135
|
+
|
136
|
+
# reset Unleash.configuration.bootstrap_data to free up memory, as we will never use it again
|
137
|
+
Unleash.configuration.bootstrap_config = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param response_body [String]
|
141
|
+
def get_features(response_body)
|
142
|
+
response_hash = JSON.parse(response_body)
|
143
|
+
return response_hash['features'] if response_hash['version'] >= 1
|
144
|
+
|
145
|
+
raise NotImplemented, "Version of features provided by unleash server" \
|
146
|
+
" is unsupported by this client."
|
147
|
+
end
|
129
148
|
end
|
130
149
|
end
|
data/lib/unleash/util/http.rb
CHANGED
@@ -4,10 +4,10 @@ require 'uri'
|
|
4
4
|
module Unleash
|
5
5
|
module Util
|
6
6
|
module Http
|
7
|
-
def self.get(uri, etag = nil)
|
7
|
+
def self.get(uri, etag = nil, headers_override = nil)
|
8
8
|
http = http_connection(uri)
|
9
9
|
|
10
|
-
request = Net::HTTP::Get.new(uri.request_uri, http_headers(etag))
|
10
|
+
request = Net::HTTP::Get.new(uri.request_uri, http_headers(etag, headers_override))
|
11
11
|
|
12
12
|
http.request(request)
|
13
13
|
end
|
@@ -30,10 +30,13 @@ module Unleash
|
|
30
30
|
http
|
31
31
|
end
|
32
32
|
|
33
|
-
|
33
|
+
# @param etag [String, nil]
|
34
|
+
# @param headers_override [Hash, nil]
|
35
|
+
def self.http_headers(etag = nil, headers_override = nil)
|
34
36
|
Unleash.logger.debug "ETag: #{etag}" unless etag.nil?
|
35
37
|
|
36
38
|
headers = (Unleash.configuration.http_headers || {}).dup
|
39
|
+
headers = headers_override if headers_override.is_a?(Hash)
|
37
40
|
headers['Content-Type'] = 'application/json'
|
38
41
|
headers['If-None-Match'] = etag unless etag.nil?
|
39
42
|
|
data/lib/unleash/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unleash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renato Arruda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: murmurhash3
|
@@ -157,9 +157,16 @@ files:
|
|
157
157
|
- bin/console
|
158
158
|
- bin/setup
|
159
159
|
- bin/unleash-client
|
160
|
+
- examples/bootstrap.rb
|
161
|
+
- examples/default-toggles.json
|
160
162
|
- examples/simple.rb
|
161
163
|
- lib/unleash.rb
|
162
164
|
- lib/unleash/activation_strategy.rb
|
165
|
+
- lib/unleash/bootstrap/configuration.rb
|
166
|
+
- lib/unleash/bootstrap/handler.rb
|
167
|
+
- lib/unleash/bootstrap/provider/base.rb
|
168
|
+
- lib/unleash/bootstrap/provider/from_file.rb
|
169
|
+
- lib/unleash/bootstrap/provider/from_url.rb
|
163
170
|
- lib/unleash/client.rb
|
164
171
|
- lib/unleash/configuration.rb
|
165
172
|
- lib/unleash/constraint.rb
|
@@ -204,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
211
|
- !ruby/object:Gem::Version
|
205
212
|
version: '0'
|
206
213
|
requirements: []
|
207
|
-
rubygems_version: 3.
|
214
|
+
rubygems_version: 3.3.6
|
208
215
|
signing_key:
|
209
216
|
specification_version: 4
|
210
217
|
summary: Unleash feature toggle client.
|