a4tools 1.2.7

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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/install.log +38 -0
  3. data/.gitignore +2 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +38 -0
  6. data/a4tools.gemspec +38 -0
  7. data/bin/deploy_latest_clients +32 -0
  8. data/bin/devsite_config_server +48 -0
  9. data/bin/netshell +23 -0
  10. data/bin/update_server +101 -0
  11. data/bin/usher +54 -0
  12. data/lib/a4tools.rb +61 -0
  13. data/lib/a4tools/version.rb +3 -0
  14. data/lib/acres_client.rb +376 -0
  15. data/lib/clients/caching_client.rb +151 -0
  16. data/lib/clients/deployment_client.rb +53 -0
  17. data/lib/clients/kai_config_client.rb +39 -0
  18. data/lib/clients/usher_client.rb +72 -0
  19. data/lib/clients/usher_mgmt_client.rb +201 -0
  20. data/lib/event_manager.rb +24 -0
  21. data/lib/events.json +1 -0
  22. data/lib/net_shell/builtin_command.rb +312 -0
  23. data/lib/net_shell/builtin_commands/build.rb +251 -0
  24. data/lib/net_shell/builtin_commands/cd.rb +12 -0
  25. data/lib/net_shell/builtin_commands/connect.rb +122 -0
  26. data/lib/net_shell/builtin_commands/deploy.rb +280 -0
  27. data/lib/net_shell/builtin_commands/disconnect.rb +15 -0
  28. data/lib/net_shell/builtin_commands/excerpt.rb +97 -0
  29. data/lib/net_shell/builtin_commands/exit.rb +7 -0
  30. data/lib/net_shell/builtin_commands/get.rb +38 -0
  31. data/lib/net_shell/builtin_commands/help.rb +40 -0
  32. data/lib/net_shell/builtin_commands/host.rb +126 -0
  33. data/lib/net_shell/builtin_commands/inject.rb +42 -0
  34. data/lib/net_shell/builtin_commands/jsoncache.rb +80 -0
  35. data/lib/net_shell/builtin_commands/kai_event.rb +151 -0
  36. data/lib/net_shell/builtin_commands/persist.rb +24 -0
  37. data/lib/net_shell/builtin_commands/pwd.rb +6 -0
  38. data/lib/net_shell/builtin_commands/recap.rb +188 -0
  39. data/lib/net_shell/builtin_commands/references.rb +63 -0
  40. data/lib/net_shell/builtin_commands/select.rb +36 -0
  41. data/lib/net_shell/builtin_commands/send.rb +74 -0
  42. data/lib/net_shell/builtin_commands/set.rb +29 -0
  43. data/lib/net_shell/builtin_commands/show.rb +183 -0
  44. data/lib/net_shell/builtin_commands/site.rb +122 -0
  45. data/lib/net_shell/builtin_commands/ssh.rb +62 -0
  46. data/lib/net_shell/builtin_commands/talk.rb +90 -0
  47. data/lib/net_shell/builtin_commands/translate.rb +45 -0
  48. data/lib/net_shell/builtin_commands/unset.rb +14 -0
  49. data/lib/net_shell/builtin_commands/usher.rb +55 -0
  50. data/lib/net_shell/builtin_commands/usher_device.rb +39 -0
  51. data/lib/net_shell/builtin_commands/usher_site.rb +245 -0
  52. data/lib/net_shell/builtin_commands/usherm_connect.rb +21 -0
  53. data/lib/net_shell/colors.rb +149 -0
  54. data/lib/net_shell/command.rb +97 -0
  55. data/lib/net_shell/io.rb +132 -0
  56. data/lib/net_shell/net_shell.rb +396 -0
  57. data/lib/net_shell/prompt.rb +335 -0
  58. data/lib/object_builder/definitions/app_info_for_script.rb +83 -0
  59. data/lib/object_builder/definitions/connection_request.rb +28 -0
  60. data/lib/object_builder/definitions/device_info_for_system.rb +37 -0
  61. data/lib/object_builder/object_builder.rb +145 -0
  62. data/lib/talk.json +1 -0
  63. data/lib/talk_consumer.rb +235 -0
  64. metadata +279 -0
