unleash 3.2.1 → 3.2.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 +4 -4
- data/.rubocop.yml +4 -4
- data/.travis.yml +2 -3
- data/README.md +2 -2
- data/lib/unleash.rb +1 -1
- data/lib/unleash/activation_strategy.rb +16 -3
- data/lib/unleash/constraint.rb +26 -0
- data/lib/unleash/context.rb +18 -5
- data/lib/unleash/feature_toggle.rb +44 -17
- data/lib/unleash/strategy/flexible_rollout.rb +55 -0
- data/lib/unleash/strategy/gradual_rollout_sessionid.rb +1 -1
- data/lib/unleash/strategy/gradual_rollout_userid.rb +2 -2
- data/lib/unleash/toggle_fetcher.rb +1 -0
- data/lib/unleash/version.rb +1 -1
- data/unleash-client.gemspec +2 -2
- metadata +12 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da9995687fc28b71318711bd1575d9f5493fb9514cc3f276966323e4a73e15f8
|
4
|
+
data.tar.gz: e4a29a197e0faeaeaf093bb5a67103ed36aba352facf9633a6845739b17ff58c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7831894183515cbe05fb10520542b93d8a8cd521eab6ff33e9c7a4f88540519b479a76fb360a085b4f1ee059be1d03d0b531eb0daa4ff3db0147380b6f5db52
|
7
|
+
data.tar.gz: 93ce839564b8ef8af502ca148bd3e369f5463d32cd42af1d947be5d751dda88c52d8da71ee5a456658de899afe7fff159655f7f8579cf8030c81ff62655f1804
|
data/.rubocop.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# inherit_from: .rubocop_todo.yml
|
2
2
|
|
3
3
|
AllCops:
|
4
|
-
TargetRubyVersion: 2.
|
4
|
+
TargetRubyVersion: 2.5
|
5
5
|
|
6
6
|
Naming/PredicateName:
|
7
7
|
AllowedMethods:
|
@@ -9,7 +9,7 @@ Naming/PredicateName:
|
|
9
9
|
|
10
10
|
Metrics/ClassLength:
|
11
11
|
Max: 120
|
12
|
-
|
12
|
+
Layout/LineLength:
|
13
13
|
Max: 140
|
14
14
|
Metrics/MethodLength:
|
15
15
|
Max: 20
|
@@ -22,9 +22,9 @@ Metrics/BlockLength:
|
|
22
22
|
Metrics/AbcSize:
|
23
23
|
Max: 25
|
24
24
|
Metrics/CyclomaticComplexity:
|
25
|
-
Max:
|
25
|
+
Max: 9
|
26
26
|
Metrics/PerceivedComplexity:
|
27
|
-
Max:
|
27
|
+
Max: 9
|
28
28
|
|
29
29
|
Style/Documentation:
|
30
30
|
Enabled: false
|
data/.travis.yml
CHANGED
@@ -2,14 +2,13 @@ sudo: false
|
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
4
|
- jruby
|
5
|
+
- 3.0
|
5
6
|
- 2.7
|
6
7
|
- 2.6
|
7
8
|
- 2.5
|
8
|
-
- 2.4
|
9
9
|
before_install:
|
10
10
|
- gem install bundler -v 2.1.4
|
11
|
-
- git clone --depth 5 --branch v3.
|
12
|
-
client-specification
|
11
|
+
- git clone --depth 5 --branch v3.3.0 https://github.com/Unleash/client-specification.git client-specification
|
13
12
|
|
14
13
|
notifications:
|
15
14
|
slack:
|
data/README.md
CHANGED
@@ -10,10 +10,10 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f
|
|
10
10
|
|
11
11
|
## Supported Ruby Interpreters
|
12
12
|
|
13
|
+
* MRI 3.0
|
13
14
|
* MRI 2.7
|
14
15
|
* MRI 2.6
|
15
16
|
* MRI 2.5
|
16
|
-
* MRI 2.4
|
17
17
|
* jruby
|
18
18
|
|
19
19
|
## Installation
|
@@ -21,7 +21,7 @@ Leverage the [Unleash Server](https://github.com/Unleash/unleash) for powerful f
|
|
21
21
|
Add this line to your application's Gemfile:
|
22
22
|
|
23
23
|
```ruby
|
24
|
-
gem 'unleash', '~> 3.2.
|
24
|
+
gem 'unleash', '~> 3.2.2'
|
25
25
|
```
|
26
26
|
|
27
27
|
And then execute:
|
data/lib/unleash.rb
CHANGED
@@ -5,7 +5,7 @@ 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 }
|
8
|
+
Gem.find_files('unleash/strategy/**/*.rb').each{ |path| require path unless path.end_with? '_spec.rb' }
|
9
9
|
|
10
10
|
module Unleash
|
11
11
|
TIME_RESOLUTION = 3
|
@@ -1,16 +1,29 @@
|
|
1
1
|
module Unleash
|
2
2
|
class ActivationStrategy
|
3
|
-
attr_accessor :name, :params
|
3
|
+
attr_accessor :name, :params, :constraints
|
4
4
|
|
5
|
-
def initialize(name, params)
|
5
|
+
def initialize(name, params, constraints = [])
|
6
6
|
self.name = name
|
7
7
|
|
8
8
|
if params.is_a?(Hash)
|
9
9
|
self.params = params
|
10
|
+
elsif params.nil?
|
11
|
+
self.params = {}
|
10
12
|
else
|
11
|
-
Unleash.logger.warn "Invalid params provided for ActivationStrategy
|
13
|
+
Unleash.logger.warn "Invalid params provided for ActivationStrategy (params:#{params})"
|
12
14
|
self.params = {}
|
13
15
|
end
|
16
|
+
|
17
|
+
if constraints.is_a?(Array) && constraints.each{ |c| c.is_a?(Constraint) }
|
18
|
+
self.constraints = constraints
|
19
|
+
else
|
20
|
+
Unleash.logger.warn "Invalid constraints provided for ActivationStrategy (contraints: #{constraints})"
|
21
|
+
self.constraints = []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches_context?(context)
|
26
|
+
self.constraints.any?{ |c| c.matches_context? context }
|
14
27
|
end
|
15
28
|
end
|
16
29
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Unleash
|
2
|
+
class Constraint
|
3
|
+
attr_accessor :context_name, :operator, :values
|
4
|
+
|
5
|
+
VALID_OPERATORS = ['IN', 'NOT_IN'].freeze
|
6
|
+
|
7
|
+
def initialize(context_name, operator, values = [])
|
8
|
+
raise ArgumentError, "context_name is not a String" unless context_name.is_a?(String)
|
9
|
+
raise ArgumentError, "operator does not hold a valid value:" + VALID_OPERATORS unless VALID_OPERATORS.include? operator
|
10
|
+
raise ArgumentError, "values does not hold an Array" unless values.is_a?(Array)
|
11
|
+
|
12
|
+
self.context_name = context_name
|
13
|
+
self.operator = operator
|
14
|
+
self.values = values
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches_context?(context)
|
18
|
+
Unleash.logger.debug "Unleash::Constraint matches_context? values: #{self.values} context.get_by_name(#{self.context_name})" \
|
19
|
+
" #{context.get_by_name(self.context_name)} "
|
20
|
+
|
21
|
+
is_included = self.values.include? context.get_by_name(self.context_name)
|
22
|
+
|
23
|
+
operator == 'IN' ? is_included : !is_included
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/unleash/context.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Unleash
|
2
2
|
class Context
|
3
|
-
|
3
|
+
ATTRS = [:app_name, :environment, :user_id, :session_id, :remote_address].freeze
|
4
|
+
|
5
|
+
attr_accessor(*[ATTRS, :properties].flatten)
|
4
6
|
|
5
7
|
def initialize(params = {})
|
6
8
|
raise ArgumentError, "Unleash::Context must be initialized with a hash." unless params.is_a?(Hash)
|
@@ -12,18 +14,29 @@ module Unleash
|
|
12
14
|
self.remote_address = value_for('remoteAddress', params)
|
13
15
|
|
14
16
|
properties = value_for('properties', params)
|
15
|
-
self.properties = properties.is_a?(Hash) ? properties : {}
|
17
|
+
self.properties = properties.is_a?(Hash) ? properties.transform_keys(&:to_sym) : {}
|
16
18
|
end
|
17
19
|
|
18
20
|
def to_s
|
19
|
-
"<Context: user_id=#{
|
21
|
+
"<Context: user_id=#{@user_id},session_id=#{@session_id},remote_address=#{@remote_address},properties=#{@properties}" \
|
22
|
+
",app_name=#{@app_name},environment=#{@environment}>"
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_by_name(name)
|
26
|
+
normalized_name = underscore(name).to_sym
|
27
|
+
|
28
|
+
if ATTRS.include? normalized_name
|
29
|
+
self.send(normalized_name)
|
30
|
+
else
|
31
|
+
self.properties.fetch(normalized_name)
|
32
|
+
end
|
20
33
|
end
|
21
34
|
|
22
35
|
private
|
23
36
|
|
24
37
|
# Method to fetch values from hash for two types of keys: string in camelCase and symbol in snake_case
|
25
|
-
def value_for(key, params, default_value =
|
26
|
-
params.values_at(key, underscore(key).to_sym).compact.first || default_value
|
38
|
+
def value_for(key, params, default_value = nil)
|
39
|
+
params.values_at(key, key.to_sym, underscore(key), underscore(key).to_sym).compact.first || default_value
|
27
40
|
end
|
28
41
|
|
29
42
|
# converts CamelCase to snake_case
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'unleash/activation_strategy'
|
2
|
+
require 'unleash/constraint'
|
2
3
|
require 'unleash/variant_definition'
|
3
4
|
require 'unleash/variant'
|
4
5
|
require 'unleash/strategy/util'
|
@@ -13,20 +14,9 @@ module Unleash
|
|
13
14
|
|
14
15
|
self.name = params.fetch('name', nil)
|
15
16
|
self.enabled = params.fetch('enabled', false)
|
16
|
-
self.strategies = params.fetch('strategies', [])
|
17
|
-
.select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
|
18
|
-
.map{ |s| ActivationStrategy.new(s['name'], s['parameters'] || {}) } || []
|
19
17
|
|
20
|
-
self.
|
21
|
-
|
22
|
-
.map do |v|
|
23
|
-
VariantDefinition.new(
|
24
|
-
v.fetch('name', ''),
|
25
|
-
v.fetch('weight', 0),
|
26
|
-
v.fetch('payload', nil),
|
27
|
-
v.fetch('overrides', [])
|
28
|
-
)
|
29
|
-
end || []
|
18
|
+
self.strategies = initialize_strategies(params)
|
19
|
+
self.variant_definitions = initialize_variant_definitions(params)
|
30
20
|
end
|
31
21
|
|
32
22
|
def to_s
|
@@ -64,23 +54,29 @@ module Unleash
|
|
64
54
|
result =
|
65
55
|
if self.enabled
|
66
56
|
self.strategies.empty? ||
|
67
|
-
self.strategies.any?
|
57
|
+
self.strategies.any? do |s|
|
58
|
+
strategy_enabled?(s, context) && strategy_constraint_matches?(s, context)
|
59
|
+
end
|
68
60
|
else
|
69
61
|
default_result
|
70
62
|
end
|
71
63
|
|
72
|
-
Unleash.logger.debug "FeatureToggle (enabled:#{self.enabled} default_result:#{default_result} " \
|
73
|
-
"and Strategies combined returned #{result})"
|
64
|
+
Unleash.logger.debug "Unleash::FeatureToggle (enabled:#{self.enabled} default_result:#{default_result} " \
|
65
|
+
"and Strategies combined with contraints returned #{result})"
|
74
66
|
|
75
67
|
result
|
76
68
|
end
|
77
69
|
|
78
70
|
def strategy_enabled?(strategy, context)
|
79
71
|
r = Unleash::STRATEGIES.fetch(strategy.name.to_sym, :unknown).is_enabled?(strategy.params, context)
|
80
|
-
Unleash.logger.debug "Strategy #{strategy.name} returned #{r} with context: #{context}"
|
72
|
+
Unleash.logger.debug "Unleash::FeatureToggle.strategy_enabled? Strategy #{strategy.name} returned #{r} with context: #{context}"
|
81
73
|
r
|
82
74
|
end
|
83
75
|
|
76
|
+
def strategy_constraint_matches?(strategy, context)
|
77
|
+
strategy.constraints.empty? || strategy.constraints.all?{ |c| c.matches_context?(context) }
|
78
|
+
end
|
79
|
+
|
84
80
|
def disabled_variant
|
85
81
|
Unleash::Variant.new(name: 'disabled', enabled: false)
|
86
82
|
end
|
@@ -127,5 +123,36 @@ module Unleash
|
|
127
123
|
end
|
128
124
|
context
|
129
125
|
end
|
126
|
+
|
127
|
+
def initialize_strategies(params)
|
128
|
+
params.fetch('strategies', [])
|
129
|
+
.select{ |s| s.has_key?('name') && Unleash::STRATEGIES.has_key?(s['name'].to_sym) }
|
130
|
+
.map do |s|
|
131
|
+
ActivationStrategy.new(
|
132
|
+
s['name'],
|
133
|
+
s['parameters'],
|
134
|
+
(s['constraints'] || []).map do |c|
|
135
|
+
Constraint.new(
|
136
|
+
c.fetch('contextName'),
|
137
|
+
c.fetch('operator'),
|
138
|
+
c.fetch('values')
|
139
|
+
)
|
140
|
+
end
|
141
|
+
)
|
142
|
+
end || []
|
143
|
+
end
|
144
|
+
|
145
|
+
def initialize_variant_definitions(params)
|
146
|
+
(params.fetch('variants', []) || [])
|
147
|
+
.select{ |v| v.is_a?(Hash) && v.has_key?('name') }
|
148
|
+
.map do |v|
|
149
|
+
VariantDefinition.new(
|
150
|
+
v.fetch('name', ''),
|
151
|
+
v.fetch('weight', 0),
|
152
|
+
v.fetch('payload', nil),
|
153
|
+
v.fetch('overrides', [])
|
154
|
+
)
|
155
|
+
end || []
|
156
|
+
end
|
130
157
|
end
|
131
158
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'unleash/strategy/util'
|
2
|
+
|
3
|
+
module Unleash
|
4
|
+
module Strategy
|
5
|
+
class FlexibleRollout < Base
|
6
|
+
def name
|
7
|
+
'flexibleRollout'
|
8
|
+
end
|
9
|
+
|
10
|
+
# need: params['percentage']
|
11
|
+
def is_enabled?(params = {}, context = nil)
|
12
|
+
return false unless params.is_a?(Hash)
|
13
|
+
return false unless context.class.name == 'Unleash::Context'
|
14
|
+
|
15
|
+
stickiness = params.fetch('stickiness', 'default')
|
16
|
+
stickiness_id = resolve_stickiness(stickiness, context)
|
17
|
+
|
18
|
+
begin
|
19
|
+
percentage = Integer(params.fetch('rollout', 0))
|
20
|
+
percentage = 0 if percentage > 100 || percentage.negative?
|
21
|
+
rescue ArgumentError
|
22
|
+
return false
|
23
|
+
end
|
24
|
+
|
25
|
+
group_id = params.fetch('groupId', '')
|
26
|
+
normalized_number = Util.get_normalized_number(stickiness_id, group_id)
|
27
|
+
|
28
|
+
return false if stickiness_id.nil?
|
29
|
+
|
30
|
+
(percentage.positive? && normalized_number <= percentage)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def random
|
36
|
+
Random.rand(0..100)
|
37
|
+
end
|
38
|
+
|
39
|
+
def resolve_stickiness(stickiness, context)
|
40
|
+
case stickiness
|
41
|
+
when 'userId'
|
42
|
+
context.user_id
|
43
|
+
when 'sessionId'
|
44
|
+
context.session_id
|
45
|
+
when 'random'
|
46
|
+
random
|
47
|
+
when 'default'
|
48
|
+
context.user_id || context.session_id || random
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -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?('percentage')
|
13
13
|
return false unless context.class.name == 'Unleash::Context'
|
14
|
-
return false if context.session_id.empty?
|
14
|
+
return false if context.session_id.nil? || context.session_id.empty?
|
15
15
|
|
16
16
|
percentage = Integer(params['percentage'] || 0)
|
17
17
|
(percentage.positive? && Util.get_normalized_number(context.session_id, params['groupId'] || "") <= percentage)
|
@@ -8,10 +8,10 @@ module Unleash
|
|
8
8
|
end
|
9
9
|
|
10
10
|
# need: params['percentage'], params['groupId'], context.user_id,
|
11
|
-
def is_enabled?(params = {}, context = nil)
|
11
|
+
def is_enabled?(params = {}, context = nil, _constraints = [])
|
12
12
|
return false unless params.is_a?(Hash) && params.has_key?('percentage')
|
13
13
|
return false unless context.class.name == 'Unleash::Context'
|
14
|
-
return false if context.user_id.empty?
|
14
|
+
return false if context.user_id.nil? || context.user_id.empty?
|
15
15
|
|
16
16
|
percentage = Integer(params['percentage'] || 0)
|
17
17
|
(percentage.positive? && Util.get_normalized_number(context.user_id, params['groupId'] || "") <= percentage)
|
data/lib/unleash/version.rb
CHANGED
data/unleash-client.gemspec
CHANGED
@@ -21,14 +21,14 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.bindir = 'bin'
|
22
22
|
spec.executables = spec.files.grep(%r{^bin/unleash}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
|
-
spec.required_ruby_version = "
|
24
|
+
spec.required_ruby_version = ">= 2.5"
|
25
25
|
|
26
26
|
spec.add_dependency "murmurhash3", "~> 0.1.6"
|
27
27
|
|
28
28
|
spec.add_development_dependency "bundler", "~> 2.1"
|
29
29
|
spec.add_development_dependency "rake", "~> 12.3"
|
30
30
|
spec.add_development_dependency "rspec", "~> 3.9"
|
31
|
-
spec.add_development_dependency "rspec-json_expectations", "~> 2.
|
31
|
+
spec.add_development_dependency "rspec-json_expectations", "~> 2.2"
|
32
32
|
spec.add_development_dependency "webmock", "~> 3.8"
|
33
33
|
|
34
34
|
spec.add_development_dependency "coveralls", "~> 0.8"
|
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: 3.2.
|
4
|
+
version: 3.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renato Arruda
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: murmurhash3
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '2.
|
75
|
+
version: '2.2'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '2.
|
82
|
+
version: '2.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: webmock
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -148,6 +148,7 @@ files:
|
|
148
148
|
- lib/unleash/activation_strategy.rb
|
149
149
|
- lib/unleash/client.rb
|
150
150
|
- lib/unleash/configuration.rb
|
151
|
+
- lib/unleash/constraint.rb
|
151
152
|
- lib/unleash/context.rb
|
152
153
|
- lib/unleash/feature_toggle.rb
|
153
154
|
- lib/unleash/metrics.rb
|
@@ -156,6 +157,7 @@ files:
|
|
156
157
|
- lib/unleash/strategy/application_hostname.rb
|
157
158
|
- lib/unleash/strategy/base.rb
|
158
159
|
- lib/unleash/strategy/default.rb
|
160
|
+
- lib/unleash/strategy/flexible_rollout.rb
|
159
161
|
- lib/unleash/strategy/gradual_rollout_random.rb
|
160
162
|
- lib/unleash/strategy/gradual_rollout_sessionid.rb
|
161
163
|
- lib/unleash/strategy/gradual_rollout_userid.rb
|
@@ -173,23 +175,23 @@ homepage: https://github.com/unleash/unleash-client-ruby
|
|
173
175
|
licenses:
|
174
176
|
- Apache-2.0
|
175
177
|
metadata: {}
|
176
|
-
post_install_message:
|
178
|
+
post_install_message:
|
177
179
|
rdoc_options: []
|
178
180
|
require_paths:
|
179
181
|
- lib
|
180
182
|
required_ruby_version: !ruby/object:Gem::Requirement
|
181
183
|
requirements:
|
182
|
-
- - "
|
184
|
+
- - ">="
|
183
185
|
- !ruby/object:Gem::Version
|
184
|
-
version: '2.
|
186
|
+
version: '2.5'
|
185
187
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
188
|
requirements:
|
187
189
|
- - ">="
|
188
190
|
- !ruby/object:Gem::Version
|
189
191
|
version: '0'
|
190
192
|
requirements: []
|
191
|
-
rubygems_version: 3.
|
192
|
-
signing_key:
|
193
|
+
rubygems_version: 3.2.3
|
194
|
+
signing_key:
|
193
195
|
specification_version: 4
|
194
196
|
summary: Unleash feature toggle client.
|
195
197
|
test_files: []
|