rhc 1.7.8 → 1.8.9

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 (39) hide show
  1. data/autocomplete/rhc_bash +3 -3
  2. data/features/lib/rhc_helper/commandify.rb +1 -1
  3. data/features/lib/rhc_helper/loggable.rb +2 -2
  4. data/features/support/before_hooks.rb +19 -4
  5. data/features/support/env.rb +3 -3
  6. data/lib/rhc/cartridge_helpers.rb +9 -3
  7. data/lib/rhc/commands.rb +1 -0
  8. data/lib/rhc/commands/app.rb +35 -19
  9. data/lib/rhc/commands/apps.rb +2 -2
  10. data/lib/rhc/commands/authorization.rb +2 -2
  11. data/lib/rhc/commands/cartridge.rb +9 -5
  12. data/lib/rhc/commands/domain.rb +7 -9
  13. data/lib/rhc/commands/port_forward.rb +6 -5
  14. data/lib/rhc/commands/tail.rb +5 -3
  15. data/lib/rhc/context_helper.rb +1 -1
  16. data/lib/rhc/exceptions.rb +12 -0
  17. data/lib/rhc/helpers.rb +18 -4
  18. data/lib/rhc/highline_extensions.rb +11 -4
  19. data/lib/rhc/rest/application.rb +22 -10
  20. data/lib/rhc/rest/attributes.rb +5 -0
  21. data/lib/rhc/rest/cartridge.rb +32 -2
  22. data/lib/rhc/rest/domain.rb +7 -1
  23. data/lib/rhc/rest/gear_group.rb +1 -1
  24. data/lib/rhc/rest/mock.rb +20 -5
  25. data/lib/rhc/wizard.rb +4 -4
  26. data/spec/rhc/auth_spec.rb +2 -0
  27. data/spec/rhc/command_spec.rb +3 -1
  28. data/spec/rhc/commands/app_spec.rb +19 -1
  29. data/spec/rhc/commands/apps_spec.rb +2 -2
  30. data/spec/rhc/commands/domain_spec.rb +5 -5
  31. data/spec/rhc/commands/port_forward_spec.rb +40 -0
  32. data/spec/rhc/commands/server_spec.rb +13 -8
  33. data/spec/rhc/commands/tail_spec.rb +29 -0
  34. data/spec/rhc/highline_extensions_spec.rb +15 -0
  35. data/spec/rhc/rest_application_spec.rb +41 -9
  36. data/spec/rhc/rest_spec.rb +12 -1
  37. data/spec/rhc/wizard_spec.rb +19 -2
  38. data/spec/wizard_spec_helper.rb +3 -3
  39. metadata +347 -342
@@ -12,17 +12,19 @@ module RHC::Commands
12
12
  option ["-n", "--namespace NAME"], "Namespace of your application", :context => :namespace_context, :required => true
13
13
  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')"
14
14
  option ["-f", "--files files"], "File glob relative to app (default <application_name>/logs/*) (optional)"
15
+ option ["-g", "--gear ID"], "Tail only a specific gear"
15
16
  #option ["-c", "--cartridge name"], "Tail only a specific cartridge"
16
17
  alias_action :"app tail", :root_command => true, :deprecated => true
17
18
  def run(app_name)
18
19
  rest_app = rest_client.find_application(options.namespace, app_name, :include => :cartridges)
20
+ ssh_url = options.gear ? rest_app.gear_ssh_url(options.gear) : rest_app.ssh_url
19
21
 
20
- tail('*', URI(rest_app.ssh_url), options)
22
+ tail('*', URI(ssh_url), options)
21
23
 
22
24
  0
23
25
  end
24
26
 
25
- private
27
+ private
26
28
  #Application log file tailing
27
29
  def tail(cartridge_name, ssh_url, options)
28
30
  debug "Tail in progress for cartridge #{cartridge_name}"
@@ -30,7 +32,7 @@ module RHC::Commands
30
32
  host = ssh_url.host
31
33
  uuid = ssh_url.user
32
34
 
33
- file_glob = options.files ? options.files : "#{cartridge_name}/logs/*"
35
+ file_glob = options.files ? options.files : "#{cartridge_name}/log*/*"
34
36
  remote_cmd = "tail#{options.opts ? ' --opts ' + Base64::encode64(options.opts).chomp : ''} #{file_glob}"
