ff-ruby-server-sdk 1.0.6 → 1.1.1

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: 1dc03fbc008625a0836f68d7633ed828fca000860ca0ed4bb570acac0e5d0b1c
4
- data.tar.gz: 4d7b5998b0fa349c54f08589a094c2d9674989dde242bd0ca173222e92d579c1
3
+ metadata.gz: ab04a74816651d46324faf27b7aac91d4d6ef5941c01eee61a674fb36cf54bd1
4
+ data.tar.gz: f52922e05d01426ba12f63e07b5f6e25fbfe477f83c44d13cba72038b35932dc
5
5
  SHA512:
6
- metadata.gz: 8c0be4cdc264f5efa858b9746824e0304a255f21ff1c8b030e3b9556766f895c7864c26e279bab9370d696e5005934c55a79a11be23d146e8fd477cc82ad2bcf
7
- data.tar.gz: a17fdcd1fbcdbc8d4942acf543b06751648a6c986c3abb82e0bb7b239ce0e983349fa6cab0dc9a679df4b4250bce527ab76c93c27560edfa839b328ffb06bd53
6
+ metadata.gz: 2d4038c666ffe22ce8170e17fd8bec516068c0e4fbb5034b706e5efafd3e612d06a4b9cd2b03e63d70bbb9b7b13f8e26360f5f5b26684bcb40904228afa449d7
7
+ data.tar.gz: 9164338df82c7c97cd2620d302e6d7b8f43299b2a5580988288ddf4ad6bf5b3469519d8ece420333b2c28ea908de27076d5e43905ebe8045b3c722f4909dbc69
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # [1.1.0]
2
+
3
+ - [FFM-7285] - Remove Metrics queue and implement Map for better memory usage
4
+ - [FFM-7325] - Improve authentication retry logic
5
+ - [FFM-6926] - Add basic Ruby on Rails example
6
+ - [FFM-6965] - Fixes analytics_enabled(false) which didn't fully turn off metrics
7
+ - [FFM-7005] - Add TLS support custom CAs and remove sse-client
8
+ - [FFM-7292] - Add HTTP headers for diagnostics
9
+
1
10
  # [1.0.6]
2
11
 
3
12
  - [FFM-3715] - Updates open api dependency issue
data/Gemfile CHANGED
@@ -16,10 +16,16 @@ gem "moneta"
16
16
 
17
17
  # SSE support:
18
18
  gem "rest-client"
19
- gem "sse-client"
20
19
 
21
20
  # Concurrency support:
22
- gem "concurrent-ruby", require: "concurrent"
21
+ gem "concurrent-ruby", "1.1.10", require: "concurrent"
23
22
 
24
23
  # Evaluator dependencies:
25
24
  gem "murmurhash3"
25
+
26
+ gem "typhoeus"
27
+
28
+ group :test do
29
+ gem 'simplecov', '~> 0.21.2'
30
+ end
31
+
data/README.md CHANGED
@@ -81,7 +81,7 @@ client.close
81
81
 
82
82
  ```bash
83
83
  # Install the deps
84
- gem install ff-ruby-server-sdk typhoeus openapi_client
84
+ gem install ff-ruby-server-sdk typhoeus
85
85
 
86
86
  # Set your API Key
87
87
  export FF_API_KEY=<your key here>
@@ -96,7 +96,7 @@ use docker to quickly get started
96
96
 
97
97
  ```bash
98
98
  # Install the package
99
- docker run -v $(pwd):/app -w /app -e FF_API_KEY=$FF_API_KEY ruby:2.7-buster gem install --install-dir ./gems ff-ruby-server-sdk typhoeus openapi_client
99
+ docker run -v $(pwd):/app -w /app -e FF_API_KEY=$FF_API_KEY ruby:2.7-buster gem install --install-dir ./gems ff-ruby-server-sdk typhoeus
100
100
 
101
101
  # Run the script
102
102
  docker run -v $(pwd):/app -w /app -e FF_API_KEY=$FF_API_KEY -e GEM_HOME=/app/gems ruby:2.7-buster ruby ./example/getting_started/getting_started.rb
@@ -108,6 +108,7 @@ Further examples and config options are in the further reading section:
108
108
 
109
109
  [Further Reading](docs/further_reading.md)
110
110
 
111
+ [Ruby on Rails example](ruby_on_rails_example/README.md)
111
112
 
112
113
  -------------------------
