ff-ruby-server-sdk 1.0.6 → 1.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.
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