tailslide 0.1.0 → 0.1.2

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: e1a5e8473d47cabf7b875eed87f308f7ce87146c9f985be62f03c8de6122fa04
4
- data.tar.gz: 7de6db758b0fc8fd7034212ace03061c53a7fc717ab3e68dc87bc46450f69098
3
+ metadata.gz: bd4e9eea51d59ec4a285b3531dd36e00bb2f437e1df5e50240014c59641c7499
4
+ data.tar.gz: feaae9d7f5af47900a5031426e9015290a552e0b0eb8060f80b16c2aa148df3d
5
5
  SHA512:
6
- metadata.gz: 70b5ddcb2530404fefa63309de3ffbeab55b078cfbd7578890e92e40be4d035510a4c9c3c324a02465894fd8f8e1cab55423ad638acd3911ac73b08973daaaa8
7
- data.tar.gz: 17cb8385bdb5c5f320dda627f33ff333ab6a34ae3853d66dfdc8b37bfaf64f124cf2cd2f8bd8572ab043c1fcd1c32dfc8c205e672553c1c69edeb4741bfbe68e
6
+ metadata.gz: 81e5ec75a95f6edce34ad2136376b222e02712bc30b49ed0c0f9423a0a9b9ad78d810269a0002f607d8d9d538779c5e0237e1a264e12dc98fcd59555eb35331b
7
+ data.tar.gz: c4d4c5276a112b56c2d36596e67a438a47f0f071de792cd46b04021d5e6cfb3fca8d5a712ff26c65eaca9f9565cea7913da8fba5c91db9311b34c6222900ce01
@@ -0,0 +1,14 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Debug Local File",
9
+ "type": "Ruby",
10
+ "request": "launch",
11
+ "program": "./test.rb"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "workbench.colorCustomizations": {
3
+ "sash.hoverBorder": "#3b3b3b",
4
+ "titleBar.activeBackground": "#222222",
5
+ "titleBar.activeForeground": "#e7e7e7",
6
+ "titleBar.inactiveBackground": "#22222299",
7
+ "titleBar.inactiveForeground": "#e7e7e799"
8
+ },
9
+ "editor.acceptSuggestionOnEnter": "on"
10
+ }
data/Gemfile CHANGED
@@ -5,8 +5,13 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in tailslide.gemspec
6
6
  gemspec
7
7
 
8
+ gem "async", "~> 2,0.3"
9
+ gem "nats-pure", "~> 2,1.0"
10
+ gem "redis", "~>4.7.1"
11
+ gem "redistimeseries", "~>0.1.2"
8
12
  gem "rake", "~> 13.0"
9
13
 
10
14
  gem "minitest", "~> 5.0"
11
15
 
12
16
  gem "rubocop", "~> 1.21"