113
114
  [Harness](https://www.harness.io/) is a feature management platform that helps teams to build better software and to
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ruby -v
4
+ apt-get update
5
+ apt-get install -y npm
6
+ apt-get install -y maven
7
+ apt-get install -y jq
8
+ mvn --version
9
+ npm install @openapitools/openapi-generator-cli -g
10
+ npm install @openapitools/openapi-generator-cli -D
11
+ gem install minitest-junit
12
+ sh scripts/install.sh
13
+ gem env
14
+ gem install ff-ruby-server-sdk typhoeus
15
+ ruby test/ff/ruby/server/sdk/sdk_test.rb --junit
@@ -8,6 +8,7 @@ require "securerandom"
8
8
 
9
9
  $stdout.sync = true
10
10
  logger = Logger.new $stdout
11
+ logger.level = Logger::DEBUG
11
12
 
12
13
  # API Key
13
14
  apiKey = ENV['FF_API_KEY'] || 'changeme'
@@ -32,7 +33,7 @@ target = Target.new("RubySDK", identifier="rubysdk", attributes={"location": "em
32
33
  # Loop forever reporting the state of the flag
33
34
  loop do
34
35
  result = client.bool_variation(flagName, target, false)
35
- logger.info "Flag variation: #{result}"
36
+ logger.info "Flag #{flagName} is set to: #{result}"
36
37
  sleep 10
37
38
  end
38
39
 
@@ -0,0 +1,67 @@
1
+ require 'ff/ruby/server/sdk/api/config'
2
+ require 'ff/ruby/server/sdk/dto/target'
3
+ require 'ff/ruby/server/sdk/api/cf_client'
4
+ require 'ff/ruby/server/sdk/api/config_builder'
5
+
6
+ require "logger"
7
+ require "securerandom"
8
+
9
+ $stdout.sync = true
10
+ logger = Logger.new $stdout
11
+
12
+ # API Key
13
+ apiKey = ENV['FF_API_KEY'] || 'changeme'
14
+
15
+ # Flag Name
16
+ flagName = ENV['FF_FLAG_NAME'] || 'harnessappdemodarkmode'
17
+
18
+ logger.info "Harness Ruby SDK TLS example"
19
+
20
+ =begin
21
+ For ff servers with a custom or private CAs, you can use 'tls_ca_cert' to pass in the CA bundle in ASCII PEM format.
22
+ You should also include any intermediate CAs so the full trust chain can be resolved. Typhoeus HTTP client uses libcurl
23
+ underneath, when developing you should enable debugging(true) to get more detailed error diagnostics logged, which
24
+ aren't reported through OpenAPI. Common errors include:
25
+
26
+ SSL peer certificate or SSH remote key was not OK - you have an invalid or missing CA for the server you're trying
27
+ to connect to. It can also mean the server hostname and request
28
+ hostname don't match.
29
+ SSL: no alternative certificate subject name - The hostname or IP used in your SDK URLs do not match the SANs
30
+ matches target host name ‘<host>' configured in the cert sent by the web server. You should either
31
+ fix your URLs or ensure the SANs in the X.509 cert are configured
32
+ correctly.
33
+
34
+ The example below assumes you have an ff-server (or proxy) configured with TLS for a server hosted on
35
+ 'ffserver:8000' where the web server's cert has a SANs with DNS entry 'ffserver'. CA.crt tells the SDK you trust this
36
+ server.
37
+
38
+ Typhoeus/libcurl by default has its default CA bundle stored at /etc/ssl/cert.pem. You can append your CA here if
39
+ you choose not to use 'tls_ca_cert'.
40
+
41
+ =end
42
+
43
+
44
+ client = CfClient.instance
45
+ client.init(apiKey, ConfigBuilder.new.logger(logger)
46
+ .event_url("https://ffserver:8001/api/1.0")
47
+ .config_url("https://ffserver:8000/api/1.0")
48
+ .tls_ca_cert("/path/to/CA.crt")
49
+ .debugging(true)
50
+ .build)
51
+
52
+ client.wait_for_initialization
53
+
54
+
55
+ # Create a target (different targets can get different results based on rules. This include a custom attribute 'location')
56
+ target = Target.new("RubySDK", identifier="rubysdk", attributes={"location": "emea"})
57
+
58
+ # Loop forever reporting the state of the flag
59
+ loop do
60
+ result = client.bool_variation(flagName, target, false)
61
+ logger.info "#{flagName} flag variation: #{result}"
62
+ sleep 10
63
+ end
64
+
65
+ client.close
66
+
67
+
@@ -2,56 +2,47 @@ require_relative "../common/closeable"
2
2
 
3
3
  class AuthService < Closeable
4
4
 
5
- def initialize(connector = nil, poll_interval_in_sec = 60, callback = nil, logger = nil)
5
+ def initialize(connector, callback, logger, retry_delay_ms = 6000)
6
6
 
7
7
  unless connector.kind_of?(Connector)
8
-
9
8
  raise "The 'connector' parameter must be of '" + Connector.to_s + "' data type"
10
9
  end
11
10
 
12
11
  unless callback.kind_of?(ClientCallback)
13
-
14
12
  raise "The 'callback' parameter must be of '" + ClientCallback.to_s + "' data type"
15
13
  end
16
14
 
15
+ @logger = logger
17
16
  @callback = callback
18
17
  @connector = connector
19
- @poll_interval_in_sec = poll_interval_in_sec
20
-
21
- if logger != nil
22
-
23
- @logger = logger
24
- else
25
-
26
- @logger = Logger.new(STDOUT)
27
- end
18
+ @retry_delay_ms = retry_delay_ms
19
+ @authenticated = false
28
20
  end
29
21
 
30
22
  def start_async
31
-
32
23
  @logger.debug "Async starting: " + self.to_s
33
24
 
34
- @ready = true
35
-
36
- @thread = Thread.new do
37
-
38
- @logger.debug "Async started: " + self.to_s
39
-
40
- while @ready do
41
-
42
- @logger.debug "Async auth iteration"
43
-
44
- if @connector.authenticate
25
+ @thread = Thread.new :report_on_exception => true do
26
+ attempt = 1
27
+ until @authenticated do
28
+ http_code = @connector.authenticate
45
29
 
30
+ if http_code == 200
31
+ @authenticated = true
46
32
  @callback.on_auth_success
47
33
  stop_async
48
- @logger.info "Stopping Auth service"
34
+ elsif should_retry_http_code http_code
35
+ delay_ms = @retry_delay_ms * [10, attempt].min
36
+ @logger.warn "Got HTTP code #{http_code} while authenticating on attempt #{attempt}, will retry in #{delay_ms} ms"
37
+ sleep(delay_ms/1000)
38
+ attempt += 1
39
+ SdkCodes::warn_auth_retying @logger, attempt
49
40
  else
50
-
51
- @logger.error "Exception while authenticating, retry in " + @poll_interval_in_sec.to_s + " seconds"
41
+ @logger.warn "Auth Service got HTTP code #{http_code} while authenticating, will not attempt to reconnect"
42
+ @callback.on_auth_failed
43
+ stop_async
44
+ next
52
45
  end
53
-
54
- sleep(@poll_interval_in_sec)
55
46
  end
56
47
  end
57
48
 
@@ -59,33 +50,50 @@ class AuthService < Closeable
59
50
  end
60
51
 
61
52
  def close
62
-
63
53
  stop_async
64
54
  end
65
55
 
66
- def on_auth_success
56
+ protected
67
57
 
68
- unless @callback == nil
58
+ def on_auth_success
69
59
 
60
+ if @callback != nil
70
61
  unless @callback.kind_of?(ClientCallback)
71
-
72
62
  raise "Expected '" + ClientCallback.to_s + "' data type for the callback"
73
63
  end
74
-
75
64
  @callback.on_auth_success
76
65
  end
77
66
  end
78
67
 
79
- protected
80
-
81
68
  def stop_async
82
-
83
- @ready = false
84
-
85
69
  if @thread != nil
86
-
70
+ @logger.debug "Stopping Auth service, status=#{@thread.status}"
87
71
  @thread.exit
88
72
  @thread = nil
73
+ @logger.debug "Stopping Auth service done"
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def is_authenticated
80
+ @authenticated
81
+ end
82
+
83
+ def should_retry_http_code(code)
84
+ # 408 request timeout
85
+ # 425 too early
86
+ # 429 too many requests
87
+ # 500 internal server error
88
+ # 502 bad gateway
89
+ # 503 service unavailable
90
+ # 504 gateway timeout
91
+ # -1 OpenAPI error (timeout etc)
92
+ case code
93
+ when 408,425,429,500,502,503,504,-1
94
+ return true
95
+ else
96
+ return false
89
97
  end
90
98
  end
91
99
  end
@@ -1,5 +1,5 @@
1
- require "openapi_client"
2
1
 
2
+ require_relative "../../generated/lib/openapi_client"
3
3
  require_relative "../common/closeable"
4
4
  require_relative "inner_client"
5
5
 
@@ -2,44 +2,49 @@ require_relative "../common/closeable"
2
2
 
3
3
  class ClientCallback < Closeable
4
4
 
5
+ TBI = RuntimeError.new("To be implemented")
6
+
5
7
  def initialize
6
8
  super
7
-
8
- @tbi = "To be implemented"
9
9
  end
10
10
 
11
11
  def on_auth_success
12
12
 
13
- raise @tbi
13
+ raise TBI
14
+ end
15
+
16
+ def on_auth_failed
17
+
18
+ raise TBI
14
19
  end
15
20
 
16
21
  def on_authorized
17
22
 
18
- raise @tbi
23
+ raise TBI
19
24
  end
20
25
 
21
26
  def is_closing
22
27
 
23
- raise @tbi
28
+ raise TBI
24
29
  end
25
30
 
26
31
  def on_processor_ready(processor)
27
32
 
28
- raise @tbi
33
+ raise TBI
29
34
  end
30
35
 
31
36
  def on_update_processor_ready
32
37
 
33
- raise @tbi
38
+ raise TBI
34
39
  end
35
40
 
36
41
  def on_metrics_processor_ready
37
42
 
38
- raise @tbi
43
+ raise TBI
39
44
  end
40
45
 
41
46
  def update(message, manual)
42
47
 
43
- raise @tbi
48
+ raise TBI
44
49
  end
45
50
  end
@@ -7,7 +7,7 @@ class Config
7
7
  attr_accessor :config_url, :event_url, :stream_enabled, :poll_interval_in_seconds, :analytics_enabled,
8
8
  :frequency, :buffer_size, :all_attributes_private, :private_attributes, :connection_timeout,
9
9
  :read_timeout, :write_timeout, :debugging, :metrics_service_acceptable_duration, :cache, :store,
10
- :logger
10
+ :logger, :ssl_ca_cert
11
11
 
12
12
  # Static:
13
13
  class << self
@@ -35,7 +35,7 @@ class Config
35
35
 
36
36
  @frequency = @@min_frequency
37
37
 
38
- @buffer_size = 1024
38
+ @buffer_size = 2048
39
39
 
40
40
  @all_attributes_private = false
41
41
 
@@ -82,7 +82,7 @@ class Config
82
82
 
83
83
  def verify_ssl
84
84
 
85
- nil
85
+ true
86
86
  end
87
87
 
88
88
  def cert_file
@@ -97,7 +97,7 @@ class Config
97
97
 
98
98
  def ssl_ca_cert
99
99
 
100
- nil
100
+ @ssl_ca_cert
101
101
  end
102
102
 
103
103
  def client_side_validation
@@ -83,6 +83,11 @@ class ConfigBuilder
83
83
  self
84
84
  end
85
85
 
86
+ def tls_ca_cert(cert_file)
87
+ @config.ssl_ca_cert = cert_file
88
+ self
89
+ end
90
+
86
91
  def logger(logger)
87
92
 
88
93
  @config.logger = logger
@@ -3,6 +3,7 @@ require "murmurhash3"
3
3
 
4
4
  require_relative "evaluation"
5
5
  require_relative "../common/repository"
6
+ require_relative "../common/sdk_codes"
6
7
 
7
8
  class Evaluator < Evaluation
8
9
 
@@ -33,6 +34,7 @@ class Evaluator < Evaluation
33
34
  return variation.value == "true"
34
35
  end
35
36
 
37
+ SdkCodes::warn_default_variation_served @logger, identifier, target, default_value.to_s
36
38
  default_value
37
39
  end
38
40
 
@@ -45,6 +47,7 @@ class Evaluator < Evaluation
45
47
  return variation.value
46
48
  end
47
49
 
50
+ SdkCodes::warn_default_variation_served @logger, identifier, target, default_value.to_s
48
51
  default_value
49
52
  end
50
53
 
@@ -57,6 +60,7 @@ class Evaluator < Evaluation
57
60
  return variation.value.to_i
58
61
  end
59
62
 
63
+ SdkCodes::warn_default_variation_served @logger, identifier, target, default_value.to_s
60
64
  default_value
61
65
  end
62
66
 
@@ -68,6 +72,7 @@ class Evaluator < Evaluation
68
72
  return variation.value.to_f
69
73
  end
70
74
 
75
+ SdkCodes::warn_default_variation_served @logger, identifier, target, default_value.to_s
71
76
  default_value
72
77
  end
73
78
 
@@ -80,6 +85,7 @@ class Evaluator < Evaluation
80
85
  return JSON.parse(variation.value)
81
86
  end
82
87
 
88
+ SdkCodes::warn_default_variation_served @logger, identifier, target, default_value.to_s
83
89
  default_value
84
90
  end
85
91
 
@@ -219,13 +225,13 @@ class Evaluator < Evaluation
219
225
 
220
226
  clauses.each do |clause|
221
227
 
222
- unless evaluate_clause(clause, target)
228
+ if evaluate_clause(clause, target)
223
229
 
224
- return false
230
+ return true
225
231
  end
226
232
  end
227
233
 
228
- true
234
+ false
229
235
  end
230
236
 
231
237
  def evaluate_clause(clause, target)
@@ -295,7 +301,7 @@ class Evaluator < Evaluation
295
301
 
296
302
  if operator == "in"
297
303
 
298
- return object.include?(value)
304
+ return clause.values.include?(object)
299
305
  end
300
306
 
301
307
  if operator == "segmentMatch"
@@ -460,60 +466,47 @@ class Evaluator < Evaluation
460
466
  prerequisites = parent_feature_config.prerequisites
461
467
 
462
468
  if prerequisites != nil && !prerequisites.empty?
469
+ @logger.debug "Checking prerequisite #{prerequisites.to_s} of flag #{parent_feature_config.feature}"
463
470
 
464
- @logger.debug "Checking pre requisites " + prerequisites.to_s + " of parent feature " + parent_feature_config.feature.to_s
465
-
466
- prerequisites.each do |pqs|
467
-
468
- pre_req_feature = pqs.feature
469
-
470
- pre_req_feature_config = @repository.get_flag(pre_req_feature)
471
-
472
- if pre_req_feature_config == nil
473
-
474
- @logger.debug "Could not retrieve the pre requisite details of feature flag: " + pre_req_feature.to_s
471
+ prerequisites.each do |pre_req|
472
+ pre_req_flag = @repository.get_flag(pre_req.feature)
475
473
 
474
+ if pre_req_flag == nil
475
+ @logger.debug "Could not retrieve the pre requisite details of feature flag: #{pre_req.feature}"
476
476
  return true
477
477
  end
478
478
 
479
- pre_req_evaluated_variation = evaluate_flag(pre_req_feature_config, target)
480
-
481
- if pre_req_evaluated_variation == nil
482
-
483
- @logger.debug "Could not evaluate the prerequisite details of feature flag: " + pre_req_feature.to_s
479
+ evaluated_pre_req = evaluate_flag(pre_req_flag, target)
484
480
 
481
+ if evaluated_pre_req == nil
482
+ @logger.debug "Could not evaluate the prerequisite details of feature flag: #{pre_req.feature}"
485
483
  return true
486
484
  end
487
485
 
488
- @logger.debug "Pre requisite flag " + pre_req_feature_config.feature + " has variation " +
489
- pre_req_evaluated_variation.to_s + " for target " + target.to_s
490
-
491
- valid_pre_req_variations = pqs.variations
492
-
493
- @logger.debug "Pre requisite flag " + pre_req_feature_config.to_s + " should have the variations " +
494
- valid_pre_req_variations.to_s
495
-
496
486
  none_match = true
497
-
498
- valid_pre_req_variations.each do |element|
499
-
500
- if element.include?(pre_req_evaluated_variation.identifier)
501
-
487
+ pre_req.variations.each do |next_variation|
488
+ if next_variation.include?(evaluated_pre_req.identifier)
502
489
  none_match = false
503
490
  break
504
491
  end
505
492
  end
506
493
 
507
494
  if none_match
508
-
495
+ @logger.debug "Prerequisite flag #{pre_req_flag.feature} has no matching variations for flag #{parent_feature_config.feature}"
509
496
  return false
510
497
  else
511
-
512
- return check_pre_requisite(pre_req_feature_config, target)
498
+ unless check_pre_requisite(pre_req_flag, target)
499
+ @logger.debug "Prerequisite flag #{pre_req_flag.feature} is switched off for flag #{parent_feature_config.feature}"
500
+ return false
501
+ end
513
502
  end
514
- end
503
+ end # prerequisites.each
504
+
505
+ @logger.debug "All prerequisite flags are switched on for flag #{parent_feature_config.feature}"
506
+ return true
515
507
  end
516
508
 
509
+ @logger.debug "No prerequisite flags present for flag #{parent_feature_config.feature}, skipped"
517
510
  true
518
511
  end
519
512