rhc 1.9.6 → 1.10.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/features/lib/rhc_helper.rb +0 -4
  3. data/features/lib/rhc_helper/app.rb +10 -10
  4. data/features/lib/rhc_helper/commandify.rb +6 -24
  5. data/features/support/before_hooks.rb +1 -1
  6. data/features/support/env.rb +9 -2
  7. data/features/support/platform_support.rb +11 -1
  8. data/lib/rhc/auth/basic.rb +4 -1
  9. data/lib/rhc/cartridge_helpers.rb +4 -8
  10. data/lib/rhc/commands.rb +6 -3
  11. data/lib/rhc/commands/alias.rb +1 -0
  12. data/lib/rhc/commands/app.rb +104 -67
  13. data/lib/rhc/commands/authorization.rb +2 -1
  14. data/lib/rhc/commands/base.rb +8 -1
  15. data/lib/rhc/commands/cartridge.rb +39 -16
  16. data/lib/rhc/commands/git_clone.rb +2 -1
  17. data/lib/rhc/commands/port_forward.rb +4 -6
  18. data/lib/rhc/commands/ssh.rb +54 -0
  19. data/lib/rhc/exceptions.rb +12 -7
  20. data/lib/rhc/git_helpers.rb +4 -5
  21. data/lib/rhc/help_formatter.rb +11 -6
  22. data/lib/rhc/helpers.rb +24 -3
  23. data/lib/rhc/highline_extensions.rb +31 -6
  24. data/lib/rhc/output_helpers.rb +0 -29
  25. data/lib/rhc/rest.rb +16 -1
  26. data/lib/rhc/rest/application.rb +7 -3
  27. data/lib/rhc/rest/base.rb +7 -2
  28. data/lib/rhc/rest/client.rb +7 -14
  29. data/lib/rhc/rest/gear_group.rb +10 -1
  30. data/lib/rhc/rest/mock.rb +34 -8
  31. data/lib/rhc/ssh_helpers.rb +144 -1
  32. data/lib/rhc/usage_templates/command_help.erb +16 -2
  33. data/spec/coverage_helper.rb +10 -7
  34. data/spec/rhc/commands/alias_spec.rb +28 -0
  35. data/spec/rhc/commands/app_spec.rb +64 -45
  36. data/spec/rhc/commands/authorization_spec.rb +16 -0
  37. data/spec/rhc/commands/cartridge_spec.rb +59 -3
  38. data/spec/rhc/commands/port_forward_spec.rb +6 -6
  39. data/spec/rhc/commands/ssh_spec.rb +125 -0
  40. data/spec/rhc/commands/tail_spec.rb +4 -3
  41. data/spec/rhc/helpers_spec.rb +70 -42
  42. data/spec/rhc/highline_extensions_spec.rb +23 -1
  43. data/spec/rhc/rest_client_spec.rb +6 -9
  44. data/spec/rhc/rest_spec.rb +41 -2
  45. data/spec/spec_helper.rb +38 -0
  46. metadata +336 -373
@@ -12,7 +12,8 @@ module RHC::Commands
12
12
  remembering what is available.
13
13
  DESC
14
14
  alias_action 'authorizations', :root_command => true
15
- def run
15
+ default_action :list
16
+ def list
16
17
  rest_client.authorizations.each{ |auth| paragraph{ display_authorization(auth, token_for_user) } } or info "No authorizations"
17
18
 
18
19
  0
@@ -147,7 +147,14 @@ class RHC::Commands::Base
147
147
 
148
148
  def self.default_action(action)
149
149
  options[:default] = action unless action == :help
150
- define_method(:run) { |*args| send(action, *args) }
150
+ name = self.object_name
151
+ raise InvalidCommand, "object_name must be set" if name.empty?
152
+
153
+ RHC::Commands.add((@options || {}).merge({
154
+ :name => name,
155
+ :class => self,
156
+ :method => options[:default]
157
+ }));
151
158
  end
152
159
 
153
160
  private
