rhc 1.6.8 → 1.7.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/autocomplete/rhc_bash +1167 -0
  2. data/features/README.md +1 -1
  3. data/features/domain.feature +1 -1
  4. data/features/lib/rhc_helper/persistable.rb +4 -1
  5. data/features/multiple_cartridge.feature +4 -3
  6. data/features/sshkey.feature +3 -3
  7. data/features/support/assumptions.rb +3 -3
  8. data/features/support/env.rb +10 -0
  9. data/features/support/platform_support.rb +2 -2
  10. data/lib/rhc.rb +6 -0
  11. data/lib/rhc/auth/token.rb +4 -0
  12. data/lib/rhc/autocomplete.rb +50 -52
  13. data/lib/rhc/autocomplete_templates/{rhc.erb → bash.erb} +8 -2
  14. data/lib/rhc/cartridge_helpers.rb +1 -1
  15. data/lib/rhc/cli.rb +1 -7
  16. data/lib/rhc/command_runner.rb +45 -16
  17. data/lib/rhc/commands.rb +75 -55
  18. data/lib/rhc/commands/account.rb +7 -51
  19. data/lib/rhc/commands/alias.rb +26 -17
  20. data/lib/rhc/commands/app.rb +75 -39
  21. data/lib/rhc/commands/authorization.rb +4 -2
  22. data/lib/rhc/commands/base.rb +31 -29
  23. data/lib/rhc/commands/cartridge.rb +66 -44
  24. data/lib/rhc/commands/domain.rb +20 -8
  25. data/lib/rhc/commands/git_clone.rb +3 -3
  26. data/lib/rhc/commands/logout.rb +51 -0
  27. data/lib/rhc/commands/port_forward.rb +15 -11
  28. data/lib/rhc/commands/setup.rb +25 -0
  29. data/lib/rhc/commands/snapshot.rb +20 -10
  30. data/lib/rhc/commands/sshkey.rb +21 -7
  31. data/lib/rhc/commands/tail.rb +2 -2
  32. data/lib/rhc/commands/threaddump.rb +2 -2
  33. data/lib/rhc/context_helper.rb +0 -4
  34. data/lib/rhc/core_ext.rb +96 -76
  35. data/lib/rhc/exceptions.rb +6 -0
  36. data/lib/rhc/help_formatter.rb +19 -2
  37. data/lib/rhc/helpers.rb +32 -194
  38. data/lib/rhc/highline_extensions.rb +412 -0
  39. data/lib/rhc/output_helpers.rb +31 -67
  40. data/lib/rhc/rest.rb +4 -2
  41. data/lib/rhc/rest/alias.rb +0 -2
  42. data/lib/rhc/rest/application.rb +9 -4
  43. data/lib/rhc/rest/authorization.rb +0 -2
  44. data/lib/rhc/rest/base.rb +1 -1
  45. data/lib/rhc/rest/client.rb +11 -9
  46. data/lib/rhc/rest/domain.rb +5 -1
  47. data/lib/rhc/rest/gear_group.rb +0 -2
  48. data/lib/rhc/rest/key.rb +0 -2
  49. data/lib/rhc/rest/mock.rb +32 -10
  50. data/lib/rhc/ssh_helpers.rb +2 -2
  51. data/lib/rhc/usage_templates/command_help.erb +20 -13
  52. data/lib/rhc/usage_templates/command_syntax_help.erb +1 -3
  53. data/lib/rhc/usage_templates/help.erb +15 -16
  54. data/lib/rhc/usage_templates/options_help.erb +7 -9
  55. data/lib/rhc/wizard.rb +193 -159
  56. data/spec/rest_spec_helper.rb +2 -2
  57. data/spec/rhc/cli_spec.rb +36 -5
  58. data/spec/rhc/command_spec.rb +94 -42
  59. data/spec/rhc/commands/account_spec.rb +1 -75
  60. data/spec/rhc/commands/alias_spec.rb +28 -28
  61. data/spec/rhc/commands/app_spec.rb +141 -33
  62. data/spec/rhc/commands/apps_spec.rb +4 -4
  63. data/spec/rhc/commands/authorization_spec.rb +8 -8
  64. data/spec/rhc/commands/cartridge_spec.rb +18 -9
  65. data/spec/rhc/commands/domain_spec.rb +16 -16
  66. data/spec/rhc/commands/git_clone_spec.rb +3 -3
  67. data/spec/rhc/commands/logout_spec.rb +86 -0
  68. data/spec/rhc/commands/port_forward_spec.rb +9 -9
  69. data/spec/rhc/commands/server_spec.rb +5 -5
  70. data/spec/rhc/commands/setup_spec.rb +19 -5
  71. data/spec/rhc/commands/snapshot_spec.rb +12 -12
  72. data/spec/rhc/commands/sshkey_spec.rb +11 -11
  73. data/spec/rhc/commands/tail_spec.rb +5 -5
  74. data/spec/rhc/commands/threaddump_spec.rb +3 -3
  75. data/spec/rhc/config_spec.rb +6 -6
  76. data/spec/rhc/helpers_spec.rb +72 -219
  77. data/spec/rhc/highline_extensions_spec.rb +269 -0
  78. data/spec/rhc/rest_application_spec.rb +28 -1
  79. data/spec/rhc/rest_client_spec.rb +20 -21
  80. data/spec/rhc/rest_spec.rb +10 -0
  81. data/spec/rhc/wizard_spec.rb +72 -32
  82. data/spec/spec_helper.rb +86 -56
  83. data/spec/wizard_spec_helper.rb +7 -4
  84. metadata +165 -160
  85. data/spec/spec.opts +0 -1
