a4tools 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
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