com.proofpoint.galaxy.cli 0.6 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/galaxy.rb +333 -72
- data/lib/galaxy/colorize.rb +23 -0
- data/lib/galaxy/shell.rb +6 -0
- data/lib/galaxy/table.rb +50 -0
- metadata +16 -2
data/lib/galaxy.rb
CHANGED
@@ -2,7 +2,23 @@
|
|
2
2
|
require 'optparse'
|
3
3
|
require 'httpclient'
|
4
4
|
require 'json'
|
5
|
+
require 'uri'
|
6
|
+
require 'base64'
|
7
|
+
require 'digest/md5'
|
8
|
+
require 'ssh/key/signer'
|
5
9
|
require 'galaxy/version'
|
10
|
+
require 'galaxy/colorize'
|
11
|
+
require 'galaxy/shell'
|
12
|
+
require 'galaxy/table'
|
13
|
+
|
14
|
+
# stop http client from overriding the Date header
|
15
|
+
class HTTPClient
|
16
|
+
class Session
|
17
|
+
def set_header(req)
|
18
|
+
# do nothing
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
6
22
|
|
7
23
|
module Galaxy
|
8
24
|
GALAXY_VERSION = "0.1"
|
@@ -11,28 +27,89 @@ module Galaxy
|
|
11
27
|
:success => 0,
|
12
28
|
:no_slots => 1,
|
13
29
|
:unsupported => 3,
|
14
|
-
:invalid_usage => 64
|
30
|
+
:invalid_usage => 64,
|
31
|
+
:no_signing_identities => 80,
|
32
|
+
:general_error => 99
|
15
33
|
}
|
16
34
|
|
17
35
|
#
|
18
36
|
# Slot Information
|
19
37
|
#
|
20
38
|
class Slot
|
21
|
-
attr_reader :uuid, :host, :ip, :url, :binary, :config, :status
|
39
|
+
attr_reader :uuid, :short_id, :host, :ip, :url, :binary, :config, :status, :status_message, :path
|
22
40
|
|
23
|
-
def initialize(uuid, url, binary, config, status)
|
41
|
+
def initialize(uuid, short_id, url, binary, config, status, status_message, path)
|
24
42
|
@uuid = uuid
|
43
|
+
@short_id = short_id
|
25
44
|
@url = url
|
26
45
|
@binary = binary
|
27
46
|
@config = config
|
28
47
|
@status = status
|
29
|
-
|
30
|
-
@
|
31
|
-
|
48
|
+
@status_message = status_message
|
49
|
+
@path = path
|
50
|
+
|
51
|
+
unless url.nil?
|
52
|
+
@host = URI.parse(url).host unless url.nil?
|
53
|
+
@ip = IPSocket::getaddress(host)
|
54
|
+
end
|
55
|
+
|
56
|
+
@host ||= "unknown"
|
57
|
+
@ip ||= "unknown"
|
58
|
+
end
|
59
|
+
|
60
|
+
def columns(colors = false)
|
61
|
+
status = @status
|
62
|
+
status_message = @status_message
|
63
|
+
|
64
|
+
if colors
|
65
|
+
status = case status
|
66
|
+
when "RUNNING" then Colorize::colorize(status, :bright, :green)
|
67
|
+
when "STOPPED" then status
|
68
|
+
else status
|
69
|
+
end
|
70
|
+
status_message = Colorize::colorize(status_message, :red)
|
71
|
+
end
|
72
|
+
|
73
|
+
return [@short_id, @host, status, @binary, @config, status_message].map { |value| value || '' }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
#
|
77
|
+
# Agent Information
|
78
|
+
#
|
79
|
+
class Agent
|
80
|
+
attr_reader :agent_id, :host, :ip, :url, :status, :location, :path, :instance_type
|
81
|
+
|
82
|
+
def initialize(agent_id, status, url, location, instance_type)
|
83
|
+
@agent_id = agent_id
|
84
|
+
@url = url
|
85
|
+
@status = status
|
86
|
+
@path = path
|
87
|
+
|
88
|
+
unless url.nil?
|
89
|
+
@host = URI.parse(url).host unless url.nil?
|
90
|
+
@ip = IPSocket::getaddress(host)
|
91
|
+
end
|
92
|
+
|
93
|
+
@host ||= "unknown"
|
94
|
+
@ip ||= "unknown"
|
95
|
+
|
96
|
+
@location = location
|
97
|
+
@instance_type = instance_type
|
32
98
|
end
|
33
99
|
|
34
|
-
def
|
35
|
-
|
100
|
+
def columns(colors = false)
|
101
|
+
status = @status
|
102
|
+
|
103
|
+
if colors
|
104
|
+
status = case status
|
105
|
+
when "ONLINE" then Colorize::colorize(status, :bright, :green)
|
106
|
+
when "OFFLINE" then Colorize::colorize(status, :bright, :red)
|
107
|
+
when "PROVISIONING" then Colorize::colorize(status, :bright, :blue)
|
108
|
+
else status
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
return [@agent_id, @host, status, @instance_type, @location].map { |value| value || '' }
|
36
113
|
end
|
37
114
|
end
|
38
115
|
|
@@ -54,13 +131,9 @@ module Galaxy
|
|
54
131
|
coordinator_request(filter, options, :get)
|
55
132
|
end
|
56
133
|
|
57
|
-
def self.
|
134
|
+
def self.install(filter, options, args)
|
58
135
|
if args.size != 2 then
|
59
|
-
raise CommandError.new(:invalid_usage, "You must specify a binary and config to
|
60
|
-
end
|
61
|
-
if filter.empty? then
|
62
|
-
raise CommandError.new(:invalid_usage, "You must specify a filter when for assign.")
|
63
|
-
|
136
|
+
raise CommandError.new(:invalid_usage, "You must specify a binary and config to install.")
|
64
137
|
end
|
65
138
|
if args[0].start_with? '@'
|
66
139
|
config = args[0]
|
@@ -70,21 +143,44 @@ module Galaxy
|
|
70
143
|
config = args[1]
|
71
144
|
|
72
145
|
end
|
73
|
-
|
146
|
+
installation = {
|
74
147
|
:binary => binary,
|
75
148
|
:config => config
|
76
149
|
}
|
77
|
-
coordinator_request(filter, options, :
|
150
|
+
coordinator_request(filter, options, :post, nil, installation, true)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.upgrade(filter, options, args)
|
154
|
+
if args.size <= 0 || args.size > 2 then
|
155
|
+
raise CommandError.new(:invalid_usage, "You must specify a binary version or a config version for upgrade.")
|
156
|
+
end
|
157
|
+
if filter.empty? then
|
158
|
+
raise CommandError.new(:invalid_usage, "You must specify a filter when for upgrade.")
|
159
|
+
end
|
160
|
+
|
161
|
+
if args[0].start_with? '@'
|
162
|
+
config_version = args[0][1..-1]
|
163
|
+
binary_version = args[1] if args.size > 1
|
164
|
+
else
|
165
|
+
binary_version = args[0]
|
166
|
+
config_version = args[1][1..-1] if args.size > 1
|
167
|
+
|
168
|
+
end
|
169
|
+
versions = {}
|
170
|
+
versions[:binaryVersion] = binary_version if binary_version
|
171
|
+
versions[:configVersion] = config_version if config_version
|
172
|
+
|
173
|
+
coordinator_request(filter, options, :post, 'assignment', versions, true)
|
78
174
|
end
|
79
175
|
|
80
|
-
def self.
|
176
|
+
def self.terminate(filter, options, args)
|
81
177
|
if !args.empty? then
|
82
|
-
raise CommandError.new(:invalid_usage, "You can not pass arguments to
|
178
|
+
raise CommandError.new(:invalid_usage, "You can not pass arguments to terminate.")
|
83
179
|
end
|
84
180
|
if filter.empty? then
|
85
|
-
raise CommandError.new(:invalid_usage, "You must specify a filter when for
|
181
|
+
raise CommandError.new(:invalid_usage, "You must specify a filter when for terminate.")
|
86
182
|
end
|
87
|
-
coordinator_request(filter, options, :delete
|
183
|
+
coordinator_request(filter, options, :delete)
|
88
184
|
end
|
89
185
|
|
90
186
|
def self.start(filter, options, args)
|
@@ -130,56 +226,152 @@ module Galaxy
|
|
130
226
|
end
|
131
227
|
|
132
228
|
slot = slots.first
|
133
|
-
|
134
|
-
|
229
|
+
ssh = ENV['GALAXY_SSH_COMMAND'] || "ssh"
|
230
|
+
|
231
|
+
if slot.path.nil?
|
232
|
+
path = "$HOME"
|
233
|
+
else
|
234
|
+
path = Shell::quote(slot.path)
|
235
|
+
end
|
236
|
+
remote_command = "cd #{path}; #{options[:ssh_command]}"
|
237
|
+
command = "#{ssh} #{slot.host} -t #{Shell::quote(remote_command)}"
|
238
|
+
|
239
|
+
puts command if options[:debug]
|
240
|
+
system(command)
|
135
241
|
[]
|
136
242
|
end
|
137
243
|
|
138
|
-
|
244
|
+
def self.reset_to_actual(filter, options, args)
|
245
|
+
if !args.empty? then
|
246
|
+
raise CommandError.new(:invalid_usage, "You can not pass arguments to reset-to-actual.")
|
247
|
+
end
|
248
|
+
if filter.empty? then
|
249
|
+
raise CommandError.new(:invalid_usage, "You must specify a filter when for reset-to-actual.")
|
250
|
+
end
|
251
|
+
coordinator_request(filter, options, :delete, "expected-state")
|
252
|
+
end
|
139
253
|
|
140
|
-
def self.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
254
|
+
def self.agent_show(filter, options, args)
|
255
|
+
if !args.empty? then
|
256
|
+
raise CommandError.new(:invalid_usage, "You can not pass arguments to agent show.")
|
257
|
+
end
|
258
|
+
if !filter.empty? then
|
259
|
+
raise CommandError.new(:invalid_usage, "You can not use filters with agent show.")
|
260
|
+
end
|
261
|
+
coordinator_agent_request(filter, options, :get)
|
262
|
+
end
|
146
263
|
|
147
|
-
|
148
|
-
|
264
|
+
def self.agent_add(filter, options, args)
|
265
|
+
if args.size > 1 then
|
266
|
+
raise CommandError.new(:invalid_usage, "Agent add only accepts one argument.")
|
267
|
+
end
|
268
|
+
if !filter.empty? then
|
269
|
+
raise CommandError.new(:invalid_usage, "You can not use filters with agent show.")
|
270
|
+
end
|
149
271
|
|
272
|
+
if args.size > 0 then
|
273
|
+
instance_type = args[0]
|
274
|
+
end
|
275
|
+
|
276
|
+
provisioning = {}
|
277
|
+
provisioning[:instanceType] = instance_type if instance_type
|
278
|
+
provisioning[:availabilityZone] = options[:availability_zone] if options[:availability_zone]
|
279
|
+
|
280
|
+
query = "count=#{options[:count] || 1}"
|
281
|
+
|
282
|
+
coordinator_agent_request(filter, options, :post, query, nil, provisioning, true)
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
def self.execute_request(method, uri, query, body, options, is_json)
|
150
288
|
# encode body as json if necessary
|
151
|
-
|
152
|
-
headers = {}
|
289
|
+
headers = []
|
153
290
|
if is_json
|
154
|
-
body =
|
155
|
-
headers['Content-Type'
|
291
|
+
body = body.to_json
|
292
|
+
headers += [['Content-Type', 'application/json']]
|
293
|
+
end
|
294
|
+
|
295
|
+
# add date header
|
296
|
+
request_time = Time.now
|
297
|
+
headers += [['Date', request_time.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT')]]
|
298
|
+
|
299
|
+
# remove query string if empty
|
300
|
+
query = nil if query.to_s.empty?
|
301
|
+
|
302
|
+
# build signature string
|
303
|
+
timestamp = request_time.to_i
|
304
|
+
method = method.to_s.upcase
|
305
|
+
relative_uri = URI.parse(uri).path + ('?' + query if query).to_s
|
306
|
+
body_md5 = Digest::MD5.hexdigest(body.to_s)
|
307
|
+
string_to_sign = [timestamp, method, relative_uri, body_md5].join("\n")
|
308
|
+
puts "string_to_sign:\n#{string_to_sign}\n--" if options[:debug]
|
309
|
+
|
310
|
+
# generate signature headers
|
311
|
+
signer = SSH::Key::Signer.new
|
312
|
+
signatures = signer.sign(string_to_sign)
|
313
|
+
if signatures.empty?
|
314
|
+
raise CommandError.new(:no_signing_identities, "No identities in SSH agent (use ssh-add)")
|
315
|
+
end
|
316
|
+
signatures.each do |sig|
|
317
|
+
fingerprint = sig.identity.fingerprint.delete(':')
|
318
|
+
signature = Base64.strict_encode64(sig.signature)
|
319
|
+
headers += [['Authorization', "Galaxy #{fingerprint}:#{signature}"]]
|
156
320
|
end
|
157
321
|
|
158
322
|
# log request in as a valid curl command if in debug mode
|
159
323
|
if options[:debug]
|
160
|
-
if
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
324
|
+
debug_uri = uri + ('?' + query if query).to_s
|
325
|
+
print "curl"
|
326
|
+
headers.each { |k,v| print " -H '#{k}: #{v}'" }
|
327
|
+
print " -X #{method}"
|
328
|
+
print " -d '#{body}'" if body
|
329
|
+
puts " '#{debug_uri}'"
|
330
|
+
puts
|
167
331
|
end
|
168
332
|
|
169
333
|
# execute request
|
170
|
-
response = HTTPClient.new.request(method, uri, query, body, headers)
|
171
|
-
|
172
|
-
|
173
|
-
slots_json = JSON.parse(response)
|
334
|
+
response = HTTPClient.new.request(method, uri, query, body, headers)
|
335
|
+
response_body = response.body if response.respond_to?(:body)
|
336
|
+
response_body = response_body.content if response_body.respond_to?(:content)
|
174
337
|
|
175
338
|
# log response if in debug mode
|
176
339
|
if options[:debug]
|
177
|
-
puts
|
340
|
+
puts
|
341
|
+
puts "#{response.status} #{response.reason}"
|
342
|
+
puts "--\n#{response_body.rstrip}\n--"
|
343
|
+
end
|
344
|
+
|
345
|
+
# check response code
|
346
|
+
if response.status / 100 != 2 then
|
347
|
+
raise CommandError.new(:general_error, response.reason || "Unknown error executing command")
|
178
348
|
end
|
349
|
+
response_body
|
350
|
+
end
|
351
|
+
|
352
|
+
def self.coordinator_request(filter, options, method, sub_path = nil, value = nil, is_json = false)
|
353
|
+
# build the uri
|
354
|
+
uri = options[:coordinator_url]
|
355
|
+
uri += '/' unless uri.end_with? '/'
|
356
|
+
uri += 'v1/slot/'
|
357
|
+
uri += sub_path unless sub_path.nil?
|
358
|
+
|
359
|
+
# create filter query
|
360
|
+
params = filter
|
361
|
+
# todo this arbitrary rename here is just wrong
|
362
|
+
params["limit"] <<= options[:count] unless options[:count].nil?
|
363
|
+
params["pretty"] <<= "true" unless options[:debug].nil?
|
364
|
+
query = params.map { |k, v| v.map { |v1| "#{k}=#{URI.escape(v1)}" }.join('&') }.join('&')
|
365
|
+
|
366
|
+
# execute request
|
367
|
+
response_body = execute_request(method, uri, query, value, options, is_json)
|
368
|
+
|
369
|
+
# parse response as json
|
370
|
+
slots_json = JSON.parse(response_body)
|
179
371
|
|
180
372
|
# convert parsed json into slot objects
|
181
373
|
slots = slots_json.map do |slot_json|
|
182
|
-
Slot.new(slot_json['id'], slot_json['self'], slot_json['binary'], slot_json['config'], slot_json['status'])
|
374
|
+
Slot.new(slot_json['id'], slot_json['shortId'], slot_json['self'], slot_json['binary'], slot_json['config'], slot_json['status'], slot_json['statusMessage'], slot_json['installPath'])
|
183
375
|
end
|
184
376
|
|
185
377
|
# verify response
|
@@ -189,19 +381,43 @@ module Galaxy
|
|
189
381
|
|
190
382
|
slots
|
191
383
|
end
|
384
|
+
|
385
|
+
def self.coordinator_agent_request(filter, options, method, query = '', sub_path = nil, value = nil, is_json = false)
|
386
|
+
# build the uri
|
387
|
+
uri = options[:coordinator_url]
|
388
|
+
uri += '/' unless uri.end_with? '/'
|
389
|
+
uri += 'v1/admin/agent'
|
390
|
+
uri += sub_path unless sub_path.nil?
|
391
|
+
|
392
|
+
query = '&pretty' unless options[:debug].nil?
|
393
|
+
|
394
|
+
# execute request
|
395
|
+
response_body = execute_request(method, uri, query, value, options, is_json)
|
396
|
+
|
397
|
+
# parse response as json
|
398
|
+
agents_json = JSON.parse(response_body)
|
399
|
+
|
400
|
+
# convert parsed json into slot objects
|
401
|
+
agents = agents_json.map do |agent_json|
|
402
|
+
Agent.new(agent_json['agentId'], agent_json['state'], agent_json['self'], agent_json['location'], agent_json['instanceType'])
|
403
|
+
end
|
404
|
+
|
405
|
+
return agents
|
406
|
+
end
|
192
407
|
end
|
193
408
|
|
194
409
|
class CLI
|
195
410
|
|
196
|
-
COMMANDS = [:show, :
|
411
|
+
COMMANDS = [:show, :install, :upgrade, :terminate, :start, :stop, :restart, :ssh, :reset_to_actual, :agent_show, :agent_add]
|
197
412
|
INITIAL_OPTIONS = {
|
198
|
-
:coordinator_url => ENV['GALAXY_COORDINATOR'] || 'http://localhost:64000'
|
413
|
+
:coordinator_url => ENV['GALAXY_COORDINATOR'] || 'http://localhost:64000',
|
414
|
+
:ssh_command => "bash --login"
|
199
415
|
}
|
200
416
|
|
201
417
|
def self.parse_command_line(args)
|
202
418
|
options = INITIAL_OPTIONS
|
203
419
|
|
204
|
-
filter = Hash.new
|
420
|
+
filter = Hash.new{[]}
|
205
421
|
|
206
422
|
option_parser = OptionParser.new do |opts|
|
207
423
|
opts.banner = "Usage: #{File.basename($0)} [options] <command>"
|
@@ -231,35 +447,46 @@ module Galaxy
|
|
231
447
|
opts.separator 'Filters:'
|
232
448
|
|
233
449
|
opts.on("-b", "--binary BINARY", "Select slots with a given binary") do |arg|
|
234
|
-
filter[:binary]
|
450
|
+
filter[:binary] <<= arg
|
235
451
|
end
|
236
452
|
|
237
453
|
opts.on("-c", "--config CONFIG", "Select slots with given configuration") do |arg|
|
238
|
-
filter[:config]
|
454
|
+
filter[:config] <<= arg
|
239
455
|
end
|
240
456
|
|
241
457
|
opts.on("-i", "--host HOST", "Select slots on the given hostname") do |arg|
|
242
|
-
filter[:host]
|
458
|
+
filter[:host] <<= arg
|
243
459
|
end
|
244
460
|
|
245
461
|
opts.on("-I", "--ip IP", "Select slots at the given IP address") do |arg|
|
246
|
-
filter[:ip]
|
462
|
+
filter[:ip] <<= arg
|
247
463
|
end
|
248
464
|
|
249
465
|
opts.on("-u", "--uuid SLOT_UUID", "Select slots with given slot uuid") do |arg|
|
250
|
-
filter[:uuid]
|
466
|
+
filter[:uuid] <<= arg
|
467
|
+
end
|
468
|
+
|
469
|
+
opts.on("--count count", "Number of instances to install or agents to provision") do |arg|
|
470
|
+
options[:count] = arg
|
251
471
|
end
|
252
472
|
|
253
|
-
opts.on("-
|
473
|
+
opts.on("--availability-zone AVAILABILITY_ZONE", "Availability zone to agent provisioning") do |arg|
|
474
|
+
options[:availability_zone] = arg
|
475
|
+
end
|
476
|
+
|
477
|
+
# todo find a better command line argument
|
478
|
+
opts.on("-x", "--ssh-command SSH_COMMAND", "Command to execute with ssh") do |arg|
|
479
|
+
options[:ssh_command] = arg
|
480
|
+
end
|
481
|
+
|
482
|
+
opts.on("-s", "--state STATE", "Select 'r{unning}', 's{topped}' or 'unknown' slots", [:running, :r, :stopped, :s, :unknown]) do |arg|
|
254
483
|
case arg
|
255
484
|
when :running, :r then
|
256
|
-
filter[:state]
|
485
|
+
filter[:state] <<= 'running'
|
257
486
|
when :stopped, :s then
|
258
|
-
filter[:state]
|
259
|
-
when :unassigned, :u then
|
260
|
-
filter[:state] = 'unassigned'
|
487
|
+
filter[:state] <<= 'stopped'
|
261
488
|
when :unknown then
|
262
|
-
filter[:state]
|
489
|
+
filter[:state] <<= 'unknown'
|
263
490
|
end
|
264
491
|
end
|
265
492
|
|
@@ -281,15 +508,27 @@ NOTES
|
|
281
508
|
option_parser.parse!(args)
|
282
509
|
|
283
510
|
puts options.map { |k, v| "#{k}=#{v}" }.join("\n") if options[:debug]
|
284
|
-
puts filter.map { |k, v| "#{k}=#{
|
511
|
+
puts filter.map { |k, v| v.map { |v1| "#{k}=#{URI.escape(v1)}" }.join('\n') }.join('\n') if options[:debug]
|
285
512
|
|
286
|
-
if args.length == 0
|
513
|
+
if args.length == 0 then
|
287
514
|
puts option_parser
|
288
515
|
exit EXIT_CODES[:success]
|
289
516
|
end
|
290
517
|
|
518
|
+
args[0] = args[0].gsub('-', '_');
|
291
519
|
command = args[0].to_sym
|
292
520
|
|
521
|
+
is_agent = false
|
522
|
+
if command == :agent then
|
523
|
+
args = args.drop(1)
|
524
|
+
if args.length == 0 then
|
525
|
+
puts option_parser
|
526
|
+
exit EXIT_CODES[:success]
|
527
|
+
end
|
528
|
+
command = "#{command}_#{args[0]}".to_sym
|
529
|
+
is_agent = true
|
530
|
+
end
|
531
|
+
|
293
532
|
unless COMMANDS.include?(command)
|
294
533
|
raise CommandError.new(:invalid_usage, "Unsupported command: #{command}")
|
295
534
|
end
|
@@ -298,18 +537,34 @@ NOTES
|
|
298
537
|
raise CommandError.new(:invalid_usage, "You must set Galaxy coordinator host by passing --coordinator COORDINATOR or by setting the GALAXY_COORDINATOR environment variable.")
|
299
538
|
end
|
300
539
|
|
301
|
-
return [command, filter, options, args.drop(1)]
|
540
|
+
return [command, filter, options, is_agent, args.drop(1)]
|
302
541
|
end
|
303
542
|
|
304
543
|
def self.execute(args)
|
305
544
|
begin
|
306
|
-
(command, filter, options, command_args) = parse_command_line(args)
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
545
|
+
(command, filter, options, is_agent, command_args) = parse_command_line(args)
|
546
|
+
result = Commands.send(command, filter, options, command_args)
|
547
|
+
if !is_agent then
|
548
|
+
slots = result.sort_by { |slot| [slot.ip, slot.binary || '', slot.config || '', slot.uuid] }
|
549
|
+
puts '' if options[:debug]
|
550
|
+
|
551
|
+
table = Table.new(['uuid', 'ip', 'status', 'binary', 'config', ''].map { |h| Colorize::colorize(h, :bright, :cyan) })
|
552
|
+
slots.each { |slot| table << slot.columns(STDOUT.tty?) }
|
553
|
+
puts table.render(STDOUT.tty?)
|
554
|
+
else
|
555
|
+
agents = result.sort_by { |agent| [agent.ip, agent.agent_id] }
|
556
|
+
puts '' if options[:debug]
|
557
|
+
|
558
|
+
table = Table.new(['id', 'ip', 'status', 'type', 'location'].map { |h| Colorize::colorize(h, :bright, :cyan) })
|
559
|
+
agents.each { |agent| table << agent.columns(STDOUT.tty?) }
|
560
|
+
puts table.render(STDOUT.tty?)
|
561
|
+
end
|
562
|
+
|
563
|
+
|
312
564
|
exit EXIT_CODES[:success]
|
565
|
+
rescue Errno::ECONNREFUSED
|
566
|
+
puts "Coordinator refused connection: #{options[:coordinator_url]}"
|
567
|
+
exit EXIT_CODES[:general_error]
|
313
568
|
rescue CommandError => e
|
314
569
|
puts e.message
|
315
570
|
if e.code == :invalid_usage
|
@@ -320,9 +575,15 @@ NOTES
|
|
320
575
|
puts ''
|
321
576
|
puts "exit: #{e.code}"
|
322
577
|
end
|
323
|
-
exit EXIT_CODES[e.code]
|
578
|
+
exit EXIT_CODES[e.code || :general_error]
|
324
579
|
end
|
325
580
|
end
|
326
581
|
|
327
582
|
end
|
328
583
|
end
|
584
|
+
|
585
|
+
|
586
|
+
if __FILE__ == $0 then
|
587
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
588
|
+
Galaxy::CLI.execute(ARGV)
|
589
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Colorize
|
2
|
+
module ANSIColors
|
3
|
+
COLORS = {
|
4
|
+
:normal => "\e[0m",
|
5
|
+
:bright => "\e[1m",
|
6
|
+
|
7
|
+
:red => "\e[31m",
|
8
|
+
:green => "\e[32m",
|
9
|
+
:blue => "\e[34m",
|
10
|
+
:cyan => "\e[36m"
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.colorize(string, *colors)
|
15
|
+
color_string = colors.map { |color| ANSIColors::COLORS[color] }.join
|
16
|
+
|
17
|
+
return "#{color_string}#{string}#{ANSIColors::COLORS[:normal]}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.strip_colors(string)
|
21
|
+
string.gsub(/\e\[\d+m/, '')
|
22
|
+
end
|
23
|
+
end
|
data/lib/galaxy/shell.rb
ADDED
data/lib/galaxy/table.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'galaxy/colorize'
|
2
|
+
|
3
|
+
class Table
|
4
|
+
def initialize(headers = nil, rows = nil)
|
5
|
+
@headers = headers
|
6
|
+
@rows = rows || []
|
7
|
+
end
|
8
|
+
|
9
|
+
def <<(row)
|
10
|
+
@rows << row
|
11
|
+
end
|
12
|
+
|
13
|
+
def calculate_widths
|
14
|
+
(@rows + [@headers]).
|
15
|
+
map { |cols| cols.map { |col| Colorize::strip_colors(col) }.map(&:size) }.
|
16
|
+
transpose.
|
17
|
+
slice(0..-2). # don't pad last column
|
18
|
+
map(&:max)
|
19
|
+
end
|
20
|
+
|
21
|
+
def render(tty = true)
|
22
|
+
widths = calculate_widths
|
23
|
+
|
24
|
+
rows = @rows
|
25
|
+
rows = [@headers] + rows if tty
|
26
|
+
|
27
|
+
rows.map { |row| render_row(row, widths, tty) }.join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def render_row(row, col_widths, tty)
|
31
|
+
if tty
|
32
|
+
row.zip(col_widths).map do |value, width|
|
33
|
+
width ||= Colorize.strip_colors(value).length
|
34
|
+
value + ' ' * (width - Colorize.strip_colors(value).length)
|
35
|
+
end.join(' ')
|
36
|
+
else
|
37
|
+
row.map { |value| Colorize.strip_colors(value) }.join("\t")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
if __FILE__ == $0
|
44
|
+
table = Table.new(['uuid', 'ip', 'status', 'binary', 'config', ''])
|
45
|
+
|
46
|
+
table << [Colorize::colorize('1' * 6, :green), '2' * 5, '3' * 10, '4' * 10, '5' * 5, '6' * 3]
|
47
|
+
table << ['1' * 6, '2' * 5, '3' * 15, '4' * 7, '5' * 5, '6' * 10]
|
48
|
+
|
49
|
+
puts table.render(true)
|
50
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: com.proofpoint.galaxy.cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: "0.
|
5
|
+
version: "0.7"
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Dain Sundstrom
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date:
|
13
|
+
date: 2012-01-11 00:00:00 -08:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -35,6 +35,17 @@ dependencies:
|
|
35
35
|
version: 1.5.1
|
36
36
|
type: :runtime
|
37
37
|
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: sshkeyauth
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.0.11
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
38
49
|
description: Galaxy command line interface
|
39
50
|
email:
|
40
51
|
- dain@iq80.com
|
@@ -46,6 +57,9 @@ extra_rdoc_files: []
|
|
46
57
|
|
47
58
|
files:
|
48
59
|
- bin/galaxy
|
60
|
+
- lib/galaxy/colorize.rb
|
61
|
+
- lib/galaxy/shell.rb
|
62
|
+
- lib/galaxy/table.rb
|
49
63
|
- lib/galaxy/version.rb
|
50
64
|
- lib/galaxy.rb
|
51
65
|
has_rdoc: true
|