rhc 1.6.8 → 1.7.8

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