@@ -0,0 +1,3 @@
1
+ module A4Tools
2
+ VERSION = "1.2.7"
3
+ end
@@ -0,0 +1,376 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'net/https'
4
+ require 'faye/websocket'
5
+ require 'eventmachine'
6
+ require 'thread'
7
+
8
+
9
+ module A4Tools
10
+ module EventGenerator
11
+ def on(key, &callback)
12
+ @callbacks ||= {}
13
+ @callbacks[key] ||= []
14
+ @callbacks[key].push callback
15
+ end
16
+
17
+ def signal(key, value=nil)
18
+ return if @callbacks.nil?
19
+ [ key, :all ].each do |k|
20
+ next unless @callbacks.has_key? k
21
+ @callbacks[k].each { |callback| callback.call(key, value) }
22
+ end
23
+ end
24
+
25
+ def passthrough(target)
26
+ target.on(:all) { |trigger, value| signal(trigger, value) }
27
+ end
28
+ end
29
+
30
+ class Transporter
31
+ include EventGenerator
32
+
33
+ def initialize
34
+ @callbacks = {}
35
+ end
36
+
37
+ def connect(timeout=5)
38
+ signal(:connect)
39
+ true
40
+ end
41
+
42
+ def disconnect
43
+ signal(:disconnect)
44
+ end
45
+ end
46
+
47
+ class HTTPTransporter < Transporter
48
+ def initialize(uri)
49
+ super()
50
+ @uri = uri
51
+ @http = Net::HTTP.new(@uri.host, @uri.port)
52
+ @http.use_ssl = @uri.scheme == 'https'
53
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
54
+ end
55
+
56
+ def response_body(result)
57
+ return nil unless result.code.to_i >= 200 and result.code.to_i < 300
58
+
59
+ jsonrpc = symbolify JSON.parse(result.body)
60
+ return nil unless jsonrpc[:jsonrpc] == "2.0"
61
+ return nil unless jsonrpc[:error].nil?
62
+ return nil if jsonrpc[:result].nil?
63
+
64
+ return jsonrpc[:result][:body] unless jsonrpc[:result][:body].nil?
65
+
66
+ return jsonrpc[:result]
67
+ end
68
+
69
+ def send_message(msg)
70
+ req = Net::HTTP::Post.new(@uri.to_s,
71
+ initheader = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
72
+ )
73
+ req.body = msg
74
+ signal(:sent, msg)
75
+ response = @http.request(req)
76
+ signal(:message, response.body)
77
+
78
+ response
79
+ end
80
+ end
81
+
82
+ class WebSocketTransporter < Transporter
83
+ attr_reader :ready
84
+
85
+ def initialize(uri)
86
+ super()
87
+ @uri = uri
88
+ @queue = []
89
+ @ready = false
90
+ @mutex = Mutex.new
91
+ @condition = ConditionVariable.new
92
+ end
93
+
94
+ def connect(timeout=5)
95
+ return true if @ready
96
+ start_socket
97
+ @mutex.synchronize { @condition.wait(@mutex, timeout) }
98
+ @ready
99
+ end
100
+
101
+ def disconnect
102
+ @client.kill unless @client.nil?
103
+ @client = nil
104
+ @ws = nil
105
+ true
106
+ end
107
+
108
+ def start_socket
109
+ @client = Thread.new do
110
+ EM.run do
111
+ @ws = Faye::WebSocket::Client.new(@uri.to_s)
112
+
113
+ @ws.on :open do |event|
114
+ @mutex.synchronize do
115
+ @ready = true
116
+ @condition.signal
117
+ end
118
+
119
+ signal(:connect)
120
+ empty_queue
121
+ end
122
+
123
+ @ws.on :error do |event|
124
+ @mutex.synchronize { @condition.signal }
125
+ end
126
+
127
+ @ws.on :message do |event|
128
+ signal(:message, event.data)
129
+ end
130
+
131
+ @ws.on :close do |event|
132
+ @ready = false
133
+ @ws = nil
134
+ signal(:disconnect)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def empty_queue
141
+ @queue.each { |msg| send_message(msg) }
142
+ @queue = []
143
+ end
144
+
145
+ def send_message(msg)
146
+ unless @ready then
147
+ @queue.push msg
148
+ return
149
+ end
150
+
151
+ signal(:sent, msg)
152
+ @ws.send(msg)
153
+ end
154
+ end
155
+
156
+ class AcresClient
157
+ include EventGenerator
158
+
159
+ attr_reader :uri, :token, :server_info, :history, :start_time, :connect_time, :connected, :ready
160
+ attr_accessor :username, :password, :version
161
+
162
+ def initialize(destination, username=nil, password=nil)
163
+ if destination.is_a? String then
164
+ @uri = URI(destination)
165
+ else
166
+ @uri = URI(destination[:url] + destination[:ctx] + "/json?wrap")
167
+ end
168
+
169
+ @username = username
170
+ @password = password
171
+ @history = []
172
+
173
+ case @uri.scheme
174
+ when 'http', 'https'
175
+ @transport = HTTPTransporter.new(@uri)
176
+ when 'ws', 'wss'
177
+ @transport = WebSocketTransporter.new(@uri)
178
+ end
179
+
180
+ passthrough(@transport)
181
+ @transport.on(:message) { |trigger, message| snoop_token(message) }
182
+ @transport.on(:message) { |trigger, message| add_message_to_history(:server, message) }
183
+ @transport.on(:sent) { |trigger, message| add_message_to_history(:client, message) }
184
+ @transport.on(:connect) { |trigger, message| @ready = true }
185
+ @transport.on(:disconnect) do |trigger, message|
186
+ @ready = false
187
+ @start_time = nil
188
+ end
189
+
190
+ @connected = false
191
+ @connect_time = nil
192
+ @start_time = nil
193
+ @authenticated = false
194
+ @token = nil
195
+ end
196
+
197
+ def add_message_to_history(sender, message, timestamp=nil)
198
+ timestamp ||= Time.now
199
+ @start_time ||= timestamp
200
+ history.push({ time: timestamp, sender: sender, message: (symbolify(JSON.parse(message)) rescue nil), raw:message })
201
+ end
202
+
203
+ def snoop_token(message)
204
+ begin
205
+ response = symbolify JSON.parse(message)
206
+ result = response[:result]
207
+ return if attempt_snoop(result)
208
+ attempt_snoop(result[:body]) unless result.nil? or result[:body].nil?
209
+ rescue
210
+ ""
211
+ end
212
+ end
213
+
214
+ def attempt_snoop(result)
215
+ if not(result.nil?) and result.has_key? :tok and result.has_key? :serverInfo then
216
+ # this is a connection token
217
+ @connected = true
218
+ @token = result[:tok] if result.has_key? :tok
219
+ @server_info = result[:serverInfo] if result.has_key? :serverInfo
220
+ signal(:connect)
221
+ true
222
+ else
223
+ false
224
+ end
225
+ end
226
+
227
+ def transport_connect
228
+ @transport.connect
229
+ end
230
+
231
+ def disconnect
232
+ @transport.disconnect
233
+ end
234
+
235
+ def connect
236
+ return nil unless transport_connect
237
+
238
+ request = {
239
+ appInfo: app_info,
240
+ deviceInfo: device_info
241
+ }
242
+
243
+ result = send_message(wrapped_message("connect", "com.acres4.common.info.ConnectionRequest", request))
244
+ response = response_body(result)
245
+ return nil unless response
246
+
247
+ response
248
+ end
249
+
250
+ def connect_if_needed
251
+ return true if @connected
252
+ return true unless connect.nil?
253
+ return false
254
+ end
255
+
256
+ def authenticate(username=nil, password=nil)
257
+ return nil unless connect_if_needed
258
+
259
+ request = {
260
+ tok: @token,
261
+ username: username || @username,
262
+ password: make_password(password || @password),
263
+ automaticReconnect: false
264
+ }
265
+
266
+ result = send_message(wrapped_message("login", "com.acres4.common.info.ClientLoginRequest", request))
267
+ response = response_body(result)
268
+ @authenticated = not(response.nil?)
269
+ response
270
+ end
271
+
272
+ def authenticate_if_needed(username=nil, password=nil)
273
+ return true if @authenticated
274
+ return true unless authenticate(username, password).nil?
275
+ return false
276
+ end
277
+
278
+ def make_password(password)
279
+ {
280
+ algorithm:"CLEARTEXT",
281
+ contents:password,
282
+ parameters:[]
283
+ }
284
+ end
285
+
286
+ def next_msg_id
287
+ @msg_id = @msg_id.nil? ? 0 : @msg_id + 1
288
+ end
289
+
290
+ def wrapped_message(method, cls, body)
291
+ cls ||= talk.guess_class(body)
292
+ body[:__class] = cls.to_s;
293
+ body[:tok] = @token unless @token.nil?
294
+
295
+ wrapper = {
296
+ className:cls,
297
+ body:body,
298
+ namedObjectEncodingType:0,
299
+ serializedEncoding:nil,
300
+ __class:"com.acres4.common.info.NamedObjectWrapper"
301
+ }
302
+
303
+ jsonrpc_message(method, nil, "com.acres4.common.info.NamedObjectWrapper", wrapper)
304
+ end
305
+
306
+ def inject_token(message, force=false, type=nil)
307
+ return nil if message.nil?
308
+ return message unless force or message[:tok].nil?
309
+ type ||= talk.guess_class(message)
310
+ unless type.nil? then
311
+ cls = talk.class_named(type)
312
+ has_tok = (cls[:field].select do |f|
313
+ is_token_class = talk.name_matches?("com.acres4.common.info.ConnectionToken", f[:type].first)
314
+ f[:name] == "tok" and is_token_class
315
+ end).length >= 1
316
+
317
+ message[:tok] = @token if has_tok
318
+ end
319
+
320
+ message
321
+ end
322
+
323
+ def jsonrpc_message(method, interface=nil, cls=nil, body={})
324
+ body[:__class] = (cls || talk.guess_class(body)) unless body.nil?
325
+
326
+ json = {
327
+ __class:"com.acres4.common.info.JSONRPCRequest",
328
+ method:method,
329
+ params:body,
330
+ id:next_msg_id,
331
+ jsonrpc:"2.0"
332
+ }
333
+
334
+ json[:intf] = interface unless interface.nil? # don't even include it if we didn't specify it
335
+ json
336
+ end
337
+
338
+ def empty_query(method, interface=nil)
339
+ jsonrpc_message(method, interface)
340
+ end
341
+
342
+ def send_message(message)
343
+ message = message.to_json unless message.is_a? String
344
+ @transport.send_message(message)
345
+ end
346
+
347
+ def response_body(result)
348
+ return nil unless result.code.to_i >= 200 and result.code.to_i < 300
349
+
350
+ jsonrpc = JSON.parse(result.body)
351
+ return nil unless jsonrpc["jsonrpc"] == "2.0"
352
+ return nil unless jsonrpc["error"].nil?
353
+ return nil if jsonrpc["result"].nil?
354
+
355
+ return symbolify jsonrpc["result"]["body"] unless jsonrpc["result"]["body"].nil?
356
+
357
+ return symbolify jsonrpc["result"]
358
+ end
359
+
360
+ def app_info
361
+ info = ObjectBuilder[:app_info_for_script].value
362
+ info[:currentTalkVersion] = @version unless @version.nil?
363
+ info
364
+ end
365
+
366
+ def device_info
367
+ ObjectBuilder[:device_info_for_system].value
368
+ end
369
+ end
370
+ end
371
+
372
+ require 'clients/caching_client.rb'
373
+ require 'clients/deployment_client.rb'
374
+ require 'clients/kai_config_client.rb'
375
+ require 'clients/usher_client.rb'
376
+ require 'clients/usher_mgmt_client.rb'
@@ -0,0 +1,151 @@
1
+ module A4Tools
2
+ class AuthenticationError < StandardError
3
+ end
4
+
5
+ class UpdateError < StandardError
6
+ end
7
+
8
+ class CachingClient < AcresClient
9
+ attr_accessor :timeout
10
+
11
+ class << self
12
+ attr_accessor :cache_methods, :query_methods
13
+ def cache(name, params={}, &blk)
14
+ @cache_methods ||= {}
15
+ @cache_methods[name] = {
16
+ params:params,
17
+ block:blk
18
+ }
19
+ end
20
+
21
+ def query(name, params={}, &blk)
22
+ @query_methods ||= {}
23
+ @query_methods[name] = {
24
+ params:params,
25
+ block:blk
26
+ }
27
+ end
28
+ end
29
+
30
+ def initialize(destination, username=nil, password=nil, timeout=300)
31
+ super(destination, username, password)
32
+ @timeout = timeout
33
+ @query_cache = {}
34
+ @cache = {}
35
+ @update_thread = nil
36
+ @query_methods = self.class.query_methods.clone rescue {}
37
+ end
38
+
39
+ def send_update_request
40
+ new_cache = {}
41
+ return {} if self.class.cache_methods.nil?
42
+ self.class.cache_methods.each do |method, defn|
43
+ if defn[:params][:authenticate] then
44
+ raise(AuthenticationError, "Unable to authenticate") unless authenticate_if_needed
45
+ end
46
+
47
+ request = defn[:block].nil? ? nil : defn[:block].call
48
+ new_cache[method] = update_from_method(method, request)
49
+ end
50
+ new_cache
51
+ end
52
+
53
+ def update_from_method(method, request, type=nil)
54
+ type ||= talk.expand_name(talk.method_named(method.to_s)[0][:request])
55
+ inject_token(request, false, type)
56
+ body = request.nil? ? empty_query(method) : wrapped_message(method.to_s, type, request)
57
+ result = send_message(body)
58
+ begin
59
+ parsed = symbolify(JSON.parse(result.body))
60
+ raise(UpdateError, "#{method}: #{parsed[:error][:code]} #{parsed[:error][:message]}") unless parsed[:error].nil?
61
+ raise(UpdateError, "#{method}: Server did not include a response field") if parsed[:result].nil?
62
+ toplevel = parsed[:result]
63
+ if toplevel.has_key?(:body) and toplevel.has_key?(:className) then
64
+ toplevel = toplevel[:body]
65
+ toplevel[:__class] = parsed[:result][:className] # strip namedobjectwrapper
66
+ end
67
+ toplevel
68
+ rescue JSON::ParserError
69
+ truncated = result.body.length > 100 ? result.body[0..99] + "..." : result.body
70
+ raise(UpdateError, "#{method}: Server returned non-JSON response: (#{result.body.length} bytes) '#{truncated}'")
71
+ end
72
+ end
73
+
74
+ def [](key)
75
+ cache(key)
76
+ end
77
+
78
+ def cache(key, sync=false)
79
+ if sync then
80
+ refresh unless fresh? # force caller to wait until we've refreshed
81
+ else
82
+ refresh_async unless fresh? # kick off an async update if we're stale, but still return what we have
83
+ end
84
+
85
+ @cache[key]
86
+ end
87
+
88
+ def query(method, *args)
89
+ defn = @query_methods[method]
90
+ raise UpdateError, "#{method}: Unsupported method" if defn.nil?
91
+
92
+ if defn[:params][:authenticate] then
93
+ raise(AuthenticationError, "Unable to authenticate as #{username}") unless authenticate_if_needed
94
+ end
95
+
96
+ @query_cache[method] ||= {}
97
+ item = @query_cache[method] || {}
98
+ return item[:cache] unless timestamp_expired?(item[:update])
99
+
100
+ request = defn[:block].nil? ? nil : defn[:block].call(*args)
101
+ result = update_from_method(method, request)
102
+ item[:update] = Time.now
103
+ item[:cache] = result
104
+ @query_cache[method] = item
105
+ result
106
+ end
107
+
108
+ # Force a cache update next time someone makes a request
109
+ def dirty
110
+ @cache_update_ts = nil
111
+ @query_methods.each { |method, defn| defn[:update] = nil }
112
+ end
113
+
114
+ def ensure_fresh
115
+ refresh unless fresh?
116
+ end
117
+
118
+ def fresh?
119
+ not timestamp_expired?(@cache_update_ts)
120
+ end
121
+
122
+ def timestamp_expired?(ts)
123
+ ts.nil? or (Time.now - ts) > @timeout
124
+ end
125
+
126
+ def refresh_async
127
+ # Create a new thread to get the update cache, unless one is already in flight
128
+ if @update_thread.nil? then
129
+ begin
130
+ Thread.new { refresh }
131
+ rescue AuthenticationError, UpdateError => exc
132
+ signal(:error, exc.message)
133
+ end
134
+ end
135
+ nil
136
+ end
137
+
138
+ def refresh
139
+ unless @update_thread.nil? then
140
+ # We already have an update thread in flight; block on that
141
+ @update_thread.join unless @update_thread == Thread.current
142
+ return @cache
143
+ end
144
+
145
+ @update_thread = Thread.current
146
+ @cache = send_update_request
147
+ @cache_update_ts = Time.now
148
+ @update_thread = nil
149
+ end
150
+ end
151
+ end