rhc 0.98.16 → 1.0.4

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 (94) hide show
  1. data/bin/rhc +7 -49
  2. data/bin/rhc-app +14 -3
  3. data/bin/rhc-chk +16 -16
  4. data/bin/rhc-create-app +2 -0
  5. data/bin/rhc-create-domain +1 -2
  6. data/bin/rhc-ctl-app +12 -3
  7. data/bin/rhc-ctl-domain +1 -2
  8. data/bin/rhc-domain +1 -2
  9. data/bin/rhc-domain-info +1 -2
  10. data/bin/rhc-port-forward +1 -2
  11. data/bin/rhc-snapshot +3 -0
  12. data/bin/rhc-sshkey +1 -2
  13. data/bin/rhc-tail-files +1 -1
  14. data/bin/rhc-user-info +1 -3
  15. data/features/application.feature +4 -1
  16. data/features/domain.feature +0 -4
  17. data/features/geared_application.feature +11 -0
  18. data/features/lib/rhc_helper/app.rb +16 -5
  19. data/features/lib/rhc_helper/cartridge.rb +25 -9
  20. data/features/lib/rhc_helper/commandify.rb +34 -7
  21. data/features/lib/rhc_helper/domain.rb +2 -2
  22. data/features/lib/rhc_helper/httpify.rb +24 -14
  23. data/features/lib/rhc_helper/persistable.rb +1 -1
  24. data/features/lib/rhc_helper/sshkey.rb +11 -7
  25. data/features/lib/rhc_helper.rb +5 -3
  26. data/features/multiple_cartridge.feature +1 -1
  27. data/features/scaled_application.feature +48 -0
  28. data/features/sshkey.feature +37 -31
  29. data/features/step_definitions/application_steps.rb +18 -7
  30. data/features/step_definitions/cartridge_steps.rb +29 -3
  31. data/features/step_definitions/domain_steps.rb +2 -2
  32. data/features/step_definitions/sshkey_steps.rb +34 -34
  33. data/features/support/assumptions.rb +21 -9
  34. data/features/support/before_hooks.rb +24 -6
  35. data/features/support/env.rb +45 -19
  36. data/lib/rhc/cartridge_helper.rb +27 -0
  37. data/lib/rhc/cli.rb +1 -1
  38. data/lib/rhc/command_runner.rb +31 -3
  39. data/lib/rhc/commands/alias.rb +38 -0
  40. data/lib/rhc/commands/app.rb +478 -0
  41. data/lib/rhc/commands/base.rb +42 -12
  42. data/lib/rhc/commands/cartridge.rb +189 -0
  43. data/lib/rhc/commands/domain.rb +11 -49
  44. data/lib/rhc/commands/port-forward.rb +0 -1
  45. data/lib/rhc/commands/setup.rb +2 -1
  46. data/lib/rhc/commands/snapshot.rb +118 -0
  47. data/lib/rhc/commands/sshkey.rb +24 -38
  48. data/lib/rhc/commands/tail.rb +24 -0
  49. data/lib/rhc/commands/threaddump.rb +16 -0
  50. data/lib/rhc/commands.rb +33 -7
  51. data/lib/rhc/config.rb +28 -12
  52. data/lib/rhc/context_helper.rb +19 -5
  53. data/lib/rhc/core_ext.rb +86 -0
  54. data/lib/rhc/exceptions.rb +44 -0
  55. data/lib/rhc/git_helper.rb +59 -0
  56. data/lib/rhc/helpers.rb +86 -5
  57. data/lib/rhc/output_helpers.rb +213 -0
  58. data/lib/rhc/rest/application.rb +134 -67
  59. data/lib/rhc/rest/base.rb +48 -0
  60. data/lib/rhc/rest/cartridge.rb +40 -44
  61. data/lib/rhc/rest/client.rb +127 -59
  62. data/lib/rhc/rest/domain.rb +29 -39
  63. data/lib/rhc/rest/gear_group.rb +10 -0
  64. data/lib/rhc/rest/key.rb +8 -23
  65. data/lib/rhc/rest/user.rb +8 -24
  66. data/lib/rhc/rest.rb +22 -11
  67. data/lib/rhc/ssh_key_helpers.rb +47 -0
  68. data/lib/rhc/usage_templates/help.erb +0 -1
  69. data/lib/rhc/version.rb +3 -3
  70. data/lib/rhc/wizard.rb +123 -225
  71. data/lib/rhc-common.rb +43 -62
  72. data/spec/rest_spec_helper.rb +159 -36
  73. data/spec/rhc/cli_spec.rb +29 -1
  74. data/spec/rhc/command_spec.rb +32 -35
  75. data/spec/rhc/commands/alias_spec.rb +123 -0
  76. data/spec/rhc/commands/app_spec.rb +414 -0
  77. data/spec/rhc/commands/cartridge_spec.rb +342 -0
  78. data/spec/rhc/commands/domain_spec.rb +8 -8
  79. data/spec/rhc/commands/setup_spec.rb +17 -6
  80. data/spec/rhc/commands/snapshot_spec.rb +140 -0
  81. data/spec/rhc/commands/sshkey_spec.rb +26 -4
  82. data/spec/rhc/commands/tail_spec.rb +34 -0
  83. data/spec/rhc/commands/threaddump_spec.rb +83 -0
  84. data/spec/rhc/config_spec.rb +39 -13
  85. data/spec/rhc/context_spec.rb +51 -0
  86. data/spec/rhc/helpers_spec.rb +52 -12
  87. data/spec/rhc/rest_application_spec.rb +16 -3
  88. data/spec/rhc/rest_client_spec.rb +144 -36
  89. data/spec/rhc/rest_spec.rb +1 -1
  90. data/spec/rhc/wizard_spec.rb +133 -232
  91. data/spec/spec_helper.rb +4 -3
  92. metadata +56 -31
  93. data/features/support/ssh.sh +0 -2
  94. data/spec/rhc/common_spec.rb +0 -49
