ff-ruby-server-sdk 1.0.6 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +8 -2
- data/README.md +3 -2
- data/buildInContainer.sh +15 -0
- data/example/getting_started/getting_started.rb +1 -1
- data/example/tls_example/tls_example.rb +67 -0
- data/lib/ff/ruby/server/sdk/api/auth_service.rb +48 -40
- data/lib/ff/ruby/server/sdk/api/cf_client.rb +1 -1
- data/lib/ff/ruby/server/sdk/api/client_callback.rb +14 -9
- data/lib/ff/ruby/server/sdk/api/config.rb +4 -4
- data/lib/ff/ruby/server/sdk/api/config_builder.rb +5 -0
- data/lib/ff/ruby/server/sdk/api/inner_client.rb +11 -5
- data/lib/ff/ruby/server/sdk/api/inner_client_flag_evaluate_callback.rb +1 -1
- data/lib/ff/ruby/server/sdk/api/inner_client_updater.rb +1 -1
- data/lib/ff/ruby/server/sdk/api/metrics_event.rb +18 -6
- data/lib/ff/ruby/server/sdk/api/metrics_processor.rb +96 -170
- data/lib/ff/ruby/server/sdk/connector/events.rb +53 -57
- data/lib/ff/ruby/server/sdk/connector/harness_connector.rb +42 -17
- data/lib/ff/ruby/server/sdk/version.rb +1 -1
- data/scripts/openapi.sh +2 -5
- data/scripts/sdk_specs.sh +1 -1
- metadata +17 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09354edeb352628c1f9744e9c685dc27c2547fc81f3db0e9254dfe8bc9122d58'
|
4
|
+
data.tar.gz: 7f011367b6a2d16162b42f2f02db2730cd04dcff6bf75170d90b2c065e4203a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f3f09d1185dc89443b8b55bf9fe3bdb5ae6105cae81ec861df2affe25b2ade4b7aa7c3620dc1fc41cdde1979b624c8eef3d57e15474ffd8c2934d8dbc03b843
|
7
|
+
data.tar.gz: 02eabc3cce448322b89c5901e183b8d2e9f25d47b605569d26aa07743f669e51d2779efc2c87179e711e37e68f381c6a1458352568e8bff399f19a548fc2b5cd
|
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
|
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
|
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
|
data/buildInContainer.sh
ADDED
@@ -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
|
@@ -32,7 +32,7 @@ target = Target.new("RubySDK", identifier="rubysdk", attributes={"location": "em
|
|
32
32
|
# Loop forever reporting the state of the flag
|
33
33
|
loop do
|
34
34
|
result = client.bool_variation(flagName, target, false)
|
35
|
-
logger.info "Flag
|
35
|
+
logger.info "Flag #{flagName} is set to: #{result}"
|
36
36
|
sleep 10
|
37
37
|
end
|
38
38
|
|
@@ -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
|
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
|
-
@
|
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
|
-
@
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
+
@logger.info "Retrying to authenticate, attempt #{attempt}..."
|
49
40
|
else
|
50
|
-
|
51
|
-
@
|
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
|
-
|
56
|
+
protected
|
67
57
|
|
68
|
-
|
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.info "Stopping Auth service, status=#{@thread.status}"
|
87
71
|
@thread.exit
|
88
72
|
@thread = nil
|
73
|
+
@logger.info "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
|
@@ -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
|
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
|
23
|
+
raise TBI
|
19
24
|
end
|
20
25
|
|
21
26
|
def is_closing
|
22
27
|
|
23
|
-
raise
|
28
|
+
raise TBI
|
24
29
|
end
|
25
30
|
|
26
31
|
def on_processor_ready(processor)
|
27
32
|
|
28
|
-
raise
|
33
|
+
raise TBI
|
29
34
|
end
|
30
35
|
|
31
36
|
def on_update_processor_ready
|
32
37
|
|
33
|
-
raise
|
38
|
+
raise TBI
|
34
39
|
end
|
35
40
|
|
36
41
|
def on_metrics_processor_ready
|
37
42
|
|
38
|
-
raise
|
43
|
+
raise TBI
|
39
44
|
end
|
40
45
|
|
41
46
|
def update(message, manual)
|
42
47
|
|
43
|
-
raise
|
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 =
|
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
|
-
|
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
|
-
|
100
|
+
@ssl_ca_cert
|
101
101
|
end
|
102
102
|
|
103
103
|
def client_side_validation
|
@@ -97,17 +97,21 @@ class InnerClient < ClientCallback
|
|
97
97
|
@poll_processor.start
|
98
98
|
|
99
99
|
if @config.stream_enabled
|
100
|
-
|
101
100
|
@update_processor.start
|
102
101
|
end
|
103
102
|
|
104
103
|
if @config.analytics_enabled
|
105
|
-
|
106
104
|
@metrics_processor.start
|
107
105
|
end
|
108
106
|
|
109
107
|
end
|
110
108
|
|
109
|
+
def on_auth_failed
|
110
|
+
@config.logger.warn "Authentication failed with a non-recoverable error - defaults will be served"
|
111
|
+
@initialized = true
|
112
|
+
|
113
|
+
end
|
114
|
+
|
111
115
|
def close
|
112
116
|
|
113
117
|
@config.logger.info "Closing the client: " + self.to_s
|
@@ -268,12 +272,13 @@ class InnerClient < ClientCallback
|
|
268
272
|
@metrics_processor.init(@connector, @config, @metrics_callback)
|
269
273
|
|
270
274
|
@evaluator = Evaluator.new(@repository, logger = @config.logger)
|
271
|
-
@evaluator_callback = InnerClientFlagEvaluateCallback.new(@metrics_processor, logger = @config.logger)
|
272
275
|
|
273
|
-
@
|
276
|
+
if @config.analytics_enabled
|
277
|
+
@evaluator_callback = InnerClientFlagEvaluateCallback.new(@metrics_processor, logger = @config.logger)
|
278
|
+
end
|
274
279
|
|
280
|
+
@auth_service = AuthService.new(
|
275
281
|
connector = @connector,
|
276
|
-
poll_interval_in_sec = @config.poll_interval_in_seconds,
|
277
282
|
callback = self,
|
278
283
|
logger = @config.logger
|
279
284
|
)
|
@@ -312,4 +317,5 @@ class InnerClient < ClientCallback
|
|
312
317
|
|
313
318
|
@my_mutex.synchronize(&block)
|
314
319
|
end
|
320
|
+
|
315
321
|
end
|
@@ -25,6 +25,6 @@ class InnerClientFlagEvaluateCallback < FlagEvaluateCallback
|
|
25
25
|
|
26
26
|
@logger.debug "Processing evaluation: " + feature_config.feature.to_s + ", " + target.identifier.to_s
|
27
27
|
|
28
|
-
@metrics_processor.
|
28
|
+
@metrics_processor.register_evaluation(target, feature_config, variation)
|
29
29
|
end
|
30
30
|
end
|
@@ -2,15 +2,27 @@ class MetricsEvent
|
|
2
2
|
|
3
3
|
attr_accessor :feature_config, :target, :variation
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
feature_config,
|
8
|
-
target,
|
9
|
-
variation
|
10
|
-
)
|
5
|
+
def initialize(feature_config, target, variation)
|
11
6
|
|
12
7
|
@target = target
|
13
8
|
@variation = variation
|
14
9
|
@feature_config = feature_config
|
10
|
+
freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
eql?(other)
|
15
|
+
end
|
16
|
+
|
17
|
+
def eql?(other)
|
18
|
+
feature_config.feature == other.feature_config.feature and
|
19
|
+
variation.identifier == other.variation.identifier and
|
20
|
+
target.identifier == other.target.identifier
|
15
21
|
end
|
22
|
+
|
23
|
+
def hash
|
24
|
+
feature_config.feature.hash | variation.identifier.hash | target.identifier.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
|
16
28
|
end
|
@@ -9,25 +9,47 @@ require_relative "../api/summary_metrics"
|
|
9
9
|
|
10
10
|
class MetricsProcessor < Closeable
|
11
11
|
|
12
|
-
|
12
|
+
class FrequencyMap < Concurrent::Map
|
13
|
+
def initialize(options = nil, &block)
|
14
|
+
super
|
15
|
+
end
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
def increment(key)
|
18
|
+
compute(key) do |old_value|
|
19
|
+
if old_value == nil; 1 else old_value + 1 end
|
20
|
+
end
|
21
|
+
end
|
18
22
|
|
19
|
-
|
23
|
+
def get(key)
|
24
|
+
self[key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def drain_to_map
|
28
|
+
result = {}
|
29
|
+
each_key do |key|
|
30
|
+
result[key] = 0
|
31
|
+
end
|
32
|
+
result.each_key do |key|
|
33
|
+
value = get_and_set(key, 0)
|
34
|
+
result[key] = value
|
35
|
+
delete_pair(key, 0)
|
36
|
+
end
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
20
40
|
|
41
|
+
|
42
|
+
def init(connector, config, callback)
|
43
|
+
|
44
|
+
unless connector.kind_of?(Connector)
|
21
45
|
raise "The 'connector' must be of '" + Connector.to_s + "' data type"
|
22
46
|
end
|
23
47
|
|
24
48
|
unless callback.kind_of?(MetricsCallback)
|
25
|
-
|
26
49
|
raise "The 'callback' must be of '" + MetricsCallback.to_s + "' data type"
|
27
50
|
end
|
28
51
|
|
29
52
|
unless config.kind_of?(Config)
|
30
|
-
|
31
53
|
raise "The 'config' must be of '" + Config.to_s + "' data type"
|
32
54
|
end
|
33
55
|
|
@@ -51,247 +73,151 @@ class MetricsProcessor < Closeable
|
|
51
73
|
@feature_name_attribute = "featureName"
|
52
74
|
@variation_identifier_attribute = "variationIdentifier"
|
53
75
|
|
54
|
-
@
|
55
|
-
|
76
|
+
@executor = Concurrent::FixedThreadPool.new(10)
|
77
|
+
|
78
|
+
@frequency_map = FrequencyMap.new
|
79
|
+
|
80
|
+
@max_buffer_size = config.buffer_size - 1
|
56
81
|
|
57
82
|
@callback.on_metrics_ready
|
58
83
|
end
|
59
84
|
|
60
85
|
def start
|
61
|
-
|
62
86
|
@config.logger.info "Starting metrics processor with request interval: " + @config.frequency.to_s
|
63
87
|
start_async
|
64
88
|
end
|
65
89
|
|
66
90
|
def stop
|
67
|
-
|
68
91
|
@config.logger.info "Stopping metrics processor"
|
69
92
|
stop_async
|
70
93
|
end
|
71
94
|
|
72
95
|
def close
|
73
|
-
|
74
96
|
stop
|
75
97
|
@config.logger.info "Closing metrics processor"
|
76
98
|
end
|
77
99
|
|
78
|
-
def
|
79
|
-
|
80
|
-
target,
|
81
|
-
feature_config,
|
82
|
-
variation
|
83
|
-
)
|
84
|
-
|
85
|
-
@executor.post do
|
86
|
-
|
87
|
-
@config.logger.debug "Pushing to the metrics queue: START"
|
88
|
-
|
89
|
-
event = MetricsEvent.new(feature_config, target, variation)
|
90
|
-
@queue.push(event)
|
91
|
-
|
92
|
-
@config.logger.debug "Pushing to the metrics queue: END, queue size: " + @queue.size.to_s
|
93
|
-
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def send_data_and_reset_cache(data)
|
98
|
-
|
99
|
-
@config.logger.debug "Reading from queue and building cache"
|
100
|
-
|
101
|
-
@jar_version = get_version
|
102
|
-
|
103
|
-
unless data.empty?
|
104
|
-
|
105
|
-
map = {}
|
106
|
-
|
107
|
-
data.each do |event|
|
108
|
-
|
109
|
-
new_value = 1
|
110
|
-
current = map[event]
|
111
|
-
|
112
|
-
if current != nil
|
113
|
-
|
114
|
-
new_value = current + 1
|
115
|
-
end
|
116
|
-
|
117
|
-
map[event] = new_value
|
118
|
-
end
|
119
|
-
|
120
|
-
metrics = prepare_summary_metrics_body(map)
|
121
|
-
|
122
|
-
if !metrics.metrics_data.empty? && !metrics.target_data.empty?
|
100
|
+
def register_evaluation(target, feature_config, variation)
|
123
101
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end_time = (Time.now.to_f * 1000).to_i
|
129
|
-
|
130
|
-
if end_time - start_time > @config.metrics_service_acceptable_duration
|
131
|
-
|
132
|
-
@config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
|
133
|
-
end
|
102
|
+
if @frequency_map.size > @max_buffer_size
|
103
|
+
@config.logger.warn "metrics buffer is full #{@frequency_map.size} - flushing metrics"
|
104
|
+
@executor.post do
|
105
|
+
run_one_iteration
|
134
106
|
end
|
135
|
-
|
136
|
-
@global_target_set.merge(@staging_target_set)
|
137
|
-
@staging_target_set.clear
|
138
107
|
end
|
108
|
+
|
109
|
+
event = MetricsEvent.new(feature_config, target, variation)
|
110
|
+
@frequency_map.increment event
|
139
111
|
end
|
140
112
|
|
141
|
-
|
113
|
+
private
|
142
114
|
|
143
115
|
def run_one_iteration
|
116
|
+
send_data_and_reset_cache @frequency_map.drain_to_map
|
144
117
|
|
145
|
-
@config.logger.debug "
|
146
|
-
|
147
|
-
data = []
|
118
|
+
@config.logger.debug "metrics: frequency map size #{@frequency_map.size}. global target size #{@global_target_set.size}"
|
119
|
+
end
|
148
120
|
|
149
|
-
|
121
|
+
def send_data_and_reset_cache(map)
|
122
|
+
metrics = prepare_summary_metrics_body(map)
|
150
123
|
|
151
|
-
|
152
|
-
|
124
|
+
if !metrics.metrics_data.empty? && !metrics.target_data.empty?
|
125
|
+
start_time = (Time.now.to_f * 1000).to_i
|
126
|
+
@connector.post_metrics(metrics)
|
127
|
+
end_time = (Time.now.to_f * 1000).to_i
|
128
|
+
if end_time - start_time > @config.metrics_service_acceptable_duration
|
129
|
+
@config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
|
130
|
+
end
|
153
131
|
end
|
154
132
|
|
155
|
-
|
156
|
-
|
133
|
+
@global_target_set.merge(@staging_target_set)
|
134
|
+
@staging_target_set.clear
|
157
135
|
|
158
|
-
|
136
|
+
end
|
159
137
|
|
160
|
-
|
138
|
+
def prepare_summary_metrics_body(freq_map)
|
161
139
|
metrics = OpenapiClient::Metrics.new({ :target_data => [], :metrics_data => [] })
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
metrics,
|
166
|
-
Target.new(
|
167
|
-
|
168
|
-
name = @global_target_name,
|
169
|
-
identifier = @global_target
|
170
|
-
)
|
171
|
-
)
|
172
|
-
|
173
|
-
data.each do |key, value|
|
174
|
-
|
175
|
-
target = key.target
|
176
|
-
|
177
|
-
add_target_data(metrics, target)
|
178
|
-
|
179
|
-
summary_metrics = prepare_summary_metrics_key(key)
|
180
|
-
|
181
|
-
summary_metrics_data[summary_metrics] = value
|
140
|
+
add_target_data(metrics, Target.new(name = @global_target_name, identifier = @global_target))
|
141
|
+
freq_map.each_key do |key|
|
142
|
+
add_target_data(metrics, key.target)
|
182
143
|
end
|
183
|
-
|
184
|
-
|
185
|
-
|
144
|
+
total_count = 0
|
145
|
+
freq_map.each do |key, value|
|
146
|
+
total_count += value
|
186
147
|
metrics_data = OpenapiClient::MetricsData.new({ :attributes => [] })
|
187
148
|
metrics_data.timestamp = (Time.now.to_f * 1000).to_i
|
188
149
|
metrics_data.count = value
|
189
150
|
metrics_data.metrics_type = "FFMETRICS"
|
190
|
-
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => key.
|
191
|
-
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => key.
|
151
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => key.feature_config.feature }))
|
152
|
+
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => key.variation.identifier }))
|
192
153
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @target_attribute, :value => @global_target }))
|
193
154
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_type, :value => @server }))
|
194
155
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_language, :value => "ruby" }))
|
195
156
|
metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_version, :value => @jar_version }))
|
196
|
-
|
197
157
|
metrics.metrics_data.push(metrics_data)
|
198
158
|
end
|
159
|
+
@config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{freq_map.size}"
|
199
160
|
|
200
161
|
metrics
|
201
162
|
end
|
202
163
|
|
203
|
-
private
|
204
|
-
|
205
|
-
def start_async
|
206
|
-
|
207
|
-
@config.logger.debug "Async starting: " + self.to_s
|
208
|
-
|
209
|
-
@ready = true
|
210
|
-
|
211
|
-
@thread = Thread.new do
|
212
|
-
|
213
|
-
@config.logger.debug "Async started: " + self.to_s
|
214
|
-
|
215
|
-
while @ready do
|
216
|
-
|
217
|
-
unless @initialized
|
218
|
-
|
219
|
-
@initialized = true
|
220
|
-
@config.logger.info "Metrics processor initialized"
|
221
|
-
end
|
222
|
-
|
223
|
-
sleep(@config.frequency)
|
224
|
-
|
225
|
-
run_one_iteration
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
@thread.run
|
230
|
-
end
|
231
|
-
|
232
|
-
def stop_async
|
233
|
-
|
234
|
-
@queue.clear
|
235
|
-
|
236
|
-
@ready = false
|
237
|
-
@initialized = false
|
238
|
-
end
|
239
|
-
|
240
|
-
def prepare_summary_metrics_key(key)
|
241
|
-
|
242
|
-
SummaryMetrics.new(
|
243
|
-
|
244
|
-
feature_name = key.feature_config.feature,
|
245
|
-
variation_identifier = key.variation.identifier,
|
246
|
-
variation_value = key.variation.value
|
247
|
-
)
|
248
|
-
end
|
249
|
-
|
250
164
|
def add_target_data(metrics, target)
|
251
165
|
|
252
166
|
target_data = OpenapiClient::TargetData.new({ :attributes => [] })
|
253
167
|
private_attributes = target.private_attributes
|
254
168
|
|
255
169
|
if !@staging_target_set.include?(target) && !@global_target_set.include?(target) && !target.is_private
|
256
|
-
|
257
170
|
@staging_target_set.add(target)
|
258
|
-
|
259
171
|
attributes = target.attributes
|
260
|
-
|
261
172
|
attributes.each do |k, v|
|
262
|
-
|
263
173
|
key_value = OpenapiClient::KeyValue.new
|
264
|
-
|
265
174
|
if !private_attributes.empty?
|
266
|
-
|
267
175
|
unless private_attributes.include?(k)
|
268
|
-
|
269
176
|
key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
|
270
177
|
end
|
271
178
|
else
|
272
|
-
|
273
179
|
key_value = OpenapiClient::KeyValue.new({ :key => k, :value => v.to_s })
|
274
180
|
end
|
275
|
-
|
276
181
|
target_data.attributes.push(key_value)
|
277
182
|
end
|
278
|
-
|
279
183
|
target_data.identifier = target.identifier
|
280
|
-
|
281
184
|
if target.name == nil || target.name == ""
|
282
|
-
|
283
185
|
target_data.name = target.identifier
|
284
186
|
else
|
285
|
-
|
286
187
|
target_data.name = target.name
|
287
188
|
end
|
288
|
-
|
289
189
|
metrics.target_data.push(target_data)
|
290
190
|
end
|
291
191
|
end
|
292
192
|
|
293
|
-
def
|
193
|
+
def start_async
|
194
|
+
@config.logger.debug "Async starting: " + self.to_s
|
195
|
+
@ready = true
|
196
|
+
@thread = Thread.new do
|
197
|
+
@config.logger.debug "Async started: " + self.to_s
|
198
|
+
while @ready do
|
199
|
+
unless @initialized
|
200
|
+
@initialized = true
|
201
|
+
@config.logger.info "Metrics processor initialized"
|
202
|
+
end
|
203
|
+
sleep(@config.frequency)
|
204
|
+
run_one_iteration
|
205
|
+
end
|
206
|
+
end
|
207
|
+
@thread.run
|
208
|
+
end
|
294
209
|
|
210
|
+
def stop_async
|
211
|
+
@ready = false
|
212
|
+
@initialized = false
|
213
|
+
end
|
214
|
+
|
215
|
+
def get_version
|
295
216
|
Ff::Ruby::Server::Sdk::VERSION
|
296
217
|
end
|
218
|
+
|
219
|
+
def get_frequency_map
|
220
|
+
@frequency_map
|
221
|
+
end
|
222
|
+
|
297
223
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require "json"
|
2
|
-
require
|
2
|
+
require 'restclient'
|
3
3
|
|
4
4
|
require_relative './service'
|
5
5
|
|
@@ -10,109 +10,105 @@ class Events < Service
|
|
10
10
|
url,
|
11
11
|
headers,
|
12
12
|
updater,
|
13
|
-
|
13
|
+
config
|
14
14
|
)
|
15
15
|
|
16
|
-
|
16
|
+
@url = url
|
17
|
+
@headers = headers
|
18
|
+
@headers['params'] = {}
|
19
|
+
@updater = updater
|
20
|
+
@config = config
|
17
21
|
|
22
|
+
if @updater != nil
|
18
23
|
unless @updater.kind_of?(Updater)
|
19
|
-
|
20
24
|
raise "The 'callback' parameter must be of '" + Updater.to_s + "' data type"
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
24
|
-
@
|
25
|
-
|
26
|
-
if logger != nil
|
27
|
-
|
28
|
-
@logger = logger
|
28
|
+
if @config.logger != nil
|
29
|
+
@logger = @config.logger
|
29
30
|
else
|
30
|
-
|
31
31
|
@logger = Logger.new(STDOUT)
|
32
32
|
end
|
33
33
|
|
34
|
-
@sse = SSE::EventSource.new(
|
35
|
-
|
36
|
-
url = url,
|
37
|
-
query = {},
|
38
|
-
headers = headers
|
39
|
-
)
|
40
|
-
|
41
|
-
@sse.open do
|
42
|
-
|
43
|
-
on_open
|
44
|
-
end
|
45
|
-
|
46
|
-
@sse.error do |error|
|
47
|
-
|
48
|
-
if error != nil
|
49
|
-
|
50
|
-
@logger.error "SSE ERROR: " + error.body
|
51
|
-
end
|
52
|
-
|
53
|
-
on_error
|
54
|
-
end
|
55
|
-
|
56
|
-
@sse.message do |message|
|
57
|
-
|
58
|
-
on_message(message)
|
59
|
-
end
|
60
|
-
|
61
|
-
@sse.on("*") do |message|
|
62
|
-
|
63
|
-
on_message(message)
|
64
|
-
end
|
65
|
-
|
66
34
|
@updater.on_ready
|
67
35
|
end
|
68
36
|
|
69
37
|
def start
|
70
|
-
|
71
38
|
@logger.info "Starting EventSource service"
|
72
|
-
|
73
|
-
|
39
|
+
begin
|
40
|
+
conn = RestClient::Request.execute(method: :get,
|
41
|
+
url: @url,
|
42
|
+
headers: @headers,
|
43
|
+
block_response: proc { |response| response_handler response },
|
44
|
+
before_execution_proc: nil,
|
45
|
+
log: false,
|
46
|
+
read_timeout: 60,
|
47
|
+
ssl_ca_file: @config.ssl_ca_cert)
|
48
|
+
|
49
|
+
|
50
|
+
rescue => e
|
51
|
+
@logger.warn "SSE connection failed: " + e.message
|
52
|
+
on_error
|
53
|
+
end
|
74
54
|
end
|
75
55
|
|
76
56
|
def stop
|
77
|
-
|
78
57
|
@logger.info "Stopping EventSource service"
|
79
|
-
|
80
58
|
on_closed
|
81
59
|
end
|
82
60
|
|
83
61
|
def close
|
84
|
-
|
85
62
|
stop
|
86
63
|
end
|
87
64
|
|
88
65
|
def on_open
|
89
|
-
|
90
66
|
@logger.info "EventSource connected"
|
91
|
-
|
92
67
|
@updater.on_connected
|
93
68
|
end
|
94
69
|
|
95
70
|
def on_error
|
96
|
-
|
97
71
|
@logger.error "EventSource error"
|
98
|
-
|
99
72
|
@updater.on_error
|
100
|
-
|
101
73
|
stop
|
102
74
|
end
|
103
75
|
|
104
76
|
def on_closed
|
105
|
-
|
106
77
|
@logger.info "EventSource disconnected"
|
107
|
-
|
108
78
|
@updater.on_disconnected
|
109
79
|
end
|
110
80
|
|
111
81
|
def on_message(message)
|
112
|
-
|
113
82
|
@logger.debug "EventSource message received " + message.to_s
|
114
|
-
|
115
83
|
msg = JSON.parse(message)
|
116
84
|
@updater.update(msg)
|
117
85
|
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def emit_line(line)
|
90
|
+
if line.start_with?("data:")
|
91
|
+
@logger.debug "SSE emit line: " + line
|
92
|
+
on_message line[line.index("{")..-1]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def response_handler(response)
|
97
|
+
on_open
|
98
|
+
case response.code
|
99
|
+
when "200"
|
100
|
+
line = ""
|
101
|
+
response.read_body do |chunk|
|
102
|
+
line << chunk
|
103
|
+
while line.sub!(/^(.*)\n/,"")
|
104
|
+
emit_line $1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
close
|
108
|
+
else
|
109
|
+
@logger.error "SSE ERROR: http_code=%d body=%d" % [response.code, response.body]
|
110
|
+
on_error
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
118
114
|
end
|
@@ -13,6 +13,7 @@ class HarnessConnector < Connector
|
|
13
13
|
@config = config
|
14
14
|
@on_unauthorized = on_unauthorized
|
15
15
|
@user_agent = "RubySDK " + Ff::Ruby::Server::Sdk::VERSION
|
16
|
+
@sdk_info = "Ruby #{Ff::Ruby::Server::Sdk::VERSION} Server"
|
16
17
|
|
17
18
|
@api = OpenapiClient::ClientApi.new(make_api_client)
|
18
19
|
@metrics_api = OpenapiClient::MetricsApi.new(make_metrics_api_client)
|
@@ -36,14 +37,21 @@ class HarnessConnector < Connector
|
|
36
37
|
|
37
38
|
@config.logger.info "Token has been obtained"
|
38
39
|
process_token
|
39
|
-
return
|
40
|
+
return 200
|
40
41
|
|
41
42
|
rescue OpenapiClient::ApiError => e
|
42
43
|
|
44
|
+
if e.message.include? "the server returns an error"
|
45
|
+
# NOTE openapi-generator 5.2.1 has a bug where exceptions don't contain any useful information and we can't
|
46
|
+
# determine if a timeout has occurred. This is fixed in 6.3.0 but requires Ruby version to be increased to 2.7
|
47
|
+
# https://github.com/OpenAPITools/openapi-generator/releases/tag/v6.3.0
|
48
|
+
@config.logger.warn "OpenapiClient::ApiError [\n\n#{e}\n]"
|
49
|
+
return -1
|
50
|
+
end
|
51
|
+
|
43
52
|
log_error(e)
|
53
|
+
return e.code
|
44
54
|
end
|
45
|
-
|
46
|
-
false
|
47
55
|
end
|
48
56
|
|
49
57
|
def get_flags
|
@@ -59,6 +67,7 @@ class HarnessConnector < Connector
|
|
59
67
|
rescue OpenapiClient::ApiError => e
|
60
68
|
|
61
69
|
log_error(e)
|
70
|
+
return nil
|
62
71
|
end
|
63
72
|
end
|
64
73
|
|
@@ -75,6 +84,7 @@ class HarnessConnector < Connector
|
|
75
84
|
rescue OpenapiClient::ApiError => e
|
76
85
|
|
77
86
|
log_error(e)
|
87
|
+
return nil
|
78
88
|
end
|
79
89
|
end
|
80
90
|
|
@@ -139,15 +149,19 @@ class HarnessConnector < Connector
|
|
139
149
|
headers = {
|
140
150
|
|
141
151
|
"Authorization" => "Bearer " + @token,
|
142
|
-
"API-Key" => @api_key
|
143
|
-
|
152
|
+
"API-Key" => @api_key,
|
153
|
+
"User-Agent" => @user_agent,
|
154
|
+
"Harness-SDK-Info" => @sdk_info,
|
155
|
+
"Harness-AccountID" => @account_id,
|
156
|
+
"Harness-EnvironmentID" => @environment_id
|
157
|
+
}.compact
|
144
158
|
|
145
159
|
@event_source = Events.new(
|
146
160
|
|
147
161
|
url,
|
148
162
|
headers,
|
149
163
|
updater,
|
150
|
-
@config
|
164
|
+
@config
|
151
165
|
)
|
152
166
|
|
153
167
|
@event_source
|
@@ -169,7 +183,10 @@ class HarnessConnector < Connector
|
|
169
183
|
api_client = OpenapiClient::ApiClient.new
|
170
184
|
|
171
185
|
api_client.config = @config
|
186
|
+
api_client.config.connection_timeout = @config.read_timeout / 1000
|
187
|
+
api_client.config.read_timeout = @config.read_timeout / 1000
|
172
188
|
api_client.user_agent = @user_agent
|
189
|
+
api_client.default_headers['Harness-SDK-Info'] = @sdk_info
|
173
190
|
|
174
191
|
api_client
|
175
192
|
end
|
@@ -189,30 +206,32 @@ class HarnessConnector < Connector
|
|
189
206
|
|
190
207
|
api_client.config = config
|
191
208
|
api_client.user_agent = @user_agent
|
209
|
+
api_client.default_headers['Harness-SDK-Info'] = @sdk_info
|
192
210
|
|
193
211
|
api_client
|
194
212
|
end
|
195
213
|
|
196
214
|
def process_token
|
197
|
-
|
198
|
-
headers = {
|
199
|
-
|
200
|
-
"Authorization" => "Bearer " + @token
|
201
|
-
}
|
202
|
-
|
203
|
-
@api.api_client.default_headers = @api.api_client.default_headers.merge(headers)
|
204
|
-
@metrics_api.api_client.default_headers = @metrics_api.api_client.default_headers.merge(headers)
|
205
|
-
|
206
215
|
decoded_token = JWT.decode @token, nil, false
|
207
216
|
|
208
217
|
if decoded_token != nil && !decoded_token.empty?
|
209
218
|
|
210
219
|
@environment = decoded_token[0]["environment"]
|
211
220
|
@cluster = decoded_token[0]["clusterIdentifier"]
|
221
|
+
@environment_id = decoded_token[0]["environmentIdentifier"]
|
222
|
+
@account_id = decoded_token[0]["accountID"]
|
223
|
+
|
224
|
+
headers = {
|
225
|
+
"Authorization" => "Bearer " + @token,
|
226
|
+
"Harness-AccountID" => @account_id,
|
227
|
+
"Harness-EnvironmentID" => @environment_id
|
228
|
+
}.compact
|
229
|
+
|
230
|
+
@api.api_client.default_headers = @api.api_client.default_headers.merge(headers)
|
231
|
+
@metrics_api.api_client.default_headers = @metrics_api.api_client.default_headers.merge(headers)
|
212
232
|
|
213
233
|
@config.logger.debug "Token has been processed: environment='" + @environment.to_s + "', cluster='" + @cluster.to_s + "'"
|
214
234
|
else
|
215
|
-
|
216
235
|
@config.logger.error "ERROR: Could not obtain the environment and cluster data from the token"
|
217
236
|
end
|
218
237
|
end
|
@@ -231,6 +250,12 @@ class HarnessConnector < Connector
|
|
231
250
|
|
232
251
|
def log_error(e)
|
233
252
|
|
234
|
-
|
253
|
+
if e.code == 0
|
254
|
+
type = "typhoeus/libcurl"
|
255
|
+
else
|
256
|
+
type = "HTTP code #{e.code}"
|
257
|
+
end
|
258
|
+
|
259
|
+
@config.logger.warn "OpenapiClient::ApiError (#{type}) [\n\n" + e.to_s + "\n]"
|
235
260
|
end
|
236
261
|
end
|
data/scripts/openapi.sh
CHANGED
@@ -57,12 +57,9 @@ if which openapi-generator-cli; then
|
|
57
57
|
gem install concurrent-ruby -v 1.1.10 && \
|
58
58
|
gem install murmurhash3 -v 0.1.6 && \
|
59
59
|
cd "$dir_path/.." && \
|
60
|
-
openapi-generator-cli generate -i api.yaml -g ruby -o "$generated_path"
|
61
|
-
cd "$generated_path" && gem build openapi_client.gemspec && \
|
62
|
-
test -e "openapi_client-1.0.0.gem" && \
|
63
|
-
gem install --dev "openapi_client-1.0.0.gem"; then
|
60
|
+
openapi-generator-cli generate -i api.yaml -g ruby -o "$generated_path"; then
|
64
61
|
|
65
|
-
echo "
|
62
|
+
echo "API has been generated with success: $generated_path"
|
66
63
|
else
|
67
64
|
|
68
65
|
echo "ERROR: 'openapi-generator-cli' is not installed [1] 😬"
|
data/scripts/sdk_specs.sh
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ff-ruby-server-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 'Miloš Vasić, cyr.: Милош Васић'
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -122,20 +122,6 @@ dependencies:
|
|
122
122
|
- - '='
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 2.1.0
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: sse-client
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - '='
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: 1.1.0
|
132
|
-
type: :runtime
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - '='
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: 1.1.0
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
126
|
name: concurrent-ruby
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -165,19 +151,25 @@ dependencies:
|
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
version: 0.1.6
|
167
153
|
- !ruby/object:Gem::Dependency
|
168
|
-
name:
|
154
|
+
name: typhoeus
|
169
155
|
requirement: !ruby/object:Gem::Requirement
|
170
156
|
requirements:
|
171
157
|
- - "~>"
|
172
158
|
- !ruby/object:Gem::Version
|
173
|
-
version: 1.0
|
159
|
+
version: '1.0'
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 1.0.1
|
174
163
|
type: :runtime
|
175
164
|
prerelease: false
|
176
165
|
version_requirements: !ruby/object:Gem::Requirement
|
177
166
|
requirements:
|
178
167
|
- - "~>"
|
179
168
|
- !ruby/object:Gem::Version
|
180
|
-
version: 1.0
|
169
|
+
version: '1.0'
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 1.0.1
|
181
173
|
description: Harness is a feature management platform that helps teams to build better
|
182
174
|
software and to test features quicker.
|
183
175
|
email:
|
@@ -198,6 +190,7 @@ files:
|
|
198
190
|
- api.yaml
|
199
191
|
- bin/console
|
200
192
|
- bin/setup
|
193
|
+
- buildInContainer.sh
|
201
194
|
- docs/build.md
|
202
195
|
- docs/further_reading.md
|
203
196
|
- docs/images/ff-gui.png
|
@@ -207,6 +200,7 @@ files:
|
|
207
200
|
- example/number_variation_example/number_variation_example.rb
|
208
201
|
- example/simple_example/example.rb
|
209
202
|
- example/string_variation_example/string_variation_example.rb
|
203
|
+
- example/tls_example/tls_example.rb
|
210
204
|
- example/url_change_example/url_change_example.rb
|
211
205
|
- lib/ff/ruby/server/generated/lib/openapi_client.rb
|
212
206
|
- lib/ff/ruby/server/generated/lib/openapi_client/api/client_api.rb
|
@@ -291,7 +285,7 @@ metadata:
|
|
291
285
|
homepage_uri: https://www.harness.io/
|
292
286
|
source_code_uri: https://github.com/harness/ff-ruby-server-sdk
|
293
287
|
changelog_uri: https://github.com/harness/ff-ruby-server-sdk/blob/main/CHANGELOG.md
|
294
|
-
post_install_message:
|
288
|
+
post_install_message:
|
295
289
|
rdoc_options: []
|
296
290
|
require_paths:
|
297
291
|
- lib
|
@@ -307,8 +301,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
307
301
|
- !ruby/object:Gem::Version
|
308
302
|
version: '0'
|
309
303
|
requirements: []
|
310
|
-
rubygems_version: 3.
|
311
|
-
signing_key:
|
304
|
+
rubygems_version: 3.4.10
|
305
|
+
signing_key:
|
312
306
|
specification_version: 4
|
313
307
|
summary: Harness is a feature management platform that helps teams to build better
|
314
308
|
software and to test features quicker.
|