unleash 0.1.6 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|