routemaster-client 3.1.0 → 3.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,61 @@
1
+ require 'routemaster/client'
2
+ require 'yaml'
3
+
4
+ module Routemaster
5
+ module CLI
6
+ Exit = Class.new(StandardError)
7
+
8
+ class Helper
9
+ def initialize(config)
10
+ @config = config
11
+ end
12
+
13
+ def client
14
+ _configure_client
15
+ Routemaster::Client
16
+ end
17
+
18
+ private
19
+
20
+ def _bus_url
21
+ case @config.bus
22
+ when /^@(.*)/ then
23
+ domain = _rc_file_data.dig($1, :bus)
24
+ raise "No configuration for bus '#{$1}' found in .rtmrc" if domain.nil?
25
+ "https://#{domain}"
26
+ else
27
+ "https://#{@config.bus}"
28
+ end
29
+ end
30
+
31
+ def _bus_token
32
+ case @config.bus
33
+ when /^@(.*)/ then
34
+ @config.token || _rc_file_data.dig($1, :token)
35
+ else
36
+ @config.token
37
+ end
38
+ end
39
+
40
+ def _configure_client
41
+ Routemaster::Client.configure do |c|
42
+ c.url = _bus_url
43
+ c.uuid = _bus_token
44
+ end
45
+ end
46
+
47
+ def _rc_file_data
48
+ data =
49
+ if File.exist?('.rtmrc')
50
+ YAML.load_file('.rtmrc')
51
+ elsif File.exist?(File.expand_path '~/.rtmrc')
52
+ YAML.load_file(File.expand_path '~/.rtmrc')
53
+ else
54
+ {}
55
+ end
56
+
57
+ Hashie::Mash.new(data)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,23 @@
1
+ require 'routemaster/cli/base'
2
+
3
+ module Routemaster
4
+ module CLI
5
+ class Pub < Base
6
+ prefix %w[pub]
7
+ syntax 'EVENT TOPIC URL'
8
+ descr %{
9
+ Publishes an event to the bus. Note that the `TOKEN` passed in `options` must
10
+ be that of the subscriber, not a root token. `EVENT` must be one of `created`,
11
+ `updated`, `deleted`, or `noop`. `TOPIC` must be a valid topic name. `URL` must
12
+ be a valid HTTPS URL.
13
+ }
14
+
15
+ action do
16
+ bad_argc! unless argv.length == 3
17
+
18
+ event, topic, url = argv
19
+ helper.client.public_send(event, topic, url)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,78 @@
1
+ require 'routemaster/cli/base'
2
+ require 'yaml'
3
+
4
+ module Routemaster
5
+ module CLI
6
+ module Sub
7
+ class Add < Base
8
+ prefix %w[sub add]
9
+ syntax 'URL TOPIC [TOPIC...]'
10
+ descr %{
11
+ Adds (or updates) a subscription. Note that the `TOKEN` passed in `option` must be
12
+ that of the subscriber, not a root token.
13
+
14
+ `URL` must be HTTPS and include an authentication username (used by the bus
15
+ when delivering events).
16
+
17
+ `TOPICS` are topic names (which might not be published to yet).
18
+ }
19
+
20
+ options do |p|
21
+ p.on('--latency MS', %{
22
+ The target delivery latency for this subscriber (ie. how long to buffer events for).
23
+ }) do |x|
24
+ config.latency = Integer(x)
25
+ end
26
+
27
+ p.on('--batch-size COUNT', %{
28
+ The maximum number of events in a delivered batch.
29
+ }) do |x|
30
+ config.batch_size = Integer(x)
31
+ end
32
+ end
33
+
34
+ action do
35
+ bad_argc! unless argv.length > 1
36
+
37
+ url, *topics = argv
38
+ params = {}
39
+ params[:timeout] = config.latency if config.latency
40
+ params[:max] = config.batch_size if config.batch_size
41
+ helper.client.subscribe(callback: url, topics: topics, **params)
42
+ end
43
+ end
44
+
45
+ class Del < Base
46
+ prefix %w[sub del]
47
+ syntax '[TOPIC...]'
48
+ descr %{
49
+ Updates or removes a subscription. Note that the `TOKEN` passed in `options` must
50
+ be that of the subscriber, not a root token. If no `TOPICS` are specified, the
51
+ subscription is entirely removed.
52
+ }
53
+
54
+ action do
55
+ if argv.length > 0
56
+ helper.client.unsubscribe(*argv)
57
+ else
58
+ helper.client.unsubscribe_all
59
+ end
60
+ end
61
+ end
62
+
63
+ class List < Base
64
+ prefix %w[sub list]
65
+ descr %{
66
+ List existing subscriptions.
67
+ }
68
+
69
+ action do
70
+ bad_argc! if argv.length > 0
71
+
72
+ puts YAML.dump(helper.client.monitor_subscriptions.map(&:attributes))
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,52 @@
1
+ require 'routemaster/cli/base'
2
+
3
+ module Routemaster
4
+ module CLI
5
+ module Token
6
+ class Add < Base
7
+ prefix %w[token add]
8
+ syntax 'SERVICE [TOKEN]'
9
+ descr %{
10
+ Adds `TOKEN` to the list of API tokens permitted to use the bus API. `SERVICE`
11
+ is a human-readable name for this token.
12
+ }
13
+
14
+ action do
15
+ bad_argc! unless (1..2).include? argv.length
16
+
17
+ service, token = argv
18
+ puts helper.client.token_add(name: service, token: token)
19
+ end
20
+ end
21
+
22
+ class Del < Base
23
+ prefix %w[token del]
24
+ syntax 'TOKEN'
25
+ descr %{
26
+ Removes `TOKEN` from permitted tokens if it exists.
27
+ }
28
+
29
+ action do
30
+ bad_argc! unless argv.length == 1
31
+
32
+ helper.client.token_del(token: argv.first)
33
+ end
34
+ end
35
+
36
+ class List < Base
37
+ prefix %w[token list]
38
+ descr %{
39
+ Lists currently permitted API tokens.
40
+ }
41
+
42
+ action do
43
+ bad_argc! unless argv.length == 0
44
+
45
+ helper.client.token_list.each do |t,n|
46
+ puts "#{t}\t#{n}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ require 'routemaster/cli/helper'
2
+ require 'routemaster/cli/token'
3
+ require 'routemaster/cli/pub'
4
+ require 'routemaster/cli/sub'
5
+
6
+ module Routemaster
7
+ module CLI
8
+ class Toplevel
9
+ SUBCOMMANDS = [
10
+ Token::Add, Token::Del, Token::List,
11
+ Pub,
12
+ Sub::Add, Sub::Del, Sub::List,
13
+ ]
14
+
15
+ def initialize(stderr: STDERR, stdout: STDOUT)
16
+ @stderr = stderr
17
+ @stdout = stdout
18
+ end
19
+
20
+ def run(argv)
21
+ handler = SUBCOMMANDS.find do |kls|
22
+ argv.take(kls.prefix.length) == kls.prefix
23
+ end
24
+
25
+ bad_subcommand! if handler.nil?
26
+
27
+ subargv = argv[handler.prefix.length..-1]
28
+ handler.new(stderr: @stderr, stdout: @stdout).run(subargv)
29
+ end
30
+
31
+ private
32
+
33
+ def bad_subcommand!
34
+ log "Usage:"
35
+ SUBCOMMANDS.each do |kls|
36
+ log kls.syntax
37
+ log kls.descr
38
+ end
39
+ raise Exit, 1
40
+ end
41
+
42
+ def log(message)
43
+ @stderr.puts(message)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -4,12 +4,8 @@ require 'routemaster/client/configuration'
4
4
  require 'routemaster/client/version'
