com.proofpoint.galaxy.cli 0.6 → 0.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.
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
- uri = URI.parse(url)
30
- @host = uri.host
31
- @ip = IPSocket::getaddress(host)
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 print_col
35
- puts "#{uuid}\t#{host}\t#{status}\t#{binary}\t#{config}"
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.assign(filter, options, args)
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 assign.")
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
- assignment = {
146
+ installation = {
74
147
  :binary => binary,
75
148
  :config => config
76
149
  }
77
- coordinator_request(filter, options, :put, 'assignment', assignment, true)
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.clear(filter, options, args)
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 clear.")
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 clear.")
181
+ raise CommandError.new(:invalid_usage, "You must specify a filter when for terminate.")
86
182
  end
87
- coordinator_request(filter, options, :delete, 'assignment')
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
- command = ENV['GALAXY_SSH_COMMAND'] || "ssh"
134
- Kernel.system "#{command} #{slot.host}"
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
- private
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.coordinator_request(filter, options, method, sub_path = nil, value = nil, is_json = false)
141
- # build the uri
142
- uri = options[:coordinator_url]
143
- uri += '/' unless uri.end_with? '/'
144
- uri += 'v1/slot/'
145
- uri += sub_path unless sub_path.nil?
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
- # create filter query
148
- query = filter.map { |k, v| "#{URI.escape(k.to_s)}=#{URI.escape(v)}" }.join('&')
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
- body = value
152
- headers = {}
289
+ headers = []
153
290
  if is_json
154
- body = value.to_json
155
- headers['Content-Type'] = 'application/json'
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 value then
161
- puts "curl -H 'Content-Type: application/json' -X#{method.to_s.upcase} '#{uri}?#{query}' -d '"
162
- puts body
163
- puts "'"
164
- else
165
- puts "curl -X#{method.to_s.upcase} '#{uri}?#{query}'"
166
- end
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).body
171
-
172
- # parse response as json
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 slots_json
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, :assign, :clear, :start, :stop, :restart, :ssh]
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] = arg
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] = arg
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] = arg
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] = arg
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] = arg
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("-s", "--state STATE", "Select 'r{unning}', 's{topped}', 'u{assigned}' or 'unknown' slots", [:running, :r, :stopped, :s, :unassigned, :u, :unknown]) do |arg|
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] = 'running'
485
+ filter[:state] <<= 'running'
257
486
  when :stopped, :s then
258
- filter[:state] = 'stopped'
259
- when :unassigned, :u then
260
- filter[:state] = 'unassigned'
487
+ filter[:state] <<= 'stopped'
261
488
  when :unknown then
262
- filter[:state] = 'unknown'
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}=#{v}" }.join("\n") if options[:debug]
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
- slots = Commands.send(command, filter, options, command_args)
308
- slots = slots.sort_by { |slot| "#{slot.ip}|#{slot.binary}|#{slot.config}|#{slot.uuid}" }
309
- puts '' if options[:debug]
310
- puts "uuid\tip\tstatus\tbinary\tconfig"
311
- slots.each { |slot| slot.print_col } unless slots.nil?
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
@@ -0,0 +1,6 @@
1
+ module Shell
2
+ def self.quote(string)
3
+ string = string.gsub("'", %q('\\\''))
4
+ "'#{string}'"
5
+ end
6
+ end
@@ -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.6"
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: 2011-06-22 00:00:00 -07:00
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