@@ -44,7 +44,9 @@ module RHC::Commands
44
44
  if options.verbose
45
45
  carts.each do |c|
46
46
  paragraph do
47
- name = c.display_name != c.name && "#{color(c.display_name, :cyan)} [#{c.name}]" || c.name
47
+ name = c.name
48
+ name += '*' if c.usage_rate?
49
+ name = c.display_name != c.name && "#{color(c.display_name, :cyan)} [#{name}]" || name
48
50
  tags = c.tags - RHC::Rest::Cartridge::HIDDEN_TAGS
49
51
  say header([name, "(#{c.only_in_existing? ? 'addon' : 'web'})"])
50
52
  say c.description
@@ -85,9 +87,8 @@ module RHC::Commands
85
87
  success "Success"
86
88
 
87
89
  paragraph{ display_cart(rest_cartridge) }
88
-
89
- results{ rest_cartridge.messages.each { |msg| success msg } }
90
-
90
+ paragraph{ rest_cartridge.messages.each { |msg| success msg } }
91
+
91
92
  0
92
93
  end
93
94
 
@@ -122,6 +123,8 @@ module RHC::Commands
122
123
  rest_cartridge.destroy
123
124
  success "removed"
124
125
 
126
+ paragraph{ rest_cartridge.messages.each { |msg| success msg } }
127
+
125
128
  0
126
129
  end
127
130
 
@@ -182,14 +185,30 @@ module RHC::Commands
182
185
  0
183
186
  end
184
187
 
185
- summary "Set the scaling range of a cartridge"
186
- syntax "<cartridge> [--namespace NAME] [--app NAME] [--min min] [--max max]"
187
- argument :cart_type, "The name of the cartridge you are reloading", ["-c", "--cartridge cartridge"]
188
+ summary "Set the scale range for a cartridge"
189
+ description <<-DESC
190
+ Each cartridge capable of scaling may have a minimum and a maximum set, although within that range
191
+ each type of cartridge may make decisions to autoscale. Web cartridges will scale based on incoming
192
+ request traffic - see https://www.openshift.com/developers/scaling for more information. Non web
193
+ cartridges such as databases may require specific increments of scaling (1, 3, 5) in order to
194
+ properly function. Please consult the cartridge documentation for more on specifics of scaling.
195
+
196
+ Set both values the same to guarantee a scale value. You may pecify both values with the argument
197
+ 'multiplier' or use '--min' and '--max' independently.
198
+
199
+ Scaling may take several minutes or more if the server must provision multiple gears. Your operation
200
+ will continue in the background if your client is disconnected.
201
+ DESC
202
+ syntax "<cartridge> [multiplier] [--namespace NAME] [--app NAME] [--min min] [--max max]"
203
+ argument :cartridge, "The name of the cartridge you are scaling", ["-c", "--cartridge cartridge"]
204
+ argument :multiplier, "The number of instances of this cartridge you need", [], :optional => true, :hide => true
188
205
  option ["-n", "--namespace NAME"], "Namespace of the application the cartridge belongs to", :context => :namespace_context, :required => true
189
206
  option ["-a", "--app NAME"], "Application the cartridge belongs to", :context => :app_context, :required => true
190
207
  option ["--min min", Integer], "Minimum scaling value"
191
208
  option ["--max max", Integer], "Maximum scaling value"
192
- def scale(cartridge)
209
+ def scale(cartridge, multiplier)
210
+ options.default(:min => Integer(multiplier), :max => Integer(multiplier)) if multiplier rescue raise ArgumentError, "Multiplier must be a positive integer."
211
+
193
212
  raise RHC::MissingScalingValueException unless options.min || options.max
194
213
 
195
214
  rest_app = rest_client.find_application(options.namespace, options.app, :include => :cartridges)
@@ -197,17 +216,22 @@ module RHC::Commands
197
216
 
198
217
  raise RHC::CartridgeNotScalableException unless rest_cartridge.scalable?
199
218
 