5
5
  require 'routemaster/client/errors'
6
6
  require 'routemaster/client/assertion_helpers'
7
- require 'routemaster/topic'
8
- require 'uri'
9
- require 'json'
10
- require 'faraday'
11
- require 'typhoeus'
12
- require 'typhoeus/adapters/faraday'
7
+ require 'routemaster/client/topic'
8
+ require 'routemaster/client/subscription'
13
9
  require 'oj'
14
10
 
15
11
  module Routemaster
@@ -89,18 +85,14 @@ module Routemaster
89
85
  topics.each do |t|
90
86
  response = _conn.delete("/subscriber/topics/#{t}")
91
87
 
92
- unless response.success?
93
- raise 'unsubscribe rejected'
94
- end
88
+ raise ConnectionError, "unsubscribe rejected (status: #{response.status})" unless response.success?
95
89
  end
96
90
  end
97
91
 
98
92
  def unsubscribe_all
99
93
  response = _conn.delete('/subscriber')
100
94
 
101
- unless response.success?
102
- raise 'unsubscribe all rejected'
103
- end
95
+ raise ConnectionError, "unsubscribe all rejected (status: #{response.status})" unless response.success?
104
96
  end
105
97
 
106
98
  def delete_topic(topic)
@@ -108,22 +100,51 @@ module Routemaster
108
100
 
109
101
  response = _conn.delete("/topics/#{topic}")
110
102
 
111
- unless response.success?
112
- raise 'failed to delete topic'
113
- end
103
+ raise ConnectionError, "failed to delete topic (status: #{response.status})" unless response.success?
114
104
  end
115
105
 
116
106
  def monitor_topics
117
- response = _conn.get('/topics') do |r|
118
- r.headers['Content-Type'] = 'application/json'
107
+ response = _conn.get('/topics')
108
+ raise ConnectionError, "failed to connect to /topics (status: #{response.status})" unless response.success?
109
+
110
+ Oj.load(response.body).map do |raw_topic|
111
+ Topic.new raw_topic
119
112
  end
