unleash 3.2.2 → 4.3.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.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Unleash::Client
2
2
 
3
- [![Build Status](https://travis-ci.org/Unleash/unleash-client-ruby.svg?branch=master)](https://travis-ci.org/Unleash/unleash-client-ruby)
4
- [![Coverage Status](https://coveralls.io/repos/github/Unleash/unleash-client-ruby/badge.svg?branch=master)](https://coveralls.io/github/Unleash/unleash-client-ruby?branch=master)
3
+ ![Build Status](https://github.com/Unleash/unleash-client-ruby/actions/workflows/pull_request.yml/badge.svg?branch=main)
4
+ [![Coverage Status](https://coveralls.io/repos/github/Unleash/unleash-client-ruby/badge.svg?branch=main)](https://coveralls.io/github/Unleash/unleash-client-ruby?branch=main)
5
5
  [![Gem Version](https://badge.fury.io/rb/unleash.svg)](https://badge.fury.io/rb/unleash)
6
6
 
7
7
  Unleash client so you can roll out your features with confidence.
@@ -10,18 +10,20 @@ 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
 
21
23
  Add this line to your application's Gemfile:
22
24
 
23
25
  ```ruby
24
- gem 'unleash', '~> 3.2.2'
26
+ gem 'unleash', '~> 4.0.0'
25
27
  ```
26
28
 
27
29
  And then execute:
@@ -34,22 +36,45 @@ Or install it yourself as:
34
36
 
35
37
  ## Configure
36
38
 
37
- It is **required** to configure the `url` of the unleash server and `app_name` with the name of the runninng application. Please substitute the sample `'http://unleash.herokuapp.com/api'` for the url of your own instance.
39
+ It is **required** to configure:
40
+ - `url` of the unleash server
41
+ - `app_name` with the name of the runninng application.
42
+ - `custom_http_headers` with `{'Authorization': '<API token>'}` when using Unleash v4.0.0 and later.
38
43
 
39
- It is **highly recommended** to configure the `instance_id` parameter as well.
44
+ Please substitute the example `'https://unleash.herokuapp.com/api'` for the url of your own instance.
45
+
46
+ It is **highly recommended** to configure:
47
+ - `instance_id` parameter with a unique identifier for the running instance.
40
48
 
41
49
 
42
50
  ```ruby
43
51
  Unleash.configure do |config|
44
- config.url = 'http://unleash.herokuapp.com/api'
45
- config.app_name = 'my_ruby_app'
52
+ config.app_name = 'my_ruby_app'
53
+ config.url = 'https://unleash.herokuapp.com/api'
54
+ config.custom_http_headers = {'Authorization': '<API token>'}
46
55
  end
47
56
  ```
48
57
 
49
58
  or instantiate the client with the valid configuration:
50
59
 
51
60
  ```ruby
52
- UNLEASH = Unleash::Client.new(url: 'http://unleash.herokuapp.com/api', app_name: 'my_ruby_app')
61
+ UNLEASH = Unleash::Client.new(url: 'https://unleash.herokuapp.com/api', app_name: 'my_ruby_app', custom_http_headers: {'Authorization': '<API token>'})
62
+ ```
63
+
64
+ ## Dynamic custom HTTP headers
65
+ If you need custom HTTP headers that change during the lifetime of the client, the `custom_http_headers` can be given as a `Proc`.
66
+
67
+ ```ruby
68
+ Unleash.configure do |config|
69
+ config.app_name = 'my_ruby_app'
70
+ config.url = 'https://unleash.herokuapp.com/api'
71
+ config.custom_http_headers = proc do
72
+ {
73
+ 'Authorization': '<API token>',
74
+ 'X-Client-Request-Time': Time.now.iso8601
75
+ }
76
+ end
77
+ end
53
78
  ```
54
79
 
55
80
  #### List of Arguments
@@ -60,19 +85,25 @@ Argument | Description | Required? | Type | Default Value|
60
85
  `app_name` | Name of your program. | Y | String | N/A |
61
86
  `instance_id` | Identifier for the running instance of program. Important so you can trace back to where metrics are being collected from. **Highly recommended be be set.** | N | String | random UUID |
62
87
  `environment` | Environment the program is running on. Could be for example `prod` or `dev`. Not yet in use. | N | String | `default` |
88
+ `project_name` | Name of the project to retrieve features from. If not set, all feature flags will be retrieved. | N | String | nil |
63
89
  `refresh_interval` | How often the unleash client should check with the server for configuration changes. | N | Integer | 15 |
64
- `metrics_interval` | How often the unleash client should send metrics to server. | N | Integer | 10 |
90
+ `metrics_interval` | How often the unleash client should send metrics to server. | N | Integer | 60 |
65
91
  `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` |
66
92
  `disable_metrics` | Disables sending metrics to Unleash server. | N | Boolean | `false` |
67
- `custom_http_headers` | Custom headers to send to Unleash. | N | Hash | {} |
93
+ `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/Proc | {} |
68
94
  `timeout` | How long to wait for the connection to be established or wait in reading state (open_timeout/read_timeout) | N | Integer | 30 |
69
- `retry_limit` | How many consecutive failures in connecting to the Unleash server are allowed before giving up. Use `Float::INFINITY` if you would like it to never give up. | N | Numeric | 5 |
95
+ `retry_limit` | How many consecutive failures in connecting to the Unleash server are allowed before giving up. The default is to retry indefinitely. | N | Float::INFINITY | 5 |
70
96
  `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` |
71
97
  `logger` | Specify a custom `Logger` class to handle logs for the Unleash client. | N | Class | `Logger.new(STDOUT)` |
72
- `log_level` | Change the log level for the `Logger` class. Constant from `Logger::Severity`. | N | Constant | `Logger::ERROR` |
98
+ `log_level` | Change the log level for the `Logger` class. Constant from `Logger::Severity`. | N | Constant | `Logger::WARN` |
99
+ `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` |
73
100
 
74
- For in a more in depth look, please see `lib/unleash/configuration.rb`.
101
+ For a more in-depth look, please see `lib/unleash/configuration.rb`.
75
102
 
103
+ Environment Variable | Description
104
+ ---------|---------
105
+ `UNLEASH_BOOTSTRAP_FILE` | File to read bootstrap data from
106
+ `UNLEASH_BOOTSTRAP_URL` | URL to read bootstrap data from
76
107
 
77
108
  ## Usage in a plain Ruby Application
78
109
 
@@ -80,7 +111,7 @@ For in a more in depth look, please see `lib/unleash/configuration.rb`.
80
111
  require 'unleash'
81
112
  require 'unleash/context'
82
113
 
83
- @unleash = Unleash::Client.new(url: 'http://unleash.herokuapp.com/api', app_name: 'my_ruby_app')
114
+ @unleash = Unleash::Client.new(app_name: 'my_ruby_app', url: 'https://unleash.herokuapp.com/api', custom_http_headers: { 'Authorization': '<API token>' })
84
115
 
85
116
  feature_name = "AwesomeFeature"
86
117
  unleash_context = Unleash::Context.new
@@ -101,37 +132,145 @@ Put in `config/initializers/unleash.rb`:
101
132
 
102
133
  ```ruby
103
134
  Unleash.configure do |config|
104
- config.url = 'http://unleash.herokuapp.com/api'
105
135
  config.app_name = Rails.application.class.parent.to_s
136
+ config.url = 'https://unleash.herokuapp.com/api'
106
137
  # config.instance_id = "#{Socket.gethostname}"
107
138
  config.logger = Rails.logger
108
139
  config.environment = Rails.env
109
140
  end
110
141
 
111
142
  UNLEASH = Unleash::Client.new
143
+
144
+ # Or if preferred:
145
+ # Rails.configuration.unleash = Unleash::Client.new
146
+ ```
147
+ For `config.instance_id` use a string with a unique identification for the running instance.
148
+ For example: it could be the hostname, if you only run one App per host.
149
+ Or the docker container id, if you are running in docker.
150
+ If it is not set the client will generate an unique UUID for each execution.
151
+
152
+ To have it available in the `rails console` command as well, also add to the file above:
153
+ ```ruby
154
+ Rails.application.console do
155
+ UNLEASH = Unleash::Client.new
156
+ # or
157
+ # Rails.configuration.unleash = Unleash::Client.new
158
+ end
159
+ ```
160
+
161
+ #### Add Initializer if using [Puma in clustered mode](https://github.com/puma/puma#clustered-mode)
162
+
163
+ That is, multiple workers configured in `puma.rb`:
164
+ ```ruby
165
+ workers ENV.fetch("WEB_CONCURRENCY") { 2 }
166
+ ```
167
+
168
+ ##### with `preload_app!`
169
+
170
+ Then you may keep the client configuration still in `config/initializers/unleash.rb`:
171
+ ```ruby
172
+ Unleash.configure do |config|
173
+ config.app_name = Rails.application.class.parent.to_s
174
+ config.environment = Rails.env
175
+ config.url = 'https://unleash.herokuapp.com/api'
176
+ config.custom_http_headers = {'Authorization': '<API token>'}
177
+ end
178
+ ```
179
+
180
+ But you must ensure that the unleash client is instantiated only after the process is forked.
181
+ This is done by creating the client inside the `on_worker_boot` code block in `puma.rb` as below:
182
+
183
+ ```ruby
184
+ #...
185
+ preload_app!
186
+ #...
187
+
188
+ on_worker_boot do
189
+ # ...
190
+
191
+ ::UNLEASH = Unleash::Client.new
192
+ end
193
+
194
+ on_worker_shutdown do
195
+ ::UNLEASH.shutdown
196
+ end
112
197
  ```
113
- For `config.instance_id` use a string with a unique identification for the running instance. For example: it could be the hostname, if you only run one App per host. Or the docker container id, if you are running in docker. If it is not set the client will generate an unique UUID for each execution.
114
198
 
199
+ ##### without `preload_app!`
115
200
 
116
- #### Add Initializer if using [Puma](https://github.com/puma/puma)
201
+ By not using `preload_app!`:
202
+ - the `Rails` constant will NOT be available.
203
+ - but phased restarts will be possible.
117
204
 
118
- In `puma.rb` ensure that the unleash client is configured and instantiated as below, inside the `on_worker_boot` code block:
205
+ You need to ensure that in `puma.rb`:
206
+ - loading unleash sdk with `require 'unleash'` explicitly, as it will not be pre-loaded.
207
+ - all parameters must be explicitly set in the `on_worker_boot` block, as `config/initializers/unleash.rb` is not read.
208
+ - there are no references to `Rails` constant, as that is not yet available.
119
209
 
210
+ Example for `puma.rb`:
120
211
  ```ruby
212
+ require 'unleash'
213
+
214
+ #...
215
+ # no preload_app!
216
+
121
217
  on_worker_boot do
122
218
  # ...
123
219
 
124
- Unleash.configure do |config|
125
- config.url = 'http://unleash.herokuapp.com/api'
126
- config.app_name = Rails.application.class.parent.to_s
127
- config.environment = Rails.env
220
+ ::UNLEASH = Unleash::Client.new(
221
+ app_name: 'my_rails_app',
222
+ environment: 'development',
223
+ url: 'https://unleash.herokuapp.com/api',
224
+ custom_http_headers: {'Authorization': '<API token>'},
225
+ )
226
+ end
227
+
228
+ on_worker_shutdown do
229
+ ::UNLEASH.shutdown
230
+ end
231
+ ```
232
+
233
+ Note that we also added shutdown hooks in `on_worker_shutdown`, to ensure a clean shutdown.
234
+
235
+ #### Add Initializer if using [Phusion Passenger](https://github.com/phusion/passenger)
236
+
237
+ The unleash client needs to be configured and instantiated inside the `PhusionPassenger.on_event(:starting_worker_process)` code block due to [smart spawning](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/#smart-spawning-caveats):
238
+
239
+ The initializer in `config/initializers/unleash.rb` should look like:
240
+
241
+ ```ruby
242
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
243
+ if forked
244
+ Unleash.configure do |config|
245
+ config.app_name = Rails.application.class.parent.to_s
246
+ # config.instance_id = "#{Socket.gethostname}"
247
+ config.logger = Rails.logger
248
+ config.environment = Rails.env
249
+ config.url = 'https://unleash.herokuapp.com/api'
250
+ config.custom_http_headers = {'Authorization': '<API token>'}
251
+ end
252
+
253
+ UNLEASH = Unleash::Client.new
128
254
  end
129
- Rails.configuration.unleash = Unleash::Client.new
130
255
  end
131
256
  ```
132
257
 
133
- Instead of the configuration in `config/initializers/unleash.rb`.
258
+ #### Add Initializer hooks when using within [Sidekiq](https://github.com/mperham/sidekiq)
259
+
260
+ Note that in this case we require that the code block for `Unleash.configure` is set beforehand.
261
+ For example in `config/initializers/unleash.rb`.
134
262
 
263
+ ```ruby
264
+ Sidekiq.configure_server do |config|
265
+ config.on(:startup) do
266
+ UNLEASH = Unleash::Client.new
267
+ end
268
+
269
+ config.on(:shutdown) do
270
+ UNLEASH.shutdown
271
+ end
272
+ end
273
+ ```
135
274
 
136
275
  #### Set Unleash::Context
137
276
 
@@ -172,7 +311,8 @@ if Rails.configuration.unleash.is_enabled? "AwesomeFeature", @unleash_context
172
311
  end
173
312
  ```
174
313
 
175
- If the feature is not found in the server, it will by default return false. However you can override that by setting the default return value to `true`:
314
+ If the feature is not found in the server, it will by default return false.
315
+ However you can override that by setting the default return value to `true`:
176
316
 
177
317
  ```ruby
178
318
  if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context, true
@@ -180,6 +320,40 @@ if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context, true
180
320
  end
181
321
  ```
182
322
 
323
+ Another possibility is to send a block, [Lambda](https://ruby-doc.org/core-3.0.1/Kernel.html#method-i-lambda) or [Proc](https://ruby-doc.org/core-3.0.1/Proc.html#method-i-yield)
324
+ to evaluate the default value:
325
+
326
+ ```ruby
327
+ net_check_proc = proc do |feature_name, context|
328
+ context.remote_address.starts_with?("10.0.0.")
329
+ end
330
+
331
+ if UNLEASH.is_enabled?("AwesomeFeature", @unleash_context, &net_check_proc)
332
+ puts "AwesomeFeature is enabled by default if you are in the 10.0.0.* network."
333
+ end
334
+ ```
335
+
336
+ or
337
+
338
+ ```ruby
339
+ awesomeness = 10
340
+ @unleash_context.properties[:coolness] = 10
341
+
342
+ if UNLEASH.is_enabled?("AwesomeFeature", @unleash_context) { |feat, ctx| awesomeness >= 6 && ctx.properties[:coolness] >= 8 }
343
+ puts "AwesomeFeature is enabled by default if both the user has a high enought coolness and the application has a high enough awesomeness"
344
+ end
345
+ ```
346
+
347
+ Note:
348
+ - The block/lambda/proc can use feature name and context as an arguments.
349
+ - The client will evaluate the fallback function once per call of `is_enabled()`.
350
+ Please keep this in mind when creating your fallback function!
351
+ - The returned value of the block should be a boolean.
352
+ However the client will coerce the result to boolean via `!!`.
353
+ - If both a `default_value` and `fallback_function` are supplied,
354
+ the client will define the default value by `OR`ing the default value and the output of the fallback function.
355
+
356
+
183
357
  Alternatively by using `if_enabled` you can send a code block to be executed as a parameter:
184
358
 
185
359
  ```ruby
@@ -188,6 +362,8 @@ UNLEASH.if_enabled "AwesomeFeature", @unleash_context, true do
188
362
  end
189
363
  ```
190
364
 
365
+ Note: `if_enabled` only supports `default_value`, but not `fallback_function`.
366
+
191
367
  ##### Variations
192
368
 
193
369
  If no variant is found in the server, use the fallback variant.
@@ -199,6 +375,65 @@ variant = UNLEASH.get_variant "ColorVariants", @unleash_context, fallback_varian
199
375
  puts "variant color is: #{variant.payload.fetch('color')}"
200
376
  ```
201
377
 
378
+ ## Bootstrapping
379
+
380
+ Bootstrap configuration allows the client to be initialized with a predefined set of toggle states.
381
+ Bootstrapping can be configured by providing a bootstrap configuration when initializing the client.
382
+ ```ruby
383
+ @unleash = Unleash::Client.new(
384
+ url: 'https://unleash.herokuapp.com/api',
385
+ app_name: 'my_ruby_app',
386
+ custom_http_headers: { 'Authorization': '<API token>' },
387
+ bootstrap_config: Unleash::Bootstrap::Configuration.new({
388
+ url: "https://unleash.herokuapp.com/api/client/features",
389
+ url_headers: {'Authorization': '<API token>'}
390
+ })
391
+ )
392
+ ```
393
+ The `Bootstrap::Configuration` initializer takes a hash with one of the following options specified:
394
+
395
+ * `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.
396
+ * `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.
397
+ * `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.
398
+ * `data` - A raw JSON string as returned by the Unleash server.
399
+ * `block` - A lambda containing custom logic if you need it, an example is provided below.
400
+
401
+ You should only specify one type of bootstrapping since only one will be invoked and the others will be ignored.
402
+ The order of preference is as follows:
403
+
404
+ - Select a data bootstrapper if it exists.
405
+ - If no data bootstrapper exists, select the block bootstrapper.
406
+ - If no block bootstrapper exists, select the file bootstrapper from either parameters or the specified environment variable.
407
+ - If no file bootstrapper exists, then check for a URL bootstrapper from either the parameters or the specified environment variable.
408
+
409
+
410
+ Example usage:
411
+
412
+ First saving the toggles locally:
413
+ ```shell
414
+ curl -H 'Authorization: <API token>' -XGET 'https://unleash.herokuapp.com/api' > ./default-toggles.json
415
+ ```
416
+
417
+ Now using them on start up:
418
+
419
+ ```ruby
420
+
421
+ custom_boostrapper = lambda {
422
+ File.read('./default-toggles.json')
423
+ }
424
+
425
+ @unleash = Unleash::Client.new(
426
+ app_name: 'my_ruby_app',
427
+ url: 'https://unleash.herokuapp.com/api',
428
+ custom_http_headers: { 'Authorization': '<API token>' },
429
+ bootstrap_config: Unleash::Bootstrap::Configuration.new({
430
+ block: custom_boostrapper
431
+ })
432
+ )
433
+ ```
434
+
435
+ This example could be easily achieved with a file bootstrapper, this is just to illustrate the usage of custom bootstrapping.
436
+ Be aware that the client initializer will block until bootstrapping is complete.
202
437
 
203
438
  #### Client methods
204
439
 
@@ -211,6 +446,7 @@ Method Name | Description | Return Type |
211
446
  `shutdown` | Save metrics to disk, flush metrics to server, and then kill ToggleFetcher and MetricsReporter threads. A safe shutdown. Not really useful in long running applications, like web applications. | nil |
212
447
  `shutdown!` | Kill ToggleFetcher and MetricsReporter threads immediately. | nil |
213
448
 
449
+ For the full method signatures, please see [client.rb](lib/unleash/client.rb)
214
450
 
215
451
  ## Local test client
216
452
 
@@ -228,6 +464,7 @@ This client comes with the all the required strategies out of the box:
228
464
 
229
465
  * ApplicationHostnameStrategy
230
466
  * DefaultStrategy
467
+ * FlexibleRolloutStrategy
231
468
  * GradualRolloutRandomStrategy
232
469
  * GradualRolloutSessionIdStrategy
233
470
  * GradualRolloutUserIdStrategy
@@ -235,7 +472,6 @@ This client comes with the all the required strategies out of the box:
235
472
  * UnknownStrategy
236
473
  * UserWithIdStrategy
237
474
 
238
-
239
475
  ## Development
240
476
 
241
477
  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.
data/bin/unleash-client CHANGED
@@ -12,11 +12,13 @@ options = {
12
12
  url: 'http://localhost:4242',
13
13
  demo: false,
14
14
  disable_metrics: true,
15
+ custom_http_headers: {},
15
16
  sleep: 0.1
16
17
  }
17
18
 
18
19
  OptionParser.new do |opts|
19
- opts.banner = "Usage: #{__FILE__} [options] feature [key1=val1] [key2=val2]"
20
+ opts.banner = "Usage: #{__FILE__} [options] feature [contextKey1=val1] [contextKey2=val2] \n\n" \
21
+ "Where contextKey1 could be user_id, session_id, remote_address or any field in the Context class (or any property within it).\n"
20
22
 
21
23
  opts.on("-V", "--variant", "Fetch variant for feature") do |v|
22
24
  options[:variant] = v
@@ -46,6 +48,13 @@ OptionParser.new do |opts|
46
48
  options[:sleep] = s
47
49
  end
48
50
 
51
+ opts.on("-H", "--http-headers='Authorization: *:developement.secretstring'",
52
+ "Adds http headers to all requests on the unleash server. Use multiple times for multiple headers.") do |h|
53
+ http_header_as_hash = [h].to_h{ |l| l.split(": ") }.transform_keys(&:to_sym)
54
+
55
+ options[:custom_http_headers].merge!(http_header_as_hash)
56
+ end
57
+
49
58
  opts.on("-h", "--help", "Prints this help") do
50
59
  puts opts
51
60
  exit
@@ -70,10 +79,11 @@ log_level = \
70
79
  url: options[:url],
71
80
  app_name: 'unleash-client-ruby-cli',
72
81
  disable_metrics: options[:metrics],
82
+ custom_http_headers: options[:custom_http_headers],
73
83
  log_level: log_level
74
84
  )
75
85
 
76
- context_params = ARGV.map{ |e| e.split("=") }.map{ |k, v| [k.to_sym, v] }.to_h
86
+ context_params = ARGV.to_h{ |l| l.split("=") }.transform_keys(&:to_sym)
77
87
  context_properties = context_params.reject{ |k, _v| [:user_id, :session_id, :remote_address].include? k }
78
88
  context_params.select!{ |k, _v| [:user_id, :session_id, :remote_address].include? k }
79
89
  context_params.merge!(properties: context_properties) unless context_properties.nil?
@@ -97,12 +107,12 @@ if options[:demo]
97
107
  end
98
108
  elsif options[:variant]
99
109
  variant = @unleash.get_variant(feature_name, unleash_context)
100
- puts " For feature \'#{feature_name}\' got variant \'#{variant}\'"
110
+ puts " For feature '#{feature_name}' got variant '#{variant}'"
101
111
  else
102
112
  if @unleash.is_enabled?(feature_name, unleash_context)
103
- puts " \'#{feature_name}\' is enabled according to unleash"
113
+ puts " '#{feature_name}' is enabled according to unleash"
104
114
  else
105
- puts " \'#{feature_name}\' is disabled according to unleash"
115
+ puts " '#{feature_name}' is disabled according to unleash"
106
116
  end
107
117
  end
108
118
 
@@ -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: 'https://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
+
data/examples/simple.rb CHANGED
@@ -6,7 +6,8 @@ require 'unleash/context'
6
6
  puts ">> START simple.rb"
7
7
 
8
8
  # Unleash.configure do |config|
9
- # config.url = 'http://unleash.herokuapp.com/api'
9
+ # config.url = 'https://unleash.herokuapp.com/api'
10
+ # config.custom_http_headers = { 'Authorization': '943ca9171e2c884c545c5d82417a655fb77cec970cc3b78a8ff87f4406b495d0' }
10
11
  # config.app_name = 'simple-test'
11
12
  # config.refresh_interval = 2
12
13
  # config.metrics_interval = 2
@@ -17,13 +18,13 @@ puts ">> START simple.rb"
17
18
  # or:
18
19
 
19
20
  @unleash = Unleash::Client.new(
20
- url: 'http://unleash.herokuapp.com/api',
21
+ url: 'https://unleash.herokuapp.com/api',
22
+ custom_http_headers: { 'Authorization': '943ca9171e2c884c545c5d82417a655fb77cec970cc3b78a8ff87f4406b495d0' },
21
23
  app_name: 'simple-test',
22
24
  instance_id: 'local-test-cli',
23
25
  refresh_interval: 2,
24
26
  metrics_interval: 2,
25
- retry_limit: 2,
26
- log_level: Logger::DEBUG
27
+ retry_limit: 2
27
28
  )
28
29
 
29
30
  # feature_name = "AwesomeFeature"
@@ -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,14 @@
1
+ module Unleash
2
+ module Bootstrap
3
+ module Provider
4
+ class NotImplemented < RuntimeError
5
+ end
6
+
7
+ class Base
8
+ def read
9
+ raise NotImplemented, "Bootstrap is not implemented"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end