219
+ warn "This operation will run until the application is at the minimum scale and may take several minutes."
220
+ say "Setting scale range for #{rest_cartridge.name} ... "
221
+
200
222
  cart = rest_cartridge.set_scales({
201
223
  :scales_from => options.min,
202
224
  :scales_to => options.max
203
225
  })
204
226
 
205
- results do
206
- paragraph{ display_cart(cart) }
207
- success "Success: Scaling values updated"
208
- end
227
+ success "done"
228
+ paragraph{ display_cart(cart) }
209
229
 
210
230
  0
231
+ rescue RHC::Rest::TimeoutException => e
232
+ raise unless e.on_receive?
233
+ info "The server has closed the connection, but your scaling operation is still in progress. Please check the status of your operation via 'rhc show-app'."
234
+ 1
211
235
  end
212
236
 
213
237
  summary 'View/manipulate storage on a cartridge'
@@ -269,11 +293,10 @@ module RHC::Commands
269
293
  total_amount = amount
270
294
  end
271
295
 
296
+ say "Set storage on cartridge ... "
272
297
  cart = rest_cartridge.set_storage(:additional_gear_storage => total_amount)
273
- results do
274
- say "Success: additional storage space set to #{total_amount}GB\n"
275
- display_cart_storage_info cart
276
- end
298
+ success "set to #{total_amount}GB"
299
+ paragraph{ display_cart_storage_info cart }
277
300
  end
278
301
 
279
302
  0
@@ -18,7 +18,8 @@ module RHC::Commands
18
18
  # argument :directory, "The name of a new directory to clone into", [], :default => nil
19
19
  def run(app_name)
20
20
  rest_app = rest_client.find_application(options.namespace, app_name)
21
- git_clone_application(rest_app)
21
+ dir = git_clone_application(rest_app)
22
+ success "Your application Git repository has been cloned to '#{system_path(dir)}'"
22
23
 
23
24
  0
24
25
  end
@@ -110,14 +110,12 @@ module RHC::Commands
110
110
 
111
111
  if forwarding_specs.length == 0
112
112
  # check if the gears have been stopped
113
- ggs = rest_app.gear_groups
114
- if ggs.any? { |gg|
115
- gears = gg.gears
116
- true if gears.any? { |g| g["state"] == "stopped" }
117
- }
118
- warn "Application #{rest_app.name} is stopped. Please restart the application and try again."
113
+ if rest_app.gear_groups.all?{ |gg| gg.gears.all?{ |g| g["state"] == "stopped" } }
114
+ warn "none"
115
+ error "The application is stopped. Please restart the application and try again."
119
116
  return 1
120
117
  else
118
+ warn "none"
121
119
  raise RHC::NoPortsToForwardException.new "There are no available ports to forward for this application. Your application may be stopped or idled."
122
120
  end
123
121
  end