17
+
@@ -0,0 +1,41 @@
1
+ require_relative 'nats_client'
2
+ require_relative 'redis_timeseries_client'
3
+ require_relative 'toggler'
4
+
5
+ class FlagManger
6
+ attr_reader :nats_client, :redis_ts_client, :user_context
7
+
8
+ def initialize(nats_server:'', stream:'', app_id:'', sdk_key:'', user_context:'', redis_host:'', redis_port:'')
9
+ @nats_client = NatsClient.new(server_url: nats_server, stream:stream, subject:app_id, callback:method(:set_flags), token:sdk_key)
10
+ @redis_ts_client = RedisTimeSeriesClient.new(redis_host, redis_port)
11
+ @user_context = user_context
12
+ @flags = []
13
+ end
14
+
15
+ def initialize_flags
16
+ nats_client.initialize_flags
17
+ redis_ts_client.init
18
+ end
19
+
20
+ def set_flags(flags)
21
+ @flags = flags
22
+ end
23
+
24
+ def get_flags
25
+ return @flags
26
+ end
27
+
28
+ def disconnect
29
+ nats_client.disconnect
30
+ redis_ts_client.disconnect
31
+ end
32
+
33
+ def new_toggler(config)
34
+ p config
35
+
36
+ return Toggler.new(**config, get_flags:method(:get_flags), user_context:user_context,
37
+ emit_redis_signal:redis_ts_client.method(:emit_signal)
38
+ )
39
+ end
40
+
41
+ end
@@ -0,0 +1,58 @@
1
+ require 'async'
2
+ require "nats/client"
3
+ TimeoutError = NATS::IO::Timeout
4
+ require 'json'
5
+
6
+
7
+ class NatsClient
8
+ attr_accessor :nats_connection, :jetstream, :subscribed_stream
9
+ attr_reader :connection_string, :stream, :subject, :callback
10
+ def initialize(server_url:'localhost:4222', stream:'', subject:'', callback:nil, token:'')
11
+ @stream = stream
12
+ @subject = subject
13
+ @connection_string = "nats://#{token}#{'@' if token}#{server_url}"
14
+ @callback = callback
15
+ end
16
+
17
+ def initialize_flags
18
+ connect()
19
+ fetch_latest_message()
20
+ fetch_ongoing_event_messages()
21
+ end
22
+
23
+ private
24
+ def connect
25
+ self.nats_connection = NATS.connect(connection_string)
26
+ self.jetstream = nats_connection.jetstream
27
+ end
28
+
29
+ def fetch_latest_message
30
+ begin
31
+ latest_msg = jetstream.get_last_msg(stream, subject)
32
+ json_data = JSON.parse latest_msg.data
33
+ callback.call(json_data)
34
+ rescue NATS::Timeout => e
35
+ end
36
+ end
37
+
38
+ def fetch_ongoing_event_messages
39
+ Async do |task|
40
+ self.subscribed_stream = jetstream.pull_subscribe(subject, 'me', config: { deliver_policy: 'new' })
41
+ begin
42
+ messages = subscribed_stream.fetch(1)
43
+ messages.each do |message|
44
+ message.ack
45
+ json_data = JSON.parse message.data
46
+ p json_data
47
+ callback.call(json_data)
48
+ end
49
+ rescue NATS::IO::Timeout => e
50
+ p e
51
+ end until nats_connection.closed?
52
+ end
53
+ end
54
+
55
+ def disconnect
56
+ nats_connection.close
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+
2
+ require 'redistimeseries'
3
+ using Redistimeseries::RedisRefinement
4
+
5
+ class RedisTimeSeriesClient
6
+ attr_reader :host, :port
7
+ attr_accessor :redis_client
8
+ def initialize(host, port)
9
+ @host = host || 'localhost'
10
+ @port = port || 6379
11
+ end
12
+
13
+ def init
14
+ self.redis_client = Redis.new(host:host, port:port)
15
+ end
16
+
17
+ def emit_signal(flag_id, app_id, status)
18
+ redis_client.ts_add(key: "#{flag_id}:#{status}", timestamp:"*", value:1, labels:["status", status, "appId", app_id, "flagId", flag_id])
19
+ end
20
+
21
+ end
@@ -0,0 +1,73 @@
1
+ require 'digest'
2
+
3
+ class Toggler
4
+ attr_reader :flag_name, :get_flags, :feature_cb, :default_cb, :error_condition, :emit_redis_signal, :user_context
5
+ attr_accessor :app_id, :flag_id
6
+ def initialize(flag_name:'', feature_cb:nil, default_cb:nil, error_condition:nil, get_flags:nil, emit_redis_signal:nil, user_context:'')
7
+ @flag_name = flag_name
8
+ @feature_cb = feature_cb
9
+ @default_cb = default_cb
10
+ @error_condition = error_condition
11
+ @get_flags = get_flags
12
+ @flag_id = nil
13
+ @app_id = nil
14
+ set_flag_id_and_app_id(flag_name)
15
+ @emit_redis_signal = emit_redis_signal
16
+ @user_context = user_context
17
+ end
18
+
19
+ def is_flag_active
20
+ flag = get_matching_flag
21
+ flag["is_active"] && (is_user_white_listed(flag) || validate_user_rollout(flag))
22
+ end
23
+
24
+ def emit_success
25
+ return unless flag_id
26
+ p 'emiting success'
27
+ emit_redis_signal.call(flag_id, app_id, 'success')
28
+ end
29
+
30
+ def emit_failure
31
+ return unless flag_id
32
+ emit_redis_signal.call(flag_id, app_id, 'failure')
33
+ end
34
+
35
+ private
36
+ def get_matching_flag
37
+ flag = get_flags.call.find { |flag| flag["title"] == flag_name}
38
+ raise Exception.new "Cannot find flag with flag name of: #{flag_name}" unless flag
39
+ flag
40
+ end
41
+
42
+ def set_flag_id_and_app_id(flag_name)
43
+ matching_flag = get_matching_flag
44
+ self.flag_id = matching_flag["id"]
45
+ self.app_id = matching_flag["app_id"]
46
+ end
47
+
48
+ def is_user_white_listed(flag)
49
+ flag["white_listed_users"].split(',').include?(user_context)
50
+ end
51
+
52
+ def validate_user_rollout(flag)
53
+ rollout = flag["rollout_percentage"] / 100.0
54
+ if is_circuit_in_recovery(flag)
55
+ rollout = rollout * (flag["circuit_recovery_percentage"] / 100.0)
56
+ end
57
+ is_user_in_rollout(rollout)
58
+ end
59
+
60
+ def is_circuit_in_recovery(flag)
61
+ flag["is_recoverable"] && flag["circuit_status"] == "recovery"
62
+ end
63
+
64
+ def is_user_in_rollout(rollout)
65
+ puts "User context hash #{hash_user_context}"
66
+ puts "Rollout: #{rollout}"
67
+ hash_user_context <= rollout
68
+ end
69
+
70
+ def hash_user_context
71
+ (Digest::MD5.hexdigest(user_context).to_i(base=16) % 100) / 100.0
72
+ end
73
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tailslide
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/tailslide.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "tailslide/version"
4
+ require_relative "tailslide/flag_manager"
4
5
 