@@ -0,0 +1,412 @@
1
+ require 'delegate'
2
+
3
+ #
4
+ # Add specific improved functionality
5
+ #
6
+ class HighLineExtension < HighLine
7
+ [:ask, :agree].each do |sym|
8
+ define_method(sym) do |*args, &block|
9
+ separate_blocks
10
+ r = super(*args, &block)
11
+ @last_line_open = false
12
+ r
13
+ end
14
+ end
15
+
16
+ # OVERRIDE
17
+ def say(msg)
18
+ if msg.respond_to? :to_str
19
+ separate_blocks
20
+
21
+ statement = msg.to_str
22
+ return statement unless statement.present?
23
+
24
+ template = ERB.new(statement, nil, "%")
25
+ statement = template.result(binding)
26
+
27
+ statement = statement.textwrap_ansi(@wrap_at, false).join("#{indentation}\n") unless @wrap_at.nil?
28
+ statement = send(:page_print, statement) unless @page_at.nil?
29
+
30
+ @output.print(indentation) unless @last_line_open
31
+
32
+ @last_line_open =
33
+ if statement[-1, 1] == " " or statement[-1, 1] == "\t"
34
+ @output.print(statement)
35
+ @output.flush
36
+ true
37
+ else
38
+ @output.puts(statement)
39
+ false
40
+ end
41
+
42
+ elsif msg.respond_to? :each
43
+ separate_blocks
44
+
45
+ @output.print if @last_line_open
46
+ @last_line_open = false
47
+
48
+ color = msg.color if msg.respond_to? :color
49
+ @output.print HighLine::Style(color).code if color
50
+
51
+ msg.each do |s|
52
+ @output.print indentation
53
+ @output.puts s
54
+ end
55
+
56
+ @output.print HighLine::CLEAR if color
57
+ @output.flush
58
+ end
59
+
60
+ msg
61
+ end
62
+
63
+ # given an array of arrays "items", construct an array of strings that can
64
+ # be used to print in tabular form.
65
+ def table(items, opts={}, &block)
66
+ items = items.map(&block) if block_given?
67
+ opts[:width] ||= default_max_width
68
+ Table.new(items, opts)
69
+ end
70
+
71
+ def table_args(indent=nil, *args)
72
+ opts = {}
73
+ opts[:indent] = indent
74
+ opts[:width] = [default_max_width, *args]
75
+ opts
76
+ end
77
+
78
+ def default_max_width
79
+ @wrap_at ? @wrap_at - indentation.length : nil
80
+ end
81
+
82
+ def header(str,opts = {}, &block)
83
+ say Header.new(str, default_max_width, ' ')
84
+ if block_given?
85
+ indent &block
86
+ end
87
+ end
88
+
89
+ #:nocov:
90
+ # Backport from Highline 1.6.16
91
+ unless HighLine.method_defined? :indent
92
+ #
93
+ # Outputs indentation with current settings
94
+ #
95
+ def indentation
96
+ @indent_size ||= 2
97
+ @indent_level ||= 0
98
+ return ' '*@indent_size*@indent_level
99
+ end
100
+
101
+ #
102
+ # Executes block or outputs statement with indentation
103
+ #
104
+ def indent(increase=1, statement=nil, multiline=nil)
105
+ @indent_size ||= 2
106
+ @indent_level ||= 0
107
+ @indent_level += increase
108
+ multi = @multi_indent
109
+ @multi_indent = multiline unless multiline.nil?
110
+ if block_given?
111
+ yield self
112
+ else
113
+ say(statement)
114
+ end
115
+ ensure
116
+ @multi_indent = multi
117
+ @indent_level -= increase
118
+ end
119
+ end
120
+ #:nocov:
121
+
122
+ ##
123
+ # section
124
+ #
125
+ # highline helper mixin which correctly formats block of say and ask
126
+ # output to have correct margins. section remembers the last margin
127
+ # used and calculates the relitive margin from the previous section.
128
+ # For example:
129
+ #
130
+ # section(bottom=1) do
131
+ # say "Hello"
132
+ # end
133
+ #
134
+ # section(top=1) do
135
+ # say "World"
136
+ # end
137
+ #
138
+ # Will output:
139
+ #
140
+ # > Hello
141
+ # >
142
+ # > World
143
+ #
144
+ # with only one newline between the two. Biggest margin wins.
145
+ #
146
+ # params:
147
+ # top - top margin specified in lines
148
+ # bottom - bottom margin specified in line
149
+ #
150
+ def section(params={}, &block)
151
+ top = params[:top] || 0
152
+ bottom = params[:bottom] || 0
153
+
154
+ # the first section cannot take a newline
155
+ top = 0 unless @margin
156
+ @margin = [top, @margin || 0].max
157
+
158
+ value = block.call
159
+
160
+ say "\n" if @last_line_open
161
+ @margin = [bottom, @margin].max
162
+
163
+ value
164
+ end
165
+
166
+ ##
167
+ # paragraph
168
+ #
169
+ # highline helper which creates a section with margins of 1, 1
170
+ #
171
+ def paragraph(&block)
172
+ section(:top => 1, :bottom => 1, &block)
173
+ end
174
+
175
+ def pager
176
+ #:nocov:
177
+ return if RHC::Helpers.windows?
178
+ return unless @output.tty?
179
+
180
+ read, write = IO.pipe
181
+
182
+ unless Kernel.fork # Child process
183
+ STDOUT.reopen(write)
184
+ STDERR.reopen(write) if STDERR.tty?
185
+ read.close
186
+ write.close
187
+ return
188
+ end
189
+
190
+ # Parent process, become pager
191
+ STDIN.reopen(read)
192
+ read.close
193
+ write.close
194
+
195
+ ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
196
+
197
+ Kernel.select [STDIN] # Wait until we have input before we start the pager
198
+ pager = ENV['PAGER'] || 'less'
199
+ exec pager rescue exec "/bin/sh", "-c", pager
200
+ #:nocov:
201
+ end
202
+
203
+ private
204
+ def separate_blocks
205
+ if (@margin ||= 0) > 0 && !@last_line_open
206
+ @output.print "\n" * @margin
207
+ @margin = 0
208
+ end
209
+ end
210
+ end
211
+
212
+ #
213
+ # An element capable of being split into multiple logical rows
214
+ #
215
+ module RowBased
216
+ extend Forwardable
217
+ def_delegators :rows, :each, :to_a, :join
218
+ alias_method :each_line, :each
219
+ end
220
+
221
+ class HighLine::Header < Struct.new(:text, :width, :indent, :color)
222
+ include RowBased
223
+
224
+ protected
225
+ def rows
226
+ @rows ||= begin
227
+ if !width || width == 0
228
+ [text.is_a?(Array) ? text.join(' ') : text]
229
+
230
+ elsif text.is_a? Array
231
+ widths = text.map{ |s| s.strip_ansi.length }
232
+ chars, join, indented = 0, 1, (indent || '').length
233
+ narrow = width - indented
234
+ text.zip(widths).inject([]) do |rows, (section, w)|
235
+ if rows.empty?
236
+ if w > width
237
+ rows.concat(section.textwrap_ansi(width))
238
+ else
239
+ rows << section.dup
240
+ chars += w
241
+ end
242
+ else
243
+ if w + chars + join > narrow
244
+ rows.concat(section.textwrap_ansi(narrow).map{ |s| "#{indent}#{s}" })
245
+ chars = 0
246
+ elsif chars == 0
247
+ rows << "#{indent}#{section}"
248
+ chars += w + indented
249
+ else
250
+ rows[-1] << " #{section}"
251
+ chars += w + join
252
+ end
253
+ end
254
+ rows
255
+ end
256
+ else
257
+ text.textwrap_ansi(width)
258
+ end
259
+ end.tap do |rows|
260
+ rows << '-' * rows.map{ |s| s.strip_ansi.length }.max
261
+ end
262
+ end
263
+ end
264
+
265
+ #
266
+ # Represent a columnar layout of items with wrapping and flexible layout.
267
+ #
268
+ class HighLine::Table
269
+ include RowBased
270
+
271
+ def initialize(items=nil,options={},&mapper)
272
+ @items, @options, @mapper = items, options, mapper
273
+ end
274
+
275
+ def color
276
+ opts[:color]
277
+ end
278
+
279
+ protected
280
+ attr_reader :items
281
+
282
+ def opts
283
+ @options
284
+ end
285
+
286
+ def align
287
+ opts[:align] || []
288
+ end
289
+ def joiner
290
+ opts[:join] || ' '
291
+ end
292
+ def indent
293
+ opts[:indent] || ''
294
+ end
295
+ def heading
296
+ @heading ||= opts[:heading] ? HighLine::Header.new(opts[:heading], max_width, indent) : nil
297
+ end
298
+
299
+ def source_rows
300
+ @source_rows ||= begin
301
+ (@mapper ? (items.map &@mapper) : items).each do |row|
302
+ row.map!{ |col| col.is_a?(String) ? col : col.to_s }
303
+ end
304
+ end
305
+ end
306
+
307
+ def headers
308
+ @headers ||= opts[:header] ? [Array(opts[:header])] : []
309
+ end
310
+
311
+ def columns
312
+ @columns ||= source_rows.map(&:length).max
313
+ end
314
+
315
+ def column_widths
316
+ @column_widths ||= begin
317
+ widths = Array.new(columns){ Width.new(0,0,0) }
318
+ (source_rows + headers).each do |row|
319
+ row.each_with_index do |col, i|
320
+ w = widths[i]
321
+ s = col.strip_ansi
322
+ word_length = s.scan(/\b\S+/).inject(0){ |l, word| l = word.length if l <= word.length; l }
323
+ w.min = word_length unless w.min > word_length
324
+ w.max = s.length unless w.max > s.length
325
+ end
326
+ end
327
+ widths
328
+ end
329
+ end
330
+
331
+ Width = Struct.new(:min, :max, :set)
332
+
333
+ def allocate_widths_for(available)
334
+ available -= (columns-1) * joiner.length + indent.length
335
+ return column_widths.map{ |w| w.max } if available >= column_widths.inject(0){ |sum, w| sum + w.max } || column_widths.inject(0){ |sum, w| sum + w.min } > available
336
+
337
+ fair = available / columns
338
+ column_widths.each do |w|
339
+ if w.set > 0
340
+ available -= w.set
341
+ next
342
+ end
343
+ w.set = if w.max <= fair
344
+ available -= w.max
345
+ w.max
346
+ else
347
+ 0
348
+ end
349
+ end
350
+
351
+ remaining = column_widths.inject(0){ |sum, w| if w.set == 0; sum += w.max; available -= w.min; end; sum }
352
+ fair = available.to_f / remaining.to_f
353
+
354
+ column_widths.
355
+ each do |w|
356
+ if w.set == 0
357
+ available -= (alloc = (w.max * fair).to_i)
358
+ w.set = alloc + w.min
359
+ end
360
+ end.
361
+ each{ |w| if available > 0 && w.set < w.max; w.set += 1; available -= 1; end }.
362
+ map(&:set)
363
+ end
364
+
365
+ def widths
366
+ @widths ||= begin
367
+ case w = opts[:width]
368
+ when Array
369
+ column_widths.zip(w[1..-1]).each do |width, col|
370
+ width.set = col || 0
371
+ width.max = width.set if width.set > width.max
372
+ end
373
+ allocate_widths_for(w.first || 0)
374
+ when Integer
375
+ allocate_widths_for(w)
376
+ else
377
+ column_widths.map{ |w| w.max }
378
+ end
379
+ end
380
+ end
381
+
382
+ def max_width
383
+ @max_width ||= opts[:width].is_a?(Array) ? opts[:width].first : (opts[:width] ? opts[:width] : 0)
384
+ end
385
+
386
+ def header_rows
387
+ @header_rows ||= begin
388
+ headers << column_widths.map{ |w| '-' * (w.set > 0 ? w.set : w.max) } if headers.present?
389
+ headers
390
+ end
391
+ end
392
+
393
+ def rows
394
+ @rows ||= begin
395
+ fmt = "#{indent}#{widths.zip(align).map{ |w, al| "%#{al == :right ? '' : '-'}#{w}s" }.join(joiner)}"
396
+
397
+ body = (header_rows + source_rows).inject([]) do |a,row|
398
+ row = row.zip(widths).map{ |column,w| w && w > 0 ? column.textwrap_ansi(w, false) : [column] }
399
+ row.map(&:length).max.times do |i|
400
+ a << (fmt % row.map{ |r| r[i] }).rstrip
401
+ end
402
+ a
403
+ end
404
+
405
+ body = heading.to_a.concat(body) if heading
406
+ body
407
+ end
408
+ end
409
+ end
410
+
411
+ $terminal = HighLineExtension.new
412
+ $terminal.indent_size = 2 if $terminal.respond_to? :indent_size
@@ -34,35 +34,23 @@ module RHC
34
34
  # Application information