@@ -0,0 +1,24 @@
1
+ require 'rhc/commands/base'
2
+ require 'rhc/config'
3
+ require 'rhc-common'
4
+ module RHC::Commands
5
+ class Tail < Base
6
+ summary "Tail the logs of an application"
7
+ syntax "<application>"
8
+ argument :app, "Name of application you wish to view the logs of", ["-a", "--app app"]
9
+ option ["-n", "--namespace namespace"], "Namespace of your application", :context => :namespace_context, :required => true
10
+ option ["-o", "--opts options"], "Options to pass to the server-side (linux based) tail command (applicable to tail command only) (-f is implicit. See the linux tail man page full list of options.) (Ex: --opts '-n 100')"
11
+ option ["-f", "--files files"], "File glob relative to app (default <application_name>/logs/*) (optional)"
12
+ alias_action :"app tail", :root_command => true, :deprecated => true
13
+ def run(app)
14
+ begin
15
+ rest_domain = rest_client.find_domain(options.namespace)
16
+ rest_app = rest_domain.find_application(app)
17
+ rest_app.tail(options)
18
+ rescue Interrupt
19
+ results { say "Terminating..." }
20
+ end
21
+ 0
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ require 'rhc/commands/base'
2
+ module RHC::Commands
3
+ class Threaddump < Base
4
+ summary "Trigger a thread dump for JBoss and Ruby applications."
5
+ syntax "<application>"
6
+ option ["-n", "--namespace namespace"], "Namespace of your application", :context => :namespace_context, :required => true
7
+ argument :app, "Name of the application on which to execute the thread dump", ["-a", "--app name"]
8
+ def run(app)
9
+ rest_domain = rest_client.find_domain(options.namespace)
10
+ rest_app = rest_domain.find_application(app)
11
+ rest_app.threaddump.messages.each { |m| say m }
12
+
13
+ 0
14
+ end
15
+ end
16
+ end
data/lib/rhc/commands.rb CHANGED
@@ -1,6 +1,32 @@
1
1
  require 'commander'
