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 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