35
35
  #---------------------------
36
36
  def display_app(app,cartridges = nil)
37
- heading = "%s @ %s (uuid: %s)" % [app.name, app.app_url, app.uuid]
38
37
  paragraph do
39
- header heading do
38
+ header [app.name, "@ #{app.app_url}", "(uuid: #{app.uuid})"] do
40
39
  section(:bottom => 1) do
41
- display_app_properties(
42
- app,
43
- :creation_time,
44
- :gear_info,
45
- :git_url,
46
- :initial_git_url,
47
- :ssh_string,
48
- :aliases)
40
+ say format_table \
41
+ nil,
42
+ get_properties(
43
+ app,
44
+ :creation_time,
45
+ :gear_info,
46
+ :git_url,
47
+ :initial_git_url,
48
+ :ssh_string,
49
+ :aliases
50
+ ),
51
+ :delete => true
49
52
  end
50
- display_included_carts(cartridges) if cartridges
51
- end
52
- end
53
- end
54
-
55
- def display_app_properties(app,*properties)
56
- say_table \
57
- nil,
58
- get_properties(app,*properties),
59
- :delete => true
60
- end
61
-
62
- def display_included_carts(carts)
63
- carts.each do |c|
64
- section(:bottom => 1) do
65
- display_cart(c)
53
+ cartridges.each{ |c| section(:bottom => 1){ display_cart(c) } } if cartridges
66
54
  end