2
2
  require 'rhc/helpers'
3
3
 
4
+ ## monkey patch option parsing to also parse global options all at once
5
+ # to avoid conflicts and side effects of similar short switches
6
+ module Commander
7
+ class Command
8
+ def parse_options_and_call_procs *args
9
+ return args if args.empty?
10
+ opts = OptionParser.new
11
+ runner = Commander::Runner.instance
12
+ # add global options
13
+ runner.options.each do |option|
14
+ opts.on *option[:args],
15
+ &runner.global_option_proc(option[:switches], &option[:proc])
16
+
17
+ end
18
+
19
+ # add command options
20
+ @options.each do |option|
21
+ opts.on(*option[:args], &option[:proc])
22
+ opts
23
+ end
24
+
25
+ opts.parse! args
26
+ end
27
+ end
28
+ end
29
+
4
30
  module RHC
5
31
  module Commands
6
32
  def self.load
@@ -27,12 +53,10 @@ module RHC
27
53
  command_name = Commander::Runner.instance.command_name_from_args
28
54
  command = Commander::Runner.instance.active_command
29
55
 
30
- if deprecated[command_name]
31
- msg = "The command 'rhc #{command_name}' is deprecated. Please use 'rhc #{command.name}' instead."
32
-
33
- raise DeprecatedError.new("#{msg} For porting and testing purposes you may switch this error to a warning by setting the DISABLE_DEPRECATED environment variable to 0. It is not recommended to do so in a production environment as this command may be removed in future releases.") if RHC::Helpers.disable_deprecated?
34
-
35
- warn "Warning: #{msg} For porting and testing purposes you may switch this warning to an error by setting the DISABLE_DEPRECATED environment variable to 1. This command may be removed in future releases."
56
+ new_cmd = deprecated[command_name.to_sym]
57
+ if new_cmd
58
+ new_cmd = "rhc #{command.name}" if new_cmd == true
59
+ RHC::Helpers.deprecated_command new_cmd
36
60
  end
37
61
  end
38
62
 
@@ -64,6 +88,8 @@ module RHC
64
88
  o[:arg] = Commander::Runner.switch_to_sym(o[:switches].last)
65
89
  end
66
90
 
91
+ deprecated[name.to_sym] = opts[:deprecated] unless opts[:deprecated].nil?
92
+
67
93
  args_metadata = opts[:args] || []
68
94
  args_metadata.each do |arg_meta|
69
95
  arg_switches = arg_meta[:switches]
@@ -81,7 +107,7 @@ module RHC
81
107
  # prepend the current resource
82
108
  alias_components = name.split(" ")
83
109
  alias_components[-1] = a[:action]
84
- alias_cmd = alias_components.join(' ')
110
+ alias_cmd = alias_components.join(' ').to_sym
85
111
  end
86
112
 
87
113
  deprecated[alias_cmd] = true if a[:deprecated]
data/lib/rhc/config.rb CHANGED
@@ -116,6 +116,10 @@ module RHC
116
116
  def opts_login=(username)
117
117
  @opts.add('default_rhlogin', username)
118
118
  end
119
+
120
+ def opts_login
121
+ @opts['default_rhlogin']
122
+ end
119
123
 
120
124
  # password is not allowed in config files and can only be passed on comman line
121
125
  def password=(password)
@@ -170,6 +174,10 @@ module RHC
170
174
  def has_opts_config?
171
175
  !@opts_config.nil?
172
176
  end
177
+
178
+ def has_opts?
179
+ !@opts.nil?
180
+ end
173
181
 
174
182
  def should_run_ssh_wizard?
175
183
  not File.exists? @ssh_priv_key_file_path
@@ -206,21 +214,29 @@ module RHC
206
214
  end