5
6
  module Tailslide
6
- class Error < StandardError; end
7
+ class FlagManger < FlagManger
8
+ end
7
9
  # Your code goes here...
8
10
  end
data/redis_test.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'redistimeseries'
2
+ using Redistimeseries::RedisRefinement
3
+
4
+ app_id = 1
5
+ flag_id = 1
6
+ status = 'success'
7
+ redis_client = Redis.new(host:'localhost', port: 6379)
8
+ redis_client.ts_add(key: "#{flag_id}:#{status}", timestamp:"*", value:1, labels:["status", status, "appId", app_id, "flagId", flag_id])
Binary file
data/test.rb ADDED
@@ -0,0 +1,129 @@
1
+ require "async"
2
+ # require "./lib/tailslide/nats_client.rb"
3
+ require_relative "lib/tailslide/flag_manager.rb"
4
+ require_relative 'lib/tailslide/toggler'
5
+
6
+ # def logMessage(message)
7
+ # p message
8
+ # end
9
+
10
+ # config = {server_url: "localhost:4222", callback: :p, token: 'myToken', stream:"flags", subject:'1'}
11
+ app_id = "1"
12
+ flag_name = 'Flag in app 1 number 1'
13
+ flag_config = {"flag_name": flag_name}
14
+
15
+ config = {nats_server:'localhost:4222', stream:'flags', app_id:app_id, sdk_key:'myToken', user_context:'375d39e6-9c3f-4f58-80bd-e5960b710295',
16
+ redis_host:'localhost', redis_port:6379}
17
+
18
+
19
+ Async do |task|
20
+ manager = FlagManger.new(**config)
21
+ manager.initialize_flags
22
+ flag_toggler = manager.new_toggler(flag_config)
23
+
24
+
25
+ if flag_toggler.is_flag_active
26
+ puts "Flag in #{app_id} with name \"#{flag_name}\" is active!"
27
+ flag_toggler.emit_success()
28
+ else
29
+ puts "Flag in #{app_id} with name \"#{flag_name}\" is not active!"
30
+ flag_toggler.emit_failure()
31
+ end
32
+ sleep 5
33
+
34
+ if flag_toggler.is_flag_active
35
+ puts "Flag in #{app_id} with name \"#{flag_name}\" is active!"
36
+ flag_toggler.emit_success()
37
+ else
38
+ puts "Flag in #{app_id} with name \"#{flag_name}\" is not active!"
39
+ flag_toggler.emit_failure()
40
+ end
41
+ end
42
+
43
+
44
+ # require "nats/client"
45
+ # require "async"
46
+ # TimeoutError = NATS::IO::Timeout
47
+ # require 'json'
48
+
49
+ # token = "myToken"
50
+
51
+ # nats_client = NATS.connect("nats://#{token}@127.0.0.1:4222")
52
+ # jet_stream = nats_client.jetstream
53
+
54
+ # # get last message
55
+ # latest_msg = jet_stream.get_last_msg("flags", "test")
56
+ # json_data = JSON.parse latest_msg.data
57
+ # p json_data
58
+
59
+
60
+ # # pull subscribe for new onging messages (workaround until Nats.rb make new update)
61
+ # subscribed_stream = jet_stream.pull_subscribe("test", 'mydurable', config: { deliver_policy: 'new' })
62
+ # Async do |task|
63
+ # task.async do
64
+ # begin
65
+ # messages = subscribed_stream.fetch(1)
66
+ # messages.each do |message|
67
+ # message.ack
68
+ # json_data = JSON.parse message.data
69
+ # p json_data
70
+ # end
71
+ # rescue NATS::Timeout => e
72
+ # p e
73
+ # end until nats_client.closed?
74
+ # end
75
+ # p "hello past async"
76
+ # end
77
+
78
+
79
+
80
+ # push subscribe for new ongoing messages
81
+ # setting deliver_policy still results in delivering all messages in "test" subject
82
+ # push_sub = jetStream.subscribe("test", {manual_ack: true, deliver_policy:"new"} ) do |msg|
83
+ # msg.ack
84
+ # puts msg.data
85
+ # end
86
+
87
+ # Get ongoing messages
88
+ # Push subscribe
89
+ # consumer_req = {
90
+ # stream_name: "test",
91
+ # config: {
92
+ # durable_name: "sample",
93
+ # deliver_policy: "new",
94
+ # ack_policy: "explicit",
95
+ # max_deliver: -1,
96
+ # replay_policy: "instant"
97
+ # }
98
+ # }
99
+
100
+ # config = NATS::JetStream::API::ConsumerConfig.new({ deliver_policy: "new" })
101
+ # Create inbox for push consumer.
102
+ # deliver = natsClient.new_inbox
103
+ # config.deliver_subject = deliver
104
+
105
+ # push_sub = jetStream.subscribe("test", {manual_ack: true, deliver_policy:"new"} ) do |msg|
106
+ # puts msg.data
107
+ # end
108
+
109
+ # cinfo = push_sub.consumer_info["config"]
110
+ # puts cinfo
111
+ # msg = push_sub.next_msg(timeout: 10000000000)
112
+ # msg.ack
113
+ # puts msg.data
114
+ # push_sub.consumer_info["config"]["deliver_policy"] = "new"
115
+ # puts cinfo
116
+
117
+ # loop do
118
+ # msgs = push_sub.next_msg()
119
+ # puts msgs.data
120
+ # rescue TimeoutError => e
121
+ # puts e
122
+ # sleep 1
123
+ # end
124
+
125
+ # js.publish("9", "Hello JetStream! 1")
126
+ # js.publish("9", "Hello JetStream! 2")
127
+ # js.publish("9", "Hello JetStream! 3")
128
+ # js.publish("9", "Hello JetStream! 4")
129
+ # js.publish("9", "Hello JetStream! Latest")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tailslide
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Liou
@@ -11,8 +11,22 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2022-07-13 00:00:00.000000000 Z
15
- dependencies: []
14
+ date: 2022-07-24 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: "[]"
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
16
30
  description: " Write a longer description or delete this line."
