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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +3 -3
- data/CHANGELOG.md +19 -2
- data/Gemfile.lock +32 -30
- data/README.md +103 -12
- data/exe/rtm +11 -0
- data/routemaster-client.gemspec +5 -0
- data/routemaster/cli/base.rb +134 -0
- data/routemaster/cli/helper.rb +61 -0
- data/routemaster/cli/pub.rb +23 -0
- data/routemaster/cli/sub.rb +78 -0
- data/routemaster/cli/token.rb +52 -0
- data/routemaster/cli/top_level.rb +48 -0
- data/routemaster/client.rb +49 -22
- data/routemaster/client/backends/sidekiq/worker.rb +11 -1
- data/routemaster/client/configuration.rb +6 -0
- data/routemaster/client/connection.rb +10 -4
- data/routemaster/client/errors.rb +1 -0
- data/routemaster/client/subscription.rb +32 -0
- data/routemaster/client/topic.rb +22 -0
- data/routemaster/client/version.rb +1 -1
- data/spec/cli/pub_spec.rb +21 -0
- data/spec/cli/sub_spec.rb +61 -0
- data/spec/cli/token_spec.rb +50 -0
- data/spec/client/subscription_spec.rb +19 -0
- data/spec/{topic_spec.rb → client/topic_spec.rb} +2 -2
- data/spec/client_spec.rb +134 -56
- data/spec/spec_helper.rb +28 -0
- metadata +39 -8
- data/routemaster/topic.rb +0 -17
@@ -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
|
+
|
data/routemaster/client.rb
CHANGED
@@ -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 '
|
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')
|
118
|
-
|
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
|
-
|
122
|
-
|
119
|
+
Oj.load(response.body).map do |raw_subscription|
|
120
|
+
Subscription.new raw_subscription
|
123
121
|
end
|
122
|
+
end
|
124
123
|
|
125
|
-
|
126
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
47
|
-
|
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
|