unleash 4.3.0 → 4.4.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 +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +4 -1
- data/README.md +22 -0
- data/lib/unleash/activation_strategy.rb +3 -1
- data/lib/unleash/client.rb +3 -3
- data/lib/unleash/configuration.rb +6 -3
- data/lib/unleash/constraint.rb +21 -11
- data/lib/unleash/context.rb +7 -0
- data/lib/unleash/feature_toggle.rb +25 -14
- data/lib/unleash/strategies.rb +80 -0
- data/lib/unleash/strategy/flexible_rollout.rb +1 -1
- data/lib/unleash/strategy/gradual_rollout_sessionid.rb +1 -1
- data/lib/unleash/strategy/gradual_rollout_userid.rb +1 -1
- data/lib/unleash/strategy/remote_address.rb +1 -1
- data/lib/unleash/strategy/user_with_id.rb +1 -1
- data/lib/unleash/toggle_fetcher.rb +15 -4
- data/lib/unleash/variant_definition.rb +1 -1
- data/lib/unleash/variant_override.rb +1 -1
- data/lib/unleash/version.rb +1 -1
- data/lib/unleash.rb +11 -15
- data/unleash-client.gemspec +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5788566712ad3909e6707041673d359a4d9835b59c79bf0e0fac80c7c8a164ed
|
|
4
|
+
data.tar.gz: 707f6875e73a10ef24fdce900aa7c179b0762eedda07ce83a41ea5ed76ba494f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d836f1c130e5db08fdac81f75cb938e93c060f997946ec80bd5819ad098a9b6d7a6d5628f88cd70144899bbe72ac1f5a1e802b4973b6ee0d24eda85e783fab3
|
|
7
|
+
data.tar.gz: 69280e8af54ba2613ef3ae17206f0dfbd31a958d2595162dab71dd0d4cf5815d4dc3e023160c1edc1dbcaeea77d2cfd5f2e468af28051fe41b51dd77fae0f689
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
- name: Install dependencies
|
|
47
47
|
run: bundle install
|
|
48
48
|
- name: Download test cases
|
|
49
|
-
run: git clone --depth 5 --branch v4.
|
|
49
|
+
run: git clone --depth 5 --branch v4.2.2 https://github.com/Unleash/client-specification.git client-specification
|
|
50
50
|
- name: Run tests
|
|
51
51
|
run: bundle exec rake
|
|
52
52
|
env:
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -9,7 +9,7 @@ Naming/PredicateName:
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
Metrics/ClassLength:
|
|
12
|
-
Max:
|
|
12
|
+
Max: 130
|
|
13
13
|
Layout/LineLength:
|
|
14
14
|
Max: 140
|
|
15
15
|
Metrics/MethodLength:
|
|
@@ -34,6 +34,9 @@ Style/StringLiterals:
|
|
|
34
34
|
Style/RedundantSelf:
|
|
35
35
|
Enabled: false
|
|
36
36
|
|
|
37
|
+
Style/OptionalBooleanParameter:
|
|
38
|
+
Enabled: false
|
|
39
|
+
|
|
37
40
|
Style/SymbolArray:
|
|
38
41
|
EnforcedStyle: brackets
|
|
39
42
|
Style/WordArray:
|
data/README.md
CHANGED
|
@@ -97,6 +97,7 @@ Argument | Description | Required? | Type | Default Value|
|
|
|
97
97
|
`logger` | Specify a custom `Logger` class to handle logs for the Unleash client. | N | Class | `Logger.new(STDOUT)` |
|
|
98
98
|
`log_level` | Change the log level for the `Logger` class. Constant from `Logger::Severity`. | N | Constant | `Logger::WARN` |
|
|
99
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` |
|
|
100
|
+
`strategies` | Strategies manager that holds all strategies and allows to add custom strategies | N | Unleash::Strategies | `Unleash::Strategies.new` |
|
|
100
101
|
|
|
101
102
|
For a more in-depth look, please see `lib/unleash/configuration.rb`.
|
|
102
103
|
|
|
@@ -472,6 +473,27 @@ This client comes with the all the required strategies out of the box:
|
|
|
472
473
|
* UnknownStrategy
|
|
473
474
|
* UserWithIdStrategy
|
|
474
475
|
|
|
476
|
+
## Custom Strategies
|
|
477
|
+
|
|
478
|
+
Client allows to add [custom activation strategies](https://docs.getunleash.io/advanced/custom_activation_strategy) using configuration.
|
|
479
|
+
In order for strategy to work correctly it should support two methods `name` and `is_enabled?`
|
|
480
|
+
|
|
481
|
+
```ruby
|
|
482
|
+
class MyCustomStrategy
|
|
483
|
+
def name
|
|
484
|
+
'muCustomStrategy'
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def is_enabled?(params = {}, context = nil)
|
|
488
|
+
true
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
Unleash.configure do |config|
|
|
493
|
+
config.strategies.add(MyCustomStrategy.new)
|
|
494
|
+
end
|
|
495
|
+
```
|
|
496
|
+
|
|
475
497
|
## Development
|
|
476
498
|
|
|
477
499
|
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.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
module Unleash
|
|
2
2
|
class ActivationStrategy
|
|
3
|
-
attr_accessor :name, :params, :constraints
|
|
3
|
+
attr_accessor :name, :params, :constraints, :disabled
|
|
4
4
|
|
|
5
5
|
def initialize(name, params, constraints = [])
|
|
6
6
|
self.name = name
|
|
7
|
+
self.disabled = false
|
|
7
8
|
|
|
8
9
|
if params.is_a?(Hash)
|
|
9
10
|
self.params = params
|
|
@@ -18,6 +19,7 @@ module Unleash
|
|
|
18
19
|
self.constraints = constraints
|
|
19
20
|
else
|
|
20
21
|
Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (contraints: #{constraints})"
|
|
22
|
+
self.disabled = true
|
|
21
23
|
self.constraints = []
|
|
22
24
|
end
|
|
23
25
|
end
|
data/lib/unleash/client.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Unleash
|
|
|
12
12
|
attr_accessor :fetcher_scheduled_executor, :metrics_scheduled_executor
|
|
13
13
|
|
|
14
14
|
def initialize(*opts)
|
|
15
|
-
Unleash.configuration
|
|
15
|
+
Unleash.configuration = Unleash::Configuration.new(*opts) unless opts.empty?
|
|
16
16
|
Unleash.configuration.validate!
|
|
17
17
|
|
|
18
18
|
Unleash.logger = Unleash.configuration.logger.clone
|
|
@@ -45,7 +45,7 @@ module Unleash
|
|
|
45
45
|
return default_value
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
toggle = Unleash::FeatureToggle.new(toggle_as_hash)
|
|
48
|
+
toggle = Unleash::FeatureToggle.new(toggle_as_hash, Unleash&.segment_cache)
|
|
49
49
|
|
|
50
50
|
toggle.is_enabled?(context)
|
|
51
51
|
end
|
|
@@ -110,7 +110,7 @@ module Unleash
|
|
|
110
110
|
'appName': Unleash.configuration.app_name,
|
|
111
111
|
'instanceId': Unleash.configuration.instance_id,
|
|
112
112
|
'sdkVersion': "unleash-client-ruby:" + Unleash::VERSION,
|
|
113
|
-
'strategies': Unleash
|
|
113
|
+
'strategies': Unleash.strategies.keys,
|
|
114
114
|
'started': Time.now.iso8601(Unleash::TIME_RESOLUTION),
|
|
115
115
|
'interval': Unleash.configuration.metrics_interval_in_millis
|
|
116
116
|
}
|
|
@@ -20,7 +20,8 @@ module Unleash
|
|
|
20
20
|
:backup_file,
|
|
21
21
|
:logger,
|
|
22
22
|
:log_level,
|
|
23
|
-
:bootstrap_config
|
|
23
|
+
:bootstrap_config,
|
|
24
|
+
:strategies
|
|
24
25
|
|
|
25
26
|
def initialize(opts = {})
|
|
26
27
|
validate_custom_http_headers!(opts[:custom_http_headers]) if opts.has_key?(:custom_http_headers)
|
|
@@ -51,7 +52,8 @@ module Unleash
|
|
|
51
52
|
def http_headers
|
|
52
53
|
{
|
|
53
54
|
'UNLEASH-INSTANCEID' => self.instance_id,
|
|
54
|
-
'UNLEASH-APPNAME' => self.app_name
|
|
55
|
+
'UNLEASH-APPNAME' => self.app_name,
|
|
56
|
+
'Unleash-Client-Spec' => '4.2.2'
|
|
55
57
|
}.merge!(generate_custom_http_headers)
|
|
56
58
|
end
|
|
57
59
|
|
|
@@ -94,12 +96,13 @@ module Unleash
|
|
|
94
96
|
self.backup_file = nil
|
|
95
97
|
self.log_level = Logger::WARN
|
|
96
98
|
self.bootstrap_config = nil
|
|
99
|
+
self.strategies = Unleash::Strategies.new
|
|
97
100
|
|
|
98
101
|
self.custom_http_headers = {}
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
def initialize_default_logger
|
|
102
|
-
self.logger = Logger.new(
|
|
105
|
+
self.logger = Logger.new($stdout)
|
|
103
106
|
|
|
104
107
|
# on default logger, use custom formatter that includes thread_name:
|
|
105
108
|
self.logger.formatter = proc do |severity, datetime, _progname, msg|
|
data/lib/unleash/constraint.rb
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
require 'date'
|
|
2
|
-
|
|
3
2
|
module Unleash
|
|
4
3
|
class Constraint
|
|
5
4
|
attr_accessor :context_name, :operator, :value, :inverted, :case_insensitive
|
|
6
5
|
|
|
7
6
|
OPERATORS = {
|
|
8
|
-
IN: ->(context_v, constraint_v){ constraint_v.include? context_v },
|
|
9
|
-
NOT_IN: ->(context_v, constraint_v){ !constraint_v.include? context_v },
|
|
7
|
+
IN: ->(context_v, constraint_v){ constraint_v.include? context_v.to_s },
|
|
8
|
+
NOT_IN: ->(context_v, constraint_v){ !constraint_v.include? context_v.to_s },
|
|
10
9
|
STR_STARTS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.start_with? v } },
|
|
11
10
|
STR_ENDS_WITH: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.end_with? v } },
|
|
12
11
|
STR_CONTAINS: ->(context_v, constraint_v){ constraint_v.any?{ |v| context_v.include? v } },
|
|
@@ -19,16 +18,21 @@ module Unleash
|
|
|
19
18
|
DATE_BEFORE: ->(context_v, constraint_v){ on_valid_date(constraint_v, context_v){ |x, y| (x > y) } },
|
|
20
19
|
SEMVER_EQ: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x == y) } },
|
|
21
20
|
SEMVER_GT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x < y) } },
|
|
22
|
-
SEMVER_LT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x > y) } }
|
|
21
|
+
SEMVER_LT: ->(context_v, constraint_v){ on_valid_version(constraint_v, context_v){ |x, y| (x > y) } },
|
|
22
|
+
FALLBACK_VALIDATOR: ->(_context_v, _constraint_v){ false }
|
|
23
23
|
}.freeze
|
|
24
24
|
|
|
25
25
|
LIST_OPERATORS = [:IN, :NOT_IN, :STR_STARTS_WITH, :STR_ENDS_WITH, :STR_CONTAINS].freeze
|
|
26
26
|
|
|
27
27
|
def initialize(context_name, operator, value = [], inverted: false, case_insensitive: false)
|
|
28
28
|
raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String)
|
|
29
|
-
raise ArgumentError, "operator does not hold a valid value:" + OPERATORS.keys unless OPERATORS.include? operator.to_sym
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
unless OPERATORS.include? operator.to_sym
|
|
31
|
+
Unleash.logger.warn "Operator #{operator} is not a supported operator, " \
|
|
32
|
+
"falling back to FALLBACK_VALIDATOR which skips this constraint."
|
|
33
|
+
operator = "FALLBACK_VALIDATOR"
|
|
34
|
+
end
|
|
35
|
+
self.log_inconsistent_constraint_configuration(operator.to_sym, value)
|
|
32
36
|
|
|
33
37
|
self.context_name = context_name
|
|
34
38
|
self.operator = operator.to_sym
|
|
@@ -38,8 +42,8 @@ module Unleash
|
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def matches_context?(context)
|
|
41
|
-
Unleash.logger.debug "Unleash::Constraint matches_context? value: #{self.value} context.get_by_name(#{self.context_name})"
|
|
42
|
-
|
|
45
|
+
Unleash.logger.debug "Unleash::Constraint matches_context? value: #{self.value} context.get_by_name(#{self.context_name})"
|
|
46
|
+
|
|
43
47
|
match = matches_constraint?(context)
|
|
44
48
|
self.inverted ? !match : match
|
|
45
49
|
rescue KeyError
|
|
@@ -79,19 +83,25 @@ module Unleash
|
|
|
79
83
|
end
|
|
80
84
|
|
|
81
85
|
# This should be a private method but for some reason this fails on Ruby 2.5
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
def log_inconsistent_constraint_configuration(operator, value)
|
|
87
|
+
Unleash.logger.warn "value is a String, operator is expecting an Array" if LIST_OPERATORS.include?(operator) && value.is_a?(String)
|
|
88
|
+
Unleash.logger.warn "value is an Array, operator is expecting a String" if !LIST_OPERATORS.include?(operator) && value.is_a?(Array)
|
|
85
89
|
end
|
|
86
90
|
|
|
87
91
|
private
|
|
88
92
|
|
|
89
93
|
def matches_constraint?(context)
|
|
94
|
+
Unleash.logger.debug "Unleash::Constraint matches_constraint? value: #{self.value} operator: #{self.operator} " \
|
|
95
|
+
" context.get_by_name(#{self.context_name})"
|
|
96
|
+
|
|
90
97
|
unless OPERATORS.include?(self.operator)
|
|
91
98
|
Unleash.logger.warn "Invalid constraint operator: #{self.operator}, this should be unreachable. Always returning false."
|
|
92
99
|
false
|
|
93
100
|
end
|
|
94
101
|
|
|
102
|
+
# when the operator is NOT_IN and there is no data, return true. In all other cases the operator doesn't match.
|
|
103
|
+
return self.operator == :NOT_IN unless context.include?(self.context_name)
|
|
104
|
+
|
|
95
105
|
v = self.value.dup
|
|
96
106
|
context_value = context.get_by_name(self.context_name)
|
|
97
107
|
|
data/lib/unleash/context.rb
CHANGED
|
@@ -33,6 +33,13 @@ module Unleash
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def include?(name)
|
|
37
|
+
normalized_name = underscore(name)
|
|
38
|
+
return self.instance_variable_defined? "@#{normalized_name}" if ATTRS.include? normalized_name.to_sym
|
|
39
|
+
|
|
40
|
+
self.properties.include?(normalized_name.to_sym) || self.properties.include?(name.to_sym)
|
|
41
|
+
end
|
|
42
|
+
|
|
36
43
|
private
|
|
37
44
|
|
|
38
45
|
# Method to fetch values from hash for two types of keys: string in camelCase and symbol in snake_case
|
|
@@ -9,13 +9,13 @@ module Unleash
|
|
|
9
9
|
class FeatureToggle
|
|
10
10
|
attr_accessor :name, :enabled, :strategies, :variant_definitions
|
|
11
11
|
|
|
12
|
-
def initialize(params = {})
|
|
12
|
+
def initialize(params = {}, segment_map = {})
|
|
13
13
|
params = {} if params.nil?
|
|
14
14
|
|
|
15
15
|
self.name = params.fetch('name', nil)
|
|
16
16
|
self.enabled = params.fetch('enabled', false)
|
|
17
17
|
|
|
18
|
-
self.strategies = initialize_strategies(params)
|
|
18
|
+
self.strategies = initialize_strategies(params, segment_map)
|
|
19
19
|
self.variant_definitions = initialize_variant_definitions(params)
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -75,12 +75,14 @@ module Unleash
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def strategy_enabled?(strategy, context)
|
|
78
|
-
r = Unleash
|
|
78
|
+
r = Unleash.strategies.fetch(strategy.name).is_enabled?(strategy.params, context)
|
|
79
79
|
Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
|
|
80
80
|
r
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def strategy_constraint_matches?(strategy, context)
|
|
84
|
+
return false if strategy.disabled
|
|
85
|
+
|
|
84
86
|
strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
|
|
85
87
|
end
|
|
86
88
|
|
|
@@ -128,26 +130,35 @@ module Unleash
|
|
|
128
130
|
context
|
|
129
131
|
end
|
|
130
132
|
|
|
131
|
-
def initialize_strategies(params)
|
|
133
|
+
def initialize_strategies(params, segment_map)
|
|
132
134
|
params.fetch('strategies', [])
|
|
133
|
-
.select{ |s| s.has_key?('name') && Unleash
|
|
135
|
+
.select{ |s| s.has_key?('name') && Unleash.strategies.includes?(s['name']) }
|
|
134
136
|
.map do |s|
|
|
135
137
|
ActivationStrategy.new(
|
|
136
138
|
s['name'],
|
|
137
139
|
s['parameters'],
|
|
138
|
-
(s
|
|
139
|
-
Constraint.new(
|
|
140
|
-
c.fetch('contextName'),
|
|
141
|
-
c.fetch('operator'),
|
|
142
|
-
c.fetch('values', nil) || c.fetch('value', nil),
|
|
143
|
-
inverted: c.fetch('inverted', false),
|
|
144
|
-
case_insensitive: c.fetch('caseInsensitive', false)
|
|
145
|
-
)
|
|
146
|
-
end
|
|
140
|
+
resolve_constraints(s, segment_map)
|
|
147
141
|
)
|
|
148
142
|
end || []
|
|
149
143
|
end
|
|
150
144
|
|
|
145
|
+
def resolve_constraints(strategy, segment_map)
|
|
146
|
+
segment_constraints = (strategy["segments"] || []).map do |segment_id|
|
|
147
|
+
segment_map[segment_id]&.fetch("constraints")
|
|
148
|
+
end
|
|
149
|
+
(strategy.fetch("constraints", []) + segment_constraints).flatten.map do |constraint|
|
|
150
|
+
return nil if constraint.nil?
|
|
151
|
+
|
|
152
|
+
Constraint.new(
|
|
153
|
+
constraint.fetch('contextName'),
|
|
154
|
+
constraint.fetch('operator'),
|
|
155
|
+
constraint.fetch('value', nil) || constraint.fetch('values', nil),
|
|
156
|
+
inverted: constraint.fetch('inverted', false),
|
|
157
|
+
case_insensitive: constraint.fetch('caseInsensitive', false)
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
151
162
|
def initialize_variant_definitions(params)
|
|
152
163
|
(params.fetch('variants', []) || [])
|
|
153
164
|
.select{ |v| v.is_a?(Hash) && v.has_key?('name') }
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'unleash/strategy/base'
|
|
2
|
+
Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path }
|
|
3
|
+
|
|
4
|
+
module Unleash
|
|
5
|
+
class Strategies
|
|
6
|
+
def initialize
|
|
7
|
+
@strategies = {}
|
|
8
|
+
register_strategies
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def keys
|
|
12
|
+
@strategies.keys
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def includes?(name)
|
|
16
|
+
@strategies.has_key?(name.to_s)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fetch(name)
|
|
20
|
+
raise Unleash::Strategy::NotImplemented, "Strategy is not implemented" unless (strategy = @strategies[name.to_s])
|
|
21
|
+
|
|
22
|
+
strategy
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def add(strategy)
|
|
26
|
+
@strategies[strategy.name] = strategy
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def []=(key, strategy)
|
|
30
|
+
warn_deprecated_registration(strategy, 'modifying Unleash::STRATEGIES')
|
|
31
|
+
@strategies[key.to_s] = strategy
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def [](key)
|
|
35
|
+
@strategies[key.to_s]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def register_strategies
|
|
39
|
+
register_base_strategies
|
|
40
|
+
register_custom_strategies
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
# Deprecated: Use Unleash.configuration to add custom strategies
|
|
46
|
+
def register_custom_strategies
|
|
47
|
+
Unleash::Strategy.constants
|
|
48
|
+
.select{ |c| Unleash::Strategy.const_get(c).is_a? Class }
|
|
49
|
+
.reject{ |c| ['NotImplemented', 'Base'].include?(c.to_s) } # Reject abstract classes
|
|
50
|
+
.map{ |c| Object.const_get("Unleash::Strategy::#{c}") }
|
|
51
|
+
.reject{ |c| DEFAULT_STRATEGIES.include?(c) } # Reject base classes
|
|
52
|
+
.each do |c|
|
|
53
|
+
strategy = c.new
|
|
54
|
+
warn_deprecated_registration(strategy, 'adding custom class into Unleash::Strategy namespace')
|
|
55
|
+
self.add(strategy)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def register_base_strategies
|
|
60
|
+
DEFAULT_STRATEGIES.each{ |c| self.add(c.new) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
DEFAULT_STRATEGIES = [
|
|
64
|
+
Unleash::Strategy::ApplicationHostname,
|
|
65
|
+
Unleash::Strategy::Default,
|
|
66
|
+
Unleash::Strategy::FlexibleRollout,
|
|
67
|
+
Unleash::Strategy::GradualRolloutRandom,
|
|
68
|
+
Unleash::Strategy::GradualRolloutSessionId,
|
|
69
|
+
Unleash::Strategy::GradualRolloutUserId,
|
|
70
|
+
Unleash::Strategy::RemoteAddress,
|
|
71
|
+
Unleash::Strategy::UserWithId
|
|
72
|
+
].freeze
|
|
73
|
+
|
|
74
|
+
def warn_deprecated_registration(strategy, method)
|
|
75
|
+
warn "[DEPRECATED] Registering custom Unleash strategy by #{method} is deprecated.
|
|
76
|
+
Please use Unleash configuration to register custom strategy: " \
|
|
77
|
+
"`Unleash.configure {|c| c.strategies.add(#{strategy.class.name}.new) }`"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -10,7 +10,7 @@ module Unleash
|
|
|
10
10
|
# need: params['percentage']
|
|
11
11
|
def is_enabled?(params = {}, context = nil)
|
|
12
12
|
return false unless params.is_a?(Hash)
|
|
13
|
-
return false unless context.
|
|
13
|
+
return false unless context.instance_of?(Unleash::Context)
|
|
14
14
|
|
|
15
15
|
stickiness = params.fetch('stickiness', 'default')
|
|
16
16
|
stickiness_id = resolve_stickiness(stickiness, context)
|
|
@@ -10,7 +10,7 @@ module Unleash
|
|
|
10
10
|
# need: params['percentage'], params['groupId'], context.user_id,
|
|
11
11
|
def is_enabled?(params = {}, context = nil)
|
|
12
12
|
return false unless params.is_a?(Hash) && params.has_key?('percentage')
|
|
13
|
-
return false unless context.
|
|
13
|
+
return false unless context.instance_of?(Unleash::Context)
|
|
14
14
|
return false if context.session_id.nil? || context.session_id.empty?
|
|
15
15
|
|
|
16
16
|
percentage = Integer(params['percentage'] || 0)
|
|
@@ -10,7 +10,7 @@ module Unleash
|
|
|
10
10
|
# need: params['percentage'], params['groupId'], context.user_id,
|
|
11
11
|
def is_enabled?(params = {}, context = nil, _constraints = [])
|
|
12
12
|
return false unless params.is_a?(Hash) && params.has_key?('percentage')
|
|
13
|
-
return false unless context.
|
|
13
|
+
return false unless context.instance_of?(Unleash::Context)
|
|
14
14
|
return false if context.user_id.nil? || context.user_id.empty?
|
|
15
15
|
|
|
16
16
|
percentage = Integer(params['percentage'] || 0)
|
|
@@ -11,7 +11,7 @@ module Unleash
|
|
|
11
11
|
def is_enabled?(params = {}, context = nil)
|
|
12
12
|
return false unless params.is_a?(Hash) && params.has_key?(PARAM)
|
|
13
13
|
return false unless params.fetch(PARAM, nil).is_a? String
|
|
14
|
-
return false unless context.
|
|
14
|
+
return false unless context.instance_of?(Unleash::Context)
|
|
15
15
|
|
|
16
16
|
remote_address = ipaddr_or_nil_from_str(context.remote_address)
|
|
17
17
|
|
|
@@ -11,7 +11,7 @@ module Unleash
|
|
|
11
11
|
def is_enabled?(params = {}, context = nil)
|
|
12
12
|
return false unless params.is_a?(Hash) && params.has_key?(PARAM)
|
|
13
13
|
return false unless params.fetch(PARAM, nil).is_a? String
|
|
14
|
-
return false unless context.
|
|
14
|
+
return false unless context.instance_of?(Unleash::Context)
|
|
15
15
|
|
|
16
16
|
params[PARAM].split(",").map(&:strip).include?(context.user_id)
|
|
17
17
|
end
|
|
@@ -5,11 +5,12 @@ require 'json'
|
|
|
5
5
|
|
|
6
6
|
module Unleash
|
|
7
7
|
class ToggleFetcher
|
|
8
|
-
attr_accessor :toggle_cache, :toggle_lock, :toggle_resource, :etag, :retry_count
|
|
8
|
+
attr_accessor :toggle_cache, :toggle_lock, :toggle_resource, :etag, :retry_count, :segment_cache
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
11
|
self.etag = nil
|
|
12
12
|
self.toggle_cache = nil
|
|
13
|
+
self.segment_cache = nil
|
|
13
14
|
self.toggle_lock = Mutex.new
|
|
14
15
|
self.toggle_resource = ConditionVariable.new
|
|
15
16
|
self.retry_count = 0
|
|
@@ -95,9 +96,10 @@ module Unleash
|
|
|
95
96
|
end
|
|
96
97
|
|
|
97
98
|
def update_running_client!
|
|
98
|
-
if Unleash.toggles != self.toggles
|
|
99
|
+
if Unleash.toggles != self.toggles["features"] || Unleash.segment_cache != self.toggles["segments"]
|
|
99
100
|
Unleash.logger.info "Updating toggles to main client, there has been a change in the server."
|
|
100
|
-
Unleash.toggles = self.toggles
|
|
101
|
+
Unleash.toggles = self.toggles["features"]
|
|
102
|
+
Unleash.segment_cache = self.toggles["segments"]
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
105
|
|
|
@@ -126,10 +128,19 @@ module Unleash
|
|
|
126
128
|
Unleash.configuration.bootstrap_config = nil
|
|
127
129
|
end
|
|
128
130
|
|
|
131
|
+
def build_segment_map(segments_array)
|
|
132
|
+
return {} if segments_array.nil?
|
|
133
|
+
|
|
134
|
+
segments_array.map{ |segment| [segment["id"], segment] }.to_h
|
|
135
|
+
end
|
|
136
|
+
|
|
129
137
|
# @param response_body [String]
|
|
130
138
|
def get_features(response_body)
|
|
131
139
|
response_hash = JSON.parse(response_body)
|
|
132
|
-
|
|
140
|
+
|
|
141
|
+
if response_hash['version'] >= 1
|
|
142
|
+
return { "features" => response_hash["features"], "segments" => build_segment_map(response_hash["segments"]) }
|
|
143
|
+
end
|
|
133
144
|
|
|
134
145
|
raise NotImplemented, "Version of features provided by unleash server" \
|
|
135
146
|
" is unsupported by this client."
|
|
@@ -4,7 +4,7 @@ module Unleash
|
|
|
4
4
|
class VariantDefinition
|
|
5
5
|
attr_accessor :name, :weight, :payload, :overrides, :stickiness
|
|
6
6
|
|
|
7
|
-
def initialize(name, weight = 0, payload = nil, stickiness = nil, overrides = [])
|
|
7
|
+
def initialize(name, weight = 0, payload = nil, stickiness = nil, overrides = []) # rubocop:disable Metrics/ParameterLists
|
|
8
8
|
self.name = name
|
|
9
9
|
self.weight = weight
|
|
10
10
|
self.payload = payload
|
|
@@ -14,7 +14,7 @@ module Unleash
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def matches_context?(context)
|
|
17
|
-
raise ArgumentError, 'context must be of class Unleash::Context' unless context.
|
|
17
|
+
raise ArgumentError, 'context must be of class Unleash::Context' unless context.instance_of?(Unleash::Context)
|
|
18
18
|
|
|
19
19
|
context_value =
|
|
20
20
|
case self.context_name
|
data/lib/unleash/version.rb
CHANGED
data/lib/unleash.rb
CHANGED
|
@@ -1,35 +1,31 @@
|
|
|
1
1
|
require 'unleash/version'
|
|
2
2
|
require 'unleash/configuration'
|
|
3
|
-
require 'unleash/
|
|
3
|
+
require 'unleash/strategies'
|
|
4
4
|
require 'unleash/context'
|
|
5
5
|
require 'unleash/client'
|
|
6
6
|
require 'logger'
|
|
7
7
|
|
|
8
|
-
Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path unless path.end_with? '_spec.rb' }
|
|
9
|
-
|
|
10
8
|
module Unleash
|
|
11
9
|
TIME_RESOLUTION = 3
|
|
12
10
|
|
|
13
|
-
STRATEGIES = Unleash::Strategy.constants
|
|
14
|
-
.select{ |c| Unleash::Strategy.const_get(c).is_a? Class }
|
|
15
|
-
.reject{ |c| ['NotImplemented', 'Base'].include?(c.to_s) }
|
|
16
|
-
.map do |c|
|
|
17
|
-
lowered_c = c.to_s
|
|
18
|
-
lowered_c[0] = lowered_c[0].downcase
|
|
19
|
-
[lowered_c.to_sym, Object.const_get("Unleash::Strategy::#{c}").new]
|
|
20
|
-
end
|
|
21
|
-
.to_h
|
|
22
|
-
|
|
23
11
|
class << self
|
|
24
|
-
attr_accessor :configuration, :toggle_fetcher, :toggles, :toggle_metrics, :reporter, :logger
|
|
12
|
+
attr_accessor :configuration, :toggle_fetcher, :toggles, :toggle_metrics, :reporter, :segment_cache, :logger
|
|
25
13
|
end
|
|
26
14
|
|
|
15
|
+
self.configuration = Unleash::Configuration.new
|
|
16
|
+
|
|
17
|
+
# Deprecated: Use Unleash.configure to add custom strategies
|
|
18
|
+
STRATEGIES = self.configuration.strategies
|
|
19
|
+
|
|
27
20
|
# Support for configuration via yield:
|
|
28
21
|
def self.configure
|
|
29
|
-
self.configuration ||= Unleash::Configuration.new
|
|
30
22
|
yield(configuration)
|
|
31
23
|
|
|
32
24
|
self.configuration.validate!
|
|
33
25
|
self.configuration.refresh_backup_file!
|
|
34
26
|
end
|
|
27
|
+
|
|
28
|
+
def self.strategies
|
|
29
|
+
self.configuration.strategies
|
|
30
|
+
end
|
|
35
31
|
end
|
data/unleash-client.gemspec
CHANGED
|
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
spec.add_development_dependency "rspec-json_expectations", "~> 2.2"
|
|
32
32
|
spec.add_development_dependency "webmock", "~> 3.8"
|
|
33
33
|
|
|
34
|
-
spec.add_development_dependency "rubocop", "
|
|
34
|
+
spec.add_development_dependency "rubocop", "~> 1.28.2"
|
|
35
35
|
spec.add_development_dependency "simplecov", "~> 0.21.2"
|
|
36
36
|
spec.add_development_dependency "simplecov-lcov", "~> 0.8.0"
|
|
37
37
|
end
|
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.4.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: 2022-
|
|
11
|
+
date: 2022-09-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: murmurhash3
|
|
@@ -98,16 +98,16 @@ dependencies:
|
|
|
98
98
|
name: rubocop
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
|
-
- - "
|
|
101
|
+
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: 1.
|
|
103
|
+
version: 1.28.2
|
|
104
104
|
type: :development
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
|
-
- - "
|
|
108
|
+
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: 1.
|
|
110
|
+
version: 1.28.2
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
112
|
name: simplecov
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -176,6 +176,7 @@ files:
|
|
|
176
176
|
- lib/unleash/metrics.rb
|
|
177
177
|
- lib/unleash/metrics_reporter.rb
|
|
178
178
|
- lib/unleash/scheduled_executor.rb
|
|
179
|
+
- lib/unleash/strategies.rb
|
|
179
180
|
- lib/unleash/strategy/application_hostname.rb
|
|
180
181
|
- lib/unleash/strategy/base.rb
|
|
181
182
|
- lib/unleash/strategy/default.rb
|
|
@@ -212,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
212
213
|
- !ruby/object:Gem::Version
|
|
213
214
|
version: '0'
|
|
214
215
|
requirements: []
|
|
215
|
-
rubygems_version: 3.3.
|
|
216
|
+
rubygems_version: 3.3.5
|
|
216
217
|
signing_key:
|
|
217
218
|
specification_version: 4
|
|
218
219
|
summary: Unleash feature toggle client.
|