17
31
  email:
18
32
  - stevenliou@gmail.com
@@ -23,7 +37,8 @@ executables: []
23
37
  extensions: []
24
38
  extra_rdoc_files: []
25
39
  files:
26
- - ".rubocop.yml"
40
+ - ".vscode/launch.json"
41
+ - ".vscode/settings.json"
27
42
  - CHANGELOG.md
28
43
  - CODE_OF_CONDUCT.md
29
44
  - Gemfile
@@ -31,8 +46,15 @@ files:
31
46
  - README.md
32
47
  - Rakefile
33
48
  - lib/tailslide.rb
49
+ - lib/tailslide/flag_manager.rb
50
+ - lib/tailslide/nats_client.rb
51
+ - lib/tailslide/redis_timeseries_client.rb
52
+ - lib/tailslide/toggler.rb
34
53
  - lib/tailslide/version.rb
54
+ - redis_test.rb
35
55
  - sig/tailslide.rbs
56
+ - tailslide-0.1.0.gem
57
+ - test.rb
36
58
  homepage: https://github.com/tailslide-io/tailslide.rb
37
59
  licenses:
38
60
  - MIT
@@ -58,5 +80,6 @@ requirements: []
58
80
  rubygems_version: 3.3.7
59
81
  signing_key:
60
82
  specification_version: 4
61
- summary: Write a short summary, because RubyGems requires one.
83
+ summary: This is the Ruby SDK for Tailslide, which is a feature flag framework with
84
+ automatic fail safe and circuit recovery.
62
85
  test_files: []
data/.rubocop.yml DELETED
@@ -1,13 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.6
3
-
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: double_quotes
7
-
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
11
-
12
- Layout/LineLength:
13
- Max: 120