unleash 3.2.1 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81f398d199ef891fbd322726d73702101bd22e78827c258d90861558662c7c26
4
- data.tar.gz: a6166d04ac912a1766d6f905f1bc86a6ed41bca15cf23f420feb93c8ca81a6e7
3
+ metadata.gz: da9995687fc28b71318711bd1575d9f5493fb9514cc3f276966323e4a73e15f8
4
+ data.tar.gz: e4a29a197e0faeaeaf093bb5a67103ed36aba352facf9633a6845739b17ff58c
5
5
  SHA512:
6
- metadata.gz: 4a4cbbdfd1f5cfef172b7afc4c4ee4e3fd850dadedec443b4b571b74ff8a9aa7cc76b48fe768fef0c9f4ea990876254e86b0650fce7d063aa6dbc172831c8884
7
- data.tar.gz: 7a247e76361465b7716db8dea33e065ad4064eaa3f8b6ac553d6cdb40caf0fcf7a4ae71873a0d7dc6e756514609d3aefb1989973fd3d83dcd7f2abf18536e000
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
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
- Metrics/LineLength:
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: 8
25
+ Max: 9
26
26
  Metrics/PerceivedComplexity:
27
- Max: 8
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.2.0 https://github.com/Unleash/client-specification.git
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.1'
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 #{params}"
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
@@ -1,6 +1,8 @@
1
1
  module Unleash
2
2
  class Context
3
- attr_accessor :app_name, :environment, :user_id, :session_id, :remote_address, :properties
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=#{self.user_id},session_id=#{self.session_id},remote_address=#{self.remote_address},properties=#{self.properties}>"
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.variant_definitions = (params.fetch('variants', []) || [])
21
- .select{ |v| v.is_a?(Hash) && v.has_key?('name') }
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?{ |s| strategy_enabled?(s, context) }
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}" # "for params #{strategy.params} "
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)
@@ -71,6 +71,7 @@ module Unleash
71
71
  self.toggle_lock.synchronize do
72
72
  file = File.open(backup_file_tmp, "w")
73
73
  file.write(self.toggle_cache.to_json)
74
+ file.close
74
75
  File.rename(backup_file_tmp, backup_file)
75
76
  end
76
77
  rescue StandardError => e
@@ -1,3 +1,3 @@
1
1
  module Unleash
2
- VERSION = "3.2.1".freeze
2
+ VERSION = "3.2.2".freeze
3
3
  end
@@ -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 = "~> 2.4"
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.1"
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.1
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: 2020-05-05 00:00:00.000000000 Z
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.1'
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.1'
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.4'
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.1.2
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: []