@@ -0,0 +1,54 @@
1
+ require 'rhc/commands/base'
2
+ require 'resolv'
3
+ require 'rhc/git_helpers'
4
+ require 'rhc/cartridge_helpers'
5
+
6
+ module RHC::Commands
7
+ class Ssh < Base
8
+ suppress_wizard
9
+
10
+ summary "SSH into the specified application"
11
+ description <<-DESC
12
+ Connect to your application using SSH. This will connect to your primary gear
13
+ (the one with your Git repository and web cartridge) by default. To SSH to
14
+ other gears run 'rhc show-app --gears' to get a list of their SSH hosts.
15
+
16
+ You may run a specific SSH command by passing one or more arguments, or use a
17
+ different SSH executable or pass options to SSH with the '--ssh' option.
18
+ DESC
19
+ syntax "<app> [--ssh path_to_ssh_executable]"
20
+ argument :app, "The name of the application you want to SSH into", ["-a", "--app NAME"], :context => :app_context
21
+ argument :command, "Command to run in the application's SSH session", [], :arg_type => :list, :required => false
22
+ option ["--ssh PATH"], "Path to your SSH executable or additional options"
23
+ option ["-n", "--namespace NAME"], "Namespace of the application", :context => :namespace_context, :required => false
24
+ option ["--gears"], "Execute this command on all gears in the app. Requires a command."
25
+ option ["--limit INTEGER", Integer], "Limit the number of simultaneous SSH connections opened with --gears (default: 5)."
26
+ option ["--raw"], "Output only the data returned by each host, no hostname prefix."
27
+ alias_action 'app ssh', :root_command => true
28
+ def run(app_name, command)
29
+ raise ArgumentError, "No application specified" unless app_name.present?
30
+ raise ArgumentError, "--gears requires a command" if options.gears && command.blank?
31
+ raise ArgumentError, "--limit must be an integer greater than zero" if options.limit && options.limit < 1
32
+ raise OptionParser::InvalidOption, "No system SSH available. Please use the --ssh option to specify the path to your SSH executable, or install SSH." unless options.ssh or has_ssh?
33
+
34
+ if options.gears
35
+ groups = rest_client.find_application_gear_groups(options.namespace, app_name)
36
+ run_on_gears(command.join(' '), groups)
37
+ 0
38
+ else
39
+ rest_app = rest_client.find_application(options.namespace, app_name)
40
+ $stderr.puts "Connecting to #{rest_app.ssh_string.to_s} ..." unless command.present?
41
+
42
+ ssh = options.ssh || 'ssh'
43
+ debug "Using user specified SSH: #{options.ssh}" if options.ssh
44
+
45
+ command_line = [ssh, rest_app.ssh_string.to_s, command].compact.flatten
46
+ debug "Invoking Kernel.exec with #{command_line.inspect}"
47
+ Kernel.send(:exec, *command_line)
48
+ end
49
+ end
50
+
51
+ protected
52
+ include RHC::SSHHelpers
53
+ end
54
+ end
@@ -135,17 +135,22 @@ module RHC
135
135
  end
136
136
  end
137
137
 
138
- class ServerAPINotSupportedException < Exception
139
- def initialize(min_version, current_version)
140
- super "The server does not support this command (requires #{min_version}, found #{current_version})."
141
- end
142
- end
143
-
144
- class OperationNotSupportedException < Exception
138
+ class UnsupportedError < Exception
145
139
  def initialize(message="This operation is not supported by the server.")
146
140
  super message, 1
147
141
  end
148
142
  end
143
+ class NoPerGearOperations < UnsupportedError
144
+ def initialize
145
+ super "The server does not support operations on individual gears."
146
+ end
147
+ end
148
+ class ServerAPINotSupportedException < UnsupportedError
149
+ def initialize(min_version, current_version)
150
+ super "The server does not support this command (requires #{min_version}, found #{current_version})."
151
+ end
152
+ end
153
+ class OperationNotSupportedException < UnsupportedError; end
149
154
 
150
155
  class InvalidURIException < Exception
151
156
  def initialize(uri)
@@ -30,10 +30,10 @@ module RHC
30
30
  repo_dir = options.repo || app.name
31
31
 
32
32
  debug "Pulling new repo down"
33
- git_clone_repo(app.git_url, repo_dir)
33
+ dir = git_clone_repo(app.git_url, repo_dir)
34
34
 
35
35
  debug "Configuring git repo"
36
- Dir.chdir(repo_dir) do |dir|
36
+ Dir.chdir(repo_dir) do
37
37
  git_config_set "rhc.app-uuid", app.uuid
38
38
  git_config_set "rhc.app-name", app.name
39
39
  git_config_set "rhc.domain-name", app.domain_id
@@ -41,7 +41,7 @@ module RHC
41
41
 
42
42
  git_clone_deploy_hooks(repo_dir)
43
43
 
44
- true
44
+ dir
45
45
  end
46
46
 
47
47
  # :nocov: These all call external binaries so test them in cucumber
@@ -86,8 +86,7 @@ module RHC
86
86
  raise RHC::GitException, "Unable to clone your repository. Called Git with: #{cmd}"
87
87
  end
88
88
  end
89
-
90
- success "Your application code is now in '#{repo_dir}'"
89
+ File.expand_path(repo_dir)
91
90
  end