113
+ end
114
+
115
+ def monitor_subscriptions
116
+ response = _conn.get('/subscriptions')
117
+ raise ConnectionError, "failed to list subscribers (status: #{response.status})" unless response.success?
120
118
 
121
- unless response.success?
122
- raise 'failed to connect to /topics'
119
+ Oj.load(response.body).map do |raw_subscription|
120
+ Subscription.new raw_subscription
123
121
  end
122
+ end
124
123
 
125
- Oj.load(response.body).map do |raw_topic|
126
- Topic.new raw_topic
124
+ def token_add(name:, token: nil)
125
+ payload = { name: name }
126
+ payload[:token] = token if token
127
+ response = _conn.post('/api_tokens') do |r|
128
+ r.headers['Content-Type'] = 'application/json'
129
+ r.body = Oj.dump(payload, mode: :compat)
130
+ end
131
+
132
+ raise ConnectionError, "Failed to add token (status: #{response.status})" unless response.success?
133
+
134
+ Oj.load(response.body)['token']
135
+ end
136
+
137
+ def token_del(token:)
138
+ response = _conn.delete("/api_tokens/#{token}")
139
+ raise ConnectionError, "Failed to delete token (status: #{response.status})" unless response.success?
140
+ nil
141
+ end
142
+
143
+ def token_list
144
+ response = _conn.get("/api_tokens")
145
+ raise ConnectionError, "Failed to list tokens (status: #{response.status})" unless response.success?
146
+ Oj.load(response.body).each_with_object({}) do |entry, result|
147
+ result[entry['token']] = entry['name']
127
148
  end
128
149
  end
129
150
 
@@ -189,13 +210,14 @@ module Routemaster
189
210
  _assert_valid_timestamp!(t) if t
190
211
  _assert_valid_data(data) if data
191
212
 
213
+ t ||= _now if async
192
214
  backend = async ? async_backend : _synchronous_backend
193
215
  backend.send_event(event, topic, callback, t: t, data: data)
194
216
  end
195
217
 
196
218
  def _check_pulse!
197
219
  _conn.get('/pulse').tap do |response|
198
- raise 'cannot connect to bus' unless response.success?
220
+ raise "Cannot connect to bus (status %s)" % response.status unless response.success?
199
221
  end
200
222
  end
201
223
 
@@ -210,6 +232,11 @@ module Routemaster
210
232
  warn "(in #{caller(2,1).first})"
211
233
  end
212
234
 
235
+ def _now
236
+ (Time.now.to_f * 1e3).to_i
237
+ end
238
+
239
+
213
240
  private :async_backend, :lazy
214
241
  end
215
242
  end
@@ -10,9 +10,19 @@ module Routemaster
10
10
  def perform(*args)
11
11
  # Sidekiq does not have transparent argument serialization.
12
12
  # This extracts the options so they can be passed on properly.
13
- options = args.last.kind_of?(Hash) ? args.pop.symbolize_keys : {}
13
+ options = args.last.kind_of?(Hash) ? _symbolize_keys(args.pop) : {}
14
14
  Routemaster::Client::Connection.send_event(*args, **options)
15
15
  end
16
+
17
+ private
18
+
19
+ def _symbolize_keys(h)
20
+ {}.tap do |result|
21
+ h.each do |k,v|
22
+ result[k.to_sym] = v
23
+ end
24
+ end
25
+ end
16
26
  end
17
27
  end
18
28
  end
@@ -19,6 +19,12 @@ module Routemaster
19
19
  _validate_all_options!
20
20
  end
21
21
 
22
+ def reset
23
+ %w[url uuid timeout async_backend lazy verify_ssl].each do |ivar|
24
+ remove_instance_variable :"@#{ivar}" if instance_variable_defined? :"@#{ivar}"
25
+ end
26
+ end
27
+
22
28
  private
23
29
 
24
30
  def _validate_all_options!
@@ -1,3 +1,6 @@
1
+ require 'faraday'
2
+ require 'typhoeus'
3
+ require 'typhoeus/adapters/faraday'
1
4
  require 'routemaster/client/configuration'
2
5
 
3
6
  module Routemaster
@@ -30,7 +33,8 @@ module Routemaster
30
33
  r.headers['Content-Type'] = 'application/json'
31
34
  r.body = Oj.dump(payload, mode: :strict)
32
35
  end
33
- fail "event rejected (#{response.status})" unless response.success?
36
+
37
+ raise ConnectionError, "event rejected (status: #{response.status})" unless response.success?
34
38
 
35
39
  # Any issues would have caused an exception to be thrown
36
40
  true
@@ -42,9 +46,11 @@ module Routemaster
42
46
  r.body = Oj.dump(_stringify_keys options)
43
47
  end
44
48
 
45
- unless response.success?
46
- raise 'subscribe rejected'
47
- end
49
+ raise ConnectionError, "subscribe rejected (status: #{response.status})" unless response.success?
50
+ end
51
+
52
+ def reset_connection
53
+ @_conn = nil
48
54
  end
49
55
 
50
56
  private