67
55
  end
68
56
  end
@@ -71,13 +59,14 @@ module RHC
71
59
  [
72
60
  cart.name,
73
61
  cart.name != cart.display_name ? "(#{cart.display_name})" : nil,
74
- ].compact.join(' ')
62
+ ].compact
75
63
  end
76
64
 
77
65
  def format_scaling_info(scaling)
78
66
  "x%d (minimum: %s, maximum: %s) on %s gears" %
79
67
  [:current_scale, :scales_from, :scales_to, :gear_profile].map{ |key| format_value(key, scaling[key]) } if scaling
80
68
  end
69
+
81
70
  def format_cart_gears(cart)
82
71
  if cart.scalable?
83
72
  format_scaling_info(cart.scaling)
@@ -87,6 +76,7 @@ module RHC
87
76
  "%d %s" % [format_value(:current_scale, cart.current_scale), format_value(:gear_profile, cart.gear_profile)]
88
77
  end
89
78
  end
79
+
90
80
  def format_gear_info(info)
91
81
  "%d (defaults to %s)" %
92
82
  [:gear_count, :gear_profile].map{ |key| format_value(key, info[key]) } if info
@@ -97,25 +87,19 @@ module RHC
97
87
  #---------------------------
98
88
 
99
89
  def display_cart(cart, *properties)
