unleash 0.1.6 → 3.2.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/.gitignore +1 -1
- data/.rubocop.yml +53 -5
- data/.travis.yml +12 -8
- data/README.md +4 -6
- data/Rakefile +1 -1
- data/bin/unleash-client +21 -17
- data/examples/simple.rb +4 -4
- data/lib/unleash.rb +7 -8
- data/lib/unleash/client.rb +49 -57
- data/lib/unleash/configuration.rb +54 -35
- data/lib/unleash/context.rb +18 -7
- data/lib/unleash/feature_toggle.rb +42 -41
- data/lib/unleash/metrics.rb +3 -4
- data/lib/unleash/metrics_reporter.rb +9 -22
- data/lib/unleash/scheduled_executor.rb +26 -13
- data/lib/unleash/strategy/application_hostname.rb +2 -2
- data/lib/unleash/strategy/base.rb +2 -2
- data/lib/unleash/strategy/default.rb +1 -1
- data/lib/unleash/strategy/gradual_rollout_random.rb +1 -1
- data/lib/unleash/strategy/gradual_rollout_sessionid.rb +2 -2
- data/lib/unleash/strategy/gradual_rollout_userid.rb +2 -2
- 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 +19 -36
- data/lib/unleash/util/http.rb +48 -0
- data/lib/unleash/variant.rb +2 -5
- data/lib/unleash/variant_definition.rb +1 -2
- data/lib/unleash/version.rb +1 -1
- data/unleash-client.gemspec +9 -9
- metadata +26 -11
- data/TODO.md +0 -37
@@ -4,7 +4,7 @@ module Unleash
|
|
4
4
|
module Strategy
|
5
5
|
class ApplicationHostname < Base
|
6
6
|
attr_accessor :hostname
|
7
|
-
PARAM = 'hostnames'
|
7
|
+
PARAM = 'hostnames'.freeze
|
8
8
|
|
9
9
|
def initialize
|
10
10
|
self.hostname = Socket.gethostname || 'undefined'
|
@@ -18,7 +18,7 @@ module Unleash
|
|
18
18
|
def is_enabled?(params = {}, _context = nil)
|
19
19
|
return false unless params.is_a?(Hash) && params.has_key?(PARAM)
|
20
20
|
|
21
|
-
params[PARAM].split(",").map(&:strip).map
|
21
|
+
params[PARAM].split(",").map(&:strip).map(&:downcase).include?(self.hostname)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Unleash
|
2
2
|
module Strategy
|
3
|
-
class NotImplemented <
|
3
|
+
class NotImplemented < RuntimeError
|
4
4
|
end
|
5
5
|
|
6
6
|
class Base
|
@@ -8,7 +8,7 @@ module Unleash
|
|
8
8
|
raise NotImplemented, "Strategy is not implemented"
|
9
9
|
end
|
10
10
|
|
11
|
-
def is_enabled?(
|
11
|
+
def is_enabled?(_params = {}, _context = nil)
|
12
12
|
raise NotImplemented, "Strategy is not implemented"
|
13
13
|
end
|
14
14
|
end
|
@@ -8,13 +8,13 @@ module Unleash
|
|
8
8
|
end
|
9
9
|
|
10
10
|
# need: params['percentage'], params['groupId'], context.user_id,
|
11
|
-
def is_enabled?(params = {}, context)
|
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
14
|
return false if context.session_id.empty?
|
15
15
|
|
16
16
|
percentage = Integer(params['percentage'] || 0)
|
17
|
-
(percentage
|
17
|
+
(percentage.positive? && Util.get_normalized_number(context.session_id, params['groupId'] || "") <= percentage)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -8,13 +8,13 @@ module Unleash
|
|
8
8
|
end
|
9
9
|
|
10
10
|
# need: params['percentage'], params['groupId'], context.user_id,
|
11
|
-
def is_enabled?(params = {}, context)
|
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
14
|
return false if context.user_id.empty?
|
15
15
|
|
16
16
|
percentage = Integer(params['percentage'] || 0)
|
17
|
-
(percentage
|
17
|
+
(percentage.positive? && Util.get_normalized_number(context.user_id, params['groupId'] || "") <= percentage)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -1,10 +1,8 @@
|
|
1
1
|
require 'unleash/configuration'
|
2
2
|
require 'net/http'
|
3
3
|
require 'json'
|
4
|
-
require 'thread'
|
5
4
|
|
6
5
|
module Unleash
|
7
|
-
|
8
6
|
class ToggleFetcher
|
9
7
|
attr_accessor :toggle_cache, :toggle_lock, :toggle_resource, :etag, :retry_count
|
10
8
|
|
@@ -18,7 +16,7 @@ module Unleash
|
|
18
16
|
# start by fetching synchronously, and failing back to reading the backup file.
|
19
17
|
begin
|
20
18
|
fetch
|
21
|
-
rescue
|
19
|
+
rescue StandardError => e
|
22
20
|
Unleash.logger.warn "ToggleFetcher was unable to fetch from the network, attempting to read from backup file."
|
23
21
|
Unleash.logger.debug "Exception Caught: #{e}"
|
24
22
|
read!
|
@@ -36,29 +34,16 @@ module Unleash
|
|
36
34
|
end
|
37
35
|
|
38
36
|
# rename to refresh_from_server! ??
|
39
|
-
# TODO: should simplify by moving uri / http initialization elsewhere
|
40
37
|
def fetch
|
41
38
|
Unleash.logger.debug "fetch()"
|
42
|
-
Unleash.
|
43
|
-
|
44
|
-
uri = URI(Unleash.configuration.fetch_toggles_url)
|
45
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
46
|
-
http.use_ssl = true if uri.scheme == 'https'
|
47
|
-
http.open_timeout = Unleash.configuration.timeout # in seconds
|
48
|
-
http.read_timeout = Unleash.configuration.timeout # in seconds
|
49
|
-
|
50
|
-
headers = (Unleash.configuration.get_http_headers || {}).dup
|
51
|
-
headers['Content-Type'] = 'application/json'
|
52
|
-
headers['If-None-Match'] = self.etag unless self.etag.nil?
|
53
|
-
|
54
|
-
request = Net::HTTP::Get.new(uri.request_uri, headers)
|
39
|
+
response = Unleash::Util::Http.get(Unleash.configuration.fetch_toggles_url, etag)
|
55
40
|
|
56
|
-
response
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
41
|
+
if response.code == '304'
|
42
|
+
Unleash.logger.debug "No changes according to the unleash server, nothing to do."
|
43
|
+
return
|
44
|
+
elsif response.code != '200'
|
45
|
+
raise IOError, "Unleash server returned a non 200/304 HTTP result."
|
46
|
+
end
|
62
47
|
|
63
48
|
self.etag = response['ETag']
|
64
49
|
response_hash = JSON.parse(response.body)
|
@@ -73,14 +58,12 @@ module Unleash
|
|
73
58
|
# always synchronize with the local cache when fetching:
|
74
59
|
synchronize_with_local_cache!(features)
|
75
60
|
|
76
|
-
|
77
|
-
update_client!
|
78
|
-
|
79
|
-
Unleash.logger.info "Saved to toggle cache, will save to disk now"
|
61
|
+
update_running_client!
|
80
62
|
save!
|
81
63
|
end
|
82
64
|
|
83
65
|
def save!
|
66
|
+
Unleash.logger.debug "Will save toggles to disk now"
|
84
67
|
begin
|
85
68
|
backup_file = Unleash.configuration.backup_file
|
86
69
|
backup_file_tmp = "#{backup_file}.tmp"
|
@@ -90,12 +73,12 @@ module Unleash
|
|
90
73
|
file.write(self.toggle_cache.to_json)
|
91
74
|
File.rename(backup_file_tmp, backup_file)
|
92
75
|
end
|
93
|
-
rescue
|
76
|
+
rescue StandardError => e
|
94
77
|
# This is not really the end of the world. Swallowing the exception.
|
95
78
|
Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
|
96
79
|
Unleash.logger.error "stacktrace: #{e.backtrace}"
|
97
80
|
ensure
|
98
|
-
file
|
81
|
+
file&.close if defined?(file)
|
99
82
|
self.toggle_lock.unlock if self.toggle_lock.locked?
|
100
83
|
end
|
101
84
|
end
|
@@ -113,7 +96,7 @@ module Unleash
|
|
113
96
|
end
|
114
97
|
end
|
115
98
|
|
116
|
-
def
|
99
|
+
def update_running_client!
|
117
100
|
if Unleash.toggles != self.toggles
|
118
101
|
Unleash.logger.info "Updating toggles to main client, there has been a change in the server."
|
119
102
|
Unleash.toggles = self.toggles
|
@@ -130,15 +113,15 @@ module Unleash
|
|
130
113
|
|
131
114
|
backup_as_hash = JSON.parse(file_content)
|
132
115
|
synchronize_with_local_cache!(backup_as_hash)
|
133
|
-
|
116
|
+
update_running_client!
|
134
117
|
rescue IOError => e
|
135
|
-
Unleash.logger.error "Unable to read the backup_file
|
118
|
+
Unleash.logger.error "Unable to read the backup_file: #{e}"
|
136
119
|
rescue JSON::ParserError => e
|
137
|
-
Unleash.logger.error "Unable to parse JSON from existing backup_file
|
138
|
-
rescue
|
139
|
-
Unleash.logger.error "Unable to extract valid data from backup_file. Exception thrown
|
120
|
+
Unleash.logger.error "Unable to parse JSON from existing backup_file: #{e}"
|
121
|
+
rescue StandardError => e
|
122
|
+
Unleash.logger.error "Unable to extract valid data from backup_file. Exception thrown: #{e}"
|
140
123
|
ensure
|
141
|
-
file
|
124
|
+
file&.close
|
142
125
|
end
|
143
126
|
end
|
144
127
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Unleash
|
5
|
+
module Util
|
6
|
+
module Http
|
7
|
+
def self.get(url, etag = nil)
|
8
|
+
uri = URI(url)
|
9
|
+
http = http_connection(uri)
|
10
|
+
|
11
|
+
request = Net::HTTP::Get.new(uri.request_uri, http_headers(etag))
|
12
|
+
|
13
|
+
http.request(request)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.post(url, body)
|
17
|
+
uri = URI(url)
|
18
|
+
http = http_connection(uri)
|
19
|
+
|
20
|
+
request = Net::HTTP::Post.new(uri.request_uri, http_headers)
|
21
|
+
request.body = body
|
22
|
+
|
23
|
+
http.request(request)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.http_connection(uri)
|
27
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
28
|
+
http.use_ssl = true if uri.scheme == 'https'
|
29
|
+
http.open_timeout = Unleash.configuration.timeout # in seconds
|
30
|
+
http.read_timeout = Unleash.configuration.timeout # in seconds
|
31
|
+
|
32
|
+
http
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.http_headers(etag = nil)
|
36
|
+
Unleash.logger.debug "ETag: #{etag}" unless etag.nil?
|
37
|
+
|
38
|
+
headers = (Unleash.configuration.http_headers || {}).dup
|
39
|
+
headers['Content-Type'] = 'application/json'
|
40
|
+
headers['If-None-Match'] = etag unless etag.nil?
|
41
|
+
|
42
|
+
headers
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method :http_connection, :http_headers
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/unleash/variant.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
1
|
module Unleash
|
4
2
|
class Variant
|
5
3
|
attr_accessor :name, :enabled, :payload
|
@@ -18,9 +16,8 @@ module Unleash
|
|
18
16
|
"<Variant: name=#{self.name},enabled=#{self.enabled},payload=#{self.payload}>"
|
19
17
|
end
|
20
18
|
|
21
|
-
def ==(
|
22
|
-
self.name ==
|
19
|
+
def ==(other)
|
20
|
+
self.name == other.name && self.enabled == other.enabled && self.payload == other.payload
|
23
21
|
end
|
24
|
-
|
25
22
|
end
|
26
23
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'unleash/variant_override'
|
2
2
|
|
3
|
-
|
4
3
|
module Unleash
|
5
4
|
class VariantDefinition
|
6
5
|
attr_accessor :name, :weight, :payload, :overrides
|
@@ -11,7 +10,7 @@ module Unleash
|
|
11
10
|
self.payload = payload
|
12
11
|
# self.overrides = overrides
|
13
12
|
self.overrides = (overrides || [])
|
14
|
-
.select{ |v| v.is_a?(Hash) && v.
|
13
|
+
.select{ |v| v.is_a?(Hash) && v.has_key?('contextName') }
|
15
14
|
.map{ |v| VariantOverride.new(v.fetch('contextName', ''), v.fetch('values', [])) } || []
|
16
15
|
end
|
17
16
|
|
data/lib/unleash/version.rb
CHANGED
data/unleash-client.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'unleash/version'
|
5
4
|
|
@@ -10,19 +9,19 @@ Gem::Specification.new do |spec|
|
|
10
9
|
spec.email = ["rarruda@rarruda.org"]
|
11
10
|
spec.licenses = ["Apache-2.0"]
|
12
11
|
|
13
|
-
spec.summary =
|
14
|
-
spec.description =
|
15
|
-
that gives you a great overview over all feature toggles across all your applications and services.
|
12
|
+
spec.summary = "Unleash feature toggle client."
|
13
|
+
spec.description = "This is the ruby client for Unleash, a powerful feature toggle system
|
14
|
+
that gives you a great overview over all feature toggles across all your applications and services."
|
16
15
|
|
17
16
|
spec.homepage = "https://github.com/unleash/unleash-client-ruby"
|
18
17
|
|
19
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
20
19
|
f.match(%r{^(test|spec|features)/})
|
21
20
|
end
|
22
|
-
spec.bindir =
|
23
|
-
spec.executables = spec.files.grep(%r{^
|
21
|
+
spec.bindir = 'bin'
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/unleash}) { |f| File.basename(f) }
|
24
23
|
spec.require_paths = ["lib"]
|
25
|
-
spec.required_ruby_version = "~> 2.
|
24
|
+
spec.required_ruby_version = "~> 2.4"
|
26
25
|
|
27
26
|
spec.add_dependency "murmurhash3", "~> 0.1.6"
|
28
27
|
|
@@ -31,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
31
30
|
spec.add_development_dependency "rspec", "~> 3.0"
|
32
31
|
spec.add_development_dependency "rspec-json_expectations", "~> 2.1"
|
33
32
|
spec.add_development_dependency "webmock", "~> 3.0"
|
34
|
-
spec.add_development_dependency "coveralls"
|
35
33
|
|
34
|
+
spec.add_development_dependency "coveralls", "~> 0.8"
|
35
|
+
spec.add_development_dependency "rubocop", "~> 0.72"
|
36
36
|
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
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renato Arruda
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: murmurhash3
|
@@ -98,22 +98,37 @@ dependencies:
|
|
98
98
|
name: coveralls
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
101
|
+
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
103
|
+
version: '0.8'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - "
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.8'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.72'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
124
|
+
version: '0.72'
|
111
125
|
description: |-
|
112
126
|
This is the ruby client for Unleash, a powerful feature toggle system
|
113
127
|
that gives you a great overview over all feature toggles across all your applications and services.
|
114
128
|
email:
|
115
129
|
- rarruda@rarruda.org
|
116
|
-
executables:
|
130
|
+
executables:
|
131
|
+
- unleash-client
|
117
132
|
extensions: []
|
118
133
|
extra_rdoc_files: []
|
119
134
|
files:
|
@@ -125,7 +140,6 @@ files:
|
|
125
140
|
- LICENSE
|
126
141
|
- README.md
|
127
142
|
- Rakefile
|
128
|
-
- TODO.md
|
129
143
|
- bin/console
|
130
144
|
- bin/setup
|
131
145
|
- bin/unleash-client
|
@@ -149,6 +163,7 @@ files:
|
|
149
163
|
- lib/unleash/strategy/user_with_id.rb
|
150
164
|
- lib/unleash/strategy/util.rb
|
151
165
|
- lib/unleash/toggle_fetcher.rb
|
166
|
+
- lib/unleash/util/http.rb
|
152
167
|
- lib/unleash/variant.rb
|
153
168
|
- lib/unleash/variant_definition.rb
|
154
169
|
- lib/unleash/variant_override.rb
|
@@ -166,14 +181,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
166
181
|
requirements:
|
167
182
|
- - "~>"
|
168
183
|
- !ruby/object:Gem::Version
|
169
|
-
version: '2.
|
184
|
+
version: '2.4'
|
170
185
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
186
|
requirements:
|
172
187
|
- - ">="
|
173
188
|
- !ruby/object:Gem::Version
|
174
189
|
version: '0'
|
175
190
|
requirements: []
|
176
|
-
rubygems_version: 3.
|
191
|
+
rubygems_version: 3.1.2
|
177
192
|
signing_key:
|
178
193
|
specification_version: 4
|
179
194
|
summary: Unleash feature toggle client.
|