207
215
 
208
216
  def default_proxy
209
- #
210
- # Check for proxy environment
211
- #
212
- if @default_proxy.nil?
213
- if ENV['http_proxy']
214
- if ENV['http_proxy']!~/^(\w+):\/\// then
215
- ENV['http_proxy']="http://" + ENV['http_proxy']
217
+ @default_proxy ||= (
218
+ proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
219
+ if proxy
220
+ if proxy !~ /^(\w+):\/\// then
221
+ proxy = "http://#{proxy}"
216
222
  end
217
- proxy_uri=URI.parse(ENV['http_proxy'])
218
- @default_proxy = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
223
+ ENV['http_proxy'] = proxy
224
+ proxy_uri = URI.parse(ENV['http_proxy'])
225
+ Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
219
226
  else
220
- @default_proxy = Net::HTTP
227
+ Net::HTTP
221
228
  end
222
- end
223
- @default_proxy
229
+ )
230
+ end
231
+
232
+ def using_proxy?
233
+ default_proxy.instance_variable_get(:@is_proxy_class) || false
234
+ end
235
+
236
+ def proxy_vars
237
+ Hash[[:address,:user,:pass,:port].map do |x|
238
+ [x,default_proxy.instance_variable_get("@proxy_#{x}")]
239
+ end]
224
240
  end
225
241
  end
226
242
  end
@@ -1,17 +1,31 @@
1
+ require 'rhc/git_helper'
2
+
1
3
  module RHC
2
4
  module ContextHelpers
5
+ include RHC::GitHelpers
6
+
3
7
  def app_context
4
- # We currently do not have a way of determening an app context so return nil
5
- # In the future we will use the uuid embeded in the git config to query
6
- # the server for the repo's app name
8
+ debug "Getting app context"
9
+
10
+ uuid = git_config_get "rhc.app-uuid"
11
+
12
+ # proof of concept - we shouldn't be traversing
13
+ # the broker should expose apis for getting the application via a uuid
14
+ rest_client.domains.each do |rest_domain|
15
+ rest_domain.applications.each do |rest_app|
16
+ return rest_app.name if rest_app.uuid == uuid
17
+ end
18
+ end
19
+
20
+ debug "Couldn't find app with UUID == #{uuid}"
7
21
  nil
8
22
  end
9
23
 
10
24
  def namespace_context
11
25
  # right now we don't have any logic since we only support one domain
12
- # :nocov: remove nocov when cart tests go back in
26
+ # TODO: add domain lookup based on uuid
13
27
  domain = rest_client.domains[0]
14
- raise RHC::DomainNotFoundException("No domains configured for this user. You may create one using 'rhc domain create'.") if domain.nil?
28
+ raise RHC::DomainNotFoundException, "No domains configured for this user. You may create one using 'rhc domain create'." if domain.nil?
15
29
 
16
30
  domain.id
17
31
  end
data/lib/rhc/core_ext.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # From Rails core_ext/object.rb
2
2
  require 'rhc/json'
3
3
  require 'open-uri'
4
+ require 'highline'
4
5
 
5
6
  class Object
6
7
  def present?
@@ -24,6 +25,15 @@ class File
24
25
  end
25
26
  end
26
27
 