35
37
  ssh_cmd = "ssh -t #{uuid}@#{host} '#{remote_cmd}'"
36
38
  begin
@@ -38,7 +38,7 @@ module RHC
38
38
  # right now we don't have any logic since we only support one domain
39
39
  # TODO: add domain lookup based on uuid
40
40
  domain = rest_client.domains.first
41
- raise RHC::Rest::DomainNotFoundException, "No domains configured for this user. You may create one using 'rhc domain create'." if domain.nil?
41
+ raise RHC::Rest::DomainNotFoundException, "No domains configured for this user. You may create one using 'rhc create-domain'." if domain.nil?
42
42
 
43
43
  domain.id
44
44
  end
@@ -140,4 +140,16 @@ module RHC
140
140
  super "The server does not support this command (requires #{min_version}, found #{current_version})."
141
141
  end
142
142
  end
143
+
144
+ class OperationNotSupportedException < Exception
145
+ def initialize(message="This operation is not supported by the server.")
146
+ super message, 1
147
+ end
148
+ end
149
+
150
+ class InvalidURIException < Exception
151
+ def initialize(uri)
152
+ super "Invalid URI specified: #{uri}"
153
+ end
154
+ end
143
155
  end
@@ -140,16 +140,30 @@ module RHC
140
140
  s =~ %r(^http(?:s)?://) ? URI(s).host : s
141
141
  end
142
142
  def to_uri(s)
143
- URI(s =~ %r(^http(?:s)?://) ? s : "https://#{s}")
143
+ begin
144
+ URI(s =~ %r(^http(?:s)?://) ? s : "https://#{s}")
145
+ rescue URI::InvalidURIError
146
+ raise RHC::InvalidURIException.new(s)
147
+ end
148
+ end
149
+
150
+ def ssh_string(ssh_url)
151
+ return nil if ssh_url.nil?
152
+ uri = URI.parse(ssh_url)
153
+ "#{uri.user}@#{uri.host}"
154
+ rescue => e
155
+ RHC::Helpers.debug_error(e)
156
+ ssh_url
144
157
  end
158
+
145
159
  def openshift_rest_endpoint
146
160
  uri = to_uri((options.server rescue nil) || ENV['LIBRA_SERVER'] || "openshift.redhat.com")
147
161
  uri.path = '/broker/rest/api' if uri.path.blank? || uri.path == '/'
148
162
  uri
149
163
  end
150
-
164
+
151
165
  def token_for_user
152
- options.token or (token_store.get(options.rhlogin, options.server) if options.rhlogin)
166
+ options.token or (token_store.get(options.rhlogin, options.server) if options.rhlogin && options.use_authorization_tokens)
153
167
  end
154
168
 
155
169
  def client_from_options(opts)
@@ -338,7 +352,7 @@ module RHC
338
352
  end
339
353
  end
340
354
  end
341
-
355
+
342
356
  def with_tolerant_encoding(&block)
343
357
  # :nocov:
344
358
  if RUBY_VERSION.to_f >= 1.9
@@ -24,7 +24,14 @@ class HighLineExtension < HighLine
24
24
  template = ERB.new(statement, nil, "%")
25
25
  statement = template.result(binding)
26
26
 
27
- statement = statement.textwrap_ansi(@wrap_at, false).join("#{indentation}\n") unless @wrap_at.nil?
27
+ if @wrap_at
28
+ statement = statement.textwrap_ansi(@wrap_at, false)
29
+ if @last_line_open && statement.length > 1
30
+ @last_line_open = false
31
+ @output.puts
32
+ end
33
+ statement = statement.join("#{indentation}\n")
34
+ end
28
35
  statement = send(:page_print, statement) unless @page_at.nil?
29
36
 
30
37
  @output.print(indentation) unless @last_line_open
@@ -33,7 +40,7 @@ class HighLineExtension < HighLine
33
40
  if statement[-1, 1] == " " or statement[-1, 1] == "\t"
34
41
  @output.print(statement)
35
42
  @output.flush
36
- true
43
+ statement.strip_ansi.length + (@last_line_open || 0)
37
44
  else
38
45
  @output.puts(statement)
39
46
  false
@@ -309,7 +316,7 @@ class HighLine::Table
309
316
  end
310
317
 
311
318
  def columns
312
- @columns ||= source_rows.map(&:length).max
319
+ @columns ||= source_rows.map(&:length).max || 0
313
320
  end
314
321
 
315
322
  def column_widths
@@ -396,7 +403,7 @@ class HighLine::Table
396
403
 
397
404
  body = (header_rows + source_rows).inject([]) do |a,row|
398
405
  row = row.zip(widths).map{ |column,w| w && w > 0 ? column.textwrap_ansi(w, false) : [column] }
399
- row.map(&:length).max.times do |i|
406
+ (row.map(&:length).max || 0).times do |i|
400
407
  a << (fmt % row.map{ |r| r[i] }).rstrip
401
408
  end
402
409
  a
@@ -21,11 +21,20 @@ module RHC
21
21
  carts.delete_if{|x| scales_with.include?(x.name)}
22
22
  end
23
23
 
24
- def add_cartridge(name, options={})
24
+ def add_cartridge(cart, options={})
25
25
  debug "Adding cartridge #{name}"
26
- @cartridges = nil
27
- attributes['cartridges'] = nil
28
- rest_method "ADD_CARTRIDGE", {:name => name}, options
26
+ clear_attribute :cartridges
27
+ rest_method(
28
+ "ADD_CARTRIDGE",
29
+ if cart.is_a? String
30
+ {:name => cart}
31
+ elsif cart.respond_to? :[]
32
+ cart
33
+ else
34
+ cart.url ? {:url => cart.url} : {:name => cart.name}
35
+ end,
36
+ options
37
+ )
29
38
  end
30
39
 
31
40
  def cartridges
@@ -47,6 +56,13 @@ module RHC
47
56
  rest_method "GET_GEAR_GROUPS"
48
57
  end
49
58
 
59
+ def gear_ssh_url(gear_id)
60
+ gear = gear_groups.map { |group| group.gears }.flatten.find { |g| g['id'] == gear_id }
61
+
62
+ raise ArgumentError.new("Gear #{gear_id} not found") if gear.nil?
63
+ gear['ssh_url'] or raise OperationNotSupportedException.new("The server does not support per gear operations")
64
+ end
65
+
50
66
  def tidy
51
67
  debug "Starting application #{name}"
52
68
  rest_method 'TIDY', :event => "tidy"
@@ -111,7 +127,7 @@ module RHC
111
127
  if aliases.nil? or not aliases.is_a?(Array)
112
128
  supports?('LIST_ALIASES') ? rest_method("LIST_ALIASES") : []
113
129
  else
114
- aliases.map do |a|
130
+ aliases.map do |a|
115
131
  Alias.new(a.is_a?(String) ? {'id' => a} : a, client)
116
132
  end
117
133
  end
@@ -173,11 +189,7 @@ module RHC
173
189
  end
174
190
 
175
191
  def ssh_string
176
- uri = URI.parse(ssh_url)
177
- "#{uri.user}@#{uri.host}"
178
- rescue => e
179
- RHC::Helpers.debug_error(e)
180
- ssh_url
192
+ RHC::Helpers.ssh_string(ssh_url)
181
193
  end
182
194
 
183
195
  def <=>(other)
@@ -10,6 +10,11 @@ module RHC::Rest::Attributes
10
10
  def attribute(name)
11
11
  instance_variable_get("@#{name}") || attributes[name.to_s]
12
12
  end
13
+
14
+ def clear_attribute(name)
15
+ v = instance_variable_set("@#{name}", nil)
16
+ attributes.delete(name.to_s) or v
17
+ end
13
18
  end
14
19
 
15
20
  module RHC::Rest::AttributesClass
@@ -3,15 +3,24 @@ module RHC
3
3
  class Cartridge < Base
4
4
  HIDDEN_TAGS = [:framework, :web_framework, :cartridge].map(&:to_s)
5
5
 
6
- define_attr :type, :name, :display_name, :properties, :gear_profile, :status_messages, :scales_to, :scales_from, :scales_with, :current_scale, :supported_scales_to, :supported_scales_from, :tags, :description, :collocated_with, :base_gear_storage, :additional_gear_storage
6
+ define_attr :type, :name, :display_name, :properties, :gear_profile, :status_messages, :scales_to, :scales_from, :scales_with, :current_scale, :supported_scales_to, :supported_scales_from, :tags, :description, :collocated_with, :base_gear_storage, :additional_gear_storage, :url
7
7
 
8
8
  def scalable?
9
9
  supported_scales_to != supported_scales_from
10
10
  end
11
11
 
12
+ def custom?
13
+ url.present?
14
+ end
15
+
12
16
  def only_in_new?
13
17
  type == 'standalone'
14
18
  end
19
+
20
+ def only_in_existing?
21
+ type == 'embedded'
22
+ end
23
+
15
24
  def shares_gears?
16
25
  Array(collocated_with).present?
17
26
  end
@@ -28,7 +37,14 @@ module RHC
28
37
  end
29
38
 
30
39
  def display_name
31
- attribute(:display_name) || name
40
+ attribute(:display_name) || name || url_basename
41
+ end
42
+
43
+ #
44
+ # Use this value when the user should interact with this cart via CLI arguments
45
+ #
46
+ def short_name
47
+ name || url
32
48
  end
33
49
 
34
50
  def usage_rate?
@@ -113,6 +129,20 @@ module RHC
113
129
  return 1 if type == 'standalone' && other.type != 'standalone'
114
130
  name <=> other.name
115
131
  end
132
+
133
+ def url_basename
134
+ uri = URI.parse(url)
135
+ name = uri.fragment
136
+ name = Rack::Utils.parse_nested_query(uri.query)['name'] if name.blank? && uri.query
137
+ name = File.basename(uri.path) if name.blank? && uri.path.present? && uri.path != '/'
138
+ name.presence || url
139
+ rescue
140
+ url
141
+ end
142
+
143
+ def self.for_url(url)
144
+ new 'url' => url
145
+ end
116
146
  end
117
147
  end
118
148
  end
@@ -15,7 +15,13 @@ module RHC
15
15
  payload = {:name => name}
16
16
  options.each{ |key, value| payload[key.to_sym] = value }
17
17
 
18
- cartridges = Array(payload.delete(:cartridge)).concat(Array(payload.delete(:cartridges))).compact.uniq
18
+ cartridges = Array(payload.delete(:cartridge)).concat(Array(payload.delete(:cartridges))).map do |cart|
19
+ if cart.is_a? String or cart.respond_to? :[]
20
+ cart
21
+ else
22
+ cart.url ? {:url => cart.url} : cart.name
23
+ end
24
+ end.compact.uniq
19
25
  if client.api_version_negotiated >= 1.3
20
26
  payload[:cartridges] = cartridges
21
27
  else
@@ -1,7 +1,7 @@
1
1
  module RHC
2
2
  module Rest
3
3
  class GearGroup < Base
4
- define_attr :gears, :cartridges
4
+ define_attr :gears, :cartridges, :gear_profile
5
5
  end
6
6
  end
7
7
  end
@@ -392,12 +392,13 @@ module RHC::Rest::Mock
392
392
  ['UPDATE', "domains/#{domain_id}/apps/#{app_id}/aliases/#{alias_id}/update", 'post' ]]
393
393
  end
394
394
 
395
- def mock_cartridge_response(cart_count=1)
395
+ def mock_cartridge_response(cart_count=1, url=false)
396
396
  carts = []
397
397
  while carts.length < cart_count
398
398
  carts << {
399
399
  :name => "mock_cart_#{carts.length}",
400
- :type => "mock_cart_#{carts.length}_type",
400
+ :url => url ? "http://a.url/#{carts.length}" : nil,
401
+ :type => carts.empty? ? 'standalone' : 'embedded',
401
402
  :links => mock_response_links(mock_cart_links('mock_domain','mock_app',"mock_cart_#{carts.length}"))
402
403
  }
403
404
  end
@@ -427,7 +428,7 @@ module RHC::Rest::Mock
427
428
  :status => 200
428
429
  }
429
430
  end
430
-
431
+
431
432
  def mock_gear_groups_response()
432
433
  groups = [{}]
433
434
  type = 'gear_groups'
@@ -608,7 +609,8 @@ module RHC::Rest::Mock
608
609
  def initialize(client=nil)
609
610
  super({}, client)
610
611
  @cartridges = [{'name' => 'fake_geargroup_cart-0.1'}]
611
- @gears = [{'state' => 'started', 'id' => 'fakegearid'}]
612
+ @gears = [{'state' => 'started', 'id' => 'fakegearid', 'ssh_url' => 'ssh://fakegearid@fakesshurl.com'}]
613
+ @gear_profile = 'small'
612
614
  end
613
615
  end
614
616
 
@@ -689,9 +691,22 @@ module RHC::Rest::Mock
689
691
  @domain.applications.delete self
690
692
  end
691
693
 
692
- def add_cartridge(name, embedded=true)
694
+ def add_cartridge(cart, embedded=true)
695
+ name, url =
696
+ if cart.is_a? String
697
+ [cart, nil]
698
+ elsif cart.respond_to? :[]
699
+ [cart[:name] || cart['name'], cart[:url] || cart['url']]
700
+ elsif RHC::Rest::Cartridge === cart
701
+ [cart.name, cart.url]
702
+ end
703
+
693
704
  type = embedded ? "embedded" : "standalone"
694
705
  c = MockRestCartridge.new(client, name, type, self)
706
+ if url
707
+ c.url = url
708
+ c.name = c.url_basename
709
+ end
695
710
  c.properties << {'name' => 'prop1', 'value' => 'value1', 'description' => 'description1' }
696
711
  @cartridges << c
697
712
  c.messages << "Cartridge added with properties"
@@ -389,7 +389,7 @@ module RHC
389
389
  paragraph do
390
390
  say "Your namespace is unique to your account and is the suffix of the " \
391
391
  "public URLs we assign to your applications. You may configure your " \
392
- "namespace here or leave it blank and use 'rhc domain create' to " \
392
+ "namespace here or leave it blank and use 'rhc create-domain' to " \
393
393
  "create a namespace later. You will not be able to create " \
394
394
  "applications without first creating a namespace."
395
395
  end
@@ -420,10 +420,10 @@ module RHC
420
420
  else
421
421
  info "none"
422
422
 
423
- paragraph{ say "Run 'rhc app create' to create your first application." }
423
+ paragraph{ say "Run 'rhc create-app' to create your first application." }
424
424
  paragraph do
425
425
  say table(standalone_cartridges.sort {|a,b| a.display_name <=> b.display_name }.map do |cart|
426
- [' ', cart.display_name, "rhc app create <app name> #{cart.name}"]
426
+ [' ', cart.display_name, "rhc create-app <app name> #{cart.name}"]
427
427
  end)
428
428
  end
429
429
  end
@@ -514,7 +514,7 @@ module RHC
514
514
  def config_namespace(namespace)
515
515
  # skip if string is empty
516
516
  if namespace_optional? and (namespace.nil? or namespace.chomp.blank?)
517
- paragraph{ info "You may create a namespace later through 'rhc domain create'" }
517
+ paragraph{ info "You may create a namespace later through 'rhc create-domain'" }
518
518
  return true
519
519
  end
520
520
 
@@ -422,6 +422,8 @@ describe RHC::Auth::TokenStore do
422
422
  before{ subject.put('foo', 'bar', 'token') }
423
423
  it("can be retrieved"){ subject.get('foo', 'bar').should == 'token' }
424
424
  end
425
+
426
+ it("should handle missing files"){ subject.get('foo', 'other').should be_nil }
425
427
  it("should put a file on disk"){ expect{ subject.put('test', 'server', 'value') }.to change{ Dir.entries(dir).length }.by(1) }
426
428
 
427
429
  describe "#clear" do
@@ -118,7 +118,7 @@ describe RHC::Commands::Base do
118
118
  alias_action :exe, :deprecated => true
119
119
  def execute(testarg); 1; end
120
120
 
121
- argument :args, "Test arg list", ['--tests'], :arg_type => :list
121
+ argument :args, "Test arg list", ['--tests ARG'], :arg_type => :list
122
122
  summary "Test command execute-list"
123
123
  def execute_list(args); 1; end
124
124
 
@@ -185,6 +185,7 @@ describe RHC::Commands::Base do
185
185
  it { expects_running('static-execute-list', '--trace').should call(:execute_list).on(instance).with([]) }
186
186
  it { expects_running('static-execute-list', '1', '2', '3').should call(:execute_list).on(instance).with(['1', '2', '3']) }
187
187
  it { expects_running('static-execute-list', '1', '2', '3').should call(:execute_list).on(instance).with(['1', '2', '3']) }
188
+ it('should make the option an array') { expects_running('static-execute-list', '--tests', '1').should call(:execute_list).on(instance).with(['1']) }
188
189
  it('should make the option available') { command_for('static-execute-list', '1', '2', '3').send(:options).tests.should == ['1','2','3'] }
189
190
  end
190
191
 
@@ -305,6 +306,7 @@ describe RHC::Commands::Base do
305
306
 
306
307
  context "when tokens are not allowed" do
307
308
  it("calls the server") { rest_client.send(:auth).is_a? RHC::Auth::Basic }
309
+ it("does not have a token set") { command_for(*arguments).send(:token_for_user).should be_nil }
308
310
  end
309
311
 
310
312
  context "when tokens are allowed" do
@@ -195,6 +195,14 @@ describe RHC::Commands::App do
195
195
  after{ rest_client.domains.first.applications.first.cartridges.find{ |c| c.name == 'mock_cart-1' }.should be_true }
196
196
  end
197
197
 
198
+ context 'when run with a cart URL' do
199
+ let(:arguments) { ['app', 'create', 'app1', 'http://foo.com', 'mock_cart-1', '--noprompt', '-p', 'password'] }
200
+ it { expect { run }.to exit_with_code(0) }
201
+ it { run_output.should match("Success") }
202
+ it { run_output.should match("Cartridges: http://foo.com, mock_cart-1\n") }
203
+ after{ rest_client.domains.first.applications.first.cartridges.find{ |c| c.url == 'http://foo.com' }.should be_true }
204
+ end
205
+
198
206
  context 'when run with a git url' do
199
207
  let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1', '--from', 'git://url', '--noprompt', '-p', 'password'] }
200
208
  it { expect { run }.to exit_with_code(0) }
@@ -253,6 +261,16 @@ describe RHC::Commands::App do
253
261
  let(:arguments) { ['app', 'create', 'app1', 'unique_standalone', 'mock_standalone_cart', '--trace', '--noprompt'] }
254
262
  it { expect { run }.to raise_error(RHC::MultipleCartridgesException, /You must select only a single web cart/) }
255
263
  end
264
+ context 'when I pick a custom URL cart' do
265
+ let(:arguments) { ['app', 'create', 'app1', 'http://foo.com', '--trace', '--noprompt'] }
266
+ it('tells me about custom carts') { run_output.should match("The cartridge 'http://foo.com' will be downloaded") }
267
+ it('lists the cart using the short_name') { run_output.should match(%r(Cartridges:\s+http://foo.com$)) }
268
+ end
269
+ context 'when I pick a custom URL cart and a web cart' do
270
+ let(:arguments) { ['app', 'create', 'app1', 'http://foo.com', 'unique_standalone', '--trace', '--noprompt'] }
271
+ it('tells me about custom carts') { run_output.should match("The cartridge 'http://foo.com' will be downloaded") }
272
+ it('lists the carts using the short_name') { run_output.should match(%r(Cartridges:\s+http://foo.com, mock_unique_standalone_cart-1$)) }
273
+ end
256
274
  end
257
275
 
258
276
  describe 'app create enable-jenkins' do
@@ -539,7 +557,7 @@ describe RHC::Commands::App do
539
557
  @domain = rest_client.add_domain("mockdomain")
540
558
  @domain.add_application("app1", "mock_type")
541
559
  end
542
- it { run_output.should match("fakegearid started fake_geargroup_cart-0.1") }
560
+ it { run_output.should match("fakegearid started fake_geargroup_cart-0.1 small fakegearid@fakesshurl.com") }
543
561
  end
544
562
  end
545
563