unleash 0.1.1

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.
@@ -0,0 +1,24 @@
1
+ require 'socket'
2
+
3
+ module Unleash
4
+ module Strategy
5
+ class ApplicationHostname < Base
6
+ attr_accessor :hostname
7
+
8
+ def initialize
9
+ self.hostname = Socket.gethostname || 'undefined'
10
+ end
11
+
12
+ def name
13
+ 'applicationHostname'
14
+ end
15
+
16
+ # need: :params[:hostnames]
17
+ def is_enabled?(params = {}, context = nil)
18
+ return false unless params.is_a?(Hash) && params.has_key?('hostnames')
19
+
20
+ params['hostnames'].split(",").map(&:strip).map{|h| h.downcase }.include?(self.hostname)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Unleash
2
+ module Strategy
3
+ class NotImplemented < Exception
4
+ end
5
+
6
+ class Base
7
+ def name
8
+ raise NotImplemented, "Strategy is not implemented"
9
+ end
10
+
11
+ def is_enabled?(params = {}, context = nil)
12
+ raise NotImplemented, "Strategy is not implemented"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Unleash
2
+ module Strategy
3
+ class Default < Base
4
+ def name
5
+ 'default'
6
+ end
7
+
8
+ def is_enabled?(params = {}, context = nil)
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'unleash/strategy/util'
2
+
3
+ module Unleash
4
+ module Strategy
5
+ class GradualRolloutRandom < Base
6
+ def name
7
+ 'gradualRolloutRandom'
8
+ end
9
+
10
+ # need: params['percentage']
11
+ def is_enabled?(params = {}, context = nil)
12
+ return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
+
14
+ begin
15
+ percentage = Integer(params['percentage'] || 0)
16
+ rescue ArgumentError => e
17
+ return false
18
+ end
19
+
20
+ randomNumber = Random.rand(100) + 1
21
+
22
+ (percentage >= randomNumber)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ require 'unleash/strategy/util'
2
+
3
+ module Unleash
4
+ module Strategy
5
+ class GradualRolloutSessionId < Base
6
+ def name
7
+ 'gradualRolloutSessionId'
8
+ end
9
+
10
+ # need: params['percentage'], params['groupId'], context.user_id,
11
+ def is_enabled?(params = {}, context)
12
+ return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
+ return false unless context.class.name == 'Unleash::Context'
14
+
15
+ percentage = Integer(params['percentage'] || 0)
16
+ (percentage > 0 && Util.get_normalized_number(context.session_id, params['groupId'] || "") <= percentage)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'unleash/strategy/util'
2
+
3
+ module Unleash
4
+ module Strategy
5
+ class GradualRolloutUserId < Base
6
+ def name
7
+ 'gradualRolloutUserId'
8
+ end
9
+
10
+ # need: params['percentage'], params['groupId'], context.user_id,
11
+ def is_enabled?(params = {}, context)
12
+ return false unless params.is_a?(Hash) && params.has_key?('percentage')
13
+ return false unless context.class.name == 'Unleash::Context'
14
+
15
+ percentage = Integer(params['percentage'] || 0)
16
+ (percentage > 0 && Util.get_normalized_number(context.user_id, params['groupId'] || "") <= percentage)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module Unleash
2
+ module Strategy
3
+ class RemoteAddress < Base
4
+ def name
5
+ 'remoteAddress'
6
+ end
7
+
8
+ # need: params['ips'], context.remote_address
9
+ def is_enabled?(params = {}, context = nil)
10
+ return false unless params.is_a?(Hash) && params.has_key?('ips')
11
+ return false unless params.fetch('ips', nil).is_a? String
12
+ return false unless context.class.name == 'Unleash::Context'
13
+
14
+ params['ips'].split(',').map(&:strip).include?( context.remote_address )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Unleash
2
+ module Strategy
3
+ class Unknown < Base
4
+ def name
5
+ 'unknown'
6
+ end
7
+
8
+ def is_enabled?(params = {})
9
+ false
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module Unleash
2
+ module Strategy
3
+ class UserWithId < Base
4
+ def name
5
+ 'userWithId'
6
+ end
7
+
8
+ # requires: params['userIds'], context.user_id,
9
+ def is_enabled?(params = {}, context = nil)
10
+ return false unless params.is_a?(Hash) && params.has_key?('userIds')
11
+ return false unless params.fetch('userIds', nil).is_a? String
12
+ return false unless context.class.name == 'Unleash::Context'
13
+
14
+ params['userIds'].split(",").map(&:strip).include?(context.user_id)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require 'murmurhash3'
2
+
3
+ module Unleash
4
+ module Strategy
5
+ module Util
6
+ module_function
7
+
8
+ NORMALIZER = 100
9
+
10
+ # convert the two strings () into a number between 1 and 100
11
+ def get_normalized_number(identifier, group_id)
12
+ MurmurHash3::V32.str_hash("#{group_id}:#{identifier}") % NORMALIZER + 1
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,144 @@
1
+ require 'unleash/configuration'
2
+ require 'unleash/scheduled_executor'
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'thread'
6
+
7
+ module Unleash
8
+
9
+ class ToggleFetcher
10
+ attr_accessor :toggle_cache, :toggle_lock, :toggle_resource, :etag, :retry_count
11
+
12
+ def initialize
13
+ self.etag = nil
14
+ self.toggle_cache = nil
15
+ self.toggle_lock = Mutex.new
16
+ self.toggle_resource = ConditionVariable.new
17
+ self.retry_count = 0
18
+
19
+ # start by fetching synchronously, and failing back to reading the backup file.
20
+ begin
21
+ fetch
22
+ rescue Exception => e
23
+ Unleash.logger.warn "ToggleFetcher was unable to fetch from the network, attempting to read from backup file."
24
+ read!
25
+ raise e
26
+ end
27
+
28
+ # once we have initialized, start the fetcher loop
29
+ scheduledExecutor = Unleash::ScheduledExecutor.new('ToggleFetcher', Unleash.configuration.refresh_interval)
30
+ scheduledExecutor.run { remote_toggles = fetch() }
31
+ end
32
+
33
+ def toggles
34
+ self.toggle_lock.synchronize do
35
+ # wait for resource, only if it is null
36
+ self.toggle_resource.wait(self.toggle_lock) if self.toggle_cache.nil?
37
+ return self.toggle_cache
38
+ end
39
+ end
40
+
41
+ # rename to refresh_from_server! ??
42
+ def fetch
43
+ Unleash.logger.debug "fetch()"
44
+ Unleash.logger.debug "ETag: #{self.etag}" unless self.etag.nil?
45
+
46
+ uri = URI(Unleash.configuration.fetch_toggles_url)
47
+ http = Net::HTTP.new(uri.host, uri.port)
48
+ http.open_timeout = Unleash.configuration.timeout # in seconds
49
+ http.read_timeout = Unleash.configuration.timeout # in seconds
50
+ request = Net::HTTP::Get.new(uri.request_uri)
51
+ request['If-None-Match'] = self.etag unless self.etag.nil?
52
+
53
+ response = http.request(request)
54
+
55
+ Unleash.logger.debug "No changes according to the unleash server, nothing to do." if response.code == '304'
56
+ return if response.code == '304'
57
+
58
+ raise IOError, "Unleash server returned a non 200/304 HTTP result." if response.code != '200'
59
+
60
+ self.etag = response['ETag']
61
+ response_hash = JSON.parse(response.body)
62
+
63
+ if response_hash['version'] == 1
64
+ features = response_hash['features']
65
+ else
66
+ raise NotImplemented, "Version of features provided by unleash server" \
67
+ " is unsupported by this client."
68
+ end
69
+
70
+ # always synchronize with the local cache when fetching:
71
+ synchronize_with_local_cache!(features)
72
+
73
+ Unleash.logger.info "Flush changes to running client variable"
74
+ update_client!
75
+
76
+ Unleash.logger.info "Saved to toggle cache, will save to disk now"
77
+ save!
78
+ end
79
+
80
+ private
81
+
82
+ def synchronize_with_local_cache!(features)
83
+ if self.toggle_cache != features
84
+ self.toggle_lock.synchronize do
85
+ self.toggle_cache = features
86
+ end
87
+
88
+ # notify all threads waiting for this resource to no longer wait
89
+ self.toggle_resource.broadcast
90
+ end
91
+ end
92
+
93
+ def update_client!
94
+ if Unleash.toggles != self.toggles
95
+ Unleash.logger.info "Updating toggles to main client, there has been a change in the server."
96
+ Unleash.toggles = self.toggles
97
+ end
98
+ end
99
+
100
+ def backup_file_exists?
101
+ File.exists?(backup_file)
102
+ end
103
+
104
+ def save!
105
+ begin
106
+ file = File.open(Unleash.configuration.backup_file, "w")
107
+
108
+ self.toggle_lock.synchronize do
109
+ file.write(self.toggle_cache.to_json)
110
+ end
111
+ rescue Exception => e
112
+ # This is not really the end of the world. Swallowing the exception.
113
+ Unleash.logger.error "Unable to save backup file."
114
+ ensure
115
+ file.close unless file.nil?
116
+ end
117
+ end
118
+
119
+ def read!
120
+ Unleash.logger.debug "read!()"
121
+ return nil unless File.exists?(Unleash.configuration.backup_file)
122
+
123
+ begin
124
+ file = File.open(Unleash.configuration.backup_file, "r")
125
+ line_cache = ""
126
+ file.each_line do |line|
127
+ line_cache += line
128
+ end
129
+
130
+ backup_as_hash = JSON.parse(line_cache)
131
+ synchronize_with_local_cache!(backup_as_hash)
132
+
133
+ rescue IOError => e
134
+ Unleash.logger.error "Unable to read the backup_file."
135
+ rescue JSON::ParserError => e
136
+ Unleash.logger.error "Unable to parse JSON from existing backup_file."
137
+ rescue Exception => e
138
+ Unleash.logger.error "Unable to extract valid data from backup_file."
139
+ ensure
140
+ file.close unless file.nil?
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,3 @@
1
+ module Unleash
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'unleash/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "unleash"
8
+ spec.version = Unleash::VERSION
9
+ spec.authors = ["Renato Arruda"]
10
+ spec.email = ["rarruda@rarruda.org"]
11
+ spec.licenses = ["Apache-2.0"]
12
+
13
+ spec.summary = %q{Unleash feature toggle client.}
14
+ spec.description = %q{Unleash is a feature toggle system, that gives you a great overview
15
+ over all feature toggles across all your applications and services.}
16
+ spec.homepage = "https://github.com/unleash/unleash-client-ruby"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency "murmurhash3", "~> 0.1.6"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.14"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unleash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Renato Arruda
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: murmurhash3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: |-
70
+ Unleash is a feature toggle system, that gives you a great overview
71
+ over all feature toggles across all your applications and services.
72
+ email:
73
+ - rarruda@rarruda.org
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".rspec"
80
+ - ".travis.yml"
81
+ - Gemfile
82
+ - LICENSE
83
+ - README.md
84
+ - Rakefile
85
+ - TODO.md
86
+ - bin/console
87
+ - bin/setup
88
+ - examples/simple.rb
89
+ - lib/unleash.rb
90
+ - lib/unleash/activation_strategy.rb
91
+ - lib/unleash/client.rb
92
+ - lib/unleash/configuration.rb
93
+ - lib/unleash/context.rb
94
+ - lib/unleash/feature_toggle.rb
95
+ - lib/unleash/metrics.rb
96
+ - lib/unleash/metrics_reporter.rb
97
+ - lib/unleash/scheduled_executor.rb
98
+ - lib/unleash/strategy/application_hostname.rb
99
+ - lib/unleash/strategy/base.rb
100
+ - lib/unleash/strategy/default.rb
101
+ - lib/unleash/strategy/gradual_rollout_random.rb
102
+ - lib/unleash/strategy/gradual_rollout_sessionid.rb
103
+ - lib/unleash/strategy/gradual_rollout_userid.rb
104
+ - lib/unleash/strategy/remote_address.rb
105
+ - lib/unleash/strategy/unknown.rb
106
+ - lib/unleash/strategy/user_with_id.rb
107
+ - lib/unleash/strategy/util.rb
108
+ - lib/unleash/toggle_fetcher.rb
109
+ - lib/unleash/version.rb
110
+ - unleash-client.gemspec
111
+ homepage: https://github.com/unleash/unleash-client-ruby
112
+ licenses:
113
+ - Apache-2.0
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.6.14
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Unleash feature toggle client.
135
+ test_files: []