28
+ class String
29
+ # Wrap string by the given length, and join it with the given character.
30
+ # The method doesn't distinguish between words, it will only work based on
31
+ # the length.
32
+ def wrap(wrap_length=80, char="\n")
33
+ scan(/.{#{wrap_length}}|.+/).join(char)
34
+ end
35
+ end
36
+
27
37
  #
28
38
  # Allow http => https redirection, see
29
39
  # http://bugs.ruby-lang.org/issues/859 to 1.8.7 for rough
@@ -38,3 +48,79 @@ module OpenURI
38
48
  (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:https?|ftp)\z/i =~ uri2.scheme)
39
49
  end
40
50
  end
51
+
52
+ # Some versions of highline get in an infinite loop when trying to wrap.
53
+ # Fixes BZ 866530.
54
+ class HighLine
55
+
56
+ def wrap_line(line)
57
+ wrapped_line = []
58
+ i = chars_in_line = 0
59
+ word = []
60
+
61
+ while i < line.length
62
+ # we have to give a length to the index because ruby 1.8 returns the
63
+ # byte code when using a single fixednum index
64
+ c = line[i, 1]
65
+ color_code = nil
66
+ # escape character probably means color code, let's check
67
+ if c == "\e"
68
+ color_code = line[i..i+6].match(/\e\[\d{1,2}m/)
69
+ if color_code
70
+ # first the existing word buffer then the color code
71
+ wrapped_line << word.join.wrap(@wrap_at) << color_code[0]
72
+ word.clear
73
+
74
+ i += color_code[0].length
75
+ end
76
+ end
77
+
78
+ # visible character
79
+ if !color_code
80
+ chars_in_line += 1
81
+ word << c
82
+
83
+ # time to wrap the line?
84
+ if chars_in_line == @wrap_at
85
+ if c == ' ' or line[i+1, 1] == ' ' or word.length == @wrap_at
86
+ wrapped_line << word.join
87
+ word.clear
88
+ end
89
+
90
+ wrapped_line[-1].rstrip!
91
+ wrapped_line << "\n"
92
+
93
+ # consume any spaces at the begining of the next line
94
+ word = word.join.lstrip.split(//)
95
+ chars_in_line = word.length
96
+
97
+ if line[i+1, 1] == ' '
98
+ i += 1 while line[i+1, 1] == ' '
99
+ end
100
+
101
+ else
102
+ if c == ' '
103
+ wrapped_line << word.join
104
+ word.clear
105
+ end
106
+ end
107
+
108
+ i += 1
109
+ end
110
+ end
111
+
112
+ wrapped_line << word.join
113
+ wrapped_line.join
114
+ end
115
+
116
+ def wrap(text)
117
+ wrapped_text = []
118
+ lines = text.split(/\r?\n/)
119
+ lines.each_with_index do |line, i|
120
+ wrapped_text << wrap_line(i == lines.length - 1 ? line : line.rstrip)
121
+ end
122
+
123
+ return wrapped_text.join("\n")
124
+ end
125
+
126
+ end
@@ -19,12 +19,32 @@ module RHC
19
19
  end
20
20
  end
21
21
 
22
+ class CartridgeNotFoundException < Exception
23
+ def initialize(message="Cartridge not found")
24
+ super message, 154
25
+ end
26
+ end
27
+
28
+ class MultipleCartridgesException < Exception
29
+ def initialize(message="Multiple cartridge found")
30
+ super message, 155
31
+ end
32
+ end
33
+
22
34
  class KeyNotFoundException < Exception
23
35
  def initialize(message="SSHKey not found")
24
36
  super message, 118
25
37
  end
26
38
  end
27
39
 
40
+ # Makes sense to use its own exit code since this is different from a
41
+ # resource error
42
+ class GitException < Exception
43
+ def initialize(message="Git returned an error")
44
+ super message, 216
45
+ end
46
+ end
47
+
28
48
  class DeprecatedError < RuntimeError; end
29
49
 
30
50
  class KeyFileNotExistentException < Exception
@@ -68,4 +88,28 @@ module RHC
68
88
  super message, 1
69
89
  end
70
90
  end
91
+
92
+ class SnapshotSaveException < Exception
93
+ def initialize(message="Error trying to save snapshot")
94
+ super message, 130
95
+ end
96
+ end
97
+
98
+ class SnapshotRestoreException < Exception
99
+ def initialize(message="Error trying to restore snapshot")
100
+ super message, 130
101
+ end
102
+ end
103
+
104
+ class MissingScalingValueException < Exception
105
+ def initialize(message="Must provide either a min or max value for scaling")
106
+ super message, 1
107
+ end
108
+ end
109
+
110
+ class CartridgeNotScalableException < Exception
111
+ def initialize(message="Cartridge is not scalable")
112
+ super message, 1
113
+ end
114
+ end
71
115
  end
@@ -0,0 +1,59 @@
1
+ require 'open4'
2
+
3
+ module RHC
4
+ module GitHelpers
5
+ # :nocov: These all call external binaries so test them in cucumber
6
+ def git_config_get(key)
7
+ config_get_cmd = "git config --get #{key}"
8
+ debug "Running #{config_get_cmd}"
9
+ uuid = %x[#{config_get_cmd}].strip
10
+ debug "UUID = '#{uuid}'"
11
+ uuid = nil if $?.exitstatus != 0 or uuid.empty?
12
+
13
+ uuid
14
+ end
15
+
16
+ def git_config_set(key, value)
17
+ unset_cmd = "git config --unset-all #{key}"
18
+ config_cmd = "git config --add #{key} #{value}"
19
+ debug "Adding #{key} = #{value} to git config"
20
+ commands = [unset_cmd, config_cmd]
21
+ commands.each do |cmd|
22
+ debug "Running #{cmd} 2>&1"
23
+ output = %x[#{cmd} 2>&1]
24
+ raise RHC::GitException, "Error while adding config values to git - #{output}" unless output.empty?
25
+ end
26
+ end
27
+
28
+ def git_clone_repo(git_url, repo_dir)
29
+ # quote the repo to avoid input injection risk
30
+ repo_dir = (repo_dir ? " \"#{repo_dir}\"" : "")
31
+ clone_cmd = "git clone #{git_url}#{repo_dir}"
32
+ debug "Running #{clone_cmd}"
33
+
34
+ err = nil
35
+ if RHC::Helpers.windows?
36
+ # windows does not support Open4 so redirect stderr to stdin
37
+ # and print the whole output which is not as clean
38
+ output = %x[#{clone_cmd} 2>&1]
39
+ if $?.exitstatus != 0
40
+ err = output + " - Check to make sure you have correctly installed git and it is added to your path."
41
+ else
42
+ say output
43
+ end
44
+ else
45
+ paragraph do
46
+ Open4.popen4(clone_cmd) do |pid, stdin, stdout, stderr|
47
+ stdin.close
48
+ say stdout.read
49
+ err = stderr.read
50
+ end
51
+ say "done"
52
+ end
53
+ end
54
+
55
+ raise RHC::GitException, "Error in git clone - #{err}" if $?.exitstatus != 0
56
+ end
57
+ # :nocov:
58
+ end
59
+ end
data/lib/rhc/helpers.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'commander/user_interaction'
2
+ require 'rhc/version'
2
3
  require 'rhc/config'
3
4
  require 'rhc/commands'
5
+ require 'rhc/output_helpers'
4
6
 
5
7
  OptionParser.accept(URI) {|s,| URI.parse(s) if s}
6
8
 
@@ -18,9 +20,13 @@ module RHC
18
20
  # helpers always have Commander UI available
19
21
  include Commander::UI
20
22
  include Commander::UI::AskForClass
23
+ include RHC::OutputHelpers
21
24
 
22
25
  extend self
23
26
 
27
+ MAX_RETRIES = 7
28
+ DEFAULT_DELAY_THROTTLE = 2.0
29
+
24
30
  def disable_deprecated?
25
31
  # 1) default for now is false
26
32
  # 2) when releasing a 1.0 beta flip this to true
@@ -74,7 +80,7 @@ module RHC
74
80
  global_option '-p', '--password password', "OpenShift password"
75
81
  global_option '-d', '--debug', "Turn on debugging"
76
82
 
77
- global_option '--noprompt', "Do not ask for input"
83
+ global_option '--noprompt', "Suppress the interactive setup wizard from running before a command"
78
84
  global_option '--config FILE', "Path of a different config file"
79
85
  def config
80
86
  raise "Operations requiring configuration must define a config accessor"
@@ -94,6 +100,29 @@ module RHC
94
100
  # Output helpers
95
101
  #
96
102
 
103
+ def debug(msg)
104
+ $stderr.puts "DEBUG: #{msg}" if debug?
105
+ end
106
+
107
+ def deprecated_command(correct,short = false)
108
+ deprecated("This command is deprecated. Please use '#{correct}' instead.",short)
109
+ end
110
+
111
+ def deprecated_option(deprecated,new)
112
+ deprecated("The option '#{deprecated}' is deprecated. Please use '#{new}' instead")
113
+ end
114
+
115
+ def deprecated(msg,short = false)
116
+ info = " For porting and testing purposes you may switch this %s to %s by setting the DISABLE_DEPRECATED environment variable to %d. It is not recommended to do so in a production environment as this option may be removed in future releases."
117
+
118
+ msg << info unless short
119
+ if RHC::Helpers.disable_deprecated?
120
+ raise DeprecatedError.new(msg % ['an error','a warning',0])
121
+ else
122
+ warn "Warning: #{msg}\n" % ['a warning','an error',1]
123
+ end
124
+ end
125
+
97
126
  def say(msg)
98
127
  super
99
128
  msg
@@ -132,12 +161,54 @@ module RHC
132
161
  end
133
162
  end
134
163
 
135
- def header(s)
136
- say s
137
- say "=" * s.length
164
+ # This will format table headings for a consistent look and feel
165
+ # If a heading isn't explicitly defined, it will attempt to look up the parts
166
+ # If those aren't found, it will capitalize the string
167
+ def table_heading(value)
168
+ # Set the default proc to look up undefined values
169
+ headings = Hash.new do |hash,key|
170
+ items = key.to_s.split('_')
171
+ # Look up each piece individually
172
+ hash[key] = items.length > 1 ?
173
+ # Recusively look up the heading for the parts
174
+ items.map{|x| headings[x.to_sym]}.join(' ') :
175
+ # Capitalize if this part isn't defined
176
+ items.first.capitalize
177
+ end
178
+
179
+ # Predefined headings (or parts of headings)
180
+ headings.merge!({
181
+ :creation_time => "Created",
182
+ :uuid => "UUID",
183
+ :current_scale => "Current",
184
+ :scales_from => "Minimum",
185
+ :scales_to => "Maximum",
186
+ :url => "URL",
187
+ :ssh => "SSH",
188
+ :gear_profile => "Gear Size"
189
+ })
190
+
191
+ headings[value]
192
+ end
193
+
194
+ def header(s,opts = {})
195
+ @indent ||= 0
196
+ indent s
197
+ indent "="*s.length
198
+ if block_given?
199
+ @indent += 1
200
+ yield
201
+ @indent -= 1
202
+ end
138
203
  end
139
204
 
140
- ##
205
+ INDENT = 2
206
+ def indent(str)
207
+ @indent ||= 0
208
+ say "%s%s" % [" " * @indent * INDENT,str]
209
+ end
210
+
211
+ ##
141
212
  # section
142
213
  #
143
214
  # highline helper mixin which correctly formats block of say and ask
@@ -225,5 +296,15 @@ module RHC
225
296
  def windows? ; RUBY_PLATFORM =~ /win(32|dows|ce)|djgpp|(ms|cyg|bcc)win|mingw32/i end
226
297
  def unix? ; !jruby? && !windows? end
227
298
 
299
+ # common SSH key display format in ERB
300
+ def ssh_key_display_format
301
+ ERB.new <<-FORMAT
302
+ Name: <%= key.name %>
303
+ Type: <%= key.type %>
304
+ Fingerprint: <%= key.fingerprint %>
305
+
306
+ FORMAT
307
+ end
308
+
228
309
  end
229
310
  end