100
- @table_displayed = false
101
-
102
- say_table \
90
+ say format_table \
103
91
  format_cart_header(cart),
104
92
  get_properties(cart, *properties).
105
93
  concat([[cart.scalable? ? :scaling : :gears, format_cart_gears(cart)]]).
106
94
  concat(cart.properties.map{ |p| ["#{table_heading(p['name'])}:", p['value']] }.sort{ |a,b| a[0] <=> b[0] }),
107
95
  :delete => true
108
- display_no_info("cartridge") unless @table_displayed
109
-
110
- if cart.usage_rate?
111
- say "\n"
112
- say format_usage_message(cart)
113
- end
96
+
97
+ say format_usage_message(cart) if cart.usage_rate?
114
98
  end
115
99
 
116
100
  def display_key(key, *properties)
117
101
  properties = [:fingerprint, :visible_to_ssh?] if properties.empty?
118
- say_table(
102
+ say format_table(
119
103
  properties.include?(:name) ? nil : format_key_header(key),
120
104
  get_properties(key, *properties),
121
105
  {
@@ -126,7 +110,7 @@ module RHC
126
110
  end
127
111
 
128
112
  def display_authorization(auth, default=nil)
129
- say_table(
113
+ say format_table(
130
114
  auth.note || "<no description>",
131
115
  get_properties(auth, :token, :scopes, :creation_time, :expires_in_seconds),
132
116
  {
@@ -140,59 +124,39 @@ module RHC
140
124
  [
141
125
  key.name,
142
126
  "(type: #{key.type})",
143
- ].compact.join(' ')
127
+ ].compact
144
128
  end
145
129
 
146
130
  def display_cart_storage_info(cart, title="Storage Info")
147
- say_table \
131
+ say format_table \
148
132
  title,
149
133
  get_properties(cart,:base_gear_storage,:additional_gear_storage)
150
134
  end
151
135
 
152
136
  def display_cart_storage_list(carts)
153
- carts.each do |cart|
154
- puts
155
- display_cart_storage_info(cart, cart.display_name)
156
- end
137
+ carts.each{ |cart| paragraph{ display_cart_storage_info(cart, cart.display_name) } }
157
138
  end
158
139
 
159
140
  def format_usage_message(cart)
160
141
  "This gear costs an additional $#{cart.usage_rate} per gear after the first 3 gears."
161
142
  end
162
143
 
163
- #---------------------------
164
- # Misc information
165
- #---------------------------
166
-
167
- def display_no_info(type)
168
- say_table \
169
- nil,
170
- [["This #{type} has no information to show"]]
171
- end
172
-
173
144
  private
174
- def say_table(heading,values,opts = {})
175
- @table_displayed = true
176
-
145
+ def format_table(heading,values,opts = {})
177
146
  values = values.to_a if values.is_a? Hash
178
147
  values.delete_if do |arr|
179
148
  arr[0] = "#{table_heading(arr.first)}:" if arr[0].is_a? Symbol
180
149
  opts[:delete] and arr.last.blank?
181
150
  end
182
151
 
183
- table = self.table(values)
184
- table = table.map{ |s| color(s, opts[:color]) } if opts[:color]
152
+ table(values, :heading => heading, :indent => heading ? ' ' : nil, :color => opts[:color])
153
+ end
185
154
 
186
- # Make sure we nest properly
187
- if heading
188
- header(heading, opts) do
189
- say table
190
- end
191
- else
192
- say table
193
- end
155
+ def format_no_info(type)
156
+ ["This #{type} has no information to show"]
194
157
  end
195
158
 
159
+
196
160
  # This uses the array of properties to retrieve them from an object
197
161
  def get_properties(object,*properties)
198
162
  properties.map do |prop|