92
91
  end
93
92
  end
@@ -33,12 +33,17 @@ module RHC
33
33
 
34
34
  def initialize(command, instance_commands, runner)
35
35
  @command = command
36
- @actions = instance_commands.collect do |command_name, command_class|
37
- next if command_class.summary.nil?
38
- m = /^#{command.name}[\-]([^ ]+)/.match(command_name)
39
- # if we have a match and it is not an alias then we can use it
40
- m and command_name == command_class.name ? {:name => m[1], :summary => command_class.summary || ""} : nil
41
- end
36
+ @actions =
37
+ if command.root?
38
+ instance_commands.collect do |command_name, command_class|
39
+ next if command_class.summary.nil?
40
+ m = /^#{command.name}[\-]([^ ]+)/.match(command_name)
41
+ # if we have a match and it is not an alias then we can use it
42
+ m and command_name == command_class.name ? {:name => m[1], :summary => command_class.summary || ""} : nil
43
+ end
44
+ else
45
+ []
46
+ end
42
47
  @actions.compact!
43
48
  @global_options = runner.options
44
49
  @runner = runner
@@ -38,6 +38,19 @@ module RHC
38
38
  path
39
39
  end
40
40
 
41
+ PREFIX = %W(TB GB MB KB B).freeze
42
+
43
+ def human_size( s )
44
+ return "unknown" unless s
45
+ s = s.to_f
46
+ i = PREFIX.length - 1
47
+ while s > 500 && i > 0
48
+ i -= 1
49
+ s /= 1000
50
+ end
51
+ ((s > 9 || s.modulo(1) < 0.1 ? '%d' : '%.1f') % s) + ' ' + PREFIX[i]
52
+ end
53
+
41
54
  def date(s)
42
55
  now = Date.today
43
56
  d = datetime_rfc3339(s).to_time
@@ -100,6 +113,10 @@ module RHC
100
113
  global_option '--server NAME', String, 'An OpenShift server hostname (default: openshift.redhat.com)'
101
114
  global_option '-k', '--insecure', "Allow insecure SSL connections. Potential security risk.", :hide => true
102
115
 
116
+ global_option '--limit INTEGER', Integer, "Maximum number of simultaneous operations to execute.", :hide => true
117
+ global_option '--raw', "Do not format the output from the requested operations.", :hide => true
118
+ global_option '--always-prefix', "Include the gear prefix on all output from the server.", :hide => true
119
+
103
120
  OptionParser.accept(SSLVersion = Class.new){ |s| OpenSSL::SSL::SSLContext::METHODS.find{ |m| m.to_s.downcase == s.downcase } or raise OptionParser::InvalidOption.new(nil, "The provided SSL version '#{s}' is not valid. Supported values: #{OpenSSL::SSL::SSLContext::METHODS.map(&:to_s).map(&:downcase).join(', ')}") }
104
121
  global_option '--ssl-version VERSION', SSLVersion, "The version of SSL to use", :hide => true do |value|
105
122
  raise RHC::Exception, "You are using an older version of the httpclient gem which prevents the use of --ssl-version. Please run 'gem update httpclient' to install a newer version (2.2.6 or newer)." unless HTTPClient::SSLConfig.method_defined? :ssl_version
@@ -148,7 +165,7 @@ module RHC
148
165
  end
149
166
 
150
167
  def ssh_string(ssh_url)
151
- return nil if ssh_url.nil?
168
+ return nil if ssh_url.blank?
152
169
  uri = URI.parse(ssh_url)
153
170
  "#{uri.user}@#{uri.host}"
154
171
  rescue => e
@@ -248,8 +265,12 @@ module RHC
248
265
  end
249
266
 
250
267
  # OVERRIDE: Replaces default commander behavior
251
- def color(*args)
252
- $terminal.color(*args)
268
+ def color(item, *args)
269
+ if item.is_a? Array
270
+ item.map{ |i| $terminal.color(i, *args) }
271
+ else
272
+ $terminal.color(item, *args)
273
+ end
253
274
  end
254
275
 
255
276
  [:pager, :indent, :paragraph, :section, :header, :table, :table_args].each do |sym|
@@ -174,6 +174,7 @@ class HighLineExtension < HighLine
174
174
  top = 0 unless @margin
175
175
  @margin = [top, @margin || 0].max
176
176
 
177
+ separate_blocks if params[:now]
177
178
  value = block.call
178
179
 
179
180
  say "\n" if @last_line_open
@@ -276,7 +277,7 @@ class HighLine::Header < Struct.new(:text, :width, :indent, :color)
276
277
  text.textwrap_ansi(width)
277
278
  end
278
279
  end.tap do |rows|
279
- rows << '-' * rows.map{ |s| s.strip_ansi.length }.max
280
+ rows << '-' * (rows.map{ |s| s.strip_ansi.length }.max || 0)
280
281
  end
281
282
  end
282
283
  end
@@ -318,7 +319,13 @@ class HighLine::Table
318
319
  def source_rows
319
320
  @source_rows ||= begin
320
321
  (@mapper ? (items.map &@mapper) : items).each do |row|
321
- row.map!{ |col| col.is_a?(String) ? col : col.to_s }
322
+ row.map! do |col|
323
+ case col
324
+ when Array then col.join("\n")
325
+ when String then col
326
+ else col.to_s
327
+ end
328
+ end
322
329
  end
323
330
  end
324
331
  end
@@ -367,18 +374,36 @@ class HighLine::Table
367
374
  end
368
375
  end
369
376
 
370
- remaining = column_widths.inject(0){ |sum, w| if w.set == 0; sum += w.max; available -= w.min; end; sum }
377
+ remaining = column_widths.inject(0) do |sum, w|
378
+ if w.set == 0
379
+ sum += w.max
380
+ available -= w.min
381
+ end
382
+ sum
383
+ end
371
384
  fair = available.to_f / remaining.to_f
372
385
 
373
386
  column_widths.
374
387
  each do |w|
375
388
  if w.set == 0
376
- available -= (alloc = (w.max * fair).to_i)
389
+ alloc = (w.max * fair).to_i
390
+ overflow = alloc + w.min - w.max
391
+ if overflow > 0
392
+ alloc -= overflow
393
+ end
394
+ available -= alloc
377
395
  w.set = alloc + w.min
378
396
  end
379
397
  end.
380
- each{ |w| if available > 0 && w.set < w.max; w.set += 1; available -= 1; end }.
381
- map(&:set)
398
+ each do |w|
399
+ next unless available > 0
400
+ gap = w.max - w.set
401
+ if gap > 0
402
+ left = [gap,available].min
403
+ available -= left
404
+ w.set += left
405
+ end
406
+ end.map(&:set)
382
407
  end
383
408
 
384
409
  def widths
@@ -1,34 +1,5 @@
1
1
  module RHC
2
2
  module OutputHelpers
3
- # Issues collector collects a set of recoverable issues and steps to fix them
4
- # for output at the end of a complex command
5
- def add_issue(reason, commands_header, *commands)
6
- @issues ||= []
7
- issue = {:reason => reason,
8
- :commands_header => commands_header,
9
- :commands => commands}
10
- @issues << issue
11
- end
12
-
13
- def format_issues(indent)
14
- return nil unless issues?
15
-
16
- indentation = " " * indent
17
- reasons = ""
18
- steps = ""
19
-
20
- @issues.each_with_index do |issue, i|
21
- reasons << "#{indentation}#{i+1}. #{issue[:reason].strip}\n"
22
- steps << "#{indentation}#{i+1}. #{issue[:commands_header].strip}\n"
23
- issue[:commands].each { |cmd| steps << "#{indentation} $ #{cmd}\n" }
24
- end
25
-
26
- [reasons, steps]
27
- end
28
-
29
- def issues?
30
- not @issues.nil?
31
- end
32
3
 
33
4
  #---------------------------
34
5
  # Application information